--- 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 /* SB interrupt (SB DMA/SB F2 command) */ +#define ENVY24_CCS_NMI1_DSDMA 0x08 /* DS channel C DMA interrupt */ +#define ENVY24_CCS_NMI1_MIDI 0x04 /* MIDI 330h or [PCI_10]h+Ch write */ +#define ENVY24_CCS_NMI1_FM 0x01 /* FM data register write */ + +#define ENVY24_CCS_NMIDAT 0x06 /* NMI Data Register */ +#define ENVY24_CCS_NMIIDX 0x07 /* NMI Index Register */ +#define ENVY24_CCS_AC97IDX 0x08 /* Consumer AC'97 Index Register */ + +#define ENVY24_CCS_AC97CMD 0x09 /* Consumer AC'97 Command/Status Register */ +#define ENVY24_CCS_AC97CMD_COLD 0x80 /* Cold reset */ +#define ENVY24_CCS_AC97CMD_WARM 0x40 /* Warm reset */ +#define ENVY24_CCS_AC97CMD_WRCODEC 0x20 /* Write to AC'97 codec registers */ +#define ENVY24_CCS_AC97CMD_RDCODEC 0x10 /* Read from AC'97 codec registers */ +#define ENVY24_CCS_AC97CMD_READY 0x08 /* AC'97 codec ready status bit */ +#define ENVY24_CCS_AC97CMD_PVSR 0x02 /* VSR for Playback */ +#define ENVY24_CCS_AC97CMD_RVSR 0x01 /* VSR for Record */ + +#define ENVY24_CCS_AC97DAT 0x0a /* Consumer AC'97 Data Port Register */ +#define ENVY24_CCS_PMIDIDAT 0x0c /* Primary MIDI UART Data Register */ +#define ENVY24_CCS_PMIDICMD 0x0d /* Primary MIDI UART Command/Status Register */ + +#define ENVY24_CCS_NMI2 0x0e /* NMI Status Register 2 */ +#define ENVY24_CCS_NMI2_FMBANK 0x30 /* FM bank indicator */ +#define ENVY24_CCS_NMI2_FM0 0x10 /* FM bank 0 (388h/220h/228h) */ +#define ENVY24_CCS_NMI2_FM1 0x20 /* FM bank 1 (38ah/222h) */ +#define ENVY24_CCS_NMI2_PICIO 0x0f /* PIC I/O cycle */ +#define ENVY24_CCS_NMI2_PIC20W 0x01 /* 20h write */ +#define ENVY24_CCS_NMI2_PICA0W 0x02 /* a0h write */ +#define ENVY24_CCS_NMI2_PIC21W 0x05 /* 21h write */ +#define ENVY24_CCS_NMI2_PICA1W 0x06 /* a1h write */ +#define ENVY24_CCS_NMI2_PIC20R 0x09 /* 20h read */ +#define ENVY24_CCS_NMI2_PICA0R 0x0a /* a0h read */ +#define ENVY24_CCS_NMI2_PIC21R 0x0d /* 21h read */ +#define ENVY24_CCS_NMI2_PICA1R 0x0e /* a1h read */ + +#define ENVY24_CCS_JOY 0x0f /* Game port register */ + +#define ENVY24_CCS_I2CDEV 0x10 /* I2C Port Device Address Register */ +#define ENVY24_CCS_I2CDEV_ADDR 0xfe /* I2C device address */ +#define ENVY24_CCS_I2CDEV_ROM 0xa0 /* reserved for the external I2C E2PROM */ +#define ENVY24_CCS_I2CDEV_WR 0x01 /* write */ +#define ENVY24_CCS_I2CDEV_RD 0x00 /* read */ + +#define ENVY24_CCS_I2CADDR 0x11 /* I2C Port Byte Address Register */ +#define ENVY24_CCS_I2CDATA 0x12 /* I2C Port Read/Write Data Register */ + +#define ENVY24_CCS_I2CSTAT 0x13 /* I2C Port Control and Status Register */ +#define ENVY24_CCS_I2CSTAT_ROM 0x80 /* external E2PROM exists */ +#define ENVY24_CCS_I2CSTAT_BSY 0x01 /* I2C port read/write status busy */ + +#define ENVY24_CCS_CDMABASE 0x14 /* Consumer Record DMA Current/Base Address Register */ +#define ENVY24_CCS_CDMACNT 0x18 /* Consumer Record DMA Current/Base Count Register */ +#define ENVY24_CCS_SERR 0x1b /* PCI Configuration SERR# Shadow Register */ +#define ENVY24_CCS_SMIDIDAT 0x1c /* Secondary MIDI UART Data Register */ +#define ENVY24_CCS_SMIDICMD 0x1d /* Secondary MIDI UART Command/Status Register */ + +#define ENVY24_CCS_TIMER 0x1e /* Timer Register */ +#define ENVY24_CCS_TIMER_EN 0x8000 /* Timer count enable */ +#define ENVY24_CCS_TIMER_MASK 0x7fff /* Timer counter mask */ + +/* Controller Indexed Registers */ + +#define ENVY24_CCI_PTCHIGH 0x00 /* Playback Terminal Count Register (High Byte) */ +#define ENVY24_CCI_PTCLOW 0x01 /* Playback Terminal Count Register (Low Byte) */ + +#define ENVY24_CCI_PCTL 0x02 /* Playback Control Register */ +#define ENVY24_CCI_PCTL_TURBO 0x80 /* 4x up sampling in the host by software */ +#define ENVY24_CCI_PCTL_U8 0x10 /* 8 bits unsigned */ +#define ENVY24_CCI_PCTL_S16 0x00 /* 16 bits signed */ +#define ENVY24_CCI_PCTL_STEREO 0x08 /* stereo */ +#define ENVY24_CCI_PCTL_MONO 0x00 /* mono */ +#define ENVY24_CCI_PCTL_FLUSH 0x04 /* FIFO flush (sticky bit. Requires toggling) */ +#define ENVY24_CCI_PCTL_PAUSE 0x02 /* Pause */ +#define ENVY24_CCI_PCTL_ENABLE 0x01 /* Playback enable */ + +#define ENVY24_CCI_PLVOL 0x03 /* Playback Left Volume/Pan Register */ +#define ENVY24_CCI_PRVOL 0x04 /* Playback Right Volume/Pan Register */ +#define ENVY24_CCI_VOL_MASK 0x3f /* Volume value mask */ + +#define ENVY24_CCI_SOFTVOL 0x05 /* Soft Volume/Mute Control Register */ +#define ENVY24_CCI_PSRLOW 0x06 /* Playback Sampling Rate Register (Low Byte) */ +#define ENVY24_CCI_PSRMID 0x07 /* Playback Sampling Rate Register (Middle Byte) */ +#define ENVY24_CCI_PSRHIGH 0x08 /* Playback Sampling Rate Register (High Byte) */ +#define ENVY24_CCI_RTCHIGH 0x10 /* Record Terminal Count Register (High Byte) */ +#define ENVY24_CCI_RTCLOW 0x11 /* Record Terminal Count Register (Low Byte) */ + +#define ENVY24_CCI_RCTL 0x12 /* Record Control Register */ +#define ENVY24_CCI_RCTL_DRTN 0x80 /* Digital return enable */ +#define ENVY24_CCI_RCTL_U8 0x04 /* 8 bits unsigned */ +#define ENVY24_CCI_RCTL_S16 0x00 /* 16 bits signed */ +#define ENVY24_CCI_RCTL_STEREO 0x00 /* stereo */ +#define ENVY24_CCI_RCTL_MONO 0x02 /* mono */ +#define ENVY24_CCI_RCTL_ENABLE 0x01 /* Record enable */ + +#define ENVY24_CCI_GPIODAT 0x20 /* GPIO Data Register */ +#define ENVY24_CCI_GPIOMASK 0x21 /* GPIO Write Mask Register */ + +#define ENVY24_CCI_GPIOCTL 0x22 /* GPIO Direction Control Register */ +#define ENVY24_CCI_GPIO_OUT 1 /* output */ +#define ENVY24_CCI_GPIO_IN 0 /* input */ + +#define ENVY24_CCI_CPDWN 0x30 /* Consumer Section Power Down Register */ +#define ENVY24_CCI_CPDWN_XTAL 0x80 /* Crystal clock generation power down for XTAL_1 */ +#define ENVY24_CCI_CPDWN_GAME 0x40 /* Game port analog power down */ +#define ENVY24_CCI_CPDWN_I2C 0x10 /* I2C port clock */ +#define ENVY24_CCI_CPDWN_MIDI 0x08 /* MIDI clock */ +#define ENVY24_CCI_CPDWN_AC97 0x04 /* AC'97 clock */ +#define ENVY24_CCI_CPDWN_DS 0x02 /* DS Block clock */ +#define ENVY24_CCI_CPDWN_PCI 0x01 /* PCI clock for SB, DMA controller */ + +#define ENVY24_CCI_MTPDWN 0x31 /* Multi-Track Section Power Down Register */ +#define ENVY24_CCI_MTPDWN_XTAL 0x80 /* Crystal clock generation power down for XTAL_2 */ +#define ENVY24_CCI_MTPDWN_SPDIF 0x04 /* S/PDIF clock */ +#define ENVY24_CCI_MTPDWN_MIX 0x02 /* Professional digital mixer clock */ +#define ENVY24_CCI_MTPDWN_I2S 0x01 /* Multi-track I2S serial interface clock */ + +/* DDMA Registers */ + +#define ENVY24_DDMA_ADDR0 0x00 /* DMA Base and Current Address bit 0-7 */ +#define ENVY24_DDMA_ADDR8 0x01 /* DMA Base and Current Address bit 8-15 */ +#define ENVY24_DDMA_ADDR16 0x02 /* DMA Base and Current Address bit 16-23 */ +#define ENVY24_DDMA_ADDR24 0x03 /* DMA Base and Current Address bit 24-31 */ +#define ENVY24_DDMA_CNT0 0x04 /* DMA Base and Current Count 0-7 */ +#define ENVY24_DDMA_CNT8 0x05 /* DMA Base and Current Count 8-15 */ +#define ENVY24_DDMA_CNT16 0x06 /* (not supported) */ +#define ENVY24_DDMA_CMD 0x08 /* Status and Command */ +#define ENVY24_DDMA_MODE 0x0b /* Mode */ +#define ENVY24_DDMA_RESET 0x0c /* Master reset */ +#define ENVY24_DDMA_CHAN 0x0f /* Channel Mask */ + +/* Consumer Section DMA Channel Registers */ + +#define ENVY24_CS_INTMASK 0x00 /* DirectSound DMA Interrupt Mask Register */ +#define ENVY24_CS_INTSTAT 0x02 /* DirectSound DMA Interrupt Status Register */ +#define ENVY24_CS_CHDAT 0x04 /* Channel Data register */ + +#define ENVY24_CS_CHIDX 0x08 /* Channel Index Register */ +#define ENVY24_CS_CHIDX_NUM 0xf0 /* Channel number */ +#define ENVY24_CS_CHIDX_ADDR0 0x00 /* Buffer_0 DMA base address */ +#define ENVY24_CS_CHIDX_CNT0 0x01 /* Buffer_0 DMA base count */ +#define ENVY24_CS_CHIDX_ADDR1 0x02 /* Buffer_1 DMA base address */ +#define ENVY24_CS_CHIDX_CNT1 0x03 /* Buffer_1 DMA base count */ +#define ENVY24_CS_CHIDX_CTL 0x04 /* Channel Control and Status register */ +#define ENVY24_CS_CHIDX_RATE 0x05 /* Channel Sampling Rate */ +#define ENVY24_CS_CHIDX_VOL 0x06 /* Channel left and right volume/pan control */ +/* Channel Control and Status Register at Index 4h */ +#define ENVY24_CS_CTL_BUF 0x80 /* indicating that the current active buffer */ +#define ENVY24_CS_CTL_AUTO1 0x40 /* Buffer_1 auto init. enable */ +#define ENVY24_CS_CTL_AUTO0 0x20 /* Buffer_0 auto init. enable */ +#define ENVY24_CS_CTL_FLUSH 0x10 /* Flush FIFO */ +#define ENVY24_CS_CTL_STEREO 0x08 /* stereo(or mono) */ +#define ENVY24_CS_CTL_U8 0x04 /* 8-bit unsigned(or 16-bit signed) */ +#define ENVY24_CS_CTL_PAUSE 0x02 /* DMA request 1:pause */ +#define ENVY24_CS_CTL_START 0x01 /* DMA request 1: start, 0:stop */ +/* Consumer mode Left/Right Volume Register at Index 06h */ +#define ENVY24_CS_VOL_RIGHT 0x3f00 +#define ENVY24_CS_VOL_LEFT 0x003f + +/* Professional Multi-Track Control Registers */ + +#define ENVY24_MT_INT 0x00 /* DMA Interrupt Mask and Status Register */ +#define ENVY24_MT_INT_RMASK 0x80 /* Multi-track record interrupt mask */ +#define ENVY24_MT_INT_PMASK 0x40 /* Multi-track playback interrupt mask */ +#define ENVY24_MT_INT_RSTAT 0x02 /* Multi-track record interrupt status */ +#define ENVY24_MT_INT_PSTAT 0x01 /* Multi-track playback interrupt status */ + +#define ENVY24_MT_RATE 0x01 /* Sampling Rate Select Register */ +#define ENVY24_MT_RATE_SPDIF 0x10 /* S/PDIF input clock as the master */ +#define ENVY24_MT_RATE_48000 0x00 +#define ENVY24_MT_RATE_24000 0x01 +#define ENVY24_MT_RATE_12000 0x02 +#define ENVY24_MT_RATE_9600 0x03 +#define ENVY24_MT_RATE_32000 0x04 +#define ENVY24_MT_RATE_16000 0x05 +#define ENVY24_MT_RATE_8000 0x06 +#define ENVY24_MT_RATE_96000 0x07 +#define ENVY24_MT_RATE_64000 0x0f +#define ENVY24_MT_RATE_44100 0x08 +#define ENVY24_MT_RATE_22050 0x09 +#define ENVY24_MT_RATE_11025 0x0a +#define ENVY24_MT_RATE_88200 0x0b +#define ENVY24_MT_RATE_MASK 0x0f + +#define ENVY24_MT_I2S 0x02 /* I2S Data Format Register */ +#define ENVY24_MT_I2S_MLR128 0x08 /* MCLK/LRCLK ratio 128x(or 256x) */ +#define ENVY24_MT_I2S_SLR48 0x04 /* SCLK/LRCLK ratio 48bpf(or 64bpf) */ +#define ENVY24_MT_I2S_FORM 0x00 /* I2S data format */ + +#define ENVY24_MT_AC97IDX 0x04 /* Index Register for AC'97 Codecs */ + +#define ENVY24_MT_AC97CMD 0x05 /* Command and Status Register for AC'97 Codecs */ +#define ENVY24_MT_AC97CMD_CLD 0x80 /* Cold reset */ +#define ENVY24_MT_AC97CMD_WRM 0x40 /* Warm reset */ +#define ENVY24_MT_AC97CMD_WR 0x20 /* write to AC'97 codec register */ +#define ENVY24_MT_AC97CMD_RD 0x10 /* read AC'97 CODEC register */ +#define ENVY24_MT_AC97CMD_RDY 0x08 /* AC'97 codec ready status bit */ +#define ENVY24_MT_AC97CMD_ID 0x03 /* ID(0-3) for external AC 97 registers */ + +#define ENVY24_MT_AC97DLO 0x06 /* AC'97 codec register data low byte */ +#define ENVY24_MT_AC97DHI 0x07 /* AC'97 codec register data high byte */ +#define ENVY24_MT_PADDR 0x10 /* Playback DMA Current/Base Address Register */ +#define ENVY24_MT_PCNT 0x14 /* Playback DMA Current/Base Count Register */ +#define ENVY24_MT_PTERM 0x16 /* Playback Current/Base Terminal Count Register */ +#define ENVY24_MT_PCTL 0x18 /* Playback and Record Control Register */ +#define ENVY24_MT_PCTL_RSTART 0x04 /* 1: Record start; 0: Record stop */ +#define ENVY24_MT_PCTL_PAUSE 0x02 /* 1: Pause; 0: Resume */ +#define ENVY24_MT_PCTL_PSTART 0x01 /* 1: Playback start; 0: Playback stop */ + +#define ENVY24_MT_RADDR 0x20 /* Record DMA Current/Base Address Register */ +#define ENVY24_MT_RCNT 0x24 /* Record DMA Current/Base Count Register */ +#define ENVY24_MT_RTERM 0x26 /* Record Current/Base Terminal Count Register */ +#define ENVY24_MT_RCTL 0x28 /* Record Control Register */ +#define ENVY24_MT_RCTL_RSTART 0x01 /* 1: Record start; 0: Record stop */ + +#define ENVY24_MT_PSDOUT 0x30 /* Routing Control Register for Data to PSDOUT[0:3] */ +#define ENVY24_MT_SPDOUT 0x32 /* Routing Control Register for SPDOUT */ +#define ENVY24_MT_RECORD 0x34 /* Captured (Recorded) data Routing Selection Register */ + +#define BUS_SPACE_MAXADDR_ENVY24 0x0fffffff /* Address space beyond 256MB is not supported */ +#define BUS_SPACE_MAXSIZE_ENVY24 0x3fffc /* 64k x 4byte(1dword) */ + +#define ENVY24_MT_VOLUME 0x38 /* Left/Right Volume Control Data Register */ +#define ENVY24_MT_VOLUME_L 0x007f /* Left Volume Mask */ +#define ENVY24_MT_VOLUME_R 0x7f00 /* Right Volume Mask */ + +#define ENVY24_MT_VOLIDX 0x3a /* Volume Control Stream Index Register */ +#define ENVY24_MT_VOLRATE 0x3b /* Volume Control Rate Register */ +#define ENVY24_MT_MONAC97 0x3c /* Digital Mixer Monitor Routing Control Register */ +#define ENVY24_MT_PEAKIDX 0x3e /* Peak Meter Index Register */ +#define ENVY24_MT_PEAKDAT 0x3f /* Peak Meter Data Register */ + +/* -------------------------------------------------------------------- */ + +/* ENVY24 mixer channel defines */ +/* + ENVY24 mixer has original line matrix. So, general mixer command is not + able to use for this. If system has consumer AC'97 output, AC'97 line is + used as master mixer, and it is able to control. +*/ +#define ENVY24_CHAN_NUM 11 /* Play * 5 + Record * 5 + Mix * 1 */ + +#define ENVY24_CHAN_PLAY_DAC1 0 +#define ENVY24_CHAN_PLAY_DAC2 1 +#define ENVY24_CHAN_PLAY_DAC3 2 +#define ENVY24_CHAN_PLAY_DAC4 3 +#define ENVY24_CHAN_PLAY_SPDIF 4 +#define ENVY24_CHAN_REC_ADC1 5 +#define ENVY24_CHAN_REC_ADC2 6 +#define ENVY24_CHAN_REC_ADC3 7 +#define ENVY24_CHAN_REC_ADC4 8 +#define ENVY24_CHAN_REC_SPDIF 9 +#define ENVY24_CHAN_REC_MIX 10 + +#define ENVY24_MIX_MASK 0x3ff +#define ENVY24_MIX_REC_MASK 0x3e0 + +/* volume value constants */ +#define ENVY24_VOL_MAX 0 /* 0db(negate) */ +#define ENVY24_VOL_MIN 96 /* -144db(negate) */ +#define ENVY24_VOL_MUTE 127 /* mute */ + +/* -------------------------------------------------------------------- */ + +/* ENVY24 routing control defines */ +/* + ENVY24 has input->output data routing matrix switch. But original ENVY24 + matrix control is so complex. So, in this driver, matrix control is + defined 4 parameters. + + 1: output DAC channels (include S/PDIF output) + 2: output data classes + a. direct output from DMA + b. MIXER output which mixed the DMA outputs and input channels + (NOTICE: this class is able to set only DAC-1 and S/PDIF output) + c. direct input from ADC + d. direct input from S/PDIF + 3: input ADC channel selection(when 2:c. is selected) + 4: left/right reverse + + These parameters matrix is bit reduced from original ENVY24 matrix + pattern(ex. route different ADC input to one DAC). But almost case + this is enough to use. +*/ +#define ENVY24_ROUTE_DAC_1 0 +#define ENVY24_ROUTE_DAC_2 1 +#define ENVY24_ROUTE_DAC_3 2 +#define ENVY24_ROUTE_DAC_4 3 +#define ENVY24_ROUTE_DAC_SPDIF 4 + +#define ENVY24_ROUTE_CLASS_DMA 0 +#define ENVY24_ROUTE_CLASS_MIX 1 +#define ENVY24_ROUTE_CLASS_ADC 2 +#define ENVY24_ROUTE_CLASS_SPDIF 3 + +#define ENVY24_ROUTE_ADC_1 0 +#define ENVY24_ROUTE_ADC_2 1 +#define ENVY24_ROUTE_ADC_3 2 +#define ENVY24_ROUTE_ADC_4 3 + +#define ENVY24_ROUTE_NORMAL 0 +#define ENVY24_ROUTE_REVERSE 1 +#define ENVY24_ROUTE_LEFT 0 +#define ENVY24_ROUTE_RIGHT 1 + +/* -------------------------------------------------------------------- */ + +/* + These map values are refferd from ALSA sound driver. +*/ +/* ENVY24 configuration E2PROM map */ +#define ENVY24_E2PROM_SUBVENDOR 0x00 +#define ENVY24_E2PROM_SUBDEVICE 0x02 +#define ENVY24_E2PROM_SIZE 0x04 +#define ENVY24_E2PROM_VERSION 0x05 +#define ENVY24_E2PROM_SCFG 0x06 +#define ENVY24_E2PROM_ACL 0x07 +#define ENVY24_E2PROM_I2S 0x08 +#define ENVY24_E2PROM_SPDIF 0x09 +#define ENVY24_E2PROM_GPIOMASK 0x0a +#define ENVY24_E2PROM_GPIOSTATE 0x0b +#define ENVY24_E2PROM_GPIODIR 0x0c +#define ENVY24_E2PROM_AC97MAIN 0x0d +#define ENVY24_E2PROM_AC97PCM 0x0f +#define ENVY24_E2PROM_AC97REC 0x11 +#define ENVY24_E2PROM_AC97RECSRC 0x13 +#define ENVY24_E2PROM_DACID 0x14 +#define ENVY24_E2PROM_ADCID 0x18 +#define ENVY24_E2PROM_EXTRA 0x1c + +/* GPIO connect map of M-Audio Delta series */ +#define ENVY24_GPIO_CS84X4_PRO 0x01 +#define ENVY24_GPIO_CS8414_STATUS 0x02 +#define ENVY24_GPIO_CS84X4_CLK 0x04 +#define ENVY24_GPIO_CS84X4_DATA 0x08 +#define ENVY24_GPIO_AK4524_CDTI 0x10 /* this value is duplicated to input select */ +#define ENVY24_GPIO_AK4524_CCLK 0x20 +#define ENVY24_GPIO_AK4524_CS0 0x40 +#define ENVY24_GPIO_AK4524_CS1 0x80 + +/* M-Audio Delta series S/PDIF(CS84[01]4) control pin values */ +#define ENVY24_CS8404_PRO_RATE 0x18 +#define ENVY24_CS8404_PRO_RATE32 0x00 +#define ENVY24_CS8404_PRO_RATE441 0x10 +#define ENVY24_CS8404_PRO_RATE48 0x08 + +/* M-Audio Delta series parameter */ +#define ENVY24_DELTA_AK4524_CIF 0 + +#define I2C_DELAY 1000 + +/* PCA9554 registers */ +#define PCA9554_I2CDEV 0x40 /* I2C device address */ +#define PCA9554_IN 0x00 /* input port */ +#define PCA9554_OUT 0x01 /* output port */ +#define PCA9554_INVERT 0x02 /* polarity invert */ +#define PCA9554_DIR 0x03 /* port directions */ + +/* PCF8574 registers */ +#define PCF8574_I2CDEV_DAC 0x48 +#define PCF8574_SENSE_MASK 0x40 + +/* end of file */ --- sys/dev/sound/pci/envy24ht.c.orig Thu Jan 1 07:30:00 1970 +++ sys/dev/sound/pci/envy24ht.c Thu Jul 12 12:04:19 2007 @@ -0,0 +1,2601 @@ +/* + * Copyright (c) 2006 Konstantin Dimitrov + * 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 THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +/* + * Konstantin Dimitrov's thanks list: + * + * A huge thanks goes to Spas Filipov for his friendship, support and his + * generous gift - an 'Audiotrak Prodigy HD2' audio card! I also want to + * thank Keiichi Iwasaki and his parents, because they helped Spas to get + * the card from Japan! Having hardware sample of Prodigy HD2 made adding + * support for that great card very easy and real fun and pleasure. + * + */ + +#include +#include +#include +#include + +#include +#include + +#include "mixer_if.h" + +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/envy24ht.c,v 1.15 2007/06/02 17:28:26 ariff Exp $"); + +MALLOC_DEFINE(M_ENVY24HT, "envy24ht", "envy24ht audio"); + +/* -------------------------------------------------------------------- */ + +struct sc_info; + +#define ENVY24HT_PLAY_CHNUM 8 +#define ENVY24HT_REC_CHNUM 2 +#define ENVY24HT_PLAY_BUFUNIT (4 /* byte/sample */ * 8 /* channel */) +#define ENVY24HT_REC_BUFUNIT (4 /* byte/sample */ * 2 /* channel */) +#define ENVY24HT_SAMPLE_NUM 4096 + +#define ENVY24HT_TIMEOUT 1000 + +#define ENVY24HT_DEFAULT_FORMAT (AFMT_STEREO | AFMT_S16_LE) + +#define ENVY24HT_NAMELEN 32 + +struct envy24ht_sample { + volatile u_int32_t buffer; +}; + +typedef struct envy24ht_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_int32_t gpiomask, gpiostate, gpiodir; + u_int32_t cdti, cclk, cs; + u_int8_t cif, type, 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; + /* 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[ENVY24HT_CHAN_NUM]; + u_int8_t right[ENVY24HT_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 envy24ht_p8u(struct sc_chinfo *); +static void envy24ht_p16sl(struct sc_chinfo *); +static void envy24ht_p32sl(struct sc_chinfo *); +static void envy24ht_r16sl(struct sc_chinfo *); +static void envy24ht_r32sl(struct sc_chinfo *); + +/* channel interface */ +static void *envy24htchan_init(kobj_t, void *, struct snd_dbuf *, struct pcm_channel *, int); +static int envy24htchan_setformat(kobj_t, void *, u_int32_t); +static int envy24htchan_setspeed(kobj_t, void *, u_int32_t); +static int envy24htchan_setblocksize(kobj_t, void *, u_int32_t); +static int envy24htchan_trigger(kobj_t, void *, int); +static int envy24htchan_getptr(kobj_t, void *); +static struct pcmchan_caps *envy24htchan_getcaps(kobj_t, void *); + +/* mixer interface */ +static int envy24htmixer_init(struct snd_mixer *); +static int envy24htmixer_reinit(struct snd_mixer *); +static int envy24htmixer_uninit(struct snd_mixer *); +static int envy24htmixer_set(struct snd_mixer *, unsigned, unsigned, unsigned); +static u_int32_t envy24htmixer_setrecsrc(struct snd_mixer *, u_int32_t); + +/* SPI codec access interface */ +static void *envy24ht_spi_create(device_t, void *, int, int); +static void envy24ht_spi_destroy(void *); +static void envy24ht_spi_init(void *); +static void envy24ht_spi_reinit(void *); +static void envy24ht_spi_setvolume(void *, int, unsigned int, unsigned int); + +/* -------------------------------------------------------------------- */ + +/* + system constant tables +*/ + +/* API -> hardware channel map */ +static unsigned envy24ht_chanmap[ENVY24HT_CHAN_NUM] = { + ENVY24HT_CHAN_PLAY_DAC1, /* 1 */ + ENVY24HT_CHAN_PLAY_DAC2, /* 2 */ + ENVY24HT_CHAN_PLAY_DAC3, /* 3 */ + ENVY24HT_CHAN_PLAY_DAC4, /* 4 */ + ENVY24HT_CHAN_PLAY_SPDIF, /* 0 */ + ENVY24HT_CHAN_REC_MIX, /* 5 */ + ENVY24HT_CHAN_REC_SPDIF, /* 6 */ + ENVY24HT_CHAN_REC_ADC1, /* 7 */ + ENVY24HT_CHAN_REC_ADC2, /* 8 */ + ENVY24HT_CHAN_REC_ADC3, /* 9 */ + ENVY24HT_CHAN_REC_ADC4, /* 10 */ +}; + +/* mixer -> API channel map. see above */ +static int envy24ht_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 envy24ht_speed[] = { + 192000, 176400, 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, + 12000, 11025, 9600, 8000, 0 +}; + +/* known boards configuration */ +static struct codec_entry spi_codec = { + envy24ht_spi_create, + envy24ht_spi_destroy, + envy24ht_spi_init, + envy24ht_spi_reinit, + envy24ht_spi_setvolume, + NULL, /* setrate */ +}; + +static struct cfg_info cfg_table[] = { + { + "Envy24HT audio (Terratec Aureon 7.1 Space)", + 0x153b, 0x1145, + 0x0b, 0x80, 0xfc, 0xc3, + 0x21efff, 0x7fffff, 0x5e1000, + 0x40000, 0x80000, 0x1000, 0x00, 0x02, + 0, + &spi_codec, + }, + { + "Envy24HT audio (Terratec Aureon 5.1 Sky)", + 0x153b, 0x1147, + 0x0a, 0x80, 0xfc, 0xc3, + 0x21efff, 0x7fffff, 0x5e1000, + 0x40000, 0x80000, 0x1000, 0x00, 0x02, + 0, + &spi_codec, + }, + { + "Envy24HT audio (Terratec Aureon 7.1 Universe)", + 0x153b, 0x1153, + 0x0b, 0x80, 0xfc, 0xc3, + 0x21efff, 0x7fffff, 0x5e1000, + 0x40000, 0x80000, 0x1000, 0x00, 0x02, + 0, + &spi_codec, + }, + { + "Envy24HT audio (AudioTrak Prodigy 7.1)", + 0x4933, 0x4553, + 0x0b, 0x80, 0xfc, 0xc3, + 0x21efff, 0x7fffff, 0x5e1000, + 0x40000, 0x80000, 0x1000, 0x00, 0x02, + 0, + &spi_codec, + }, + { + "Envy24HT audio (Terratec PHASE 28)", + 0x153b, 0x1149, + 0x0b, 0x80, 0xfc, 0xc3, + 0x21efff, 0x7fffff, 0x5e1000, + 0x40000, 0x80000, 0x1000, 0x00, 0x02, + 0, + &spi_codec, + }, + { + "Envy24HT-S audio (Terratec PHASE 22)", + 0x153b, 0x1150, + 0x10, 0x80, 0xf0, 0xc3, + 0x7ffbc7, 0x7fffff, 0x438, + 0x20, 0x10, 0x400, 0x00, 0x00, + 0, + &spi_codec, + }, + { + "Envy24HT audio (AudioTrak Prodigy 7.1 LT)", + 0x3132, 0x4154, + 0x4b, 0x80, 0xfc, 0xc3, + 0x7ff8ff, 0x7fffff, 0x700, + 0x400, 0x200, 0x100, 0x00, 0x02, + 0, + &spi_codec, + }, + { + "Envy24HT audio (AudioTrak Prodigy 7.1 XT)", + 0x3136, 0x4154, + 0x4b, 0x80, 0xfc, 0xc3, + 0x7ff8ff, 0x7fffff, 0x700, + 0x400, 0x200, 0x100, 0x00, 0x02, + 0, + &spi_codec, + }, + { + "Envy24HT audio (M-Audio Revolution 7.1)", + 0x1412, 0x3630, + 0x43, 0x80, 0xf8, 0xc1, + 0x3fff85, 0x72, 0x4000fa, + 0x08, 0x02, 0x20, 0x00, 0x04, + 0, + &spi_codec, + }, + { + "Envy24GT audio (M-Audio Revolution 5.1)", + 0x1412, 0x3631, + 0x42, 0x80, 0xf8, 0xc1, + 0x3fff85, 0x72, 0x4000fa, + 0x08, 0x02, 0x10, 0x00, 0x03, + 0, + &spi_codec, + }, + { + "Envy24HT audio (M-Audio Audiophile 192)", + 0x1412, 0x3632, + 0x68, 0x80, 0xf8, 0xc3, + 0x45, 0x4000b5, 0x7fffba, + 0x08, 0x02, 0x10, 0x00, 0x03, + 0, + &spi_codec, + }, + { + "Envy24HT audio (AudioTrak Prodigy HD2)", + 0x3137, 0x4154, + 0x68, 0x80, 0x78, 0xc3, + 0xfff8ff, 0x200700, 0xdfffff, + 0x400, 0x200, 0x100, 0x00, 0x05, + 0, + &spi_codec, + }, + { + "Envy24HT audio (ESI Juli@)", + 0x3031, 0x4553, + 0x20, 0x80, 0xf8, 0xc3, + 0x7fff9f, 0x8016, 0x7fff9f, + 0x08, 0x02, 0x10, 0x00, 0x03, + 0, + &spi_codec, + }, + { + "Envy24HT audio (Generic)", + 0, 0, + 0x0b, 0x80, 0xfc, 0xc3, + 0x21efff, 0x7fffff, 0x5e1000, + 0x40000, 0x80000, 0x1000, 0x00, 0x02, + 0, + &spi_codec, /* default codec routines */ + } +}; + +static u_int32_t envy24ht_recfmt[] = { + AFMT_STEREO | AFMT_S16_LE, + AFMT_STEREO | AFMT_S32_LE, + 0 +}; +static struct pcmchan_caps envy24ht_reccaps = {8000, 96000, envy24ht_recfmt, 0}; + +static u_int32_t envy24ht_playfmt[] = { + AFMT_STEREO | AFMT_U8, + AFMT_STEREO | AFMT_S16_LE, + AFMT_STEREO | AFMT_S32_LE, + 0 +}; + +static struct pcmchan_caps envy24ht_playcaps = {8000, 192000, envy24ht_playfmt, 0}; + +struct envy24ht_emldma { + u_int32_t format; + void (*emldma)(struct sc_chinfo *); + int unit; +}; + +static struct envy24ht_emldma envy24ht_pemltab[] = { + {AFMT_STEREO | AFMT_U8, envy24ht_p8u, 2}, + {AFMT_STEREO | AFMT_S16_LE, envy24ht_p16sl, 4}, + {AFMT_STEREO | AFMT_S32_LE, envy24ht_p32sl, 8}, + {0, NULL, 0} +}; + +static struct envy24ht_emldma envy24ht_remltab[] = { + {AFMT_STEREO | AFMT_S16_LE, envy24ht_r16sl, 4}, + {AFMT_STEREO | AFMT_S32_LE, envy24ht_r32sl, 8}, + {0, NULL, 0} +}; + +/* -------------------------------------------------------------------- */ + +/* common routines */ +static u_int32_t +envy24ht_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 +envy24ht_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 +envy24ht_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 +envy24ht_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; + } +} + +/* -------------------------------------------------------------------- */ + +/* I2C port/E2PROM access routines */ + +static int +envy24ht_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, "envy24ht_rdi2c(sc, 0x%02x, 0x%02x)\n", dev, addr); +#endif + for (i = 0; i < ENVY24HT_TIMEOUT; i++) { + data = envy24ht_rdcs(sc, ENVY24HT_CCS_I2CSTAT, 1); + if ((data & ENVY24HT_CCS_I2CSTAT_BSY) == 0) + break; + DELAY(32); /* 31.25kHz */ + } + if (i == ENVY24HT_TIMEOUT) { + return -1; + } + envy24ht_wrcs(sc, ENVY24HT_CCS_I2CADDR, addr, 1); + envy24ht_wrcs(sc, ENVY24HT_CCS_I2CDEV, + (dev & ENVY24HT_CCS_I2CDEV_ADDR) | ENVY24HT_CCS_I2CDEV_RD, 1); + for (i = 0; i < ENVY24HT_TIMEOUT; i++) { + data = envy24ht_rdcs(sc, ENVY24HT_CCS_I2CSTAT, 1); + if ((data & ENVY24HT_CCS_I2CSTAT_BSY) == 0) + break; + DELAY(32); /* 31.25kHz */ + } + if (i == ENVY24HT_TIMEOUT) { + return -1; + } + data = envy24ht_rdcs(sc, ENVY24HT_CCS_I2CDATA, 1); + +#if(0) + device_printf(sc->dev, "envy24ht_rdi2c(): return 0x%x\n", data); +#endif + return (int)data; +} + +static int +envy24ht_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, "envy24ht_rdi2c(sc, 0x%02x, 0x%02x)\n", dev, addr); +#endif + for (i = 0; i < ENVY24HT_TIMEOUT; i++) { + tmp = envy24ht_rdcs(sc, ENVY24HT_CCS_I2CSTAT, 1); + if ((tmp & ENVY24HT_CCS_I2CSTAT_BSY) == 0) + break; + DELAY(32); /* 31.25kHz */ + } + if (i == ENVY24HT_TIMEOUT) { + return -1; + } + envy24ht_wrcs(sc, ENVY24HT_CCS_I2CADDR, addr, 1); + envy24ht_wrcs(sc, ENVY24HT_CCS_I2CDATA, data, 1); + envy24ht_wrcs(sc, ENVY24HT_CCS_I2CDEV, + (dev & ENVY24HT_CCS_I2CDEV_ADDR) | ENVY24HT_CCS_I2CDEV_WR, 1); + for (i = 0; i < ENVY24HT_TIMEOUT; i++) { + data = envy24ht_rdcs(sc, ENVY24HT_CCS_I2CSTAT, 1); + if ((data & ENVY24HT_CCS_I2CSTAT_BSY) == 0) + break; + DELAY(32); /* 31.25kHz */ + } + if (i == ENVY24HT_TIMEOUT) { + return -1; + } + + return 0; +} + +static int +envy24ht_rdrom(struct sc_info *sc, u_int32_t addr) +{ + u_int32_t data; + +#if(0) + device_printf(sc->dev, "envy24ht_rdrom(sc, 0x%02x)\n", addr); +#endif + data = envy24ht_rdcs(sc, ENVY24HT_CCS_I2CSTAT, 1); + if ((data & ENVY24HT_CCS_I2CSTAT_ROM) == 0) { +#if(0) + device_printf(sc->dev, "envy24ht_rdrom(): E2PROM not presented\n"); +#endif + return -1; + } + + return envy24ht_rdi2c(sc, ENVY24HT_CCS_I2CDEV_ROM, addr); +} + +static struct cfg_info * +envy24ht_rom2cfg(struct sc_info *sc) +{ + struct cfg_info *buff; + int size; + int i; + +#if(0) + device_printf(sc->dev, "envy24ht_rom2cfg(sc)\n"); +#endif + size = envy24ht_rdrom(sc, ENVY24HT_E2PROM_SIZE); + if ((size < ENVY24HT_E2PROM_GPIOSTATE + 3) || (size == 0x78)) { +#if(0) + device_printf(sc->dev, "envy24ht_rom2cfg(): ENVY24HT_E2PROM_SIZE-->%d\n", size); +#endif + buff = malloc(sizeof(*buff), M_ENVY24HT, M_NOWAIT); + if (buff == NULL) { +#if(0) + device_printf(sc->dev, "envy24ht_rom2cfg(): malloc()\n"); +#endif + return NULL; + } + buff->free = 1; + + /* no valid e2prom, using default values */ + buff->subvendor = envy24ht_rdrom(sc, ENVY24HT_E2PROM_SUBVENDOR) << 8; + buff->subvendor += envy24ht_rdrom(sc, ENVY24HT_E2PROM_SUBVENDOR + 1); + buff->subdevice = envy24ht_rdrom(sc, ENVY24HT_E2PROM_SUBDEVICE) << 8; + buff->subdevice += envy24ht_rdrom(sc, ENVY24HT_E2PROM_SUBDEVICE + 1); + buff->scfg = 0x0b; + buff->acl = 0x80; + buff->i2s = 0xfc; + buff->spdif = 0xc3; + buff->gpiomask = 0x21efff; + buff->gpiostate = 0x7fffff; + buff->gpiodir = 0x5e1000; + buff->cdti = 0x40000; + buff->cclk = 0x80000; + buff->cs = 0x1000; + buff->cif = 0x00; + buff->type = 0x02; + + 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; +#if 0 + return NULL; +#endif + } + buff = malloc(sizeof(*buff), M_ENVY24HT, M_NOWAIT); + if (buff == NULL) { +#if(0) + device_printf(sc->dev, "envy24ht_rom2cfg(): malloc()\n"); +#endif + return NULL; + } + buff->free = 1; + + buff->subvendor = envy24ht_rdrom(sc, ENVY24HT_E2PROM_SUBVENDOR) << 8; + buff->subvendor += envy24ht_rdrom(sc, ENVY24HT_E2PROM_SUBVENDOR + 1); + buff->subdevice = envy24ht_rdrom(sc, ENVY24HT_E2PROM_SUBDEVICE) << 8; + buff->subdevice += envy24ht_rdrom(sc, ENVY24HT_E2PROM_SUBDEVICE + 1); + buff->scfg = envy24ht_rdrom(sc, ENVY24HT_E2PROM_SCFG); + buff->acl = envy24ht_rdrom(sc, ENVY24HT_E2PROM_ACL); + buff->i2s = envy24ht_rdrom(sc, ENVY24HT_E2PROM_I2S); + buff->spdif = envy24ht_rdrom(sc, ENVY24HT_E2PROM_SPDIF); + buff->gpiomask = envy24ht_rdrom(sc, ENVY24HT_E2PROM_GPIOMASK) | \ + envy24ht_rdrom(sc, ENVY24HT_E2PROM_GPIOMASK + 1) << 8 | \ + envy24ht_rdrom(sc, ENVY24HT_E2PROM_GPIOMASK + 2) << 16; + buff->gpiostate = envy24ht_rdrom(sc, ENVY24HT_E2PROM_GPIOSTATE) | \ + envy24ht_rdrom(sc, ENVY24HT_E2PROM_GPIOSTATE + 1) << 8 | \ + envy24ht_rdrom(sc, ENVY24HT_E2PROM_GPIOSTATE + 2) << 16; + buff->gpiodir = envy24ht_rdrom(sc, ENVY24HT_E2PROM_GPIODIR) | \ + envy24ht_rdrom(sc, ENVY24HT_E2PROM_GPIODIR + 1) << 8 | \ + envy24ht_rdrom(sc, ENVY24HT_E2PROM_GPIODIR + 2) << 16; + + 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 +envy24ht_cfgfree(struct cfg_info *cfg) { + if (cfg == NULL) + return; + if (cfg->free) + free(cfg, M_ENVY24HT); + return; +} + +/* -------------------------------------------------------------------- */ + +/* AC'97 codec access routines */ + +#if 0 +static int +envy24ht_coldcd(struct sc_info *sc) +{ + u_int32_t data; + int i; + +#if(0) + device_printf(sc->dev, "envy24ht_coldcd()\n"); +#endif + envy24ht_wrmt(sc, ENVY24HT_MT_AC97CMD, ENVY24HT_MT_AC97CMD_CLD, 1); + DELAY(10); + envy24ht_wrmt(sc, ENVY24HT_MT_AC97CMD, 0, 1); + DELAY(1000); + for (i = 0; i < ENVY24HT_TIMEOUT; i++) { + data = envy24ht_rdmt(sc, ENVY24HT_MT_AC97CMD, 1); + if (data & ENVY24HT_MT_AC97CMD_RDY) { + return 0; + } + } + + return -1; +} + +static int +envy24ht_slavecd(struct sc_info *sc) +{ + u_int32_t data; + int i; + +#if(0) + device_printf(sc->dev, "envy24ht_slavecd()\n"); +#endif + envy24ht_wrmt(sc, ENVY24HT_MT_AC97CMD, + ENVY24HT_MT_AC97CMD_CLD | ENVY24HT_MT_AC97CMD_WRM, 1); + DELAY(10); + envy24ht_wrmt(sc, ENVY24HT_MT_AC97CMD, 0, 1); + DELAY(1000); + for (i = 0; i < ENVY24HT_TIMEOUT; i++) { + data = envy24ht_rdmt(sc, ENVY24HT_MT_AC97CMD, 1); + if (data & ENVY24HT_MT_AC97CMD_RDY) { + return 0; + } + } + + return -1; +} + +static int +envy24ht_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, "envy24ht_rdcd(obj, sc, 0x%02x)\n", regno); +#endif + envy24ht_wrmt(sc, ENVY24HT_MT_AC97IDX, (u_int32_t)regno, 1); + envy24ht_wrmt(sc, ENVY24HT_MT_AC97CMD, ENVY24HT_MT_AC97CMD_RD, 1); + for (i = 0; i < ENVY24HT_TIMEOUT; i++) { + data = envy24ht_rdmt(sc, ENVY24HT_MT_AC97CMD, 1); + if ((data & ENVY24HT_MT_AC97CMD_RD) == 0) + break; + } + data = envy24ht_rdmt(sc, ENVY24HT_MT_AC97DLO, 2); + +#if(0) + device_printf(sc->dev, "envy24ht_rdcd(): return 0x%x\n", data); +#endif + return (int)data; +} + +static int +envy24ht_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, "envy24ht_wrcd(obj, sc, 0x%02x, 0x%04x)\n", regno, data); +#endif + envy24ht_wrmt(sc, ENVY24HT_MT_AC97IDX, (u_int32_t)regno, 1); + envy24ht_wrmt(sc, ENVY24HT_MT_AC97DLO, (u_int32_t)data, 2); + envy24ht_wrmt(sc, ENVY24HT_MT_AC97CMD, ENVY24HT_MT_AC97CMD_WR, 1); + for (i = 0; i < ENVY24HT_TIMEOUT; i++) { + cmd = envy24ht_rdmt(sc, ENVY24HT_MT_AC97CMD, 1); + if ((cmd & ENVY24HT_MT_AC97CMD_WR) == 0) + break; + } + + return 0; +} + +static kobj_method_t envy24ht_ac97_methods[] = { + KOBJMETHOD(ac97_read, envy24ht_rdcd), + KOBJMETHOD(ac97_write, envy24ht_wrcd), + {0, 0} +}; +AC97_DECLARE(envy24ht_ac97); +#endif + +/* -------------------------------------------------------------------- */ + +/* GPIO access routines */ + +static u_int32_t +envy24ht_gpiord(struct sc_info *sc) +{ + if (sc->cfg->subvendor == 0x153b && sc->cfg->subdevice == 0x1150) + return envy24ht_rdcs(sc, ENVY24HT_CCS_GPIO_LDATA, 2); + else + return (envy24ht_rdcs(sc, ENVY24HT_CCS_GPIO_HDATA, 1) << 16 | envy24ht_rdcs(sc, ENVY24HT_CCS_GPIO_LDATA, 2)); +} + +static void +envy24ht_gpiowr(struct sc_info *sc, u_int32_t data) +{ +#if(0) + device_printf(sc->dev, "envy24ht_gpiowr(sc, 0x%02x)\n", data & 0x7FFFFF); + return; +#endif + envy24ht_wrcs(sc, ENVY24HT_CCS_GPIO_LDATA, data, 2); + if (sc->cfg->subdevice != 0x1150) + envy24ht_wrcs(sc, ENVY24HT_CCS_GPIO_HDATA, data >> 16, 1); + return; +} + +#if 0 +static u_int32_t +envy24ht_gpiogetmask(struct sc_info *sc) +{ + return (envy24ht_rdcs(sc, ENVY24HT_CCS_GPIO_HMASK, 1) << 16 | envy24ht_rdcs(sc, ENVY24HT_CCS_GPIO_LMASK, 2)); +} +#endif + +static void +envy24ht_gpiosetmask(struct sc_info *sc, u_int32_t mask) +{ + envy24ht_wrcs(sc, ENVY24HT_CCS_GPIO_LMASK, mask, 2); + if (sc->cfg->subdevice != 0x1150) + envy24ht_wrcs(sc, ENVY24HT_CCS_GPIO_HMASK, mask >> 16, 1); + return; +} + +#if 0 +static u_int32_t +envy24ht_gpiogetdir(struct sc_info *sc) +{ + return envy24ht_rdcs(sc, ENVY24HT_CCS_GPIO_CTLDIR, 4); +} +#endif + +static void +envy24ht_gpiosetdir(struct sc_info *sc, u_int32_t dir) +{ + if (sc->cfg->subvendor == 0x153b && sc->cfg->subdevice == 0x1150) + envy24ht_wrcs(sc, ENVY24HT_CCS_GPIO_CTLDIR, dir, 2); + else + envy24ht_wrcs(sc, ENVY24HT_CCS_GPIO_CTLDIR, dir, 4); + return; +} + +/* -------------------------------------------------------------------- */ + +/* SPI codec access interface routine */ + +struct envy24ht_spi_codec { + struct spicds_info *info; + struct sc_info *parent; + int dir; + int num; + int cs, cclk, cdti; +}; + +static void +envy24ht_spi_ctl(void *codec, unsigned int cs, unsigned int cclk, unsigned int cdti) +{ + u_int32_t data = 0; + struct envy24ht_spi_codec *ptr = codec; + +#if(0) + device_printf(ptr->parent->dev, "--> %d, %d, %d\n", cs, cclk, cdti); +#endif + data = envy24ht_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; + envy24ht_gpiowr(ptr->parent, data); + return; +} + +static void * +envy24ht_spi_create(device_t dev, void *info, int dir, int num) +{ + struct sc_info *sc = info; + struct envy24ht_spi_codec *buff = NULL; + +#if(0) + device_printf(sc->dev, "envy24ht_spi_create(dev, sc, %d, %d)\n", dir, num); +#endif + + buff = malloc(sizeof(*buff), M_ENVY24HT, M_NOWAIT); + if (buff == NULL) + return NULL; + + if (dir == PCMDIR_REC && sc->adc[num] != NULL) + buff->info = ((struct envy24ht_spi_codec *)sc->adc[num])->info; + else if (dir == PCMDIR_PLAY && sc->dac[num] != NULL) + buff->info = ((struct envy24ht_spi_codec *)sc->dac[num])->info; + else + buff->info = spicds_create(dev, buff, num, envy24ht_spi_ctl); + if (buff->info == NULL) { + free(buff, M_ENVY24HT); + return NULL; + } + + buff->parent = sc; + buff->dir = dir; + buff->num = num; + + return (void *)buff; +} + +static void +envy24ht_spi_destroy(void *codec) +{ + struct envy24ht_spi_codec *ptr = codec; + if (ptr == NULL) + return; +#if(0) + device_printf(ptr->parent->dev, "envy24ht_spi_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_ENVY24HT); +} + +static void +envy24ht_spi_init(void *codec) +{ + struct envy24ht_spi_codec *ptr = codec; + if (ptr == NULL) + return; +#if(0) + device_printf(ptr->parent->dev, "envy24ht_spicds_init()\n"); +#endif + ptr->cs = ptr->parent->cfg->cs; + 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); + if (ptr->parent->cfg->type == SPICDS_TYPE_AK4524 || \ + ptr->parent->cfg->type == SPICDS_TYPE_AK4528) { + 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); +} + +static void +envy24ht_spi_reinit(void *codec) +{ + struct envy24ht_spi_codec *ptr = codec; + if (ptr == NULL) + return; +#if(0) + device_printf(ptr->parent->dev, "envy24ht_spi_reinit()\n"); +#endif + + spicds_reinit(ptr->info); +} + +static void +envy24ht_spi_setvolume(void *codec, int dir, unsigned int left, unsigned int right) +{ + struct envy24ht_spi_codec *ptr = codec; + if (ptr == NULL) + return; +#if(0) + device_printf(ptr->parent->dev, "envy24ht_spi_set()\n"); +#endif + + spicds_set(ptr->info, dir, left, right); +} + +/* -------------------------------------------------------------------- */ + +/* hardware access routeines */ + +static struct { + u_int32_t speed; + u_int32_t code; +} envy24ht_speedtab[] = { + {48000, ENVY24HT_MT_RATE_48000}, + {24000, ENVY24HT_MT_RATE_24000}, + {12000, ENVY24HT_MT_RATE_12000}, + {9600, ENVY24HT_MT_RATE_9600}, + {32000, ENVY24HT_MT_RATE_32000}, + {16000, ENVY24HT_MT_RATE_16000}, + {8000, ENVY24HT_MT_RATE_8000}, + {96000, ENVY24HT_MT_RATE_96000}, + {192000, ENVY24HT_MT_RATE_192000}, + {64000, ENVY24HT_MT_RATE_64000}, + {44100, ENVY24HT_MT_RATE_44100}, + {22050, ENVY24HT_MT_RATE_22050}, + {11025, ENVY24HT_MT_RATE_11025}, + {88200, ENVY24HT_MT_RATE_88200}, + {176400, ENVY24HT_MT_RATE_176400}, + {0, 0x10} +}; + +static int +envy24ht_setspeed(struct sc_info *sc, u_int32_t speed) { + u_int32_t code, i2sfmt; + int i = 0; + +#if(0) + device_printf(sc->dev, "envy24ht_setspeed(sc, %d)\n", speed); + if (speed == 0) { + code = ENVY24HT_MT_RATE_SPDIF; /* external master clock */ + envy24ht_slavecd(sc); + } + else { +#endif + for (i = 0; envy24ht_speedtab[i].speed != 0; i++) { + if (envy24ht_speedtab[i].speed == speed) + break; + } + code = envy24ht_speedtab[i].code; +#if 0 + } + device_printf(sc->dev, "envy24ht_setspeed(): speed %d/code 0x%04x\n", envy24ht_speedtab[i].speed, code); +#endif + if (code < 0x10) { + envy24ht_wrmt(sc, ENVY24HT_MT_RATE, code, 1); + if ((((sc->cfg->scfg & ENVY24HT_CCSM_SCFG_XIN2) == 0x00) && (code == ENVY24HT_MT_RATE_192000)) || \ + (code == ENVY24HT_MT_RATE_176400)) { + i2sfmt = envy24ht_rdmt(sc, ENVY24HT_MT_I2S, 1); + i2sfmt |= ENVY24HT_MT_I2S_MLR128; + envy24ht_wrmt(sc, ENVY24HT_MT_I2S, i2sfmt, 1); + } + else { + i2sfmt = envy24ht_rdmt(sc, ENVY24HT_MT_I2S, 1); + i2sfmt &= ~ENVY24HT_MT_I2S_MLR128; + envy24ht_wrmt(sc, ENVY24HT_MT_I2S, i2sfmt, 1); + } + code = envy24ht_rdmt(sc, ENVY24HT_MT_RATE, 1); + code &= ENVY24HT_MT_RATE_MASK; + for (i = 0; envy24ht_speedtab[i].code < 0x10; i++) { + if (envy24ht_speedtab[i].code == code) + break; + } + speed = envy24ht_speedtab[i].speed; + } + else + speed = 0; + +#if(0) + device_printf(sc->dev, "envy24ht_setspeed(): return %d\n", speed); +#endif + return speed; +} + +static void +envy24ht_setvolume(struct sc_info *sc, unsigned ch) +{ +#if(0) + device_printf(sc->dev, "envy24ht_setvolume(sc, %d)\n", ch); + envy24ht_wrmt(sc, ENVY24HT_MT_VOLIDX, ch * 2, 1); + envy24ht_wrmt(sc, ENVY24HT_MT_VOLUME, 0x7f00 | sc->left[ch], 2); + envy24ht_wrmt(sc, ENVY24HT_MT_VOLIDX, ch * 2 + 1, 1); + envy24ht_wrmt(sc, ENVY24HT_MT_VOLUME, (sc->right[ch] << 8) | 0x7f, 2); +#endif +} + +static void +envy24ht_mutevolume(struct sc_info *sc, unsigned ch) +{ +#if 0 + u_int32_t vol; + + device_printf(sc->dev, "envy24ht_mutevolume(sc, %d)\n", ch); + vol = ENVY24HT_VOL_MUTE << 8 | ENVY24HT_VOL_MUTE; + envy24ht_wrmt(sc, ENVY24HT_MT_VOLIDX, ch * 2, 1); + envy24ht_wrmt(sc, ENVY24HT_MT_VOLUME, vol, 2); + envy24ht_wrmt(sc, ENVY24HT_MT_VOLIDX, ch * 2 + 1, 1); + envy24ht_wrmt(sc, ENVY24HT_MT_VOLUME, vol, 2); +#endif +} + +static u_int32_t +envy24ht_gethwptr(struct sc_info *sc, int dir) +{ + int unit, regno; + u_int32_t ptr, rtn; + +#if(0) + device_printf(sc->dev, "envy24ht_gethwptr(sc, %d)\n", dir); +#endif + if (dir == PCMDIR_PLAY) { + rtn = sc->psize / 4; + unit = ENVY24HT_PLAY_BUFUNIT / 4; + regno = ENVY24HT_MT_PCNT; + } + else { + rtn = sc->rsize / 4; + unit = ENVY24HT_REC_BUFUNIT / 4; + regno = ENVY24HT_MT_RCNT; + } + + ptr = envy24ht_rdmt(sc, regno, 2); + rtn -= (ptr + 1); + rtn /= unit; + +#if(0) + device_printf(sc->dev, "envy24ht_gethwptr(): return %d\n", rtn); +#endif + return rtn; +} + +static void +envy24ht_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, "envy24ht_updintr(sc, %d)\n", dir); +#endif + if (dir == PCMDIR_PLAY) { + blk = sc->blk[0]; + size = sc->psize / 4; + regptr = ENVY24HT_MT_PCNT; + regintr = ENVY24HT_MT_PTERM; + mask = ~ENVY24HT_MT_INT_PMASK; + } + else { + blk = sc->blk[1]; + size = sc->rsize / 4; + regptr = ENVY24HT_MT_RCNT; + regintr = ENVY24HT_MT_RTERM; + mask = ~ENVY24HT_MT_INT_RMASK; + } + + ptr = size - envy24ht_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, "envy24ht_updintr():ptr = %d, blk = %d, cnt = %d\n", ptr, blk, cnt); +#endif + envy24ht_wrmt(sc, regintr, cnt, 2); + intr = envy24ht_rdmt(sc, ENVY24HT_MT_INT_MASK, 1); +#if(0) + device_printf(sc->dev, "envy24ht_updintr():intr = 0x%02x, mask = 0x%02x\n", intr, mask); +#endif + envy24ht_wrmt(sc, ENVY24HT_MT_INT_MASK, intr & mask, 1); +#if(0) + device_printf(sc->dev, "envy24ht_updintr():INT-->0x%02x\n", + envy24ht_rdmt(sc, ENVY24HT_MT_INT_MASK, 1)); +#endif + + return; +} + +#if 0 +static void +envy24ht_maskintr(struct sc_info *sc, int dir) +{ + u_int32_t mask, intr; + +#if(0) + device_printf(sc->dev, "envy24ht_maskintr(sc, %d)\n", dir); +#endif + if (dir == PCMDIR_PLAY) + mask = ENVY24HT_MT_INT_PMASK; + else + mask = ENVY24HT_MT_INT_RMASK; + intr = envy24ht_rdmt(sc, ENVY24HT_MT_INT, 1); + envy24ht_wrmt(sc, ENVY24HT_MT_INT, intr | mask, 1); + + return; +} +#endif + +static int +envy24ht_checkintr(struct sc_info *sc, int dir) +{ + u_int32_t mask, stat, intr, rtn; + +#if(0) + device_printf(sc->dev, "envy24ht_checkintr(sc, %d)\n", dir); +#endif + intr = envy24ht_rdmt(sc, ENVY24HT_MT_INT_STAT, 1); + if (dir == PCMDIR_PLAY) { + if ((rtn = intr & ENVY24HT_MT_INT_PSTAT) != 0) { + mask = ~ENVY24HT_MT_INT_RSTAT; + envy24ht_wrmt(sc, 0x1a, 0x01, 1); + envy24ht_wrmt(sc, ENVY24HT_MT_INT_STAT, (intr & mask) | ENVY24HT_MT_INT_PSTAT | 0x08, 1); + stat = envy24ht_rdmt(sc, ENVY24HT_MT_INT_MASK, 1); + envy24ht_wrmt(sc, ENVY24HT_MT_INT_MASK, stat | ENVY24HT_MT_INT_PMASK, 1); + } + } + else { + if ((rtn = intr & ENVY24HT_MT_INT_RSTAT) != 0) { + mask = ~ENVY24HT_MT_INT_PSTAT; +#if 0 + stat = ENVY24HT_MT_INT_RSTAT | ENVY24HT_MT_INT_RMASK; +#endif + envy24ht_wrmt(sc, ENVY24HT_MT_INT_STAT, (intr & mask) | ENVY24HT_MT_INT_RSTAT, 1); + stat = envy24ht_rdmt(sc, ENVY24HT_MT_INT_MASK, 1); + envy24ht_wrmt(sc, ENVY24HT_MT_INT_MASK, stat | ENVY24HT_MT_INT_RMASK, 1); + } + } + + return rtn; +} + +static void +envy24ht_start(struct sc_info *sc, int dir) +{ + u_int32_t stat, sw; + +#if(0) + device_printf(sc->dev, "envy24ht_start(sc, %d)\n", dir); +#endif + if (dir == PCMDIR_PLAY) + sw = ENVY24HT_MT_PCTL_PSTART; + else + sw = ENVY24HT_MT_PCTL_RSTART; + + stat = envy24ht_rdmt(sc, ENVY24HT_MT_PCTL, 1); + envy24ht_wrmt(sc, ENVY24HT_MT_PCTL, stat | sw, 1); +#if(0) + DELAY(100); + device_printf(sc->dev, "PADDR:0x%08x\n", envy24ht_rdmt(sc, ENVY24HT_MT_PADDR, 4)); + device_printf(sc->dev, "PCNT:%ld\n", envy24ht_rdmt(sc, ENVY24HT_MT_PCNT, 2)); +#endif + + return; +} + +static void +envy24ht_stop(struct sc_info *sc, int dir) +{ + u_int32_t stat, sw; + +#if(0) + device_printf(sc->dev, "envy24ht_stop(sc, %d)\n", dir); +#endif + if (dir == PCMDIR_PLAY) + sw = ~ENVY24HT_MT_PCTL_PSTART; + else + sw = ~ENVY24HT_MT_PCTL_RSTART; + + stat = envy24ht_rdmt(sc, ENVY24HT_MT_PCTL, 1); + envy24ht_wrmt(sc, ENVY24HT_MT_PCTL, stat & sw, 1); + + return; +} + +#if 0 +static int +envy24ht_route(struct sc_info *sc, int dac, int class, int adc, int rev) +{ + return 0; +} +#endif + +/* -------------------------------------------------------------------- */ + +/* buffer copy routines */ +static void +envy24ht_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 * ENVY24HT_PLAY_CHNUM + slot].buffer = data[src]; + dmabuf[dst * ENVY24HT_PLAY_CHNUM + slot + 1].buffer = data[src + 1]; + dst++; + dst %= dsize; + src += 2; + src %= ssize; + } + + return; +} + +static void +envy24ht_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, "envy24ht_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, "envy24ht_p16sl():%lu-->%lu(%lu)\n", src, dst, length); +#endif + + for (i = 0; i < length; i++) { + dmabuf[dst * ENVY24HT_PLAY_CHNUM + slot].buffer = (u_int32_t)data[src] << 16; + dmabuf[dst * ENVY24HT_PLAY_CHNUM + slot + 1].buffer = (u_int32_t)data[src + 1] << 16; +#if(0) + if (i < 16) { + printf("%08x", dmabuf[dst * ENVY24HT_PLAY_CHNUM + slot]); + printf("%08x", dmabuf[dst * ENVY24HT_PLAY_CHNUM + slot + 1]); + } +#endif + dst++; + dst %= dsize; + src += 2; + src %= ssize; + } +#if(0) + printf("\n"); +#endif + + return; +} + +static void +envy24ht_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 * ENVY24HT_PLAY_CHNUM + slot].buffer = ((u_int32_t)data[src] ^ 0x80) << 24; + dmabuf[dst * ENVY24HT_PLAY_CHNUM + slot + 1].buffer = ((u_int32_t)data[src + 1] ^ 0x80) << 24; + dst++; + dst %= dsize; + src += 2; + src %= ssize; + } + + return; +} + +static void +envy24ht_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 - ENVY24HT_CHAN_REC_ADC1) * 2; + + for (i = 0; i < length; i++) { + data[dst] = dmabuf[src * ENVY24HT_REC_CHNUM + slot].buffer; + data[dst + 1] = dmabuf[src * ENVY24HT_REC_CHNUM + slot + 1].buffer; + dst += 2; + dst %= dsize; + src++; + src %= ssize; + } + + return; +} + +static void +envy24ht_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 - ENVY24HT_CHAN_REC_ADC1) * 2; + + for (i = 0; i < length; i++) { + data[dst] = dmabuf[src * ENVY24HT_REC_CHNUM + slot].buffer; + data[dst + 1] = dmabuf[src * ENVY24HT_REC_CHNUM + slot + 1].buffer; + dst += 2; + dst %= dsize; + src++; + src %= ssize; + } + + return; +} + +/* -------------------------------------------------------------------- */ + +/* channel interface */ +static void * +envy24htchan_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, "envy24htchan_init(obj, devinfo, b, c, %d)\n", dir); +#endif + snd_mtxlock(sc->lock); +#if 0 + if ((sc->chnum > ENVY24HT_CHAN_PLAY_SPDIF && dir != PCMDIR_REC) || + (sc->chnum < ENVY24HT_CHAN_REC_ADC1 && dir != PCMDIR_PLAY)) { + snd_mtxunlock(sc->lock); + return NULL; + } +#endif + num = sc->chnum; + + ch = &sc->chan[num]; + ch->size = 8 * ENVY24HT_SAMPLE_NUM; + ch->data = malloc(ch->size, M_ENVY24HT, 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 = envy24ht_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 +envy24htchan_free(kobj_t obj, void *data) +{ + struct sc_chinfo *ch = data; + struct sc_info *sc = ch->parent; + +#if(0) + device_printf(sc->dev, "envy24htchan_free()\n"); +#endif + snd_mtxlock(sc->lock); + if (ch->data != NULL) { + free(ch->data, M_ENVY24HT); + ch->data = NULL; + } + snd_mtxunlock(sc->lock); + + return 0; +} + +static int +envy24htchan_setformat(kobj_t obj, void *data, u_int32_t format) +{ + struct sc_chinfo *ch = data; + struct sc_info *sc = ch->parent; + struct envy24ht_emldma *emltab; + /* unsigned int bcnt, bsize; */ + int i; + +#if(0) + device_printf(sc->dev, "envy24htchan_setformat(obj, data, 0x%08x)\n", format); +#endif + snd_mtxlock(sc->lock); + /* check and get format related information */ + if (ch->dir == PCMDIR_PLAY) + emltab = envy24ht_pemltab; + else + emltab = envy24ht_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 * ENVY24HT_SAMPLE_NUM; +#if 0 + if (ch->dir == PCMDIR_PLAY) + bsize = ch->blk * 4 / ENVY24HT_PLAY_BUFUNIT; + else + bsize = ch->blk * 4 / ENVY24HT_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, "envy24htchan_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 envy24htchan_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 +envy24htchan_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, "envy24htchan_setspeed(obj, data, %d)\n", speed); +#endif + prev = 0x7fffffff; + for (i = 0; (val = envy24ht_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, "envy24htchan_setspeed(): return %d\n", ch->speed); +#endif + return ch->speed; +} + +static int +envy24htchan_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, "envy24htchan_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 *= ENVY24HT_PLAY_BUFUNIT / 4; + else + ch->blk *= ENVY24HT_REC_BUFUNIT / 4; + /* set channel buffer information */ + /* ch->size = ch->unit * ENVY24HT_SAMPLE_NUM; */ + if (ch->dir == PCMDIR_PLAY) + bsize = ch->blk * 4 / ENVY24HT_PLAY_BUFUNIT; + else + bsize = ch->blk * 4 / ENVY24HT_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, "envy24htchan_setblocksize(): return %d\n", prev); +#endif + return prev; +} + +/* semantic note: must start at beginning of buffer */ +static int +envy24htchan_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, "envy24htchan_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, "envy24htchan_trigger(): start\n"); +#endif + /* check or set channel speed */ + if (sc->run[0] == 0 && sc->run[1] == 0) { + sc->speed = envy24ht_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 = envy24ht_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); + envy24ht_setvolume(sc, ch->num); + } + envy24ht_updintr(sc, ch->dir); + if (sc->run[slot] == 1) + envy24ht_start(sc, ch->dir); + ch->run = 1; + break; + case PCMTRIG_EMLDMAWR: +#if(0) + device_printf(sc->dev, "envy24htchan_trigger(): emldmawr\n"); +#endif + if (ch->run != 1) + return -1; + ch->emldma(ch); + break; + case PCMTRIG_EMLDMARD: +#if(0) + device_printf(sc->dev, "envy24htchan_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, "envy24htchan_trigger(): abort\n"); +#endif + ch->run = 0; + sc->run[slot]--; + if (ch->dir == PCMDIR_PLAY) + envy24ht_mutevolume(sc, ch->num); + if (sc->run[slot] == 0) { + envy24ht_stop(sc, ch->dir); + sc->intr[slot] = 0; + } +/* else if (ch->blk == sc->blk[slot]) { + sc->blk[slot] = ENVY24HT_SAMPLE_NUM / 2; + for (i = 0; i < ENVY24HT_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]) + envy24ht_updintr(sc, ch->dir); + }*/ + } + break; + } + snd_mtxunlock(sc->lock); + + return 0; +} + +static int +envy24htchan_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, "envy24htchan_getptr()\n"); +#endif + snd_mtxlock(sc->lock); + ptr = envy24ht_gethwptr(sc, ch->dir); + rtn = ptr * ch->unit; + snd_mtxunlock(sc->lock); + +#if(0) + device_printf(sc->dev, "envy24htchan_getptr(): return %d\n", + rtn); +#endif + return rtn; +} + +static struct pcmchan_caps * +envy24htchan_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, "envy24htchan_getcaps()\n"); +#endif + snd_mtxlock(sc->lock); + if (ch->dir == PCMDIR_PLAY) { + if (sc->run[0] == 0) + rtn = &envy24ht_playcaps; + else + rtn = &sc->caps[0]; + } + else { + if (sc->run[1] == 0) + rtn = &envy24ht_reccaps; + else + rtn = &sc->caps[1]; + } + snd_mtxunlock(sc->lock); + + return rtn; +} + +static kobj_method_t envy24htchan_methods[] = { + KOBJMETHOD(channel_init, envy24htchan_init), + KOBJMETHOD(channel_free, envy24htchan_free), + KOBJMETHOD(channel_setformat, envy24htchan_setformat), + KOBJMETHOD(channel_setspeed, envy24htchan_setspeed), + KOBJMETHOD(channel_setblocksize, envy24htchan_setblocksize), + KOBJMETHOD(channel_trigger, envy24htchan_trigger), + KOBJMETHOD(channel_getptr, envy24htchan_getptr), + KOBJMETHOD(channel_getcaps, envy24htchan_getcaps), + { 0, 0 } +}; +CHANNEL_DECLARE(envy24htchan); + +/* -------------------------------------------------------------------- */ + +/* mixer interface */ + +static int +envy24htmixer_init(struct snd_mixer *m) +{ + struct sc_info *sc = mix_getdevinfo(m); + +#if(0) + device_printf(sc->dev, "envy24htmixer_init()\n"); +#endif + if (sc == NULL) + return -1; + + /* set volume control rate */ + snd_mtxlock(sc->lock); +#if 0 + envy24ht_wrmt(sc, ENVY24HT_MT_VOLRATE, 0x30, 1); /* 0x30 is default value */ +#endif + + pcm_setflags(sc->dev, pcm_getflags(sc->dev) | SD_F_SOFTPCMVOL); + + mix_setdevs(m, ENVY24HT_MIX_MASK); + mix_setrecdevs(m, ENVY24HT_MIX_REC_MASK); + + snd_mtxunlock(sc->lock); + + return 0; +} + +static int +envy24htmixer_reinit(struct snd_mixer *m) +{ + struct sc_info *sc = mix_getdevinfo(m); + + if (sc == NULL) + return -1; +#if(0) + device_printf(sc->dev, "envy24htmixer_reinit()\n"); +#endif + + return 0; +} + +static int +envy24htmixer_uninit(struct snd_mixer *m) +{ + struct sc_info *sc = mix_getdevinfo(m); + + if (sc == NULL) + return -1; +#if(0) + device_printf(sc->dev, "envy24htmixer_uninit()\n"); +#endif + + return 0; +} + +static int +envy24htmixer_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) +{ + struct sc_info *sc = mix_getdevinfo(m); + int ch = envy24ht_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 = envy24ht_chanmap[ch]; +#if(0) + device_printf(sc->dev, "envy24htmixer_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) > ENVY24HT_VOL_MIN) + sc->left[hwch] = ENVY24HT_VOL_MUTE; + if ((sc->right[hwch] = 100 - right) > ENVY24HT_VOL_MIN) + sc->right[hwch] = ENVY24HT_VOL_MUTE; + + /* set volume for record channel and running play channel */ + if (hwch > ENVY24HT_CHAN_PLAY_SPDIF || sc->chan[ch].run) + envy24ht_setvolume(sc, hwch); + } + snd_mtxunlock(sc->lock); + + return right << 8 | left; +} + +static u_int32_t +envy24htmixer_setrecsrc(struct snd_mixer *m, u_int32_t src) +{ + struct sc_info *sc = mix_getdevinfo(m); + int ch = envy24ht_mixmap[src]; +#if(0) + device_printf(sc->dev, "envy24htmixer_setrecsrc(m, %d)\n", src); +#endif + + if (ch > ENVY24HT_CHAN_PLAY_SPDIF) + sc->src = ch; + return src; +} + +static kobj_method_t envy24htmixer_methods[] = { + KOBJMETHOD(mixer_init, envy24htmixer_init), + KOBJMETHOD(mixer_reinit, envy24htmixer_reinit), + KOBJMETHOD(mixer_uninit, envy24htmixer_uninit), + KOBJMETHOD(mixer_set, envy24htmixer_set), + KOBJMETHOD(mixer_setrecsrc, envy24htmixer_setrecsrc), + { 0, 0 } +}; +MIXER_DECLARE(envy24htmixer); + +/* -------------------------------------------------------------------- */ + +/* The interrupt handler */ +static void +envy24ht_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, "envy24ht_intr()\n"); +#endif + snd_mtxlock(sc->lock); + if (envy24ht_checkintr(sc, PCMDIR_PLAY)) { +#if(0) + device_printf(sc->dev, "envy24ht_intr(): play\n"); +#endif + dsize = sc->psize / 4; + ptr = dsize - envy24ht_rdmt(sc, ENVY24HT_MT_PCNT, 2) - 1; +#if(0) + device_printf(sc->dev, "envy24ht_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 = ENVY24HT_CHAN_PLAY_DAC1; i <= ENVY24HT_CHAN_PLAY_SPDIF; i++) { + ch = &sc->chan[i]; +#if(0) + if (ch->run) + device_printf(sc->dev, "envy24ht_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; + envy24ht_updintr(sc, PCMDIR_PLAY); + } + if (envy24ht_checkintr(sc, PCMDIR_REC)) { +#if(0) + device_printf(sc->dev, "envy24ht_intr(): rec\n"); +#endif + dsize = sc->rsize / 4; + ptr = dsize - envy24ht_rdmt(sc, ENVY24HT_MT_RCNT, 2) - 1; + ptr -= ptr % sc->blk[1]; + feed = (ptr + dsize - sc->intr[1]) % dsize; + for (i = ENVY24HT_CHAN_REC_ADC1; i <= ENVY24HT_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; + envy24ht_updintr(sc, PCMDIR_REC); + } + snd_mtxunlock(sc->lock); + + return; +} + +/* + * Probe and attach the card + */ + +static int +envy24ht_pci_probe(device_t dev) +{ + u_int16_t sv, sd; + int i; + +#if(0) + printf("envy24ht_pci_probe()\n"); +#endif + if (pci_get_device(dev) == PCID_ENVY24HT && + 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("envy24ht_pci_probe(): return 0\n"); +#endif + return 0; + } + else { +#if(0) + printf("envy24ht_pci_probe(): return ENXIO\n"); +#endif + return ENXIO; + } +} + +static void +envy24ht_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, "envy24ht_dmapsetmap()\n"); + if (bootverbose) { + printf("envy24ht(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 +envy24ht_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, "envy24ht_dmarsetmap()\n"); + if (bootverbose) { + printf("envy24ht(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 +envy24ht_dmafree(struct sc_info *sc) +{ +#if(0) + device_printf(sc->dev, "envy24ht_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 +envy24ht_dmainit(struct sc_info *sc) +{ + u_int32_t addr; + +#if(0) + device_printf(sc->dev, "envy24ht_dmainit()\n"); +#endif + /* init values */ + sc->psize = ENVY24HT_PLAY_BUFUNIT * ENVY24HT_SAMPLE_NUM; + sc->rsize = ENVY24HT_REC_BUFUNIT * ENVY24HT_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, "envy24ht_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, "envy24ht_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, "envy24ht_dmainit(): bus_dmamem_load(): sc->pmap\n"); +#endif + if (bus_dmamap_load(sc->dmat, sc->pmap, sc->pbuf, sc->psize, envy24ht_dmapsetmap, sc, 0)) + goto bad; +#if(0) + device_printf(sc->dev, "envy24ht_dmainit(): bus_dmamem_load(): sc->rmap\n"); +#endif + if (bus_dmamap_load(sc->dmat, sc->rmap, sc->rbuf, sc->rsize, envy24ht_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 + envy24ht_wrmt(sc, ENVY24HT_MT_PADDR, addr, 4); +#if(0) + device_printf(sc->dev, "PADDR-->(0x%08x)\n", envy24ht_rdmt(sc, ENVY24HT_MT_PADDR, 4)); + device_printf(sc->dev, "psize(%ld)\n", sc->psize / 4 - 1); +#endif + envy24ht_wrmt(sc, ENVY24HT_MT_PCNT, sc->psize / 4 - 1, 2); +#if(0) + device_printf(sc->dev, "PCNT-->(%ld)\n", envy24ht_rdmt(sc, ENVY24HT_MT_PCNT, 2)); +#endif + addr = vtophys(sc->rbuf); + envy24ht_wrmt(sc, ENVY24HT_MT_RADDR, addr, 4); + envy24ht_wrmt(sc, ENVY24HT_MT_RCNT, sc->rsize / 4 - 1, 2); + + return 0; + bad: + envy24ht_dmafree(sc); + return ENOSPC; +} + +static void +envy24ht_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 & ENVY24HT_CCSM_SCFG_XIN2) { + case 0x00: + printf("24.576MHz(96kHz*256)\n"); + break; + case 0x40: + printf("49.152MHz(192kHz*256)\n"); + break; + case 0x80: + printf("reserved\n"); + break; + default: + printf("illeagal system setting\n"); + } + printf(" MPU-401 UART(s) #: "); + if (sc->cfg->scfg & ENVY24HT_CCSM_SCFG_MPU) + printf("1\n"); + else + printf("not implemented\n"); + switch (sc->adcn) { + case 0x01 || 0x02: + printf(" ADC #: "); + printf("%d\n", sc->adcn); + break; + case 0x03: + printf(" ADC #: "); + printf("%d", 1); + printf(" and SPDIF receiver connected\n"); + break; + default: + printf(" no physical inputs\n"); + } + printf(" DAC #: "); + printf("%d\n", sc->dacn); + printf(" Multi-track converter type: "); + if ((sc->cfg->acl & ENVY24HT_CCSM_ACL_MTC) == 0) { + printf("AC'97(SDATA_OUT:"); + if (sc->cfg->acl & ENVY24HT_CCSM_ACL_OMODE) + printf("packed"); + else + printf("split"); + printf(")\n"); + } + else { + printf("I2S("); + if (sc->cfg->i2s & ENVY24HT_CCSM_I2S_VOL) + printf("with volume, "); + if (sc->cfg->i2s & ENVY24HT_CCSM_I2S_192KHZ) + printf("192KHz support, "); + else + if (sc->cfg->i2s & ENVY24HT_CCSM_I2S_96KHZ) + printf("192KHz support, "); + else + printf("48KHz support, "); + switch (sc->cfg->i2s & ENVY24HT_CCSM_I2S_RES) { + case ENVY24HT_CCSM_I2S_16BIT: + printf("16bit resolution, "); + break; + case ENVY24HT_CCSM_I2S_18BIT: + printf("18bit resolution, "); + break; + case ENVY24HT_CCSM_I2S_20BIT: + printf("20bit resolution, "); + break; + case ENVY24HT_CCSM_I2S_24BIT: + printf("24bit resolution, "); + break; + } + printf("ID#0x%x)\n", sc->cfg->i2s & ENVY24HT_CCSM_I2S_ID); + } + printf(" S/PDIF(IN/OUT): "); + if (sc->cfg->spdif & ENVY24HT_CCSM_SPDIF_IN) + printf("1/"); + else + printf("0/"); + if (sc->cfg->spdif & ENVY24HT_CCSM_SPDIF_OUT) + printf("1 "); + else + printf("0 "); + if (sc->cfg->spdif & (ENVY24HT_CCSM_SPDIF_IN | ENVY24HT_CCSM_SPDIF_OUT)) + printf("ID# 0x%02x\n", (sc->cfg->spdif & ENVY24HT_CCSM_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 +envy24ht_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, "envy24ht_init()\n"); +#endif + + /* reset chip */ +#if 0 + envy24ht_wrcs(sc, ENVY24HT_CCS_CTL, ENVY24HT_CCS_CTL_RESET, 1); + DELAY(200); + envy24ht_wrcs(sc, ENVY24HT_CCS_CTL, ENVY24HT_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); +#endif + + /* 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 = envy24ht_rom2cfg(sc); + } + sc->adcn = ((sc->cfg->scfg & ENVY24HT_CCSM_SCFG_ADC) >> 2) + 1; /* need to be fixed */ + sc->dacn = (sc->cfg->scfg & ENVY24HT_CCSM_SCFG_DAC) + 1; + + if (1 /* bootverbose */) { + envy24ht_putcfg(sc); + } + + /* set system configuration */ + envy24ht_wrcs(sc, ENVY24HT_CCS_SCFG, sc->cfg->scfg, 1); + envy24ht_wrcs(sc, ENVY24HT_CCS_ACL, sc->cfg->acl, 1); + envy24ht_wrcs(sc, ENVY24HT_CCS_I2S, sc->cfg->i2s, 1); + envy24ht_wrcs(sc, ENVY24HT_CCS_SPDIF, sc->cfg->spdif, 1); + envy24ht_gpiosetmask(sc, sc->cfg->gpiomask); + envy24ht_gpiosetdir(sc, sc->cfg->gpiodir); + envy24ht_gpiowr(sc, sc->cfg->gpiostate); + + if ((sc->cfg->subvendor == 0x3031) && (sc->cfg->subdevice == 0x4553)) { + envy24ht_wri2c(sc, 0x22, 0x00, 0x07); + envy24ht_wri2c(sc, 0x22, 0x04, 0x5f | 0x80); + envy24ht_wri2c(sc, 0x22, 0x05, 0x5f | 0x80); + } + + 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, "envy24ht_init(): initialize DMA buffer\n"); +#endif + if (envy24ht_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 = envy24ht_playfmt; + sc->caps[1].fmtlist = envy24ht_recfmt; + + /* set channel router */ +#if 0 + envy24ht_route(sc, ENVY24HT_ROUTE_DAC_1, ENVY24HT_ROUTE_CLASS_MIX, 0, 0); + envy24ht_route(sc, ENVY24HT_ROUTE_DAC_SPDIF, ENVY24HT_ROUTE_CLASS_DMA, 0, 0); + envy24ht_route(sc, ENVY24HT_ROUTE_DAC_SPDIF, ENVY24HT_ROUTE_CLASS_MIX, 0, 0); +#endif + + /* set macro interrupt mask */ + data = envy24ht_rdcs(sc, ENVY24HT_CCS_IMASK, 1); + envy24ht_wrcs(sc, ENVY24HT_CCS_IMASK, data & ~ENVY24HT_CCS_IMASK_PMT, 1); + data = envy24ht_rdcs(sc, ENVY24HT_CCS_IMASK, 1); +#if(0) + device_printf(sc->dev, "envy24ht_init(): CCS_IMASK-->0x%02x\n", data); +#endif + + return 0; +} + +static int +envy24ht_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->mtid = ENVY24HT_PCIR_MT; + sc->mt = bus_alloc_resource(sc->dev, SYS_RES_IOPORT, + &sc->mtid, 0, ~0, 1, RF_ACTIVE); + if (!sc->cs || !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->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\nMT: 0x%lx\n", + pci_read_config(sc->dev, PCIR_CCS, 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, 0, envy24ht_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 +envy24ht_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, "envy24ht_pci_attach()\n"); +#endif + /* get sc_info data area */ + if ((sc = malloc(sizeof(*sc), M_ENVY24HT, 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_envy24ht 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 = envy24ht_alloc_resource(sc); + if (err) { + device_printf(dev, "unable to allocate system resources\n"); + goto bad; + } + + /* initialize card */ + err = envy24ht_init(sc); + if (err) { + device_printf(dev, "unable to initialize the card\n"); + goto bad; + } + + /* set multi track mixer */ + mixer_init(dev, &envy24htmixer_class, sc); + + /* set channel information */ + /* err = pcm_register(dev, sc, 5, 2 + sc->adcn); */ + err = pcm_register(dev, sc, 1, 2 + sc->adcn); + if (err) + goto bad; + sc->chnum = 0; + /* for (i = 0; i < 5; i++) { */ + pcm_addchan(dev, PCMDIR_PLAY, &envy24htchan_class, sc); + sc->chnum++; + /* } */ + for (i = 0; i < 2 + sc->adcn; i++) { + pcm_addchan(dev, PCMDIR_REC, &envy24htchan_class, sc); + sc->chnum++; + } + + /* set status iformation */ + snprintf(status, SND_STATUSLEN, + "at io 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->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); + envy24ht_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]); + } + envy24ht_cfgfree(sc->cfg); + if (sc->cs) + bus_release_resource(dev, SYS_RES_IOPORT, sc->csid, sc->cs); + if (sc->mt) + bus_release_resource(dev, SYS_RES_IOPORT, sc->mtid, sc->mt); + if (sc->lock) + snd_mtxfree(sc->lock); + free(sc, M_ENVY24HT); + return err; +} + +static int +envy24ht_pci_detach(device_t dev) +{ + struct sc_info *sc; + int r; + int i; + +#if(0) + device_printf(dev, "envy24ht_pci_detach()\n"); +#endif + sc = pcm_getdevinfo(dev); + if (sc == NULL) + return 0; + r = pcm_unregister(dev); + if (r) + return r; + + envy24ht_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]); + } + envy24ht_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->mtid, sc->mt); + snd_mtxfree(sc->lock); + free(sc, M_ENVY24HT); + return 0; +} + +static device_method_t envy24ht_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, envy24ht_pci_probe), + DEVMETHOD(device_attach, envy24ht_pci_attach), + DEVMETHOD(device_detach, envy24ht_pci_detach), + { 0, 0 } +}; + +static driver_t envy24ht_driver = { + "pcm", + envy24ht_methods, +#if __FreeBSD_version > 500000 + PCM_SOFTC_SIZE, +#else + sizeof(struct snddev_info), +#endif +}; + +DRIVER_MODULE(snd_envy24ht, pci, envy24ht_driver, pcm_devclass, 0, 0); +MODULE_DEPEND(snd_envy24ht, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); +MODULE_DEPEND(snd_envy24ht, snd_spicds, 1, 1, 1); +MODULE_VERSION(snd_envy24ht, 1); --- sys/dev/sound/pci/envy24ht.h.orig Thu Jan 1 07:30:00 1970 +++ sys/dev/sound/pci/envy24ht.h Thu Jul 12 12:04:19 2007 @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2006 Konstantin Dimitrov + * 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/envy24ht.h,v 1.5 2007/05/27 19:58:39 joel Exp $ + */ + + +/* -------------------------------------------------------------------- */ + +/* PCI device ID */ +#define PCIV_ENVY24 0x1412 +#define PCID_ENVY24HT 0x1724 + +#define PCIR_CCS 0x10 /* Controller I/O Base Address */ +#define ENVY24HT_PCIR_MT 0x14 /* Multi-Track I/O Base Address */ + +/* Controller Registers */ + +#define ENVY24HT_CCS_CTL 0x00 /* Control/Status Register */ +#define ENVY24HT_CCS_CTL_RESET 0x80 /* Entire Chip soft reset */ + +#define ENVY24HT_CCS_IMASK 0x01 /* Interrupt Mask Register */ +#define ENVY24HT_CCS_IMASK_PMT 0x10 /* Professional Multi-track */ + +#define ENVY24HT_CCS_I2CDEV 0x10 /* I2C Port Device Address Register */ +#define ENVY24HT_CCS_I2CDEV_ADDR 0xfe /* I2C device address */ +#define ENVY24HT_CCS_I2CDEV_ROM 0xa0 /* reserved for the external I2C E2PROM */ +#define ENVY24HT_CCS_I2CDEV_WR 0x01 /* write */ +#define ENVY24HT_CCS_I2CDEV_RD 0x00 /* read */ + +#define ENVY24HT_CCS_I2CADDR 0x11 /* I2C Port Byte Address Register */ +#define ENVY24HT_CCS_I2CDATA 0x12 /* I2C Port Read/Write Data Register */ + +#define ENVY24HT_CCS_I2CSTAT 0x13 /* I2C Port Control and Status Register */ +#define ENVY24HT_CCS_I2CSTAT_ROM 0x80 /* external E2PROM exists */ +#define ENVY24HT_CCS_I2CSTAT_BSY 0x01 /* I2C port read/write status busy */ + +#define ENVY24HT_CCS_SCFG 0x04 /* System Configuration Register */ +#define ENVY24HT_CCSM_SCFG_XIN2 0xc0 /* XIN2 Clock Source Configuration */ + /* 00: 24.576MHz(96kHz*256) */ + /* 01: 49.152MHz(192kHz*256) */ + /* 1x: Reserved */ +#define ENVY24HT_CCSM_SCFG_MPU 0x20 /* 0(not implemented)/1(1) MPU-401 UART */ +#define ENVY24HT_CCSM_SCFG_ADC 0x0c /* 1-2 stereo ADC connected, S/PDIF receiver connected */ +#define ENVY24HT_CCSM_SCFG_DAC 0x03 /* 1-4 stereo DAC connected */ + +#define ENVY24HT_CCS_ACL 0x05 /* AC-Link Configuration Register */ +#define ENVY24HT_CCSM_ACL_MTC 0x80 /* Multi-track converter type: 0:AC'97 1:I2S */ +#define ENVY24HT_CCSM_ACL_OMODE 0x02 /* AC 97 codec SDATA_OUT 0:split 1:packed */ + +#define ENVY24HT_CCS_I2S 0x06 /* I2S Converters Features Register */ +#define ENVY24HT_CCSM_I2S_VOL 0x80 /* I2S codec Volume and mute */ +#define ENVY24HT_CCSM_I2S_96KHZ 0x40 /* I2S converter 96kHz sampling rate support */ +#define ENVY24HT_CCSM_I2S_192KHZ 0x08 /* I2S converter 192kHz sampling rate support */ +#define ENVY24HT_CCSM_I2S_RES 0x30 /* Converter resolution */ +#define ENVY24HT_CCSM_I2S_16BIT 0x00 /* 16bit */ +#define ENVY24HT_CCSM_I2S_18BIT 0x10 /* 18bit */ +#define ENVY24HT_CCSM_I2S_20BIT 0x20 /* 20bit */ +#define ENVY24HT_CCSM_I2S_24BIT 0x30 /* 24bit */ +#define ENVY24HT_CCSM_I2S_ID 0x07 /* Other I2S IDs */ + +#define ENVY24HT_CCS_SPDIF 0x07 /* S/PDIF Configuration Register */ +#define ENVY24HT_CCSM_SPDIF_INT_EN 0x80 /* Enable integrated S/PDIF transmitter */ +#define ENVY24HT_CCSM_SPDIF_INT_OUT 0x40 /* Internal S/PDIF Out implemented */ +#define ENVY24HT_CCSM_SPDIF_ID 0x3c /* S/PDIF chip ID */ +#define ENVY24HT_CCSM_SPDIF_IN 0x02 /* S/PDIF Stereo In is present */ +#define ENVY24HT_CCSM_SPDIF_OUT 0x01 /* External S/PDIF Out implemented */ + +/* Professional Multi-Track Control Registers */ + +#define ENVY24HT_MT_INT_STAT 0x00 /* DMA Interrupt Mask and Status Register */ +#define ENVY24HT_MT_INT_RSTAT 0x02 /* Multi-track record interrupt status */ +#define ENVY24HT_MT_INT_PSTAT 0x01 /* Multi-track playback interrupt status */ +#define ENVY24HT_MT_INT_MASK 0x03 +#define ENVY24HT_MT_INT_RMASK 0x02 /* Multi-track record interrupt mask */ +#define ENVY24HT_MT_INT_PMASK 0x01 /* Multi-track playback interrupt mask */ + +#define ENVY24HT_MT_RATE 0x01 /* Sampling Rate Select Register */ +#define ENVY24HT_MT_RATE_SPDIF 0x10 /* S/PDIF input clock as the master */ +#define ENVY24HT_MT_RATE_48000 0x00 +#define ENVY24HT_MT_RATE_24000 0x01 +#define ENVY24HT_MT_RATE_12000 0x02 +#define ENVY24HT_MT_RATE_9600 0x03 +#define ENVY24HT_MT_RATE_32000 0x04 +#define ENVY24HT_MT_RATE_16000 0x05 +#define ENVY24HT_MT_RATE_8000 0x06 +#define ENVY24HT_MT_RATE_96000 0x07 +#define ENVY24HT_MT_RATE_192000 0x0e +#define ENVY24HT_MT_RATE_64000 0x0f +#define ENVY24HT_MT_RATE_44100 0x08 +#define ENVY24HT_MT_RATE_22050 0x09 +#define ENVY24HT_MT_RATE_11025 0x0a +#define ENVY24HT_MT_RATE_88200 0x0b +#define ENVY24HT_MT_RATE_176400 0x0c +#define ENVY24HT_MT_RATE_MASK 0x0f + +#define ENVY24HT_MT_I2S 0x02 /* I2S Data Format Register */ +#define ENVY24HT_MT_I2S_MLR128 0x08 /* MCLK/LRCLK ratio 128x (or 256x) */ + +#define ENVY24HT_MT_PADDR 0x10 /* Playback DMA Current/Base Address Register */ +#define ENVY24HT_MT_PCNT 0x14 /* Playback DMA Current/Base Count Register */ +#define ENVY24HT_MT_PTERM 0x1C /* Playback Current/Base Terminal Count Register */ + +#define ENVY24HT_MT_PCTL 0x18 /* Global Playback and Record DMA Start/Stop Register */ +#define ENVY24HT_MT_PCTL_RSTART 0x02 /* 1: Record start; 0: Record stop */ +#define ENVY24HT_MT_PCTL_PSTART 0x01 /* 1: Playback start; 0: Playback stop */ + +#define ENVY24HT_MT_RADDR 0x20 /* Record DMA Current/Base Address Register */ +#define ENVY24HT_MT_RCNT 0x24 /* Record DMA Current/Base Count Register */ +#define ENVY24HT_MT_RTERM 0x26 /* Record Current/Base Terminal Count Register */ + +/* + These map values are refferd from ALSA sound driver. +*/ +/* ENVY24 configuration E2PROM map */ +#define ENVY24HT_E2PROM_SUBVENDOR 0x02 +#define ENVY24HT_E2PROM_SUBDEVICE 0x00 +#define ENVY24HT_E2PROM_SIZE 0x04 +#define ENVY24HT_E2PROM_VERSION 0x05 +#define ENVY24HT_E2PROM_SCFG 0x06 +#define ENVY24HT_E2PROM_ACL 0x07 +#define ENVY24HT_E2PROM_I2S 0x08 +#define ENVY24HT_E2PROM_SPDIF 0x09 +#define ENVY24HT_E2PROM_GPIOMASK 0x0d +#define ENVY24HT_E2PROM_GPIOSTATE 0x10 +#define ENVY24HT_E2PROM_GPIODIR 0x0a + +/* ENVY24 mixer channel defines */ +/* + ENVY24 mixer has original line matrix. So, general mixer command is not + able to use for this. If system has consumer AC'97 output, AC'97 line is + used as master mixer, and it is able to control. +*/ +#define ENVY24HT_CHAN_NUM 11 /* Play * 5 + Record * 5 + Mix * 1 */ + +#define ENVY24HT_CHAN_PLAY_DAC1 0 +#define ENVY24HT_CHAN_PLAY_DAC2 1 +#define ENVY24HT_CHAN_PLAY_DAC3 2 +#define ENVY24HT_CHAN_PLAY_DAC4 3 +#define ENVY24HT_CHAN_PLAY_SPDIF 4 +#define ENVY24HT_CHAN_REC_ADC1 5 +#define ENVY24HT_CHAN_REC_ADC2 6 +#define ENVY24HT_CHAN_REC_ADC3 7 +#define ENVY24HT_CHAN_REC_ADC4 8 +#define ENVY24HT_CHAN_REC_SPDIF 9 +#define ENVY24HT_CHAN_REC_MIX 10 + +#define ENVY24HT_MIX_MASK 0x3fd +#define ENVY24HT_MIX_REC_MASK 0x3e0 + +/* volume value constants */ +#define ENVY24HT_VOL_MAX 0 /* 0db(negate) */ +#define ENVY24HT_VOL_MIN 96 /* -144db(negate) */ +#define ENVY24HT_VOL_MUTE 127 /* mute */ + +#define BUS_SPACE_MAXADDR_ENVY24 0x0fffffff /* Address space beyond 256MB is not + supported */ +#define BUS_SPACE_MAXSIZE_ENVY24 0x3fffc /* 64k x 4byte(1dword) */ + +#define ENVY24HT_CCS_GPIO_HDATA 0x1E +#define ENVY24HT_CCS_GPIO_LDATA 0x14 +#define ENVY24HT_CCS_GPIO_LMASK 0x16 +#define ENVY24HT_CCS_GPIO_HMASK 0x1F +#define ENVY24HT_CCS_GPIO_CTLDIR 0x18 + --- sys/dev/sound/pci/es137x.c.orig Sun Jan 30 09:00:04 2005 +++ sys/dev/sound/pci/es137x.c Thu Jul 12 12:04:19 2007 @@ -59,10 +59,7 @@ #include "mixer_if.h" -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/es137x.c,v 1.51.2.2 2005/01/30 01:00:04 imp Exp $"); - -static int debug = 0; -SYSCTL_INT(_debug, OID_AUTO, es_debug, CTLFLAG_RW, &debug, 0, ""); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/es137x.c,v 1.71 2007/07/05 10:22:37 ariff Exp $"); #define MEM_MAP_REG 0x14 @@ -90,6 +87,20 @@ #define ES_DEFAULT_BUFSZ 4096 +/* 2 DAC for playback, 1 ADC for record */ +#define ES_DAC1 0 +#define ES_DAC2 1 +#define ES_ADC 2 +#define ES_NCHANS 3 + +#define ES_DMA_SEGS_MIN 2 +#define ES_DMA_SEGS_MAX 256 +#define ES_BLK_MIN 64 +#define ES_BLK_ALIGN (~(ES_BLK_MIN - 1)) + +#define ES1370_DAC1_MINSPEED 5512 +#define ES1370_DAC1_MAXSPEED 44100 + /* device private data */ struct es_info; @@ -97,10 +108,75 @@ struct es_info *parent; struct pcm_channel *channel; struct snd_dbuf *buffer; - int dir, num; - u_int32_t fmt, blksz, bufsz; + struct pcmchan_caps caps; + int dir, num, index; + uint32_t fmt, blksz, blkcnt, bufsz; + uint32_t ptr, prevptr; + int active; }; +/* + * 32bit Ensoniq Configuration (es->escfg). + * ---------------------------------------- + * + * +-------+--------+------+------+---------+--------+---------+---------+ + * len | 16 | 1 | 1 | 1 | 2 | 2 | 1 | 8 | + * +-------+--------+------+------+---------+--------+---------+---------+ + * | fixed | single | | | | | is | general | + * | rate | pcm | DACx | DACy | numplay | numrec | es1370? | purpose | + * | | mixer | | | | | | | + * +-------+--------+------+------+---------+--------+---------+---------+ + */ +#define ES_FIXED_RATE(cfgv) \ + (((cfgv) & 0xffff0000) >> 16) +#define ES_SET_FIXED_RATE(cfgv, nv) \ + (((cfgv) & ~0xffff0000) | (((nv) & 0xffff) << 16)) +#define ES_SINGLE_PCM_MIX(cfgv) \ + (((cfgv) & 0x8000) >> 15) +#define ES_SET_SINGLE_PCM_MIX(cfgv, nv) \ + (((cfgv) & ~0x8000) | (((nv) ? 1 : 0) << 15)) +#define ES_DAC_FIRST(cfgv) \ + (((cfgv) & 0x4000) >> 14) +#define ES_SET_DAC_FIRST(cfgv, nv) \ + (((cfgv) & ~0x4000) | (((nv) & 0x1) << 14)) +#define ES_DAC_SECOND(cfgv) \ + (((cfgv) & 0x2000) >> 13) +#define ES_SET_DAC_SECOND(cfgv, nv) \ + (((cfgv) & ~0x2000) | (((nv) & 0x1) << 13)) +#define ES_NUMPLAY(cfgv) \ + (((cfgv) & 0x1800) >> 11) +#define ES_SET_NUMPLAY(cfgv, nv) \ + (((cfgv) & ~0x1800) | (((nv) & 0x3) << 11)) +#define ES_NUMREC(cfgv) \ + (((cfgv) & 0x600) >> 9) +#define ES_SET_NUMREC(cfgv, nv) \ + (((cfgv) & ~0x600) | (((nv) & 0x3) << 9)) +#define ES_IS_ES1370(cfgv) \ + (((cfgv) & 0x100) >> 8) +#define ES_SET_IS_ES1370(cfgv, nv) \ + (((cfgv) & ~0x100) | (((nv) ? 1 : 0) << 8)) +#define ES_GP(cfgv) \ + ((cfgv) & 0xff) +#define ES_SET_GP(cfgv, nv) \ + (((cfgv) & ~0xff) | ((nv) & 0xff)) + +#define ES_DAC1_ENABLED(cfgv) \ + (ES_NUMPLAY(cfgv) > 1 || \ + (ES_NUMPLAY(cfgv) == 1 && ES_DAC_FIRST(cfgv) == ES_DAC1)) +#define ES_DAC2_ENABLED(cfgv) \ + (ES_NUMPLAY(cfgv) > 1 || \ + (ES_NUMPLAY(cfgv) == 1 && ES_DAC_FIRST(cfgv) == ES_DAC2)) + +/* + * DAC 1/2 configuration through kernel hint - hint.pcm..dac="val" + * + * 0 = Enable both DACs - Default + * 1 = Enable single DAC (DAC1) + * 2 = Enable single DAC (DAC2) + * 3 = Enable both DACs, swap position (DAC2 comes first instead of DAC1) + */ +#define ES_DEFAULT_DAC_CFG 0 + struct es_info { bus_space_tag_t st; bus_space_handle_t sh; @@ -112,44 +188,41 @@ device_t dev; int num; - unsigned int bufsz; + unsigned int bufsz, blkcnt; /* Contents of board's registers */ - u_long ctrl; - u_long sctrl; - struct es_chinfo pch, rch; + uint32_t ctrl; + uint32_t sctrl; + uint32_t escfg; + struct es_chinfo ch[ES_NCHANS]; + struct mtx *lock; + struct callout poll_timer; + int poll_ticks, polling; }; -/* -------------------------------------------------------------------- */ +#define ES_LOCK(sc) snd_mtxlock((sc)->lock) +#define ES_UNLOCK(sc) snd_mtxunlock((sc)->lock) +#define ES_LOCK_ASSERT(sc) snd_mtxassert((sc)->lock) /* prototypes */ static void es_intr(void *); - -static u_int es1371_wait_src_ready(struct es_info *); -static void es1371_src_write(struct es_info *, u_short, unsigned short); -static u_int es1371_adc_rate(struct es_info *, u_int, int); -static u_int es1371_dac_rate(struct es_info *, u_int, int); -static int es1371_init(struct es_info *, device_t); +static uint32_t es1371_wait_src_ready(struct es_info *); +static void es1371_src_write(struct es_info *, + unsigned short, unsigned short); +static unsigned int es1371_adc_rate(struct es_info *, unsigned int, int); +static unsigned int es1371_dac_rate(struct es_info *, unsigned int, int); +static int es1371_init(struct es_info *); static int es1370_init(struct es_info *); -static int es1370_wrcodec(struct es_info *, u_char, u_char); - -static u_int32_t es_playfmt[] = { - AFMT_U8, - AFMT_STEREO | AFMT_U8, - AFMT_S16_LE, - AFMT_STEREO | AFMT_S16_LE, - 0 -}; -static struct pcmchan_caps es_playcaps = {4000, 48000, es_playfmt, 0}; +static int es1370_wrcodec(struct es_info *, unsigned char, unsigned char); -static u_int32_t es_recfmt[] = { +static uint32_t es_fmt[] = { AFMT_U8, AFMT_STEREO | AFMT_U8, AFMT_S16_LE, AFMT_STEREO | AFMT_S16_LE, 0 }; -static struct pcmchan_caps es_reccaps = {4000, 48000, es_recfmt, 0}; +static struct pcmchan_caps es_caps = {4000, 48000, es_fmt, 0}; static const struct { unsigned volidx:4; @@ -159,7 +232,7 @@ unsigned recmask:13; unsigned avail:1; } mixtable[SOUND_MIXER_NRDEVICES] = { - [SOUND_MIXER_VOLUME] = { 0, 0x0, 0x1, 1, 0x0000, 1 }, + [SOUND_MIXER_VOLUME] = { 0, 0x0, 0x1, 1, 0x1f7f, 1 }, [SOUND_MIXER_PCM] = { 1, 0x2, 0x3, 1, 0x0400, 1 }, [SOUND_MIXER_SYNTH] = { 2, 0x4, 0x5, 1, 0x0060, 1 }, [SOUND_MIXER_CD] = { 3, 0x6, 0x7, 1, 0x0006, 1 }, @@ -171,70 +244,142 @@ [SOUND_MIXER_OGAIN] = { 9, 0xf, 0x0, 0, 0x0000, 1 } }; +static __inline uint32_t +es_rd(struct es_info *es, int regno, int size) +{ + switch (size) { + case 1: + return (bus_space_read_1(es->st, es->sh, regno)); + case 2: + return (bus_space_read_2(es->st, es->sh, regno)); + case 4: + return (bus_space_read_4(es->st, es->sh, regno)); + default: + return (0xFFFFFFFF); + } +} + +static __inline void +es_wr(struct es_info *es, int regno, uint32_t data, int size) +{ + + switch (size) { + case 1: + bus_space_write_1(es->st, es->sh, regno, data); + break; + case 2: + bus_space_write_2(es->st, es->sh, regno, data); + break; + case 4: + bus_space_write_4(es->st, es->sh, regno, data); + break; + } +} + /* -------------------------------------------------------------------- */ /* The es1370 mixer interface */ static int es1370_mixinit(struct snd_mixer *m) { + struct es_info *es; int i; - u_int32_t v; + uint32_t v; + es = mix_getdevinfo(m); v = 0; - for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) - if (mixtable[i].avail) v |= (1 << i); + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { + if (mixtable[i].avail) + v |= (1 << i); + } + /* + * Each DAC1/2 for ES1370 can be controlled independently + * DAC1 = controlled by synth + * DAC2 = controlled by pcm + * This is indeed can confuse user if DAC1 become primary playback + * channel. Try to be smart and combine both if necessary. + */ + if (ES_SINGLE_PCM_MIX(es->escfg)) + v &= ~(1 << SOUND_MIXER_SYNTH); mix_setdevs(m, v); v = 0; - for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) - if (mixtable[i].recmask) v |= (1 << i); + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { + if (mixtable[i].recmask) + v |= (1 << i); + } + if (ES_SINGLE_PCM_MIX(es->escfg)) /* ditto */ + v &= ~(1 << SOUND_MIXER_SYNTH); mix_setrecdevs(m, v); - return 0; + return (0); } static int es1370_mixset(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) { - int l, r, rl, rr; + struct es_info *es; + int l, r, rl, rr, set_dac1; - if (!mixtable[dev].avail) return -1; + if (!mixtable[dev].avail) + return (-1); l = left; - r = mixtable[dev].stereo? right : l; - if (mixtable[dev].left == 0xf) { - rl = (l < 2)? 0x80 : 7 - (l - 2) / 14; - } else { - rl = (l < 10)? 0x80 : 15 - (l - 10) / 6; - } + r = (mixtable[dev].stereo) ? right : l; + if (mixtable[dev].left == 0xf) + rl = (l < 2) ? 0x80 : 7 - (l - 2) / 14; + else + rl = (l < 10) ? 0x80 : 15 - (l - 10) / 6; + es = mix_getdevinfo(m); + ES_LOCK(es); + if (dev == SOUND_MIXER_PCM && (ES_SINGLE_PCM_MIX(es->escfg)) && + ES_DAC1_ENABLED(es->escfg)) + set_dac1 = 1; + else + set_dac1 = 0; if (mixtable[dev].stereo) { - rr = (r < 10)? 0x80 : 15 - (r - 10) / 6; - es1370_wrcodec(mix_getdevinfo(m), mixtable[dev].right, rr); + rr = (r < 10) ? 0x80 : 15 - (r - 10) / 6; + es1370_wrcodec(es, mixtable[dev].right, rr); + if (set_dac1 && mixtable[SOUND_MIXER_SYNTH].stereo) + es1370_wrcodec(es, + mixtable[SOUND_MIXER_SYNTH].right, rr); } - es1370_wrcodec(mix_getdevinfo(m), mixtable[dev].left, rl); - return l | (r << 8); + es1370_wrcodec(es, mixtable[dev].left, rl); + if (set_dac1) + es1370_wrcodec(es, mixtable[SOUND_MIXER_SYNTH].left, rl); + ES_UNLOCK(es); + + return (l | (r << 8)); } static int -es1370_mixsetrecsrc(struct snd_mixer *m, u_int32_t src) +es1370_mixsetrecsrc(struct snd_mixer *m, uint32_t src) { + struct es_info *es; int i, j = 0; + es = mix_getdevinfo(m); if (src == 0) src = 1 << SOUND_MIXER_MIC; src &= mix_getrecdevs(m); for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) if ((src & (1 << i)) != 0) j |= mixtable[i].recmask; - es1370_wrcodec(mix_getdevinfo(m), CODEC_LIMIX1, j & 0x55); - es1370_wrcodec(mix_getdevinfo(m), CODEC_RIMIX1, j & 0xaa); - es1370_wrcodec(mix_getdevinfo(m), CODEC_LIMIX2, (j >> 8) & 0x17); - es1370_wrcodec(mix_getdevinfo(m), CODEC_RIMIX2, (j >> 8) & 0x0f); - es1370_wrcodec(mix_getdevinfo(m), CODEC_OMIX1, 0x7f); - es1370_wrcodec(mix_getdevinfo(m), CODEC_OMIX2, 0x3f); - return src; + ES_LOCK(es); + if ((src & (1 << SOUND_MIXER_PCM)) && ES_SINGLE_PCM_MIX(es->escfg) && + ES_DAC1_ENABLED(es->escfg)) + j |= mixtable[SOUND_MIXER_SYNTH].recmask; + es1370_wrcodec(es, CODEC_LIMIX1, j & 0x55); + es1370_wrcodec(es, CODEC_RIMIX1, j & 0xaa); + es1370_wrcodec(es, CODEC_LIMIX2, (j >> 8) & 0x17); + es1370_wrcodec(es, CODEC_RIMIX2, (j >> 8) & 0x0f); + es1370_wrcodec(es, CODEC_OMIX1, 0x7f); + es1370_wrcodec(es, CODEC_OMIX2, 0x3f); + ES_UNLOCK(es); + + return (src); } static kobj_method_t es1370_mixer_methods[] = { - KOBJMETHOD(mixer_init, es1370_mixinit), - KOBJMETHOD(mixer_set, es1370_mixset), - KOBJMETHOD(mixer_setrecsrc, es1370_mixsetrecsrc), + KOBJMETHOD(mixer_init, es1370_mixinit), + KOBJMETHOD(mixer_set, es1370_mixset), + KOBJMETHOD(mixer_setrecsrc, es1370_mixsetrecsrc), { 0, 0 } }; MIXER_DECLARE(es1370_mixer); @@ -242,118 +387,339 @@ /* -------------------------------------------------------------------- */ static int -es1370_wrcodec(struct es_info *es, u_char i, u_char data) +es1370_wrcodec(struct es_info *es, unsigned char i, unsigned char data) { - int wait = 100; /* 100 msec timeout */ + unsigned int t; - do { - if ((bus_space_read_4(es->st, es->sh, ES1370_REG_STATUS) & - STAT_CSTAT) == 0) { - bus_space_write_2(es->st, es->sh, ES1370_REG_CODEC, - ((u_short)i << CODEC_INDEX_SHIFT) | data); - return 0; - } - DELAY(1000); - } while (--wait); - printf("pcm: es1370_wrcodec timed out\n"); - return -1; + ES_LOCK_ASSERT(es); + + for (t = 0; t < 0x1000; t++) { + if ((es_rd(es, ES1370_REG_STATUS, 4) & + STAT_CSTAT) == 0) { + es_wr(es, ES1370_REG_CODEC, + ((unsigned short)i << CODEC_INDEX_SHIFT) | data, 2); + return (0); + } + DELAY(1); + } + device_printf(es->dev, "%s: timed out\n", __func__); + return (-1); } /* -------------------------------------------------------------------- */ /* channel interface */ static void * -eschan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) +eschan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, + struct pcm_channel *c, int dir) { struct es_info *es = devinfo; - struct es_chinfo *ch = (dir == PCMDIR_PLAY)? &es->pch : &es->rch; + struct es_chinfo *ch; + uint32_t index; + + ES_LOCK(es); + if (dir == PCMDIR_PLAY) { + index = ES_GP(es->escfg); + es->escfg = ES_SET_GP(es->escfg, index + 1); + if (index == 0) + index = ES_DAC_FIRST(es->escfg); + else if (index == 1) + index = ES_DAC_SECOND(es->escfg); + else { + device_printf(es->dev, + "Invalid ES_GP index: %d\n", index); + ES_UNLOCK(es); + return (NULL); + } + if (!(index == ES_DAC1 || index == ES_DAC2)) { + device_printf(es->dev, "Unknown DAC: %d\n", index + 1); + ES_UNLOCK(es); + return (NULL); + } + if (es->ch[index].channel != NULL) { + device_printf(es->dev, "DAC%d already initialized!\n", + index + 1); + ES_UNLOCK(es); + return (NULL); + } + } else + index = ES_ADC; + + ch = &es->ch[index]; + ch->index = index; + ch->num = es->num++; + ch->caps = es_caps; + if (ES_IS_ES1370(es->escfg)) { + if (ch->index == ES_DAC1) { + ch->caps.maxspeed = ES1370_DAC1_MAXSPEED; + ch->caps.minspeed = ES1370_DAC1_MINSPEED; + } else { + uint32_t fixed_rate = ES_FIXED_RATE(es->escfg); + if (!(fixed_rate < es_caps.minspeed || + fixed_rate > es_caps.maxspeed)) { + ch->caps.maxspeed = fixed_rate; + ch->caps.minspeed = fixed_rate; + } + } + } ch->parent = es; ch->channel = c; ch->buffer = b; ch->bufsz = es->bufsz; - ch->blksz = ch->bufsz / 2; - ch->num = ch->parent->num++; - if (sndbuf_alloc(ch->buffer, es->parent_dmat, ch->bufsz) != 0) - return NULL; - return ch; + ch->blkcnt = es->blkcnt; + ch->blksz = ch->bufsz / ch->blkcnt; + ch->dir = dir; + ES_UNLOCK(es); + if (sndbuf_alloc(ch->buffer, es->parent_dmat, 0, ch->bufsz) != 0) + return (NULL); + ES_LOCK(es); + if (dir == PCMDIR_PLAY) { + if (ch->index == ES_DAC1) { + es_wr(es, ES1370_REG_MEMPAGE, + ES1370_REG_DAC1_FRAMEADR >> 8, 1); + es_wr(es, ES1370_REG_DAC1_FRAMEADR & 0xff, + sndbuf_getbufaddr(ch->buffer), 4); + es_wr(es, ES1370_REG_DAC1_FRAMECNT & 0xff, + (ch->bufsz >> 2) - 1, 4); + } else { + es_wr(es, ES1370_REG_MEMPAGE, + ES1370_REG_DAC2_FRAMEADR >> 8, 1); + es_wr(es, ES1370_REG_DAC2_FRAMEADR & 0xff, + sndbuf_getbufaddr(ch->buffer), 4); + es_wr(es, ES1370_REG_DAC2_FRAMECNT & 0xff, + (ch->bufsz >> 2) - 1, 4); + } + } else { + es_wr(es, ES1370_REG_MEMPAGE, ES1370_REG_ADC_FRAMEADR >> 8, 1); + es_wr(es, ES1370_REG_ADC_FRAMEADR & 0xff, + sndbuf_getbufaddr(ch->buffer), 4); + es_wr(es, ES1370_REG_ADC_FRAMECNT & 0xff, + (ch->bufsz >> 2) - 1, 4); + } + ES_UNLOCK(es); + return (ch); } static int -eschan_setdir(kobj_t obj, void *data, int dir) +eschan_setformat(kobj_t obj, void *data, uint32_t format) { struct es_chinfo *ch = data; struct es_info *es = ch->parent; - if (dir == PCMDIR_PLAY) { - bus_space_write_1(es->st, es->sh, ES1370_REG_MEMPAGE, ES1370_REG_DAC2_FRAMEADR >> 8); - bus_space_write_4(es->st, es->sh, ES1370_REG_DAC2_FRAMEADR & 0xff, sndbuf_getbufaddr(ch->buffer)); - bus_space_write_4(es->st, es->sh, ES1370_REG_DAC2_FRAMECNT & 0xff, (ch->bufsz >> 2) - 1); + ES_LOCK(es); + if (ch->dir == PCMDIR_PLAY) { + if (ch->index == ES_DAC1) { + es->sctrl &= ~SCTRL_P1FMT; + if (format & AFMT_S16_LE) + es->sctrl |= SCTRL_P1SEB; + if (format & AFMT_STEREO) + es->sctrl |= SCTRL_P1SMB; + } else { + es->sctrl &= ~SCTRL_P2FMT; + if (format & AFMT_S16_LE) + es->sctrl |= SCTRL_P2SEB; + if (format & AFMT_STEREO) + es->sctrl |= SCTRL_P2SMB; + } } else { - bus_space_write_1(es->st, es->sh, ES1370_REG_MEMPAGE, ES1370_REG_ADC_FRAMEADR >> 8); - bus_space_write_4(es->st, es->sh, ES1370_REG_ADC_FRAMEADR & 0xff, sndbuf_getbufaddr(ch->buffer)); - bus_space_write_4(es->st, es->sh, ES1370_REG_ADC_FRAMECNT & 0xff, (ch->bufsz >> 2) - 1); + es->sctrl &= ~SCTRL_R1FMT; + if (format & AFMT_S16_LE) + es->sctrl |= SCTRL_R1SEB; + if (format & AFMT_STEREO) + es->sctrl |= SCTRL_R1SMB; } - ch->dir = dir; - return 0; + es_wr(es, ES1370_REG_SERIAL_CONTROL, es->sctrl, 4); + ES_UNLOCK(es); + ch->fmt = format; + return (0); } static int -eschan_setformat(kobj_t obj, void *data, u_int32_t format) +eschan1370_setspeed(kobj_t obj, void *data, uint32_t speed) { struct es_chinfo *ch = data; struct es_info *es = ch->parent; - if (ch->dir == PCMDIR_PLAY) { - es->sctrl &= ~SCTRL_P2FMT; - if (format & AFMT_S16_LE) es->sctrl |= SCTRL_P2SEB; - if (format & AFMT_STEREO) es->sctrl |= SCTRL_P2SMB; + ES_LOCK(es); + /* Fixed rate , do nothing. */ + if (ch->caps.minspeed == ch->caps.maxspeed) { + ES_UNLOCK(es); + return (ch->caps.maxspeed); + } + if (speed < ch->caps.minspeed) + speed = ch->caps.minspeed; + if (speed > ch->caps.maxspeed) + speed = ch->caps.maxspeed; + if (ch->index == ES_DAC1) { + /* + * DAC1 does not support continuous rate settings. + * Pick the nearest and use it since FEEDER_RATE will + * do the the proper conversion for us. + */ + es->ctrl &= ~CTRL_WTSRSEL; + if (speed < 8268) { + speed = 5512; + es->ctrl |= 0 << CTRL_SH_WTSRSEL; + } else if (speed < 16537) { + speed = 11025; + es->ctrl |= 1 << CTRL_SH_WTSRSEL; + } else if (speed < 33075) { + speed = 22050; + es->ctrl |= 2 << CTRL_SH_WTSRSEL; + } else { + speed = 44100; + es->ctrl |= 3 << CTRL_SH_WTSRSEL; + } } else { - es->sctrl &= ~SCTRL_R1FMT; - if (format & AFMT_S16_LE) es->sctrl |= SCTRL_R1SEB; - if (format & AFMT_STEREO) es->sctrl |= SCTRL_R1SMB; + es->ctrl &= ~CTRL_PCLKDIV; + es->ctrl |= DAC2_SRTODIV(speed) << CTRL_SH_PCLKDIV; } - bus_space_write_4(es->st, es->sh, ES1370_REG_SERIAL_CONTROL, es->sctrl); - ch->fmt = format; - return 0; + es_wr(es, ES1370_REG_CONTROL, es->ctrl, 4); + ES_UNLOCK(es); + return (speed); } static int -eschan1370_setspeed(kobj_t obj, void *data, u_int32_t speed) +eschan1371_setspeed(kobj_t obj, void *data, uint32_t speed) { - struct es_chinfo *ch = data; - struct es_info *es = ch->parent; + struct es_chinfo *ch = data; + struct es_info *es = ch->parent; + uint32_t i; + int delta; - es->ctrl &= ~CTRL_PCLKDIV; - es->ctrl |= DAC2_SRTODIV(speed) << CTRL_SH_PCLKDIV; - bus_space_write_4(es->st, es->sh, ES1370_REG_CONTROL, es->ctrl); - /* rec/play speeds locked together - should indicate in flags */ - return speed; /* XXX calc real speed */ + ES_LOCK(es); + if (ch->dir == PCMDIR_PLAY) + i = es1371_dac_rate(es, speed, ch->index); /* play */ + else + i = es1371_adc_rate(es, speed, ch->index); /* record */ + ES_UNLOCK(es); + delta = (speed > i) ? (speed - i) : (i - speed); + if (delta < 2) + return (speed); + return (i); } static int -eschan1371_setspeed(kobj_t obj, void *data, u_int32_t speed) +eschan_setfragments(kobj_t obj, void *data, uint32_t blksz, uint32_t blkcnt) { struct es_chinfo *ch = data; struct es_info *es = ch->parent; - if (ch->dir == PCMDIR_PLAY) { - return es1371_dac_rate(es, speed, 3 - ch->num); /* play */ - } else { - return es1371_adc_rate(es, speed, 1); /* record */ + blksz &= ES_BLK_ALIGN; + + if (blksz > (sndbuf_getmaxsize(ch->buffer) / ES_DMA_SEGS_MIN)) + blksz = sndbuf_getmaxsize(ch->buffer) / ES_DMA_SEGS_MIN; + if (blksz < ES_BLK_MIN) + blksz = ES_BLK_MIN; + if (blkcnt > ES_DMA_SEGS_MAX) + blkcnt = ES_DMA_SEGS_MAX; + if (blkcnt < ES_DMA_SEGS_MIN) + blkcnt = ES_DMA_SEGS_MIN; + + while ((blksz * blkcnt) > sndbuf_getmaxsize(ch->buffer)) { + if ((blkcnt >> 1) >= ES_DMA_SEGS_MIN) + blkcnt >>= 1; + else if ((blksz >> 1) >= ES_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(es->dev, "%s: failed blksz=%u blkcnt=%u\n", + __func__, blksz, blkcnt); + + ch->bufsz = sndbuf_getsize(ch->buffer); + ch->blksz = sndbuf_getblksz(ch->buffer); + ch->blkcnt = sndbuf_getblkcnt(ch->buffer); + + return (1); } static int -eschan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) +eschan_setblocksize(kobj_t obj, void *data, uint32_t blksz) { struct es_chinfo *ch = data; + struct es_info *es = ch->parent; + + eschan_setfragments(obj, data, blksz, es->blkcnt); + + return (ch->blksz); +} + +#define es_chan_active(es) ((es)->ch[ES_DAC1].active + \ + (es)->ch[ES_DAC2].active + \ + (es)->ch[ES_ADC].active) + +static __inline int +es_poll_channel(struct es_chinfo *ch) +{ + struct es_info *es; + uint32_t sz, delta; + uint32_t reg, ptr; + + if (ch == NULL || ch->channel == NULL || ch->active == 0) + return (0); + + es = ch->parent; + if (ch->dir == PCMDIR_PLAY) { + if (ch->index == ES_DAC1) + reg = ES1370_REG_DAC1_FRAMECNT; + else + reg = ES1370_REG_DAC2_FRAMECNT; + } else + reg = ES1370_REG_ADC_FRAMECNT; + sz = ch->blksz * ch->blkcnt; + es_wr(es, ES1370_REG_MEMPAGE, reg >> 8, 4); + ptr = es_rd(es, reg & 0x000000ff, 4) >> 16; + ptr <<= 2; + 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); +} + +static void +es_poll_callback(void *arg) +{ + struct es_info *es = arg; + uint32_t trigger = 0; + int i; + + if (es == NULL) + return; - ch->blksz = blocksize; - ch->bufsz = ch->blksz * 2; - sndbuf_resize(ch->buffer, 2, ch->blksz); + ES_LOCK(es); + if (es->polling == 0 || es_chan_active(es) == 0) { + ES_UNLOCK(es); + return; + } + + for (i = 0; i < ES_NCHANS; i++) { + if (es_poll_channel(&es->ch[i]) != 0) + trigger |= 1 << i; + } - return ch->blksz; + /* XXX */ + callout_reset(&es->poll_timer, 1/*es->poll_ticks*/, + es_poll_callback, es); + + ES_UNLOCK(es); + + for (i = 0; i < ES_NCHANS; i++) { + if (trigger & (1 << i)) + chn_intr(es->ch[i].channel); + } } static int @@ -361,38 +727,97 @@ { struct es_chinfo *ch = data; struct es_info *es = ch->parent; - unsigned cnt; + uint32_t cnt, b = 0; - if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) + if (!PCMTRIG_COMMON(go)) return 0; + ES_LOCK(es); cnt = (ch->blksz / sndbuf_getbps(ch->buffer)) - 1; - + if (ch->fmt & AFMT_16BIT) + b |= 0x02; + if (ch->fmt & AFMT_STEREO) + b |= 0x01; if (ch->dir == PCMDIR_PLAY) { if (go == PCMTRIG_START) { - int b = (ch->fmt & AFMT_S16_LE)? 2 : 1; - es->ctrl |= CTRL_DAC2_EN; - es->sctrl &= ~(SCTRL_P2ENDINC | SCTRL_P2STINC | SCTRL_P2LOOPSEL | SCTRL_P2PAUSE | SCTRL_P2DACSEN); - es->sctrl |= SCTRL_P2INTEN | (b << SCTRL_SH_P2ENDINC); - bus_space_write_4(es->st, es->sh, ES1370_REG_DAC2_SCOUNT, cnt); - /* start at beginning of buffer */ - bus_space_write_4(es->st, es->sh, ES1370_REG_MEMPAGE, ES1370_REG_DAC2_FRAMECNT >> 8); - bus_space_write_4(es->st, es->sh, ES1370_REG_DAC2_FRAMECNT & 0xff, (ch->bufsz >> 2) - 1); - } else es->ctrl &= ~CTRL_DAC2_EN; + if (ch->index == ES_DAC1) { + es->ctrl |= CTRL_DAC1_EN; + es->sctrl &= ~(SCTRL_P1LOOPSEL | + SCTRL_P1PAUSE | SCTRL_P1SCTRLD); + if (es->polling == 0) + es->sctrl |= SCTRL_P1INTEN; + else + es->sctrl &= ~SCTRL_P1INTEN; + es->sctrl |= b; + es_wr(es, ES1370_REG_DAC1_SCOUNT, cnt, 4); + /* start at beginning of buffer */ + es_wr(es, ES1370_REG_MEMPAGE, + ES1370_REG_DAC1_FRAMECNT >> 8, 4); + es_wr(es, ES1370_REG_DAC1_FRAMECNT & 0xff, + (ch->bufsz >> 2) - 1, 4); + } else { + es->ctrl |= CTRL_DAC2_EN; + es->sctrl &= ~(SCTRL_P2ENDINC | SCTRL_P2STINC | + SCTRL_P2LOOPSEL | SCTRL_P2PAUSE | + SCTRL_P2DACSEN); + if (es->polling == 0) + es->sctrl |= SCTRL_P2INTEN; + else + es->sctrl &= ~SCTRL_P2INTEN; + es->sctrl |= (b << 2) | + ((((b >> 1) & 1) + 1) << SCTRL_SH_P2ENDINC); + es_wr(es, ES1370_REG_DAC2_SCOUNT, cnt, 4); + /* start at beginning of buffer */ + es_wr(es, ES1370_REG_MEMPAGE, + ES1370_REG_DAC2_FRAMECNT >> 8, 4); + es_wr(es, ES1370_REG_DAC2_FRAMECNT & 0xff, + (ch->bufsz >> 2) - 1, 4); + } + } else + es->ctrl &= ~((ch->index == ES_DAC1) ? + CTRL_DAC1_EN : CTRL_DAC2_EN); } else { if (go == PCMTRIG_START) { es->ctrl |= CTRL_ADC_EN; es->sctrl &= ~SCTRL_R1LOOPSEL; - es->sctrl |= SCTRL_R1INTEN; - bus_space_write_4(es->st, es->sh, ES1370_REG_ADC_SCOUNT, cnt); + if (es->polling == 0) + es->sctrl |= SCTRL_R1INTEN; + else + es->sctrl &= ~SCTRL_R1INTEN; + es->sctrl |= b << 4; + es_wr(es, ES1370_REG_ADC_SCOUNT, cnt, 4); /* start at beginning of buffer */ - bus_space_write_4(es->st, es->sh, ES1370_REG_MEMPAGE, ES1370_REG_ADC_FRAMECNT >> 8); - bus_space_write_4(es->st, es->sh, ES1370_REG_ADC_FRAMECNT & 0xff, (ch->bufsz >> 2) - 1); - } else es->ctrl &= ~CTRL_ADC_EN; - } - bus_space_write_4(es->st, es->sh, ES1370_REG_SERIAL_CONTROL, es->sctrl); - bus_space_write_4(es->st, es->sh, ES1370_REG_CONTROL, es->ctrl); - return 0; + es_wr(es, ES1370_REG_MEMPAGE, + ES1370_REG_ADC_FRAMECNT >> 8, 4); + es_wr(es, ES1370_REG_ADC_FRAMECNT & 0xff, + (ch->bufsz >> 2) - 1, 4); + } else + es->ctrl &= ~CTRL_ADC_EN; + } + es_wr(es, ES1370_REG_SERIAL_CONTROL, es->sctrl, 4); + es_wr(es, ES1370_REG_CONTROL, es->ctrl, 4); + if (go == PCMTRIG_START) { + if (es->polling != 0) { + ch->ptr = 0; + ch->prevptr = 0; + if (es_chan_active(es) == 0) { + es->poll_ticks = 1; + callout_reset(&es->poll_timer, 1, + es_poll_callback, es); + } + } + ch->active = 1; + } else { + ch->active = 0; + if (es->polling != 0) { + if (es_chan_active(es) == 0) { + callout_stop(&es->poll_timer); + es->poll_ticks = 1; + } + } + } + ES_UNLOCK(es); + return (0); } static int @@ -400,48 +825,61 @@ { struct es_chinfo *ch = data; struct es_info *es = ch->parent; - u_int32_t reg, cnt; + uint32_t reg, cnt; - if (ch->dir == PCMDIR_PLAY) - reg = ES1370_REG_DAC2_FRAMECNT; - else - reg = ES1370_REG_ADC_FRAMECNT; + ES_LOCK(es); + if (es->polling != 0) + cnt = ch->ptr; + else { + if (ch->dir == PCMDIR_PLAY) { + if (ch->index == ES_DAC1) + reg = ES1370_REG_DAC1_FRAMECNT; + else + reg = ES1370_REG_DAC2_FRAMECNT; + } else + reg = ES1370_REG_ADC_FRAMECNT; + es_wr(es, ES1370_REG_MEMPAGE, reg >> 8, 4); + cnt = es_rd(es, reg & 0x000000ff, 4) >> 16; + /* cnt is longwords */ + cnt <<= 2; + } + ES_UNLOCK(es); - bus_space_write_4(es->st, es->sh, ES1370_REG_MEMPAGE, reg >> 8); - cnt = bus_space_read_4(es->st, es->sh, reg & 0x000000ff) >> 16; - /* cnt is longwords */ - return cnt << 2; + cnt &= ES_BLK_ALIGN; + + return (cnt); } static struct pcmchan_caps * eschan_getcaps(kobj_t obj, void *data) { struct es_chinfo *ch = data; - return (ch->dir == PCMDIR_PLAY)? &es_playcaps : &es_reccaps; + + return (&ch->caps); } static kobj_method_t eschan1370_methods[] = { - KOBJMETHOD(channel_init, eschan_init), - KOBJMETHOD(channel_setdir, eschan_setdir), - KOBJMETHOD(channel_setformat, eschan_setformat), - KOBJMETHOD(channel_setspeed, eschan1370_setspeed), - KOBJMETHOD(channel_setblocksize, eschan_setblocksize), - KOBJMETHOD(channel_trigger, eschan_trigger), - KOBJMETHOD(channel_getptr, eschan_getptr), - KOBJMETHOD(channel_getcaps, eschan_getcaps), + KOBJMETHOD(channel_init, eschan_init), + KOBJMETHOD(channel_setformat, eschan_setformat), + KOBJMETHOD(channel_setspeed, eschan1370_setspeed), + KOBJMETHOD(channel_setblocksize, eschan_setblocksize), + KOBJMETHOD(channel_setfragments, eschan_setfragments), + KOBJMETHOD(channel_trigger, eschan_trigger), + KOBJMETHOD(channel_getptr, eschan_getptr), + KOBJMETHOD(channel_getcaps, eschan_getcaps), { 0, 0 } }; CHANNEL_DECLARE(eschan1370); static kobj_method_t eschan1371_methods[] = { - KOBJMETHOD(channel_init, eschan_init), - KOBJMETHOD(channel_setdir, eschan_setdir), - KOBJMETHOD(channel_setformat, eschan_setformat), - KOBJMETHOD(channel_setspeed, eschan1371_setspeed), - KOBJMETHOD(channel_setblocksize, eschan_setblocksize), - KOBJMETHOD(channel_trigger, eschan_trigger), - KOBJMETHOD(channel_getptr, eschan_getptr), - KOBJMETHOD(channel_getcaps, eschan_getcaps), + KOBJMETHOD(channel_init, eschan_init), + KOBJMETHOD(channel_setformat, eschan_setformat), + KOBJMETHOD(channel_setspeed, eschan1371_setspeed), + KOBJMETHOD(channel_setblocksize, eschan_setblocksize), + KOBJMETHOD(channel_setfragments, eschan_setfragments), + KOBJMETHOD(channel_trigger, eschan_trigger), + KOBJMETHOD(channel_getptr, eschan_getptr), + KOBJMETHOD(channel_getcaps, eschan_getcaps), { 0, 0 } }; CHANNEL_DECLARE(eschan1371); @@ -452,102 +890,187 @@ es_intr(void *p) { struct es_info *es = p; - unsigned intsrc, sctrl; + uint32_t intsrc, sctrl; - intsrc = bus_space_read_4(es->st, es->sh, ES1370_REG_STATUS); - if ((intsrc & STAT_INTR) == 0) return; + ES_LOCK(es); + if (es->polling != 0) { + ES_UNLOCK(es); + return; + } + intsrc = es_rd(es, ES1370_REG_STATUS, 4); + if ((intsrc & STAT_INTR) == 0) { + ES_UNLOCK(es); + return; + } sctrl = es->sctrl; - if (intsrc & STAT_ADC) sctrl &= ~SCTRL_R1INTEN; - if (intsrc & STAT_DAC1) sctrl &= ~SCTRL_P1INTEN; - if (intsrc & STAT_DAC2) sctrl &= ~SCTRL_P2INTEN; - - bus_space_write_4(es->st, es->sh, ES1370_REG_SERIAL_CONTROL, sctrl); - bus_space_write_4(es->st, es->sh, ES1370_REG_SERIAL_CONTROL, es->sctrl); - - if (intsrc & STAT_ADC) chn_intr(es->rch.channel); - if (intsrc & STAT_DAC1); - if (intsrc & STAT_DAC2) chn_intr(es->pch.channel); + if (intsrc & STAT_ADC) + sctrl &= ~SCTRL_R1INTEN; + if (intsrc & STAT_DAC1) + sctrl &= ~SCTRL_P1INTEN; + if (intsrc & STAT_DAC2) + sctrl &= ~SCTRL_P2INTEN; + + es_wr(es, ES1370_REG_SERIAL_CONTROL, sctrl, 4); + es_wr(es, ES1370_REG_SERIAL_CONTROL, es->sctrl, 4); + ES_UNLOCK(es); + + if (intsrc & STAT_ADC) + chn_intr(es->ch[ES_ADC].channel); + if (intsrc & STAT_DAC1) + chn_intr(es->ch[ES_DAC1].channel); + if (intsrc & STAT_DAC2) + chn_intr(es->ch[ES_DAC2].channel); } /* ES1370 specific */ static int es1370_init(struct es_info *es) { - es->ctrl = CTRL_CDC_EN | CTRL_SERR_DIS | - (DAC2_SRTODIV(DSP_DEFAULT_SPEED) << CTRL_SH_PCLKDIV); - bus_space_write_4(es->st, es->sh, ES1370_REG_CONTROL, es->ctrl); + uint32_t fixed_rate; + int r, single_pcm; + + /* ES1370 default to fixed rate operation */ + if (resource_int_value(device_get_name(es->dev), + device_get_unit(es->dev), "fixed_rate", &r) == 0) { + fixed_rate = r; + if (fixed_rate) { + if (fixed_rate < es_caps.minspeed) + fixed_rate = es_caps.minspeed; + if (fixed_rate > es_caps.maxspeed) + fixed_rate = es_caps.maxspeed; + } + } else + fixed_rate = es_caps.maxspeed; + + if (resource_int_value(device_get_name(es->dev), + device_get_unit(es->dev), "single_pcm_mixer", &r) == 0) + single_pcm = (r != 0) ? 1 : 0; + else + single_pcm = 1; + + ES_LOCK(es); + if (ES_NUMPLAY(es->escfg) == 1) + single_pcm = 1; + /* This is ES1370 */ + es->escfg = ES_SET_IS_ES1370(es->escfg, 1); + if (fixed_rate) + es->escfg = ES_SET_FIXED_RATE(es->escfg, fixed_rate); + else { + es->escfg = ES_SET_FIXED_RATE(es->escfg, 0); + fixed_rate = DSP_DEFAULT_SPEED; + } + if (single_pcm) + es->escfg = ES_SET_SINGLE_PCM_MIX(es->escfg, 1); + else + es->escfg = ES_SET_SINGLE_PCM_MIX(es->escfg, 0); + es->ctrl = CTRL_CDC_EN | CTRL_JYSTK_EN | CTRL_SERR_DIS | + (DAC2_SRTODIV(fixed_rate) << CTRL_SH_PCLKDIV); + es->ctrl |= 3 << CTRL_SH_WTSRSEL; + es_wr(es, ES1370_REG_CONTROL, es->ctrl, 4); es->sctrl = 0; - bus_space_write_4(es->st, es->sh, ES1370_REG_SERIAL_CONTROL, es->sctrl); + es_wr(es, ES1370_REG_SERIAL_CONTROL, es->sctrl, 4); - es1370_wrcodec(es, CODEC_RES_PD, 3);/* No RST, PD */ - es1370_wrcodec(es, CODEC_CSEL, 0); /* CODEC ADC and CODEC DAC use - * {LR,B}CLK2 and run off the LRCLK2 - * PLL; program DAC_SYNC=0! */ - es1370_wrcodec(es, CODEC_ADSEL, 0);/* Recording source is mixer */ - es1370_wrcodec(es, CODEC_MGAIN, 0);/* MIC amp is 0db */ + /* No RST, PD */ + es1370_wrcodec(es, CODEC_RES_PD, 3); + /* + * CODEC ADC and CODEC DAC use {LR,B}CLK2 and run off the LRCLK2 PLL; + * program DAC_SYNC=0! + */ + es1370_wrcodec(es, CODEC_CSEL, 0); + /* Recording source is mixer */ + es1370_wrcodec(es, CODEC_ADSEL, 0); + /* MIC amp is 0db */ + es1370_wrcodec(es, CODEC_MGAIN, 0); + ES_UNLOCK(es); - return 0; + return (0); } /* ES1371 specific */ int -es1371_init(struct es_info *es, device_t dev) +es1371_init(struct es_info *es) { + uint32_t cssr, devid, revid, subdev; int idx; - int devid = pci_get_devid(dev); - int revid = pci_get_revid(dev); - - if (debug > 0) printf("es_init\n"); + ES_LOCK(es); + /* This is NOT ES1370 */ + es->escfg = ES_SET_IS_ES1370(es->escfg, 0); es->num = 0; - es->ctrl = 0; es->sctrl = 0; + cssr = 0; + devid = pci_get_devid(es->dev); + revid = pci_get_revid(es->dev); + subdev = (pci_get_subdevice(es->dev) << 16) | + pci_get_subvendor(es->dev); + /* + * Joyport blacklist. Either we're facing with broken hardware + * or because this hardware need special (unknown) initialization + * procedures. + */ + switch (subdev) { + case 0x20001274: /* old Ensoniq */ + es->ctrl = 0; + break; + default: + es->ctrl = CTRL_JYSTK_EN; + break; + } + if (devid == CT4730_PCI_ID) { + /* XXX amplifier hack? */ + es->ctrl |= (1 << 16); + } /* initialize the chips */ + es_wr(es, ES1370_REG_CONTROL, es->ctrl, 4); + es_wr(es, ES1370_REG_SERIAL_CONTROL, es->sctrl, 4); + es_wr(es, ES1371_REG_LEGACY, 0, 4); if ((devid == ES1371_PCI_ID && revid == ES1371REV_ES1373_8) || (devid == ES1371_PCI_ID && revid == ES1371REV_CT5880_A) || (devid == CT5880_PCI_ID && revid == CT5880REV_CT5880_C) || (devid == CT5880_PCI_ID && revid == CT5880REV_CT5880_D) || - (devid == CT5880_PCI_ID && revid == CT5880REV_CT5880_E) || - (devid == CT4730_PCI_ID)) { - bus_space_write_4(es->st, es->sh, ES1370_REG_STATUS, 0x20000000); + (devid == CT5880_PCI_ID && revid == CT5880REV_CT5880_E)) { + cssr = 1 << 29; + es_wr(es, ES1370_REG_STATUS, cssr, 4); DELAY(20000); - if (debug > 0) device_printf(dev, "ac97 2.1 enabled\n"); - } else { /* pre ac97 2.1 card */ - bus_space_write_4(es->st, es->sh, ES1370_REG_CONTROL, es->ctrl); - if (debug > 0) device_printf(dev, "ac97 pre-2.1 enabled\n"); } - bus_space_write_4(es->st, es->sh, ES1370_REG_SERIAL_CONTROL, es->sctrl); - bus_space_write_4(es->st, es->sh, ES1371_REG_LEGACY, 0); /* AC'97 warm reset to start the bitclk */ - bus_space_write_4(es->st, es->sh, ES1371_REG_LEGACY, es->ctrl | ES1371_SYNC_RES); + es_wr(es, ES1370_REG_CONTROL, es->ctrl, 4); + es_wr(es, ES1371_REG_LEGACY, ES1371_SYNC_RES, 4); DELAY(2000); - bus_space_write_4(es->st, es->sh, ES1370_REG_SERIAL_CONTROL, es->ctrl); + es_wr(es, ES1370_REG_CONTROL, es->sctrl, 4); + es1371_wait_src_ready(es); /* Init the sample rate converter */ - bus_space_write_4(es->st, es->sh, ES1371_REG_SMPRATE, ES1371_DIS_SRC); + es_wr(es, ES1371_REG_SMPRATE, ES1371_DIS_SRC, 4); for (idx = 0; idx < 0x80; idx++) es1371_src_write(es, idx, 0); - es1371_src_write(es, ES_SMPREG_DAC1 + ES_SMPREG_TRUNC_N, 16 << 4); + es1371_src_write(es, ES_SMPREG_DAC1 + ES_SMPREG_TRUNC_N, 16 << 4); es1371_src_write(es, ES_SMPREG_DAC1 + ES_SMPREG_INT_REGS, 16 << 10); - es1371_src_write(es, ES_SMPREG_DAC2 + ES_SMPREG_TRUNC_N, 16 << 4); + es1371_src_write(es, ES_SMPREG_DAC2 + ES_SMPREG_TRUNC_N, 16 << 4); es1371_src_write(es, ES_SMPREG_DAC2 + ES_SMPREG_INT_REGS, 16 << 10); - es1371_src_write(es, ES_SMPREG_VOL_ADC, 1 << 12); - es1371_src_write(es, ES_SMPREG_VOL_ADC + 1, 1 << 12); - es1371_src_write(es, ES_SMPREG_VOL_DAC1, 1 << 12); - es1371_src_write(es, ES_SMPREG_VOL_DAC1 + 1, 1 << 12); - es1371_src_write(es, ES_SMPREG_VOL_DAC2, 1 << 12); - es1371_src_write(es, ES_SMPREG_VOL_DAC2 + 1, 1 << 12); - es1371_adc_rate (es, 22050, 1); - es1371_dac_rate (es, 22050, 1); - es1371_dac_rate (es, 22050, 2); - /* WARNING: + es1371_src_write(es, ES_SMPREG_VOL_ADC, 1 << 12); + es1371_src_write(es, ES_SMPREG_VOL_ADC + 1, 1 << 12); + es1371_src_write(es, ES_SMPREG_VOL_DAC1, 1 << 12); + es1371_src_write(es, ES_SMPREG_VOL_DAC1 + 1, 1 << 12); + es1371_src_write(es, ES_SMPREG_VOL_DAC2, 1 << 12); + es1371_src_write(es, ES_SMPREG_VOL_DAC2 + 1, 1 << 12); + es1371_adc_rate(es, 22050, ES_ADC); + es1371_dac_rate(es, 22050, ES_DAC1); + es1371_dac_rate(es, 22050, ES_DAC2); + /* + * WARNING: * enabling the sample rate converter without properly programming * its parameters causes the chip to lock up (the SRC busy bit will * be stuck high, and I've found no way to rectify this other than * power cycle) */ - bus_space_write_4(es->st, es->sh, ES1371_REG_SMPRATE, 0); + es1371_wait_src_ready(es); + es_wr(es, ES1371_REG_SMPRATE, 0, 4); + /* try to reset codec directly */ + es_wr(es, ES1371_REG_CODEC, 0, 4); + es_wr(es, ES1370_REG_STATUS, cssr, 4); + ES_UNLOCK(es); return (0); } @@ -555,132 +1078,131 @@ /* -------------------------------------------------------------------- */ static int -es1371_wrcd(kobj_t obj, void *s, int addr, u_int32_t data) +es1371_wrcd(kobj_t obj, void *s, int addr, uint32_t data) { - int sl; - unsigned t, x; + uint32_t t, x, orig; struct es_info *es = (struct es_info*)s; - if (debug > 0) printf("wrcodec addr 0x%x data 0x%x\n", addr, data); - - for (t = 0; t < 0x1000; t++) - if (!(bus_space_read_4(es->st, es->sh,(ES1371_REG_CODEC & CODEC_WIP)))) + for (t = 0; t < 0x1000; t++) { + if (!es_rd(es, ES1371_REG_CODEC & CODEC_WIP, 4)) break; - sl = spltty(); + } /* save the current state for later */ - x = bus_space_read_4(es->st, es->sh, ES1371_REG_SMPRATE); + x = orig = es_rd(es, ES1371_REG_SMPRATE, 4); /* enable SRC state data in SRC mux */ - bus_space_write_4(es->st, es->sh, ES1371_REG_SMPRATE, - (es1371_wait_src_ready(s) & - (ES1371_DIS_SRC | ES1371_DIS_P1 | ES1371_DIS_P2 | ES1371_DIS_R1))); + es_wr(es, ES1371_REG_SMPRATE, (x & (ES1371_DIS_SRC | ES1371_DIS_P1 | + ES1371_DIS_P2 | ES1371_DIS_R1)) | 0x00010000, 4); + /* busy wait */ + for (t = 0; t < 0x1000; t++) { + if ((es_rd(es, ES1371_REG_SMPRATE, 4) & 0x00870000) == + 0x00000000) + break; + } /* wait for a SAFE time to write addr/data and then do it, dammit */ - for (t = 0; t < 0x1000; t++) - if ((bus_space_read_4(es->st, es->sh, ES1371_REG_SMPRATE) & 0x00070000) == 0x00010000) + for (t = 0; t < 0x1000; t++) { + if ((es_rd(es, ES1371_REG_SMPRATE, 4) & 0x00870000) == + 0x00010000) break; + } - if (debug > 2) - printf("one b_s_w: 0x%lx 0x%x 0x%x\n", - rman_get_start(es->reg), ES1371_REG_CODEC, - ((addr << CODEC_POADD_SHIFT) & CODEC_POADD_MASK) | - ((data << CODEC_PODAT_SHIFT) & CODEC_PODAT_MASK)); - - bus_space_write_4(es->st, es->sh,ES1371_REG_CODEC, - ((addr << CODEC_POADD_SHIFT) & CODEC_POADD_MASK) | - ((data << CODEC_PODAT_SHIFT) & CODEC_PODAT_MASK)); + es_wr(es, ES1371_REG_CODEC, ((addr << CODEC_POADD_SHIFT) & + CODEC_POADD_MASK) | ((data << CODEC_PODAT_SHIFT) & + CODEC_PODAT_MASK), 4); /* restore SRC reg */ es1371_wait_src_ready(s); - if (debug > 2) - printf("two b_s_w: 0x%lx 0x%x 0x%x\n", - rman_get_start(es->reg), ES1371_REG_SMPRATE, x); - bus_space_write_4(es->st, es->sh, ES1371_REG_SMPRATE, x); - splx(sl); + es_wr(es, ES1371_REG_SMPRATE, orig, 4); - return 0; + return (0); } static int es1371_rdcd(kobj_t obj, void *s, int addr) { - int sl; - unsigned t, x = 0; + uint32_t t, x, orig; struct es_info *es = (struct es_info *)s; - if (debug > 0) printf("rdcodec addr 0x%x ... ", addr); - - for (t = 0; t < 0x1000; t++) - if (!(x = bus_space_read_4(es->st, es->sh, ES1371_REG_CODEC) & CODEC_WIP)) + for (t = 0; t < 0x1000; t++) { + if (!(x = es_rd(es, ES1371_REG_CODEC, 4) & CODEC_WIP)) break; - if (debug > 0) printf("loop 1 t 0x%x x 0x%x ", t, x); - - sl = spltty(); + } /* save the current state for later */ - x = bus_space_read_4(es->st, es->sh, ES1371_REG_SMPRATE); + x = orig = es_rd(es, ES1371_REG_SMPRATE, 4); /* enable SRC state data in SRC mux */ - bus_space_write_4(es->st, es->sh, ES1371_REG_SMPRATE, - (es1371_wait_src_ready(s) & - (ES1371_DIS_SRC | ES1371_DIS_P1 | ES1371_DIS_P2 | ES1371_DIS_R1))); + es_wr(es, ES1371_REG_SMPRATE, (x & (ES1371_DIS_SRC | ES1371_DIS_P1 | + ES1371_DIS_P2 | ES1371_DIS_R1)) | 0x00010000, 4); + /* busy wait */ + for (t = 0; t < 0x1000; t++) { + if ((x = es_rd(es, ES1371_REG_SMPRATE, 4) & 0x00870000) == + 0x00000000) + break; + } /* wait for a SAFE time to write addr/data and then do it, dammit */ - for (t = 0; t < 0x5000; t++) - if ((x = bus_space_read_4(es->st, es->sh, ES1371_REG_SMPRATE) & 0x00070000) == 0x00010000) + for (t = 0; t < 0x1000; t++) { + if ((x = es_rd(es, ES1371_REG_SMPRATE, 4) & 0x00870000) == + 0x00010000) break; - if (debug > 0) printf("loop 2 t 0x%x x 0x%x ", t, x); - bus_space_write_4(es->st, es->sh, ES1371_REG_CODEC, - ((addr << CODEC_POADD_SHIFT) & CODEC_POADD_MASK) | CODEC_PORD); + } + + es_wr(es, ES1371_REG_CODEC, ((addr << CODEC_POADD_SHIFT) & + CODEC_POADD_MASK) | CODEC_PORD, 4); /* restore SRC reg */ es1371_wait_src_ready(s); - bus_space_write_4(es->st, es->sh, ES1371_REG_SMPRATE, x); - - splx(sl); + es_wr(es, ES1371_REG_SMPRATE, orig, 4); /* now wait for the stinkin' data (RDY) */ - for (t = 0; t < 0x1000; t++) - if ((x = bus_space_read_4(es->st, es->sh, ES1371_REG_CODEC)) & CODEC_RDY) + for (t = 0; t < 0x1000; t++) { + if ((x = es_rd(es, ES1371_REG_CODEC, 4)) & CODEC_RDY) break; - if (debug > 0) printf("loop 3 t 0x%x 0x%x ret 0x%x\n", t, x, ((x & CODEC_PIDAT_MASK) >> CODEC_PIDAT_SHIFT)); + } + return ((x & CODEC_PIDAT_MASK) >> CODEC_PIDAT_SHIFT); } static kobj_method_t es1371_ac97_methods[] = { - KOBJMETHOD(ac97_read, es1371_rdcd), - KOBJMETHOD(ac97_write, es1371_wrcd), + KOBJMETHOD(ac97_read, es1371_rdcd), + KOBJMETHOD(ac97_write, es1371_wrcd), { 0, 0 } }; AC97_DECLARE(es1371_ac97); /* -------------------------------------------------------------------- */ -static u_int -es1371_src_read(struct es_info *es, u_short reg) +static unsigned int +es1371_src_read(struct es_info *es, unsigned short reg) { - unsigned int r; + uint32_t r; - r = es1371_wait_src_ready(es) & - (ES1371_DIS_SRC | ES1371_DIS_P1 | ES1371_DIS_P2 | ES1371_DIS_R1); + r = es1371_wait_src_ready(es) & (ES1371_DIS_SRC | ES1371_DIS_P1 | + ES1371_DIS_P2 | ES1371_DIS_R1); r |= ES1371_SRC_RAM_ADDRO(reg); - bus_space_write_4(es->st, es->sh, ES1371_REG_SMPRATE,r); - return ES1371_SRC_RAM_DATAI(es1371_wait_src_ready(es)); + es_wr(es, ES1371_REG_SMPRATE, r, 4); + return (ES1371_SRC_RAM_DATAI(es1371_wait_src_ready(es))); } static void -es1371_src_write(struct es_info *es, u_short reg, u_short data){ - u_int r; +es1371_src_write(struct es_info *es, unsigned short reg, unsigned short data) +{ + uint32_t r; - r = es1371_wait_src_ready(es) & - (ES1371_DIS_SRC | ES1371_DIS_P1 | ES1371_DIS_P2 | ES1371_DIS_R1); + r = es1371_wait_src_ready(es) & (ES1371_DIS_SRC | ES1371_DIS_P1 | + ES1371_DIS_P2 | ES1371_DIS_R1); r |= ES1371_SRC_RAM_ADDRO(reg) | ES1371_SRC_RAM_DATAO(data); - /* printf("es1371_src_write 0x%x 0x%x\n",ES1371_REG_SMPRATE,r | ES1371_SRC_RAM_WE); */ - bus_space_write_4(es->st, es->sh, ES1371_REG_SMPRATE, r | ES1371_SRC_RAM_WE); + es_wr(es, ES1371_REG_SMPRATE, r | ES1371_SRC_RAM_WE, 4); } -static u_int -es1371_adc_rate(struct es_info *es, u_int rate, int set) +static unsigned int +es1371_adc_rate(struct es_info *es, unsigned int rate, int set) { - u_int n, truncm, freq, result; + unsigned int n, truncm, freq, result; + + ES_LOCK_ASSERT(es); - if (rate > 48000) rate = 48000; - if (rate < 4000) rate = 4000; + if (rate > 48000) + rate = 48000; + if (rate < 4000) + rate = 4000; n = rate / 3000; if ((1 << n) & ((1 << 15) | (1 << 13) | (1 << 11) | (1 << 9))) n--; @@ -689,60 +1211,70 @@ result = (48000UL << 15) / (freq / n); if (set) { if (rate >= 24000) { - if (truncm > 239) truncm = 239; + if (truncm > 239) + truncm = 239; es1371_src_write(es, ES_SMPREG_ADC + ES_SMPREG_TRUNC_N, - (((239 - truncm) >> 1) << 9) | (n << 4)); + (((239 - truncm) >> 1) << 9) | (n << 4)); } else { - if (truncm > 119) truncm = 119; + if (truncm > 119) + truncm = 119; es1371_src_write(es, ES_SMPREG_ADC + ES_SMPREG_TRUNC_N, - 0x8000 | (((119 - truncm) >> 1) << 9) | (n << 4)); + 0x8000 | (((119 - truncm) >> 1) << 9) | (n << 4)); } es1371_src_write(es, ES_SMPREG_ADC + ES_SMPREG_INT_REGS, - (es1371_src_read(es, ES_SMPREG_ADC + ES_SMPREG_INT_REGS) & - 0x00ff) | ((freq >> 5) & 0xfc00)); - es1371_src_write(es, ES_SMPREG_ADC + ES_SMPREG_VFREQ_FRAC, freq & 0x7fff); + (es1371_src_read(es, ES_SMPREG_ADC + ES_SMPREG_INT_REGS) & + 0x00ff) | ((freq >> 5) & 0xfc00)); + es1371_src_write(es, ES_SMPREG_ADC + ES_SMPREG_VFREQ_FRAC, + freq & 0x7fff); es1371_src_write(es, ES_SMPREG_VOL_ADC, n << 8); es1371_src_write(es, ES_SMPREG_VOL_ADC + 1, n << 8); } - return result; + return (result); } -static u_int -es1371_dac_rate(struct es_info *es, u_int rate, int set) +static unsigned int +es1371_dac_rate(struct es_info *es, unsigned int rate, int set) { - u_int freq, r, result, dac, dis; + unsigned int freq, r, result, dac, dis; - if (rate > 48000) rate = 48000; - if (rate < 4000) rate = 4000; - freq = (rate << 15) / 3000; + ES_LOCK_ASSERT(es); + + if (rate > 48000) + rate = 48000; + if (rate < 4000) + rate = 4000; + freq = ((rate << 15) + 1500) / 3000; result = (freq * 3000) >> 15; - if (set) { - dac = (set == 1)? ES_SMPREG_DAC1 : ES_SMPREG_DAC2; - dis = (set == 1)? ES1371_DIS_P2 : ES1371_DIS_P1; - r = (es1371_wait_src_ready(es) & (ES1371_DIS_SRC | ES1371_DIS_P1 | ES1371_DIS_P2 | ES1371_DIS_R1)); - bus_space_write_4(es->st, es->sh, ES1371_REG_SMPRATE, r); - es1371_src_write(es, dac + ES_SMPREG_INT_REGS, - (es1371_src_read(es, dac + ES_SMPREG_INT_REGS) & 0x00ff) | ((freq >> 5) & 0xfc00)); - es1371_src_write(es, dac + ES_SMPREG_VFREQ_FRAC, freq & 0x7fff); - r = (es1371_wait_src_ready(es) & (ES1371_DIS_SRC | dis | ES1371_DIS_R1)); - bus_space_write_4(es->st, es->sh, ES1371_REG_SMPRATE, r); - } - return result; + dac = (set == ES_DAC1) ? ES_SMPREG_DAC1 : ES_SMPREG_DAC2; + dis = (set == ES_DAC1) ? ES1371_DIS_P2 : ES1371_DIS_P1; + r = (es1371_wait_src_ready(es) & (ES1371_DIS_SRC | ES1371_DIS_P1 | + ES1371_DIS_P2 | ES1371_DIS_R1)); + es_wr(es, ES1371_REG_SMPRATE, r, 4); + es1371_src_write(es, dac + ES_SMPREG_INT_REGS, + (es1371_src_read(es, dac + ES_SMPREG_INT_REGS) & 0x00ff) | + ((freq >> 5) & 0xfc00)); + es1371_src_write(es, dac + ES_SMPREG_VFREQ_FRAC, freq & 0x7fff); + r = (es1371_wait_src_ready(es) & + (ES1371_DIS_SRC | dis | ES1371_DIS_R1)); + es_wr(es, ES1371_REG_SMPRATE, r, 4); + return (result); } -static u_int +static uint32_t es1371_wait_src_ready(struct es_info *es) { - u_int t, r; + uint32_t t, r; - for (t = 0; t < 500; t++) { - if (!((r = bus_space_read_4(es->st, es->sh, ES1371_REG_SMPRATE)) & ES1371_SRC_RAM_BUSY)) - return r; - DELAY(1000); + for (t = 0; t < 0x1000; t++) { + if (!((r = es_rd(es, ES1371_REG_SMPRATE, 4)) & + ES1371_SRC_RAM_BUSY)) + return (r); + DELAY(1); } - printf("es1371: wait src ready timeout 0x%x [0x%x]\n", ES1371_REG_SMPRATE, r); - return 0; + device_printf(es->dev, "%s: timed out 0x%x [0x%x]\n", __func__, + ES1371_REG_SMPRATE, r); + return (0); } /* -------------------------------------------------------------------- */ @@ -757,193 +1289,590 @@ switch(pci_get_devid(dev)) { case ES1370_PCI_ID: device_set_desc(dev, "AudioPCI ES1370"); - return 0; - + return (BUS_PROBE_DEFAULT); case ES1371_PCI_ID: switch(pci_get_revid(dev)) { case ES1371REV_ES1371_A: device_set_desc(dev, "AudioPCI ES1371-A"); - return 0; - + return (BUS_PROBE_DEFAULT); case ES1371REV_ES1371_B: device_set_desc(dev, "AudioPCI ES1371-B"); - return 0; - + return (BUS_PROBE_DEFAULT); case ES1371REV_ES1373_A: device_set_desc(dev, "AudioPCI ES1373-A"); - return 0; - + return (BUS_PROBE_DEFAULT); case ES1371REV_ES1373_B: device_set_desc(dev, "AudioPCI ES1373-B"); - return 0; - + return (BUS_PROBE_DEFAULT); case ES1371REV_ES1373_8: device_set_desc(dev, "AudioPCI ES1373-8"); - return 0; - + return (BUS_PROBE_DEFAULT); case ES1371REV_CT5880_A: device_set_desc(dev, "Creative CT5880-A"); - return 0; - + return (BUS_PROBE_DEFAULT); default: device_set_desc(dev, "AudioPCI ES1371-?"); - device_printf(dev, "unknown revision %d -- please report to cg@freebsd.org\n", pci_get_revid(dev)); - return 0; + device_printf(dev, + "unknown revision %d -- please report to " + "freebsd-multimedia@freebsd.org\n", + pci_get_revid(dev)); + return (BUS_PROBE_DEFAULT); } - case ES1371_PCI_ID2: device_set_desc(dev, "Strange AudioPCI ES1371-? (vid=3274)"); - device_printf(dev, "unknown revision %d -- please report to cg@freebsd.org\n", pci_get_revid(dev)); - return 0; - + device_printf(dev, + "unknown revision %d -- please report to " + "freebsd-multimedia@freebsd.org\n", pci_get_revid(dev)); + return (BUS_PROBE_DEFAULT); case CT4730_PCI_ID: switch(pci_get_revid(dev)) { case CT4730REV_CT4730_A: - device_set_desc(dev, "Creative SB AudioPCI CT4730"); - return 0; + device_set_desc(dev, + "Creative SB AudioPCI CT4730/EV1938"); + return (BUS_PROBE_DEFAULT); default: device_set_desc(dev, "Creative SB AudioPCI CT4730-?"); - device_printf(dev, "unknown revision %d -- please report to cg@freebsd.org\n", pci_get_revid(dev)); - return 0; + device_printf(dev, + "unknown revision %d -- please report to " + "freebsd-multimedia@freebsd.org\n", + pci_get_revid(dev)); + return (BUS_PROBE_DEFAULT); } - case CT5880_PCI_ID: switch(pci_get_revid(dev)) { case CT5880REV_CT5880_C: device_set_desc(dev, "Creative CT5880-C"); - return 0; - + return (BUS_PROBE_DEFAULT); case CT5880REV_CT5880_D: device_set_desc(dev, "Creative CT5880-D"); - return 0; - + return (BUS_PROBE_DEFAULT); case CT5880REV_CT5880_E: device_set_desc(dev, "Creative CT5880-E"); - return 0; - + return (BUS_PROBE_DEFAULT); default: device_set_desc(dev, "Creative CT5880-?"); - device_printf(dev, "unknown revision %d -- please report to cg@freebsd.org\n", pci_get_revid(dev)); - return 0; + device_printf(dev, + "unknown revision %d -- please report to " + "freebsd-multimedia@freebsd.org\n", + pci_get_revid(dev)); + return (BUS_PROBE_DEFAULT); } - default: - return ENXIO; + return (ENXIO); + } +} + +#ifdef SND_DYNSYSCTL +static int +sysctl_es137x_spdif_enable(SYSCTL_HANDLER_ARGS) +{ + struct es_info *es; + device_t dev; + uint32_t r; + int err, new_en; + + dev = oidp->oid_arg1; + es = pcm_getdevinfo(dev); + ES_LOCK(es); + r = es_rd(es, ES1370_REG_STATUS, 4); + ES_UNLOCK(es); + new_en = (r & ENABLE_SPDIF) ? 1 : 0; + err = sysctl_handle_int(oidp, &new_en, 0, req); + + if (err || req->newptr == NULL) + return (err); + if (new_en < 0 || new_en > 1) + return (EINVAL); + + ES_LOCK(es); + if (new_en) { + r |= ENABLE_SPDIF; + es->ctrl |= SPDIFEN_B; + es->ctrl |= RECEN_B; + } else { + r &= ~ENABLE_SPDIF; + es->ctrl &= ~SPDIFEN_B; + es->ctrl &= ~RECEN_B; + } + es_wr(es, ES1370_REG_CONTROL, es->ctrl, 4); + es_wr(es, ES1370_REG_STATUS, r, 4); + ES_UNLOCK(es); + + return (0); +} + +static int +sysctl_es137x_latency_timer(SYSCTL_HANDLER_ARGS) +{ + struct es_info *es; + device_t dev; + uint32_t val; + int err; + + dev = oidp->oid_arg1; + es = pcm_getdevinfo(dev); + ES_LOCK(es); + val = pci_read_config(dev, PCIR_LATTIMER, 1); + ES_UNLOCK(es); + err = sysctl_handle_int(oidp, &val, 0, req); + + if (err || req->newptr == NULL) + return (err); + if (val > 255) + return (EINVAL); + + ES_LOCK(es); + pci_write_config(dev, PCIR_LATTIMER, val, 1); + ES_UNLOCK(es); + + return (0); +} + +static int +sysctl_es137x_fixed_rate(SYSCTL_HANDLER_ARGS) +{ + struct es_info *es; + device_t dev; + uint32_t val; + int err; + + dev = oidp->oid_arg1; + es = pcm_getdevinfo(dev); + ES_LOCK(es); + val = ES_FIXED_RATE(es->escfg); + if (val < es_caps.minspeed) + val = 0; + ES_UNLOCK(es); + err = sysctl_handle_int(oidp, &val, 0, req); + + if (err || req->newptr == NULL) + return (err); + if (val != 0 && (val < es_caps.minspeed || val > es_caps.maxspeed)) + return (EINVAL); + + ES_LOCK(es); + if (es->ctrl & (CTRL_DAC2_EN|CTRL_ADC_EN)) { + ES_UNLOCK(es); + return (EBUSY); } + if (val) { + if (val != ES_FIXED_RATE(es->escfg)) { + es->escfg = ES_SET_FIXED_RATE(es->escfg, val); + es->ch[ES_DAC2].caps.maxspeed = val; + es->ch[ES_DAC2].caps.minspeed = val; + es->ch[ES_ADC].caps.maxspeed = val; + es->ch[ES_ADC].caps.minspeed = val; + es->ctrl &= ~CTRL_PCLKDIV; + es->ctrl |= DAC2_SRTODIV(val) << CTRL_SH_PCLKDIV; + es_wr(es, ES1370_REG_CONTROL, es->ctrl, 4); + } + } else { + es->escfg = ES_SET_FIXED_RATE(es->escfg, 0); + es->ch[ES_DAC2].caps = es_caps; + es->ch[ES_ADC].caps = es_caps; + } + ES_UNLOCK(es); + + return (0); +} + +static int +sysctl_es137x_single_pcm_mixer(SYSCTL_HANDLER_ARGS) +{ + struct es_info *es; + struct snddev_info *d; + struct snd_mixer *m; + device_t dev; + uint32_t val, set; + int recsrc, level, err; + + dev = oidp->oid_arg1; + d = device_get_softc(dev); + if (!PCM_REGISTERED(d) || d->mixer_dev == NULL || + d->mixer_dev->si_drv1 == NULL) + return (EINVAL); + es = d->devinfo; + if (es == NULL) + return (EINVAL); + ES_LOCK(es); + set = ES_SINGLE_PCM_MIX(es->escfg); + val = set; + ES_UNLOCK(es); + err = sysctl_handle_int(oidp, &val, 0, req); + + if (err || req->newptr == NULL) + return (err); + if (!(val == 0 || val == 1)) + return (EINVAL); + if (val == set) + return (0); + PCM_ACQUIRE_QUICK(d); + m = (d->mixer_dev != NULL) ? d->mixer_dev->si_drv1 : NULL; + if (m == NULL) { + PCM_RELEASE_QUICK(d); + return (ENODEV); + } + if (mixer_busy(m) != 0) { + PCM_RELEASE_QUICK(d); + return (EBUSY); + } + level = mix_get(m, SOUND_MIXER_PCM); + recsrc = mix_getrecsrc(m); + if (level < 0 || recsrc < 0) { + PCM_RELEASE_QUICK(d); + return (ENXIO); + } + + ES_LOCK(es); + if (es->ctrl & (CTRL_ADC_EN | CTRL_DAC1_EN | CTRL_DAC2_EN)) { + ES_UNLOCK(es); + PCM_RELEASE_QUICK(d); + return (EBUSY); + } + if (val) + es->escfg = ES_SET_SINGLE_PCM_MIX(es->escfg, 1); + else + es->escfg = ES_SET_SINGLE_PCM_MIX(es->escfg, 0); + ES_UNLOCK(es); + if (!val) { + mix_setdevs(m, mix_getdevs(m) | (1 << SOUND_MIXER_SYNTH)); + mix_setrecdevs(m, mix_getrecdevs(m) | (1 << SOUND_MIXER_SYNTH)); + err = mix_set(m, SOUND_MIXER_SYNTH, level & 0x7f, + (level >> 8) & 0x7f); + } else { + err = mix_set(m, SOUND_MIXER_SYNTH, level & 0x7f, + (level >> 8) & 0x7f); + mix_setdevs(m, mix_getdevs(m) & ~(1 << SOUND_MIXER_SYNTH)); + mix_setrecdevs(m, mix_getrecdevs(m) & + ~(1 << SOUND_MIXER_SYNTH)); + } + if (!err) { + level = recsrc; + if (recsrc & (1 << SOUND_MIXER_PCM)) + recsrc |= 1 << SOUND_MIXER_SYNTH; + else if (recsrc & (1 << SOUND_MIXER_SYNTH)) + recsrc |= 1 << SOUND_MIXER_PCM; + if (level != recsrc) + err = mix_setrecsrc(m, recsrc); + } + + PCM_RELEASE_QUICK(d); + + return (err); +} + +static int +sysctl_es_polling(SYSCTL_HANDLER_ARGS) +{ + struct es_info *es; + device_t dev; + int err, val; + + dev = oidp->oid_arg1; + es = pcm_getdevinfo(dev); + if (es == NULL) + return (EINVAL); + ES_LOCK(es); + val = es->polling; + ES_UNLOCK(es); + err = sysctl_handle_int(oidp, &val, 0, req); + + if (err || req->newptr == NULL) + return (err); + if (val < 0 || val > 1) + return (EINVAL); + + ES_LOCK(es); + if (val != es->polling) { + if (es_chan_active(es) != 0) + err = EBUSY; + else if (val == 0) + es->polling = 0; + else + es->polling = 1; + } + ES_UNLOCK(es); + + return (err); +} +#endif /* SND_DYNSYSCTL */ + +static void +es_init_sysctls(device_t dev) +{ +#ifdef SND_DYNSYSCTL + struct es_info *es; + int r, devid, revid; + + devid = pci_get_devid(dev); + revid = pci_get_revid(dev); + es = pcm_getdevinfo(dev); + if ((devid == ES1371_PCI_ID && revid == ES1371REV_ES1373_8) || + (devid == ES1371_PCI_ID && revid == ES1371REV_CT5880_A) || + (devid == CT5880_PCI_ID && revid == CT5880REV_CT5880_C) || + (devid == CT5880_PCI_ID && revid == CT5880REV_CT5880_D) || + (devid == CT5880_PCI_ID && revid == CT5880REV_CT5880_E)) { + /* 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_PROC(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, + "spdif_enabled", CTLTYPE_INT | CTLFLAG_RW, dev, sizeof(dev), + sysctl_es137x_spdif_enable, "I", + "Enable S/PDIF output on primary playback channel"); + } else if (devid == ES1370_PCI_ID) { + /* + * Enable fixed rate sysctl if both DAC2 / ADC enabled. + */ + if (es->ch[ES_DAC2].channel != NULL && + es->ch[ES_ADC].channel != NULL) { + /* 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_PROC(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), + OID_AUTO, "fixed_rate", CTLTYPE_INT | CTLFLAG_RW, + dev, sizeof(dev), sysctl_es137x_fixed_rate, "I", + "Enable fixed rate playback/recording"); + } + /* + * Enable single pcm mixer sysctl if both DAC1/2 enabled. + */ + if (es->ch[ES_DAC1].channel != NULL && + es->ch[ES_DAC2].channel != NULL) { + /* 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_PROC(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), + OID_AUTO, "single_pcm_mixer", + CTLTYPE_INT | CTLFLAG_RW, dev, sizeof(dev), + sysctl_es137x_single_pcm_mixer, "I", + "Single PCM mixer controller for both DAC1/DAC2"); + } + } + if (resource_int_value(device_get_name(dev), device_get_unit(dev), + "latency_timer", &r) == 0 && !(r < 0 || r > 255)) + pci_write_config(dev, PCIR_LATTIMER, r, 1); + /* XXX: 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_PROC(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, + "latency_timer", CTLTYPE_INT | CTLFLAG_RW, dev, sizeof(dev), + sysctl_es137x_latency_timer, "I", + "PCI Latency Timer configuration"); + SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, + "polling", CTLTYPE_INT | CTLFLAG_RW, dev, sizeof(dev), + sysctl_es_polling, "I", + "Enable polling mode"); +#endif /* SND_DYNSYSCTL */ } static int es_pci_attach(device_t dev) { - u_int32_t data; - struct es_info *es = 0; - int mapped; + uint32_t data; + struct es_info *es = NULL; + int mapped, i, numplay, dac_cfg; char status[SND_STATUSLEN]; - struct ac97_info *codec = 0; + struct ac97_info *codec = NULL; kobj_class_t ct = NULL; + uint32_t devid; - if ((es = malloc(sizeof *es, M_DEVBUF, M_NOWAIT | M_ZERO)) == NULL) { - device_printf(dev, "cannot allocate softc\n"); - return ENXIO; - } - + es = malloc(sizeof *es, M_DEVBUF, M_WAITOK | M_ZERO); + es->lock = snd_mtxcreate(device_get_nameunit(dev), "snd_es137x softc"); es->dev = dev; + es->escfg = 0; mapped = 0; + + pci_enable_busmaster(dev); data = pci_read_config(dev, PCIR_COMMAND, 2); - data |= (PCIM_CMD_PORTEN|PCIM_CMD_MEMEN|PCIM_CMD_BUSMASTEREN); + data |= (PCIM_CMD_PORTEN|PCIM_CMD_MEMEN); pci_write_config(dev, PCIR_COMMAND, data, 2); data = pci_read_config(dev, PCIR_COMMAND, 2); if (mapped == 0 && (data & PCIM_CMD_MEMEN)) { es->regid = MEM_MAP_REG; es->regtype = SYS_RES_MEMORY; es->reg = bus_alloc_resource_any(dev, es->regtype, &es->regid, - RF_ACTIVE); - if (es->reg) { - es->st = rman_get_bustag(es->reg); - es->sh = rman_get_bushandle(es->reg); + RF_ACTIVE); + if (es->reg) mapped++; - } } if (mapped == 0 && (data & PCIM_CMD_PORTEN)) { es->regid = PCIR_BAR(0); es->regtype = SYS_RES_IOPORT; es->reg = bus_alloc_resource_any(dev, es->regtype, &es->regid, - RF_ACTIVE); - if (es->reg) { - es->st = rman_get_bustag(es->reg); - es->sh = rman_get_bushandle(es->reg); + RF_ACTIVE); + if (es->reg) mapped++; - } } if (mapped == 0) { device_printf(dev, "unable to map register space\n"); goto bad; } + es->st = rman_get_bustag(es->reg); + es->sh = rman_get_bushandle(es->reg); + callout_init(&es->poll_timer, CALLOUT_MPSAFE); + es->poll_ticks = 1; + + if (resource_int_value(device_get_name(dev), + device_get_unit(dev), "polling", &i) == 0 && i != 0) + es->polling = 1; + else + es->polling = 0; + es->bufsz = pcm_getbuffersize(dev, 4096, ES_DEFAULT_BUFSZ, 65536); + if (resource_int_value(device_get_name(dev), + device_get_unit(dev), "blocksize", &i) == 0 && i > 0) { + i &= ES_BLK_ALIGN; + if (i < ES_BLK_MIN) + i = ES_BLK_MIN; + es->blkcnt = es->bufsz / i; + i = 0; + while (es->blkcnt >> i) + i++; + es->blkcnt = 1 << (i - 1); + if (es->blkcnt < ES_DMA_SEGS_MIN) + es->blkcnt = ES_DMA_SEGS_MIN; + else if (es->blkcnt > ES_DMA_SEGS_MAX) + es->blkcnt = ES_DMA_SEGS_MAX; + + } else + es->blkcnt = 2; + + if (resource_int_value(device_get_name(dev), device_get_unit(dev), + "dac", &dac_cfg) == 0) { + if (dac_cfg < 0 || dac_cfg > 3) + dac_cfg = ES_DEFAULT_DAC_CFG; + } else + dac_cfg = ES_DEFAULT_DAC_CFG; + + switch (dac_cfg) { + case 0: /* Enable all DAC: DAC1, DAC2 */ + numplay = 2; + es->escfg = ES_SET_DAC_FIRST(es->escfg, ES_DAC1); + es->escfg = ES_SET_DAC_SECOND(es->escfg, ES_DAC2); + break; + case 1: /* Only DAC1 */ + numplay = 1; + es->escfg = ES_SET_DAC_FIRST(es->escfg, ES_DAC1); + break; + case 3: /* Enable all DAC / swap position: DAC2, DAC1 */ + numplay = 2; + es->escfg = ES_SET_DAC_FIRST(es->escfg, ES_DAC2); + es->escfg = ES_SET_DAC_SECOND(es->escfg, ES_DAC1); + break; + case 2: /* Only DAC2 */ + default: + numplay = 1; + es->escfg = ES_SET_DAC_FIRST(es->escfg, ES_DAC2); + break; + } + es->escfg = ES_SET_NUMPLAY(es->escfg, numplay); + es->escfg = ES_SET_NUMREC(es->escfg, 1); - if (pci_get_devid(dev) == ES1371_PCI_ID || - pci_get_devid(dev) == ES1371_PCI_ID2 || - pci_get_devid(dev) == CT5880_PCI_ID || - pci_get_devid(dev) == CT4730_PCI_ID) { - if(-1 == es1371_init(es, dev)) { - device_printf(dev, "unable to initialize the card\n"); - goto bad; - } + devid = pci_get_devid(dev); + switch (devid) { + case ES1371_PCI_ID: + case ES1371_PCI_ID2: + case CT5880_PCI_ID: + case CT4730_PCI_ID: + es1371_init(es); codec = AC97_CREATE(dev, es, es1371_ac97); - if (codec == NULL) goto bad; + if (codec == NULL) + goto bad; /* our init routine does everything for us */ /* set to NULL; flag mixer_init not to run the ac97_init */ /* ac97_mixer.init = NULL; */ - if (mixer_init(dev, ac97_getmixerclass(), codec)) goto bad; + if (mixer_init(dev, ac97_getmixerclass(), codec)) + goto bad; ct = &eschan1371_class; - } else if (pci_get_devid(dev) == ES1370_PCI_ID) { - if (-1 == es1370_init(es)) { - device_printf(dev, "unable to initialize the card\n"); + break; + case ES1370_PCI_ID: + es1370_init(es); + /* + * Disable fixed rate operation if DAC2 disabled. + * This is a special case for es1370 only, where the + * speed of both ADC and DAC2 locked together. + */ + if (!ES_DAC2_ENABLED(es->escfg)) + es->escfg = ES_SET_FIXED_RATE(es->escfg, 0); + if (mixer_init(dev, &es1370_mixer_class, es)) goto bad; - } - if (mixer_init(dev, &es1370_mixer_class, es)) goto bad; ct = &eschan1370_class; - } else goto bad; + break; + default: + goto bad; + /* NOTREACHED */ + } es->irqid = 0; es->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &es->irqid, - RF_ACTIVE | RF_SHAREABLE); - if (!es->irq || snd_setup_intr(dev, es->irq, 0, es_intr, es, &es->ih)) { + RF_ACTIVE | RF_SHAREABLE); + if (!es->irq || snd_setup_intr(dev, es->irq, INTR_MPSAFE, es_intr, + es, &es->ih)) { device_printf(dev, "unable to map interrupt\n"); 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, /*maxsize*/es->bufsz, /*nsegments*/1, /*maxsegz*/0x3ffff, - /*flags*/0, /*lockfunc*/busdma_lock_mutex, - /*lockarg*/&Giant, &es->parent_dmat) != 0) { + /*flags*/0, /*lockfunc*/NULL, + /*lockarg*/NULL, &es->parent_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto bad; } snprintf(status, SND_STATUSLEN, "at %s 0x%lx irq %ld %s", - (es->regtype == SYS_RES_IOPORT)? "io" : "memory", - rman_get_start(es->reg), rman_get_start(es->irq),PCM_KLDSTRING(snd_es137x)); + (es->regtype == SYS_RES_IOPORT)? "io" : "memory", + rman_get_start(es->reg), rman_get_start(es->irq), + PCM_KLDSTRING(snd_es137x)); - if (pcm_register(dev, es, 1, 1)) goto bad; + if (pcm_register(dev, es, numplay, 1)) + goto bad; + for (i = 0; i < numplay; i++) + pcm_addchan(dev, PCMDIR_PLAY, ct, es); pcm_addchan(dev, PCMDIR_REC, ct, es); - pcm_addchan(dev, PCMDIR_PLAY, ct, es); + es_init_sysctls(dev); pcm_setstatus(dev, status); + es->escfg = ES_SET_GP(es->escfg, 0); + if (numplay == 1) + device_printf(dev, "\n", + ES_DAC_FIRST(es->escfg) + 1); + else if (numplay == 2) + device_printf(dev, "\n", + ES_DAC_FIRST(es->escfg) + 1, ES_DAC_SECOND(es->escfg) + 1); + return (0); - return 0; - - bad: - if (codec) ac97_destroy(codec); - if (es->reg) bus_release_resource(dev, es->regtype, es->regid, es->reg); - if (es->ih) bus_teardown_intr(dev, es->irq, es->ih); - if (es->irq) bus_release_resource(dev, SYS_RES_IRQ, es->irqid, es->irq); - if (es->parent_dmat) bus_dma_tag_destroy(es->parent_dmat); - if (es) free(es, M_DEVBUF); - return ENXIO; +bad: + if (es->parent_dmat) + bus_dma_tag_destroy(es->parent_dmat); + if (es->ih) + bus_teardown_intr(dev, es->irq, es->ih); + if (es->irq) + bus_release_resource(dev, SYS_RES_IRQ, es->irqid, es->irq); + if (codec) + ac97_destroy(codec); + if (es->reg) + bus_release_resource(dev, es->regtype, es->regid, es->reg); + if (es->lock) + snd_mtxfree(es->lock); + if (es) + free(es, M_DEVBUF); + return (ENXIO); } static int @@ -954,16 +1883,26 @@ r = pcm_unregister(dev); if (r) - return r; + return (r); es = pcm_getdevinfo(dev); - bus_release_resource(dev, es->regtype, es->regid, es->reg); + + if (es != NULL && es->num != 0) { + ES_LOCK(es); + es->polling = 0; + callout_stop(&es->poll_timer); + ES_UNLOCK(es); + callout_drain(&es->poll_timer); + } + bus_teardown_intr(dev, es->irq, es->ih); bus_release_resource(dev, SYS_RES_IRQ, es->irqid, es->irq); + bus_release_resource(dev, es->regtype, es->regid, es->reg); bus_dma_tag_destroy(es->parent_dmat); + snd_mtxfree(es->lock); free(es, M_DEVBUF); - return 0; + return (0); } static device_method_t es_methods[] = { --- sys/dev/sound/pci/es137x.h.orig Sun Jan 30 09:00:04 2005 +++ sys/dev/sound/pci/es137x.h Thu Jul 12 12:04:19 2007 @@ -18,7 +18,7 @@ * 4. Modifications may be freely made to this file if the above conditions * are met. * - * $FreeBSD: src/sys/dev/sound/pci/es137x.h,v 1.4.8.1 2005/01/30 01:00:04 imp Exp $ + * $FreeBSD: src/sys/dev/sound/pci/es137x.h,v 1.6 2005/07/31 13:19:38 netchild Exp $ */ #ifndef _ES1370_REG_H @@ -165,6 +165,17 @@ #define ES1371_SRC_RAM_ADDRO(o) (((o)&0x7f)<<25) /* address of the sample rate converter */ #define ES1371_SRC_RAM_DATAO(o) (((o)&0xffff)<<0) /* current value of the sample rate converter */ #define ES1371_SRC_RAM_DATAI(i) (((i)>>0)&0xffff) /* current value of the sample rate converter */ + +/* + * S/PDIF specific + */ + +/* Use ES1370_REG_CONTROL */ +#define RECEN_B 0x08000000 /* Used to control mixing of analog with digital data */ +#define SPDIFEN_B 0x04000000 /* Reset to switch digital output mux to "THRU" mode */ +/* Use ES1370_REG_STATUS */ +#define ENABLE_SPDIF 0x00040000 /* Used to enable the S/PDIF circuitry */ +#define TEST_SPDIF 0x00020000 /* Used to put the S/PDIF module in "test mode" */ /* * Sample rate converter addresses --- sys/dev/sound/pci/fm801.c.orig Mon Jun 27 04:51:17 2005 +++ sys/dev/sound/pci/fm801.c Thu Jul 12 12:04:19 2007 @@ -29,11 +29,11 @@ #include #include -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/fm801.c,v 1.23.2.3 2005/06/26 20:51:17 anholt Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/fm801.c,v 1.33 2007/06/17 06:10:42 ariff Exp $"); #define PCI_VENDOR_FORTEMEDIA 0x1319 -#define PCI_DEVICE_FORTEMEDIA1 0x08011319 -#define PCI_DEVICE_FORTEMEDIA2 0x08021319 /* ??? have no idea what's this... */ +#define PCI_DEVICE_FORTEMEDIA1 0x08011319 /* Audio controller */ +#define PCI_DEVICE_FORTEMEDIA2 0x08021319 /* Joystick controller */ #define FM_PCM_VOLUME 0x00 #define FM_FM_VOLUME 0x02 @@ -114,7 +114,7 @@ }; static struct pcmchan_caps fm801ch_caps = { - 4000, 48000, + 5500, 48000, fmts, 0 }; @@ -334,7 +334,7 @@ ch->channel = c; ch->buffer = b; ch->dir = dir; - if (sndbuf_alloc(ch->buffer, fm801->parent_dmat, fm801->bufsz) != 0) + if (sndbuf_alloc(ch->buffer, fm801->parent_dmat, 0, fm801->bufsz) != 0) return NULL; return (void *)ch; } @@ -417,15 +417,16 @@ struct fm801_chinfo *ch = data; struct fm801_info *fm801 = ch->parent; - if(ch->dir == PCMDIR_PLAY) { - if(fm801->play_flip) return fm801->play_blksize; + /* + * Don't mind for play_flip, set the blocksize to the + * desired values in any case - otherwise sound playback + * breaks here. + */ + if(ch->dir == PCMDIR_PLAY) fm801->play_blksize = blocksize; - } - if(ch->dir == PCMDIR_REC) { - if(fm801->rec_flip) return fm801->rec_blksize; + if(ch->dir == PCMDIR_REC) fm801->rec_blksize = blocksize; - } DPRINT("fm801ch_setblocksize %d (dir %d)\n",blocksize, ch->dir); @@ -442,9 +443,8 @@ DPRINT("fm801ch_trigger go %d , ", go); - if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) { + if (!PCMTRIG_COMMON(go)) return 0; - } if (ch->dir == PCMDIR_PLAY) { if (go == PCMTRIG_START) { @@ -575,11 +575,7 @@ int mapped = 0; char status[SND_STATUSLEN]; - if ((fm801 = (struct fm801_info *)malloc(sizeof(*fm801), M_DEVBUF, M_NOWAIT | M_ZERO)) == NULL) { - device_printf(dev, "cannot allocate softc\n"); - return ENXIO; - } - + fm801 = malloc(sizeof(*fm801), M_DEVBUF, M_WAITOK | M_ZERO); fm801->type = pci_get_devid(dev); data = pci_read_config(dev, PCIR_COMMAND, 2); @@ -630,7 +626,8 @@ goto oops; } - 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, @@ -705,7 +702,7 @@ if ((id = pci_get_devid(dev)) == PCI_DEVICE_FORTEMEDIA1 ) { device_set_desc(dev, "Forte Media FM801 Audio Controller"); - return 0; + return BUS_PROBE_DEFAULT; } /* if ((id = pci_get_devid(dev)) == PCI_DEVICE_FORTEMEDIA2 ) { --- sys/dev/sound/pci/hda/hda_reg.h.orig Thu Jan 1 07:30:00 1970 +++ sys/dev/sound/pci/hda/hda_reg.h Thu Jul 12 12:04:19 2007 @@ -0,0 +1,1226 @@ +/*- + * Copyright (c) 2006 Stephane E. Potvin + * 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/hda/hda_reg.h,v 1.2 2006/11/26 12:24:05 ariff Exp $ + */ + +#ifndef _HDA_REG_H_ +#define _HDA_REG_H_ + +/**************************************************************************** + * HDA Device Verbs + ****************************************************************************/ + +/* HDA Command */ +#define HDA_CMD_VERB_MASK 0x000fffff +#define HDA_CMD_VERB_SHIFT 0 +#define HDA_CMD_NID_MASK 0x0ff00000 +#define HDA_CMD_NID_SHIFT 20 +#define HDA_CMD_CAD_MASK 0xf0000000 +#define HDA_CMD_CAD_SHIFT 28 + +#define HDA_CMD_VERB_4BIT_SHIFT 16 +#define HDA_CMD_VERB_12BIT_SHIFT 8 + +#define HDA_CMD_VERB_4BIT(verb, payload) \ + (((verb) << HDA_CMD_VERB_4BIT_SHIFT) | (payload)) +#define HDA_CMD_4BIT(cad, nid, verb, payload) \ + (((cad) << HDA_CMD_CAD_SHIFT) | \ + ((nid) << HDA_CMD_NID_SHIFT) | \ + (HDA_CMD_VERB_4BIT((verb), (payload)))) + +#define HDA_CMD_VERB_12BIT(verb, payload) \ + (((verb) << HDA_CMD_VERB_12BIT_SHIFT) | (payload)) +#define HDA_CMD_12BIT(cad, nid, verb, payload) \ + (((cad) << HDA_CMD_CAD_SHIFT) | \ + ((nid) << HDA_CMD_NID_SHIFT) | \ + (HDA_CMD_VERB_12BIT((verb), (payload)))) + +/* Get Parameter */ +#define HDA_CMD_VERB_GET_PARAMETER 0xf00 + +#define HDA_CMD_GET_PARAMETER(cad, nid, payload) \ + (HDA_CMD_12BIT((cad), (nid), \ + HDA_CMD_VERB_GET_PARAMETER, (payload))) + +/* Connection Select Control */ +#define HDA_CMD_VERB_GET_CONN_SELECT_CONTROL 0xf01 +#define HDA_CMD_VERB_SET_CONN_SELECT_CONTROL 0x701 + +#define HDA_CMD_GET_CONN_SELECT_CONTROL(cad, nid) \ + (HDA_CMD_12BIT((cad), (nid), \ + HDA_CMD_VERB_GET_CONN_SELECT_CONTROL, 0x0)) +#define HDA_CMD_SET_CONNECTION_SELECT_CONTROL(cad, nid, payload) \ + (HDA_CMD_12BIT((cad), (nid), \ + HDA_CMD_VERB_SET_CONN_SELECT_CONTROL, (payload))) + +/* Connection List Entry */ +#define HDA_CMD_VERB_GET_CONN_LIST_ENTRY 0xf02 + +#define HDA_CMD_GET_CONN_LIST_ENTRY(cad, nid, payload) \ + (HDA_CMD_12BIT((cad), (nid), \ + HDA_CMD_VERB_GET_CONN_LIST_ENTRY, (payload))) + +#define HDA_CMD_GET_CONN_LIST_ENTRY_SIZE_SHORT 1 +#define HDA_CMD_GET_CONN_LIST_ENTRY_SIZE_LONG 2 + +/* Processing State */ +#define HDA_CMD_VERB_GET_PROCESSING_STATE 0xf03 +#define HDA_CMD_VERB_SET_PROCESSING_STATE 0x703 + +#define HDA_CMD_GET_PROCESSING_STATE(cad, nid) \ + (HDA_CMD_12BIT((cad), (nid), \ + HDA_CMD_VERB_GET_PROCESSING_STATE, 0x0)) +#define HDA_CMD_SET_PROCESSING_STATE(cad, nid, payload) \ + (HDA_CMD_12BIT((cad), (nid), \ + HDA_CMD_VERB_SET_PROCESSING_STATE, (payload))) + +#define HDA_CMD_GET_PROCESSING_STATE_STATE_OFF 0x00 +#define HDA_CMD_GET_PROCESSING_STATE_STATE_ON 0x01 +#define HDA_CMD_GET_PROCESSING_STATE_STATE_BENIGN 0x02 + +/* Coefficient Index */ +#define HDA_CMD_VERB_GET_COEFF_INDEX 0xd +#define HDA_CMD_VERB_SET_COEFF_INDEX 0x5 + +#define HDA_CMD_GET_COEFF_INDEX(cad, nid) \ + (HDA_CMD_4BIT((cad), (nid), \ + HDA_CMD_VERB_GET_COEFF_INDEX, 0x0)) +#define HDA_CMD_SET_COEFF_INDEX(cad, nid, payload) \ + (HDA_CMD_4BIT((cad), (nid), \ + HDA_CMD_VERB_SET_COEFF_INDEX, (payload))) + +/* Processing Coefficient */ +#define HDA_CMD_VERB_GET_PROCESSING_COEFF 0xc +#define HDA_CMD_VERB_SET_PROCESSING_COEFF 0x4 + +#define HDA_CMD_GET_PROCESSING_COEFF(cad, nid) \ + (HDA_CMD_4BIT((cad), (nid), \ + HDA_CMD_VERB_GET_PROCESSING_COEFF, 0x0)) +#define HDA_CMD_SET_PROCESSING_COEFF(cad, nid, payload) \ + (HDA_CMD_4BIT((cad), (nid), \ + HDA_CMD_VERB_SET_PROCESSING_COEFF, (payload))) + +/* Amplifier Gain/Mute */ +#define HDA_CMD_VERB_GET_AMP_GAIN_MUTE 0xb +#define HDA_CMD_VERB_SET_AMP_GAIN_MUTE 0x3 + +#define HDA_CMD_GET_AMP_GAIN_MUTE(cad, nid, payload) \ + (HDA_CMD_4BIT((cad), (nid), \ + HDA_CMD_VERB_GET_AMP_GAIN_MUTE, (payload))) +#define HDA_CMD_SET_AMP_GAIN_MUTE(cad, nid, payload) \ + (HDA_CMD_4BIT((cad), (nid), \ + HDA_CMD_VERB_SET_AMP_GAIN_MUTE, (payload))) + +#define HDA_CMD_GET_AMP_GAIN_MUTE_INPUT 0x0000 +#define HDA_CMD_GET_AMP_GAIN_MUTE_OUTPUT 0x8000 +#define HDA_CMD_GET_AMP_GAIN_MUTE_RIGHT 0x0000 +#define HDA_CMD_GET_AMP_GAIN_MUTE_LEFT 0x2000 + +#define HDA_CMD_GET_AMP_GAIN_MUTE_MUTE_MASK 0x00000008 +#define HDA_CMD_GET_AMP_GAIN_MUTE_MUTE_SHIFT 7 +#define HDA_CMD_GET_AMP_GAIN_MUTE_GAIN_MASK 0x00000007 +#define HDA_CMD_GET_AMP_GAIN_MUTE_GAIN_SHIFT 0 + +#define HDA_CMD_GET_AMP_GAIN_MUTE_MUTE(rsp) \ + (((rsp) & HDA_CMD_GET_AMP_GAIN_MUTE_MUTE_MASK) >> \ + HDA_CMD_GET_AMP_GAIN_MUTE_MUTE_SHIFT) +#define HDA_CMD_GET_AMP_GAIN_MUTE_GAIN(rsp) \ + (((rsp) & HDA_CMD_GET_AMP_GAIN_MUTE_GAIN_MASK) >> \ + HDA_CMD_GET_AMP_GAIN_MUTE_GAIN_SHIFT) + +#define HDA_CMD_SET_AMP_GAIN_MUTE_OUTPUT 0x8000 +#define HDA_CMD_SET_AMP_GAIN_MUTE_INPUT 0x4000 +#define HDA_CMD_SET_AMP_GAIN_MUTE_LEFT 0x2000 +#define HDA_CMD_SET_AMP_GAIN_MUTE_RIGHT 0x1000 +#define HDA_CMD_SET_AMP_GAIN_MUTE_INDEX_MASK 0x0f00 +#define HDA_CMD_SET_AMP_GAIN_MUTE_INDEX_SHIFT 8 +#define HDA_CMD_SET_AMP_GAIN_MUTE_MUTE 0x0080 +#define HDA_CMD_SET_AMP_GAIN_MUTE_GAIN_MASK 0x0007 +#define HDA_CMD_SET_AMP_GAIN_MUTE_GAIN_SHIFT 0 + +#define HDA_CMD_SET_AMP_GAIN_MUTE_INDEX(index) \ + (((index) << HDA_CMD_SET_AMP_GAIN_MUTE_INDEX_SHIFT) & \ + HDA_CMD_SET_AMP_GAIN_MUTE_INDEX_MASK) +#define HDA_CMD_SET_AMP_GAIN_MUTE_GAIN(index) \ + (((index) << HDA_CMD_SET_AMP_GAIN_MUTE_GAIN_SHIFT) & \ + HDA_CMD_SET_AMP_GAIN_MUTE_GAIN_MASK) + +/* Converter format */ +#define HDA_CMD_VERB_GET_CONV_FMT 0xa +#define HDA_CMD_VERB_SET_CONV_FMT 0x2 + +#define HDA_CMD_GET_CONV_FMT(cad, nid) \ + (HDA_CMD_4BIT((cad), (nid), \ + HDA_CMD_VERB_GET_CONV_FMT, 0x0)) +#define HDA_CMD_SET_CONV_FMT(cad, nid, payload) \ + (HDA_CMD_4BIT((cad), (nid), \ + HDA_CMD_VERB_SET_CONV_FMT, (payload))) + +/* Digital Converter Control */ +#define HDA_CMD_VERB_GET_DIGITAL_CONV_FMT 0xf0d +#define HDA_CMD_VERB_SET_DIGITAL_CONV_FMT1 0x70d +#define HDA_CMD_VERB_SET_DIGITAL_CONV_FMT2 0x70e + +#define HDA_CMD_GET_DIGITAL_CONV_FMT(cad, nid) \ + (HDA_CMD_12BIT((cad), (nid), \ + HDA_CMD_VERB_GET_DIGITAL_CONV_FMTT, 0x0)) +#define HDA_CMD_SET_DIGITAL_CONV_FMT1(cad, nid, payload) \ + (HDA_CMD_12BIT((cad), (nid), \ + HDA_CMD_VERB_SET_DIGITAL_CONV_FMT1, (payload))) +#define HDA_CMD_SET_DIGITAL_CONV_FMT2(cad, nid, payload) \ + (HDA_CMD_12BIT((cad), (nid), \ + HDA_CMD_VERB_SET_DIGITAL_CONV_FMT2, (payload))) + +#define HDA_CMD_GET_DIGITAL_CONV_FMT_CC_MASK 0x7f00 +#define HDA_CMD_GET_DIGITAL_CONV_FMT_CC_SHIFT 8 +#define HDA_CMD_GET_DIGITAL_CONV_FMT_L_MASK 0x0080 +#define HDA_CMD_GET_DIGITAL_CONV_FMT_L_SHIFT 7 +#define HDA_CMD_GET_DIGITAL_CONV_FMT_PRO_MASK 0x0040 +#define HDA_CMD_GET_DIGITAL_CONV_FMT_PRO_SHIFT 6 +#define HDA_CMD_GET_DIGITAL_CONV_FMT_NAUDIO_MASK 0x0020 +#define HDA_CMD_GET_DIGITAL_CONV_FMT_NAUDIO_SHIFT 5 +#define HDA_CMD_GET_DIGITAL_CONV_FMT_COPY_MASK 0x0010 +#define HDA_CMD_GET_DIGITAL_CONV_FMT_COPY_SHIFT 4 +#define HDA_CMD_GET_DIGITAL_CONV_FMT_PRE_MASK 0x0008 +#define HDA_CMD_GET_DIGITAL_CONV_FMT_PRE_SHIFT 3 +#define HDA_CMD_GET_DIGITAL_CONV_FMT_VCFG_MASK 0x0004 +#define HDA_CMD_GET_DIGITAL_CONV_FMT_VCFG_SHIFT 2 +#define HDA_CMD_GET_DIGITAL_CONV_FMT_V_MASK 0x0002 +#define HDA_CMD_GET_DIGITAL_CONV_FMT_V_SHIFT 1 +#define HDA_CMD_GET_DIGITAL_CONV_FMT_DIGEN_MASK 0x0001 +#define HDA_CMD_GET_DIGITAL_CONV_FMT_DIGEN_SHIFT 0 + +#define HDA_CMD_GET_DIGITAL_CONV_FMT_CC(rsp) \ + (((rsp) & HDA_CMD_GET_DIGITAL_CONV_FMT_CC_MASK) >> \ + HDA_CMD_GET_DIGITAL_CONV_FMT_CC_SHIFT) +#define HDA_CMD_GET_DIGITAL_CONV_FMT_L(rsp) \ + (((rsp) & HDA_CMD_GET_DIGITAL_CONV_FMT_L_MASK) >> \ + HDA_CMD_GET_DIGITAL_CONV_FMT_L_SHIFT) +#define HDA_CMD_GET_DIGITAL_CONV_FMT_PRO(rsp) \ + (((rsp) & HDA_CMD_GET_DIGITAL_CONV_FMT_PRO_MASK) >> \ + HDA_CMD_GET_DIGITAL_CONV_FMT_PRO_SHIFT) +#define HDA_CMD_GET_DIGITAL_CONV_FMT_NAUDIO(rsp) \ + (((rsp) & HDA_CMD_GET_DIGITAL_CONV_FMT_NAUDIO_MASK) >> \ + HDA_CMD_GET_DIGITAL_CONV_FMT_NAUDIO_SHIFT) +#define HDA_CMD_GET_DIGITAL_CONV_FMT_COPY(rsp) \ + (((rsp) & HDA_CMD_GET_DIGITAL_CONV_FMT_COPY_MASK) >> \ + HDA_CMD_GET_DIGITAL_CONV_FMT_COPY_SHIFT) +#define HDA_CMD_GET_DIGITAL_CONV_FMT_PRE(rsp) \ + (((rsp) & HDA_CMD_GET_DIGITAL_CONV_FMT_PRE_MASK) >> \ + HDA_CMD_GET_DIGITAL_CONV_FMT_PRE_SHIFT) +#define HDA_CMD_GET_DIGITAL_CONV_FMT_VCFG(rsp) \ + (((rsp) & HDA_CMD_GET_DIGITAL_CONV_FMT_VCFG_MASK) >> \ + HDA_CMD_GET_DIGITAL_CONV_FMT_VCFG_SHIFT) +#define HDA_CMD_GET_DIGITAL_CONV_FMT_V(rsp) \ + (((rsp) & HDA_CMD_GET_DIGITAL_CONV_FMT_V_MASK) >> \ + HDA_CMD_GET_DIGITAL_CONV_FMT_V_SHIFT) +#define HDA_CMD_GET_DIGITAL_CONV_FMT_DIGEN(rsp) \ + (((rsp) & HDA_CMD_GET_DIGITAL_CONV_FMT_DIGEN_MASK) >> \ + HDA_CMD_GET_DIGITAL_CONV_FMT_DIGEN_SHIFT) + +#define HDA_CMD_SET_DIGITAL_CONV_FMT1_L 0x80 +#define HDA_CMD_SET_DIGITAL_CONV_FMT1_PRO 0x40 +#define HDA_CMD_SET_DIGITAL_CONV_FMT1_NAUDIO 0x20 +#define HDA_CMD_SET_DIGITAL_CONV_FMT1_COPY 0x10 +#define HDA_CMD_SET_DIGITAL_CONV_FMT1_PRE 0x08 +#define HDA_CMD_SET_DIGITAL_CONV_FMT1_VCFG 0x04 +#define HDA_CMD_SET_DIGITAL_CONV_FMT1_V 0x02 +#define HDA_CMD_SET_DIGITAL_CONV_FMT1_DIGEN 0x01 + +/* Power State */ +#define HDA_CMD_VERB_GET_POWER_STATE 0xf05 +#define HDA_CMD_VERB_SET_POWER_STATE 0x705 + +#define HDA_CMD_GET_POWER_STATE(cad, nid) \ + (HDA_CMD_12BIT((cad), (nid), \ + HDA_CMD_VERB_GET_POWER_STATE, 0x0)) +#define HDA_CMD_SET_POWER_STATE(cad, nid, payload) \ + (HDA_CMD_12BIT((cad), (nid), \ + HDA_CMD_VERB_SET_POWER_STATE, (payload))) + +#define HDA_CMD_POWER_STATE_D0 0x00 +#define HDA_CMD_POWER_STATE_D1 0x01 +#define HDA_CMD_POWER_STATE_D2 0x02 +#define HDA_CMD_POWER_STATE_D3 0x03 + +#define HDA_CMD_POWER_STATE_ACT_MASK 0x000000f0 +#define HDA_CMD_POWER_STATE_ACT_SHIFT 4 +#define HDA_CMD_POWER_STATE_SET_MASK 0x0000000f +#define HDA_CMD_POWER_STATE_SET_SHIFT 0 + +#define HDA_CMD_GET_POWER_STATE_ACT(rsp) \ + (((rsp) & HDA_CMD_POWER_STATE_ACT_MASK) >> \ + HDA_CMD_POWER_STATE_ACT_SHIFT) +#define HDA_CMD_GET_POWER_STATE_SET(rsp) \ + (((rsp) & HDA_CMD_POWER_STATE_SET_MASK) >> \ + HDA_CMD_POWER_STATE_SET_SHIFT) + +#define HDA_CMD_SET_POWER_STATE_ACT(ps) \ + (((ps) << HDA_CMD_POWER_STATE_ACT_SHIFT) & \ + HDA_CMD_POWER_STATE_ACT_MASK) +#define HDA_CMD_SET_POWER_STATE_SET(ps) \ + (((ps) << HDA_CMD_POWER_STATE_SET_SHIFT) & \ + HDA_CMD_POWER_STATE_ACT_MASK) + +/* Converter Stream, Channel */ +#define HDA_CMD_VERB_GET_CONV_STREAM_CHAN 0xf06 +#define HDA_CMD_VERB_SET_CONV_STREAM_CHAN 0x706 + +#define HDA_CMD_GET_CONV_STREAM_CHAN(cad, nid) \ + (HDA_CMD_12BIT((cad), (nid), \ + HDA_CMD_VERB_GET_CONV_STREAM_CHAN, 0x0)) +#define HDA_CMD_SET_CONV_STREAM_CHAN(cad, nid, payload) \ + (HDA_CMD_12BIT((cad), (nid), \ + HDA_CMD_VERB_SET_CONV_STREAM_CHAN, (payload))) + +#define HDA_CMD_CONV_STREAM_CHAN_STREAM_MASK 0x000000f0 +#define HDA_CMD_CONV_STREAM_CHAN_STREAM_SHIFT 4 +#define HDA_CMD_CONV_STREAM_CHAN_CHAN_MASK 0x0000000f +#define HDA_CMD_CONV_STREAM_CHAN_CHAN_SHIFT 0 + +#define HDA_CMD_GET_CONV_STREAM_CHAN_STREAM(rsp) \ + (((rsp) & HDA_CMD_CONV_STREAM_CHAN_STREAM_MASK) >> \ + HDA_CMD_CONV_STREAM_CHAN_STREAM_SHIFT) +#define HDA_CMD_GET_CONV_STREAM_CHAN_CHAN(rsp) \ + (((rsp) & HDA_CMD_CONV_STREAM_CHAN_CHAN_MASK) >> \ + HDA_CMD_CONV_STREAM_CHAN_CHAN_SHIFT) + +#define HDA_CMD_SET_CONV_STREAM_CHAN_STREAM(param) \ + (((param) << HDA_CMD_CONV_STREAM_CHAN_STREAM_SHIFT) & \ + HDA_CMD_CONV_STREAM_CHAN_STREAM_MASK) +#define HDA_CMD_SET_CONV_STREAM_CHAN_CHAN(param) \ + (((param) << HDA_CMD_CONV_STREAM_CHAN_CHAN_SHIFT) & \ + HDA_CMD_CONV_STREAM_CHAN_CHAN_MASK) + +/* Input Converter SDI Select */ +#define HDA_CMD_VERB_GET_INPUT_CONVERTER_SDI_SELECT 0xf04 +#define HDA_CMD_VERB_SET_INPUT_CONVERTER_SDI_SELECT 0x704 + +#define HDA_CMD_GET_INPUT_CONVERTER_SDI_SELECT(cad, nid) \ + (HDA_CMD_12BIT((cad), (nid), \ + HDA_CMD_VERB_GET_INPUT_CONVERTER_SDI_SELECT, 0x0)) +#define HDA_CMD_SET_INPUT_CONVERTER_SDI_SELECT(cad, nid, payload) \ + (HDA_CMD_12BIT((cad), (nid), \ + HDA_CMD_VERB_SET_INPUT_CONVERTER_SDI_SELECT, (payload))) + +/* Pin Widget Control */ +#define HDA_CMD_VERB_GET_PIN_WIDGET_CTRL 0xf07 +#define HDA_CMD_VERB_SET_PIN_WIDGET_CTRL 0x707 + +#define HDA_CMD_GET_PIN_WIDGET_CTRL(cad, nid) \ + (HDA_CMD_12BIT((cad), (nid), \ + HDA_CMD_VERB_GET_PIN_WIDGET_CTRL, 0x0)) +#define HDA_CMD_SET_PIN_WIDGET_CTRL(cad, nid, payload) \ + (HDA_CMD_12BIT((cad), (nid), \ + HDA_CMD_VERB_SET_PIN_WIDGET_CTRL, (payload))) + +#define HDA_CMD_GET_PIN_WIDGET_CTRL_HPHN_ENABLE_MASK 0x00000080 +#define HDA_CMD_GET_PIN_WIDGET_CTRL_HPHN_ENABLE_SHIFT 7 +#define HDA_CMD_GET_PIN_WIDGET_CTRL_OUT_ENABLE_MASK 0x00000040 +#define HDA_CMD_GET_PIN_WIDGET_CTRL_OUT_ENABLE_SHIFT 6 +#define HDA_CMD_GET_PIN_WIDGET_CTRL_IN_ENABLE_MASK 0x00000020 +#define HDA_CMD_GET_PIN_WIDGET_CTRL_IN_ENABLE_SHIFT 5 +#define HDA_CMD_GET_PIN_WIDGET_CTRL_VREF_ENABLE_MASK 0x00000007 +#define HDA_CMD_GET_PIN_WIDGET_CTRL_VREF_ENABLE_SHIFT 0 + +#define HDA_CMD_GET_PIN_WIDGET_CTRL_HPHN_ENABLE(rsp) \ + (((rsp) & HDA_CMD_GET_PIN_WIDGET_CTRL_HPHN_ENABLE_MASK) >> \ + HDA_CMD_GET_PIN_WIDGET_CTRL_HPHN_ENABLE_SHIFT) +#define HDA_CMD_GET_PIN_WIDGET_CTRL_OUT_ENABLE(rsp) \ + (((rsp) & HDA_CMD_GET_PIN_WIDGET_CTRL_OUT_ENABLE_MASK) >> \ + HDA_GET_CMD_PIN_WIDGET_CTRL_OUT_ENABLE_SHIFT) +#define HDA_CMD_GET_PIN_WIDGET_CTRL_IN_ENABLE(rsp) \ + (((rsp) & HDA_CMD_GET_PIN_WIDGET_CTRL_IN_ENABLE_MASK) >> \ + HDA_CMD_GET_PIN_WIDGET_CTRL_IN_ENABLE_SHIFT) +#define HDA_CMD_GET_PIN_WIDGET_CTRL_VREF_ENABLE(rsp) \ + (((rsp) & HDA_CMD_GET_PIN_WIDGET_CTRL_VREF_ENABLE_MASK) >> \ + HDA_CMD_GET_PIN_WIDGET_CTRL_VREF_ENABLE_SHIFT) + +#define HDA_CMD_SET_PIN_WIDGET_CTRL_HPHN_ENABLE 0x80 +#define HDA_CMD_SET_PIN_WIDGET_CTRL_OUT_ENABLE 0x40 +#define HDA_CMD_SET_PIN_WIDGET_CTRL_IN_ENABLE 0x20 +#define HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE_MASK 0x07 +#define HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE_SHIFT 0 + +#define HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE(param) \ + (((param) << HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE_SHIFT) & \ + HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE_MASK) + +#define HDA_CMD_PIN_WIDGET_CTRL_VREF_ENABLE_HIZ 0 +#define HDA_CMD_PIN_WIDGET_CTRL_VREF_ENABLE_50 1 +#define HDA_CMD_PIN_WIDGET_CTRL_VREF_ENABLE_GROUND 2 +#define HDA_CMD_PIN_WIDGET_CTRL_VREF_ENABLE_80 4 +#define HDA_CMD_PIN_WIDGET_CTRL_VREF_ENABLE_100 5 + +/* Unsolicited Response */ +#define HDA_CMD_VERB_GET_UNSOLICITED_RESPONSE 0xf08 +#define HDA_CMD_VERB_SET_UNSOLICITED_RESPONSE 0x708 + +#define HDA_CMD_GET_UNSOLICITED_RESPONSE(cad, nid) \ + (HDA_CMD_12BIT((cad), (nid), \ + HDA_CMD_VERB_GET_UNSOLICITED_RESPONSE, 0x0)) +#define HDA_CMD_SET_UNSOLICITED_RESPONSE(cad, nid, payload) \ + (HDA_CMD_12BIT((cad), (nid), \ + HDA_CMD_VERB_SET_UNSOLICITED_RESPONSE, (payload))) + +#define HDA_CMD_GET_UNSOLICITED_RESPONSE_ENABLE_MASK 0x00000080 +#define HDA_CMD_GET_UNSOLICITED_RESPONSE_ENABLE_SHIFT 7 +#define HDA_CMD_GET_UNSOLICITED_RESPONSE_TAG_MASK 0x0000001f +#define HDA_CMD_GET_UNSOLICITED_RESPONSE_TAG_SHIFT 0 + +#define HDA_CMD_GET_UNSOLICITED_RESPONSE_ENABLE(rsp) \ + (((rsp) & HDA_CMD_GET_UNSOLICITED_RESPONSE_ENABLE_MASK) >> \ + HDA_CMD_GET_UNSOLICITED_RESPONSE_ENABLE_SHIFT) +#define HDA_CMD_GET_UNSOLICITED_RESPONSE_TAG(rsp) \ + (((rsp) & HDA_CMD_GET_UNSOLICITED_RESPONSE_TAG_MASK) >> \ + HDA_CMD_GET_UNSOLICITED_RESPONSE_TAG_SHIFT) + +#define HDA_CMD_SET_UNSOLICITED_RESPONSE_ENABLE 0x80 +#define HDA_CMD_SET_UNSOLICITED_RESPONSE_TAG_MASK 0x1f +#define HDA_CMD_SET_UNSOLICITED_RESPONSE_TAG_SHIFT 0 + +#define HDA_CMD_SET_UNSOLICITED_RESPONSE_TAG(param) \ + (((param) << HDA_CMD_SET_UNSOLICITED_RESPONSE_TAG_SHIFT) & \ + HDA_CMD_SET_UNSOLICITED_RESPONSE_TAG_MASK) + +/* Pin Sense */ +#define HDA_CMD_VERB_GET_PIN_SENSE 0xf09 +#define HDA_CMD_VERB_SET_PIN_SENSE 0x709 + +#define HDA_CMD_GET_PIN_SENSE(cad, nid) \ + (HDA_CMD_12BIT((cad), (nid), \ + HDA_CMD_VERB_GET_PIN_SENSE, 0x0)) +#define HDA_CMD_SET_PIN_SENSE(cad, nid, payload) \ + (HDA_CMD_12BIT((cad), (nid), \ + HDA_CMD_VERB_SET_PIN_SENSE, (payload))) + +#define HDA_CMD_GET_PIN_SENSE_PRESENCE_DETECT_MASK 0x80000000 +#define HDA_CMD_GET_PIN_SENSE_PRESENCE_DETECT_SHIFT 31 +#define HDA_CMD_GET_PIN_SENSE_IMP_SENSE_MASK 0x7fffffff +#define HDA_CMD_GET_PIN_SENSE_IMP_SENSE_SHIFT 0 + +#define HDA_CMD_GET_PIN_SENSE_PRESENCE_DETECT(rsp) \ + (((rsp) & HDA_CMD_GET_PIN_SENSE_PRESENCE_DETECT_MASK) >> \ + HDA_CMD_GET_PIN_SENSE_PRESENCE_DETECT_SHIFT) +#define HDA_CMD_GET_PIN_SENSE_IMP_SENSE(rsp) \ + (((rsp) & HDA_CMD_GET_PIN_SENSE_IMP_SENSE_MASK) >> \ + HDA_CMD_GET_PIN_SENSE_IMP_SENSE_SHIFT) + +#define HDA_CMD_GET_PIN_SENSE_IMP_SENSE_INVALID 0x7fffffff + +#define HDA_CMD_SET_PIN_SENSE_LEFT_CHANNEL 0x00 +#define HDA_CMD_SET_PIN_SENSE_RIGHT_CHANNEL 0x01 + +/* EAPD/BTL Enable */ +#define HDA_CMD_VERB_GET_EAPD_BTL_ENABLE 0xf0c +#define HDA_CMD_VERB_SET_EAPD_BTL_ENABLE 0x70c + +#define HDA_CMD_GET_EAPD_BTL_ENABLE(cad, nid) \ + (HDA_CMD_12BIT((cad), (nid), \ + HDA_CMD_VERB_GET_EAPD_BTL_ENABLE, 0x0)) +#define HDA_CMD_SET_EAPD_BTL_ENABLE(cad, nid, payload) \ + (HDA_CMD_12BIT((cad), (nid), \ + HDA_CMD_VERB_SET_EAPD_BTL_ENABLE, (payload))) + +#define HDA_CMD_GET_EAPD_BTL_ENABLE_LR_SWAP_MASK 0x00000004 +#define HDA_CMD_GET_EAPD_BTL_ENABLE_LR_SWAP_SHIFT 2 +#define HDA_CMD_GET_EAPD_BTL_ENABLE_EAPD_MASK 0x00000002 +#define HDA_CMD_GET_EAPD_BTL_ENABLE_EAPD_SHIFT 1 +#define HDA_CMD_GET_EAPD_BTL_ENABLE_BTL_MASK 0x00000001 +#define HDA_CMD_GET_EAPD_BTL_ENABLE_BTL_SHIFT 0 + +#define HDA_CMD_GET_EAPD_BTL_ENABLE_LR_SWAP(rsp) \ + (((rsp) & HDA_CMD_GET_EAPD_BTL_ENABLE_LR_SWAP_MASK) >> \ + HDA_CMD_GET_EAPD_BTL_ENABLE_LR_SWAP_SHIFT) +#define HDA_CMD_GET_EAPD_BTL_ENABLE_EAPD(rsp) \ + (((rsp) & HDA_CMD_GET_EAPD_BTL_ENABLE_EAPD_MASK) >> \ + HDA_CMD_GET_EAPD_BTL_ENABLE_EAPD_SHIFT) +#define HDA_CMD_GET_EAPD_BTL_ENABLE_BTL(rsp) \ + (((rsp) & HDA_CMD_GET_EAPD_BTL_ENABLE_BTL_MASK) >> \ + HDA_CMD_GET_EAPD_BTL_ENABLE_BTL_SHIFT) + +#define HDA_CMD_SET_EAPD_BTL_ENABLE_LR_SWAP 0x04 +#define HDA_CMD_SET_EAPD_BTL_ENABLE_EAPD 0x02 +#define HDA_CMD_SET_EAPD_BTL_ENABLE_BTL 0x01 + +/* GPI Data */ +#define HDA_CMD_VERB_GET_GPI_DATA 0xf10 +#define HDA_CMD_VERB_SET_GPI_DATA 0x710 + +#define HDA_CMD_GET_GPI_DATA(cad, nid) \ + (HDA_CMD_12BIT((cad), (nid), \ + HDA_CMD_VERB_GET_GPI_DATA, 0x0)) +#define HDA_CMD_SET_GPI_DATA(cad, nid) \ + (HDA_CMD_12BIT((cad), (nid), \ + HDA_CMD_VERB_SET_GPI_DATA, (payload))) + +/* GPI Wake Enable Mask */ +#define HDA_CMD_VERB_GET_GPI_WAKE_ENABLE_MASK 0xf11 +#define HDA_CMD_VERB_SET_GPI_WAKE_ENABLE_MASK 0x711 + +#define HDA_CMD_GET_GPI_WAKE_ENABLE_MASK(cad, nid) \ + (HDA_CMD_12BIT((cad), (nid), \ + HDA_CMD_VERB_GET_GPI_WAKE_ENABLE_MASK, 0x0)) +#define HDA_CMD_SET_GPI_WAKE_ENABLE_MASK(cad, nid, payload) \ + (HDA_CMD_12BIT((cad), (nid), \ + HDA_CMD_VERB_SET_GPI_WAKE_ENABLE_MASK, (payload))) + +/* GPI Unsolicited Enable Mask */ +#define HDA_CMD_VERB_GET_GPI_UNSOLICITED_ENABLE_MASK 0xf12 +#define HDA_CMD_VERB_SET_GPI_UNSOLICITED_ENABLE_MASK 0x712 + +#define HDA_CMD_GET_GPI_UNSOLICITED_ENABLE_MASK(cad, nid) \ + (HDA_CMD_12BIT((cad), (nid), \ + HDA_CMD_VERB_GET_GPI_UNSOLICITED_ENABLE_MASK, 0x0)) +#define HDA_CMD_SET_GPI_UNSOLICITED_ENABLE_MASK(cad, nid, payload) \ + (HDA_CMD_12BIT((cad), (nid), \ + HDA_CMD_VERB_SET_GPI_UNSOLICITED_ENABLE_MASK, (payload))) + +/* GPI Sticky Mask */ +#define HDA_CMD_VERB_GET_GPI_STICKY_MASK 0xf13 +#define HDA_CMD_VERB_SET_GPI_STICKY_MASK 0x713 + +#define HDA_CMD_GET_GPI_STICKY_MASK(cad, nid) \ + (HDA_CMD_12BIT((cad), (nid), \ + HDA_CMD_VERB_GET_GPI_STICKY_MASK, 0x0)) +#define HDA_CMD_SET_GPI_STICKY_MASK(cad, nid, payload) \ + (HDA_CMD_12BIT((cad), (nid), \ + HDA_CMD_VERB_SET_GPI_STICKY_MASK, (payload))) + +/* GPO Data */ +#define HDA_CMD_VERB_GET_GPO_DATA 0xf14 +#define HDA_CMD_VERB_SET_GPO_DATA 0x714 + +#define HDA_CMD_GET_GPO_DATA(cad, nid) \ + (HDA_CMD_12BIT((cad), (nid), \ + HDA_CMD_VERB_GET_GPO_DATA, 0x0)) +#define HDA_CMD_SET_GPO_DATA(cad, nid, payload) \ + (HDA_CMD_12BIT((cad), (nid), \ + HDA_CMD_VERB_SET_GPO_DATA, (payload))) + +/* GPIO Data */ +#define HDA_CMD_VERB_GET_GPIO_DATA 0xf15 +#define HDA_CMD_VERB_SET_GPIO_DATA 0x715 + +#define HDA_CMD_GET_GPIO_DATA(cad, nid) \ + (HDA_CMD_12BIT((cad), (nid), \ + HDA_CMD_VERB_GET_GPIO_DATA, 0x0)) +#define HDA_CMD_SET_GPIO_DATA(cad, nid, payload) \ + (HDA_CMD_12BIT((cad), (nid), \ + HDA_CMD_VERB_SET_GPIO_DATA, (payload))) + +/* GPIO Enable Mask */ +#define HDA_CMD_VERB_GET_GPIO_ENABLE_MASK 0xf16 +#define HDA_CMD_VERB_SET_GPIO_ENABLE_MASK 0x716 + +#define HDA_CMD_GET_GPIO_ENABLE_MASK(cad, nid) \ + (HDA_CMD_12BIT((cad), (nid), \ + HDA_CMD_VERB_GET_GPIO_ENABLE_MASK, 0x0)) +#define HDA_CMD_SET_GPIO_ENABLE_MASK(cad, nid, payload) \ + (HDA_CMD_12BIT((cad), (nid), \ + HDA_CMD_VERB_SET_GPIO_ENABLE_MASK, (payload))) + +/* GPIO Direction */ +#define HDA_CMD_VERB_GET_GPIO_DIRECTION 0xf17 +#define HDA_CMD_VERB_SET_GPIO_DIRECTION 0x717 + +#define HDA_CMD_GET_GPIO_DIRECTION(cad, nid) \ + (HDA_CMD_12BIT((cad), (nid), \ + HDA_CMD_VERB_GET_GPIO_DIRECTION, 0x0)) +#define HDA_CMD_SET_GPIO_DIRECTION(cad, nid, payload) \ + (HDA_CMD_12BIT((cad), (nid), \ + HDA_CMD_VERB_SET_GPIO_DIRECTION, (payload))) + +/* GPIO Wake Enable Mask */ +#define HDA_CMD_VERB_GET_GPIO_WAKE_ENABLE_MASK 0xf18 +#define HDA_CMD_VERB_SET_GPIO_WAKE_ENABLE_MASK 0x718 + +#define HDA_CMD_GET_GPIO_WAKE_ENABLE_MASK(cad, nid) \ + (HDA_CMD_12BIT((cad), (nid), \ + HDA_CMD_VERB_GET_GPIO_WAKE_ENABLE_MASK, 0x0)) +#define HDA_CMD_SET_GPIO_WAKE_ENABLE_MASK(cad, nid, payload) \ + (HDA_CMD_12BIT((cad), (nid), \ + HDA_CMD_VERB_SET_GPIO_WAKE_ENABLE_MASK, (payload))) + +/* GPIO Unsolicited Enable Mask */ +#define HDA_CMD_VERB_GET_GPIO_UNSOLICITED_ENABLE_MASK 0xf19 +#define HDA_CMD_VERB_SET_GPIO_UNSOLICITED_ENABLE_MASK 0x719 + +#define HDA_CMD_GET_GPIO_UNSOLICITED_ENABLE_MASK(cad, nid) \ + (HDA_CMD_12BIT((cad), (nid), \ + HDA_CMD_VERB_GET_GPIO_UNSOLICITED_ENABLE_MASK, 0x0)) +#define HDA_CMD_SET_GPIO_UNSOLICITED_ENABLE_MASK(cad, nid, payload) \ + (HDA_CMD_12BIT((cad), (nid), \ + HDA_CMD_VERB_SET_GPIO_UNSOLICITED_ENABLE_MASK, (payload))) + +/* GPIO_STICKY_MASK */ +#define HDA_CMD_VERB_GET_GPIO_STICKY_MASK 0xf1a +#define HDA_CMD_VERB_SET_GPIO_STICKY_MASK 0x71a + +#define HDA_CMD_GET_GPIO_STICKY_MASK(cad, nid) \ + (HDA_CMD_12BIT((cad), (nid), \ + HDA_CMD_VERB_GET_GPIO_STICKY_MASK, 0x0)) +#define HDA_CMD_SET_GPIO_STICKY_MASK(cad, nid, payload) \ + (HDA_CMD_12BIT((cad), (nid), \ + HDA_CMD_VERB_SET_GPIO_STICKY_MASK, (payload))) + +/* Beep Generation */ +#define HDA_CMD_VERB_GET_BEEP_GENERATION 0xf0a +#define HDA_CMD_VERB_SET_BEEP_GENERATION 0x70a + +#define HDA_CMD_GET_BEEP_GENERATION(cad, nid) \ + (HDA_CMD_12BIT((cad), (nid), \ + HDA_CMD_VERB_GET_BEEP_GENERATION, 0x0)) +#define HDA_CMD_SET_BEEP_GENERATION(cad, nid, payload) \ + (HDA_CMD_12BIT((cad), (nid), \ + HDA_CMD_VERB_SET_BEEP_GENERATION, (payload))) + +/* Volume Knob */ +#define HDA_CMD_VERB_GET_VOLUME_KNOB 0xf0f +#define HDA_CMD_VERB_SET_VOLUME_KNOB 0x70f + +#define HDA_CMD_GET_VOLUME_KNOB(cad, nid) \ + (HDA_CMD_12BIT((cad), (nid), \ + HDA_CMD_VERB_GET_VOLUME_KNOB, 0x0)) +#define HDA_CMD_SET_VOLUME_KNOB(cad, nid, payload) \ + (HDA_CMD_12BIT((cad), (nid), \ + HDA_CMD_VERB_SET_VOLUME_KNOB, (payload))) + +/* Subsystem ID */ +#define HDA_CMD_VERB_GET_SUBSYSTEM_ID 0xf20 +#define HDA_CMD_VERB_SET_SUSBYSTEM_ID1 0x720 +#define HDA_CMD_VERB_SET_SUBSYSTEM_ID2 0x721 +#define HDA_CMD_VERB_SET_SUBSYSTEM_ID3 0x722 +#define HDA_CMD_VERB_SET_SUBSYSTEM_ID4 0x723 + +#define HDA_CMD_GET_SUBSYSTEM_ID(cad, nid) \ + (HDA_CMD_12BIT((cad), (nid), \ + HDA_CMD_VERB_GET_SUBSYSTEM_ID, 0x0)) +#define HDA_CMD_SET_SUBSYSTEM_ID1(cad, nid, payload) \ + (HDA_CMD_12BIT((cad), (nid), \ + HDA_CMD_VERB_SET_SUSBYSTEM_ID1, (payload))) +#define HDA_CMD_SET_SUBSYSTEM_ID2(cad, nid, payload) \ + (HDA_CMD_12BIT((cad), (nid), \ + HDA_CMD_VERB_SET_SUSBYSTEM_ID2, (payload))) +#define HDA_CMD_SET_SUBSYSTEM_ID3(cad, nid, payload) \ + (HDA_CMD_12BIT((cad), (nid), \ + HDA_CMD_VERB_SET_SUSBYSTEM_ID3, (payload))) +#define HDA_CMD_SET_SUBSYSTEM_ID4(cad, nid, payload) \ + (HDA_CMD_12BIT((cad), (nid), \ + HDA_CMD_VERB_SET_SUSBYSTEM_ID4, (payload))) + +/* Configuration Default */ +#define HDA_CMD_VERB_GET_CONFIGURATION_DEFAULT 0xf1c +#define HDA_CMD_VERB_SET_CONFIGURATION_DEFAULT1 0x71c +#define HDA_CMD_VERB_SET_CONFIGURATION_DEFAULT2 0x71d +#define HDA_CMD_VERB_SET_CONFIGURATION_DEFAULT3 0x71e +#define HDA_CMD_VERB_SET_CONFIGURATION_DEFAULT4 0x71f + +#define HDA_CMD_GET_CONFIGURATION_DEFAULT(cad, nid) \ + (HDA_CMD_12BIT((cad), (nid), \ + HDA_CMD_VERB_GET_CONFIGURATION_DEFAULT, 0x0)) +#define HDA_CMD_SET_CONFIGURATION_DEFAULT1(cad, nid, payload) \ + (HDA_CMD_12BIT((cad), (nid), \ + HDA_CMD_VERB_SET_CONFIGURATION_DEFAULT1, (payload))) +#define HDA_CMD_SET_CONFIGURATION_DEFAULT2(cad, nid, payload) \ + (HDA_CMD_12BIT((cad), (nid), \ + HDA_CMD_VERB_SET_CONFIGURATION_DEFAULT2, (payload))) +#define HDA_CMD_SET_CONFIGURATION_DEFAULT3(cad, nid, payload) \ + (HDA_CMD_12BIT((cad), (nid), \ + HDA_CMD_VERB_SET_CONFIGURATION_DEFAULT3, (payload))) +#define HDA_CMD_SET_CONFIGURATION_DEFAULT4(cad, nid, payload) \ + (HDA_CMD_12BIT((cad), (nid), \ + HDA_CMD_VERB_SET_CONFIGURATION_DEFAULT4, (payload))) + +/* Stripe Control */ +#define HDA_CMD_VERB_GET_STRIPE_CONTROL 0xf24 +#define HDA_CMD_VERB_SET_STRIPE_CONTROL 0x724 + +#define HDA_CMD_SET_STRIPE_CONTROL(cad, nid) \ + (HDA_CMD_12BIT((cad), (nid), \ + HDA_CMD_VERB_GET_STRIPE_CONTROL, 0x0)) +#define HDA_CMD_GET_STRIPE_CONTROL(cad, nid, payload) \ + (HDA_CMD_12BIT((cad), (nid), \ + HDA_CMD_VERB_SET_STRIPE_CONTROL, (payload))) + +/* Function Reset */ +#define HDA_CMD_VERB_FUNCTION_RESET 0x7ff + +#define HDA_CMD_FUNCTION_RESET(cad, nid) \ + (HDA_CMD_12BIT((cad), (nid), \ + HDA_CMD_VERB_FUNCTION_RESET, 0x0)) + + +/**************************************************************************** + * HDA Device Parameters + ****************************************************************************/ + +/* Vendor ID */ +#define HDA_PARAM_VENDOR_ID 0x00 + +#define HDA_PARAM_VENDOR_ID_VENDOR_ID_MASK 0xffff0000 +#define HDA_PARAM_VENDOR_ID_VENDOR_ID_SHIFT 16 +#define HDA_PARAM_VENDOR_ID_DEVICE_ID_MASK 0x0000ffff +#define HDA_PARAM_VENDOR_ID_DEVICE_ID_SHIFT 0 + +#define HDA_PARAM_VENDOR_ID_VENDOR_ID(param) \ + (((param) & HDA_PARAM_VENDOR_ID_VENDOR_ID_MASK) >> \ + HDA_PARAM_VENDOR_ID_VENDOR_ID_SHIFT) +#define HDA_PARAM_VENDOR_ID_DEVICE_ID(param) \ + (((param) & HDA_PARAM_VENDOR_ID_DEVICE_ID_MASK) >> \ + HDA_PARAM_VENDOR_ID_DEVICE_ID_SHIFT) + +/* Revision ID */ +#define HDA_PARAM_REVISION_ID 0x02 + +#define HDA_PARAM_REVISION_ID_MAJREV_MASK 0x00f00000 +#define HDA_PARAM_REVISION_ID_MAJREV_SHIFT 20 +#define HDA_PARAM_REVISION_ID_MINREV_MASK 0x000f0000 +#define HDA_PARAM_REVISION_ID_MINREV_SHIFT 16 +#define HDA_PARAM_REVISION_ID_REVISION_ID_MASK 0x0000ff00 +#define HDA_PARAM_REVISION_ID_REVISION_ID_SHIFT 8 +#define HDA_PARAM_REVISION_ID_STEPPING_ID_MASK 0x000000ff +#define HDA_PARAM_REVISION_ID_STEPPING_ID_SHIFT 0 + +#define HDA_PARAM_REVISION_ID_MAJREV(param) \ + (((param) & HDA_PARAM_REVISION_ID_MAJREV_MASK) >> \ + HDA_PARAM_REVISION_ID_MAJREV_SHIFT) +#define HDA_PARAM_REVISION_ID_MINREV(param) \ + (((param) & HDA_PARAM_REVISION_ID_MINREV_MASK) >> \ + HDA_PARAM_REVISION_ID_MINREV_SHIFT) +#define HDA_PARAM_REVISION_ID_REVISION_ID(param) \ + (((param) & HDA_PARAM_REVISION_ID_REVISION_ID_MASK) >> \ + HDA_PARAM_REVISION_ID_REVISION_ID_SHIFT) +#define HDA_PARAM_REVISION_ID_STEPPING_ID(param) \ + (((param) & HDA_PARAM_REVISION_ID_STEPPING_ID_MASK) >> \ + HDA_PARAM_REVISION_ID_STEPPING_ID_SHIFT) + +/* Subordinate Node Cound */ +#define HDA_PARAM_SUB_NODE_COUNT 0x04 + +#define HDA_PARAM_SUB_NODE_COUNT_START_MASK 0x00ff0000 +#define HDA_PARAM_SUB_NODE_COUNT_START_SHIFT 16 +#define HDA_PARAM_SUB_NODE_COUNT_TOTAL_MASK 0x000000ff +#define HDA_PARAM_SUB_NODE_COUNT_TOTAL_SHIFT 0 + +#define HDA_PARAM_SUB_NODE_COUNT_START(param) \ + (((param) & HDA_PARAM_SUB_NODE_COUNT_START_MASK) >> \ + HDA_PARAM_SUB_NODE_COUNT_START_SHIFT) +#define HDA_PARAM_SUB_NODE_COUNT_TOTAL(param) \ + (((param) & HDA_PARAM_SUB_NODE_COUNT_TOTAL_MASK) >> \ + HDA_PARAM_SUB_NODE_COUNT_TOTAL_SHIFT) + +/* Function Group Type */ +#define HDA_PARAM_FCT_GRP_TYPE 0x05 + +#define HDA_PARAM_FCT_GRP_TYPE_UNSOL_MASK 0x00000100 +#define HDA_PARAM_FCT_GRP_TYPE_UNSOL_SHIFT 8 +#define HDA_PARAM_FCT_GRP_TYPE_NODE_TYPE_MASK 0x000000ff +#define HDA_PARAM_FCT_GRP_TYPE_NODE_TYPE_SHIFT 0 + +#define HDA_PARAM_FCT_GRP_TYPE_UNSOL(param) \ + (((param) & HDA_PARAM_FCT_GRP_TYPE_UNSOL_MASK) >> \ + HDA_PARAM_FCT_GROUP_TYPE_UNSOL_SHIFT) +#define HDA_PARAM_FCT_GRP_TYPE_NODE_TYPE(param) \ + (((param) & HDA_PARAM_FCT_GRP_TYPE_NODE_TYPE_MASK) >> \ + HDA_PARAM_FCT_GRP_TYPE_NODE_TYPE_SHIFT) + +#define HDA_PARAM_FCT_GRP_TYPE_NODE_TYPE_AUDIO 0x01 +#define HDA_PARAM_FCT_GRP_TYPE_NODE_TYPE_MODEM 0x02 + +/* Audio Function Group Capabilities */ +#define HDA_PARAM_AUDIO_FCT_GRP_CAP 0x08 + +#define HDA_PARAM_AUDIO_FCT_GRP_CAP_BEEP_GEN_MASK 0x00010000 +#define HDA_PARAM_AUDIO_FCT_GRP_CAP_BEEP_GEN_SHIFT 16 +#define HDA_PARAM_AUDIO_FCT_GRP_CAP_INPUT_DELAY_MASK 0x00000f00 +#define HDA_PARAM_AUDIO_FCT_GRP_CAP_INPUT_DELAY_SHIFT 8 +#define HDA_PARAM_AUDIO_FCT_GRP_CAP_OUTPUT_DELAY_MASK 0x0000000f +#define HDA_PARAM_AUDIO_FCT_GRP_CAP_OUTPUT_DELAY_SHIFT 0 + +#define HDA_PARAM_AUDIO_FCT_GRP_CAP_BEEP_GEN(param) \ + (((param) & HDA_PARAM_AUDIO_FCT_GRP_CAP_BEEP_GEN_MASK) >> \ + HDA_PARAM_AUDIO_FCT_GRP_CAP_BEEP_GEN_SHIFT) +#define HDA_PARAM_AUDIO_FCT_GRP_CAP_INPUT_DELAY(param) \ + (((param) & HDA_PARAM_AUDIO_FCT_GRP_CAP_INPUT_DELAY_MASK) >> \ + HDA_PARAM_AUDIO_FCT_GRP_CAP_INPUT_DELAY_SHIFT) +#define HDA_PARAM_AUDIO_FCT_GRP_CAP_OUTPUT_DELAY(param) \ + (((param) & HDA_PARAM_AUDIO_FCT_GRP_CAP_OUTPUT_DELAY_MASK) >> \ + HDA_PARAM_AUDIO_FCT_GRP_CAP_OUTPUT_DELAY_SHIFT) + +/* Audio Widget Capabilities */ +#define HDA_PARAM_AUDIO_WIDGET_CAP 0x09 + +#define HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_MASK 0x00f00000 +#define HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_SHIFT 20 +#define HDA_PARAM_AUDIO_WIDGET_CAP_DELAY_MASK 0x000f0000 +#define HDA_PARAM_AUDIO_WIDGET_CAP_DELAY_SHIFT 16 +#define HDA_PARAM_AUDIO_WIDGET_CAP_LR_SWAP_MASK 0x00000800 +#define HDA_PARAM_AUDIO_WIDGET_CAP_LR_SWAP_SHIFT 11 +#define HDA_PARAM_AUDIO_WIDGET_CAP_POWER_CTRL_MASK 0x00000400 +#define HDA_PARAM_AUDIO_WIDGET_CAP_POWER_CTRL_SHIFT 10 +#define HDA_PARAM_AUDIO_WIDGET_CAP_DIGITAL_MASK 0x00000200 +#define HDA_PARAM_AUDIO_WIDGET_CAP_DIGITAL_SHIFT 9 +#define HDA_PARAM_AUDIO_WIDGET_CAP_CONN_LIST_MASK 0x00000100 +#define HDA_PARAM_AUDIO_WIDGET_CAP_CONN_LIST_SHIFT 8 +#define HDA_PARAM_AUDIO_WIDGET_CAP_UNSOL_CAP_MASK 0x00000080 +#define HDA_PARAM_AUDIO_WIDGET_CAP_UNSOL_CAP_SHIFT 7 +#define HDA_PARAM_AUDIO_WIDGET_CAP_PROC_WIDGET_MASK 0x00000040 +#define HDA_PARAM_AUDIO_WIDGET_CAP_PROC_WIDGET_SHIFT 6 +#define HDA_PARAM_AUDIO_WIDGET_CAP_STRIPE_MASK 0x00000020 +#define HDA_PARAM_AUDIO_WIDGET_CAP_STRIPE_SHIFT 5 +#define HDA_PARAM_AUDIO_WIDGET_CAP_FORMAT_OVR_MASK 0x00000010 +#define HDA_PARAM_AUDIO_WIDGET_CAP_FORMAT_OVR_SHIFT 4 +#define HDA_PARAM_AUDIO_WIDGET_CAP_AMP_OVR_MASK 0x00000008 +#define HDA_PARAM_AUDIO_WIDGET_CAP_AMP_OVR_SHIFT 3 +#define HDA_PARAM_AUDIO_WIDGET_CAP_OUT_AMP_MASK 0x00000004 +#define HDA_PARAM_AUDIO_WIDGET_CAP_OUT_AMP_SHIFT 2 +#define HDA_PARAM_AUDIO_WIDGET_CAP_IN_AMP_MASK 0x00000002 +#define HDA_PARAM_AUDIO_WIDGET_CAP_IN_AMP_SHIFT 1 +#define HDA_PARAM_AUDIO_WIDGET_CAP_STEREO_MASK 0x00000001 +#define HDA_PARAM_AUDIO_WIDGET_CAP_STEREO_SHIFT 0 + +#define HDA_PARAM_AUDIO_WIDGET_CAP_TYPE(param) \ + (((param) & HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_MASK) >> \ + HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_SHIFT) +#define HDA_PARAM_AUDIO_WIDGET_CAP_DELAY(param) \ + (((param) & HDA_PARAM_AUDIO_WIDGET_CAP_DELAY_MASK) >> \ + HDA_PARAM_AUDIO_WIDGET_CAP_DELAY_SHIFT) +#define HDA_PARAM_AUDIO_WIDGET_CAP_LR_SWAP(param) \ + (((param) & HDA_PARAM_AUDIO_WIDGET_CAP_LR_SWAP_MASK) >> \ + HDA_PARAM_AUDIO_WIDGET_CAP_LR_SWAP_SHIFT) +#define HDA_PARAM_AUDIO_WIDGET_CAP_POWER_CTRL(param) \ + (((param) & HDA_PARAM_AUDIO_WIDGET_CAP_POWER_CTRL_MASK) >> \ + HDA_PARAM_AUDIO_WIDGET_CAP_POWER_CTRL_SHIFT) +#define HDA_PARAM_AUDIO_WIDGET_CAP_DIGITAL(param) \ + (((param) & HDA_PARAM_AUDIO_WIDGET_CAP_DIGITAL_MASK) >> \ + HDA_PARAM_AUDIO_WIDGET_CAP_DIGITAL_SHIFT) +#define HDA_PARAM_AUDIO_WIDGET_CAP_CONN_LIST(param) \ + (((param) & HDA_PARAM_AUDIO_WIDGET_CAP_CONN_LIST_MASK) >> \ + HDA_PARAM_AUDIO_WIDGET_CAP_CONN_LIST_SHIFT) +#define HDA_PARAM_AUDIO_WIDGET_CAP_UNSOL_CAP(param) \ + (((param) & HDA_PARAM_AUDIO_WIDGET_CAP_UNSOL_CAP_MASK) >> \ + HDA_PARAM_AUDIO_WIDGET_CAP_UNSOL_CAP_SHIFT) +#define HDA_PARAM_AUDIO_WIDGET_CAP_PROC_WIDGET(param) \ + (((param) & HDA_PARAM_AUDIO_WIDGET_CAP_PROC_WIDGET_MASK) >> \ + HDA_PARAM_AUDIO_WIDGET_CAP_PROC_WIDGET_SHIFT) +#define HDA_PARAM_AUDIO_WIDGET_CAP_STRIPE(param) \ + (((param) & HDA_PARAM_AUDIO_WIDGET_CAP_STRIPE_MASK) >> \ + HDA_PARAM_AUDIO_WIDGET_CAP_STRIPE_SHIFT) +#define HDA_PARAM_AUDIO_WIDGET_CAP_FORMAT_OVR(param) \ + (((param) & HDA_PARAM_AUDIO_WIDGET_CAP_FORMAT_OVR_MASK) >> \ + HDA_PARAM_AUDIO_WIDGET_CAP_FORMAT_OVR_SHIFT) +#define HDA_PARAM_AUDIO_WIDGET_CAP_AMP_OVR(param) \ + (((param) & HDA_PARAM_AUDIO_WIDGET_CAP_AMP_OVR_MASK) >> \ + HDA_PARAM_AUDIO_WIDGET_CAP_AMP_OVR_SHIFT) +#define HDA_PARAM_AUDIO_WIDGET_CAP_OUT_AMP(param) \ + (((param) & HDA_PARAM_AUDIO_WIDGET_CAP_OUT_AMP_MASK) >> \ + HDA_PARAM_AUDIO_WIDGET_CAP_OUT_AMP_SHIFT) +#define HDA_PARAM_AUDIO_WIDGET_CAP_IN_AMP(param) \ + (((param) & HDA_PARAM_AUDIO_WIDGET_CAP_IN_AMP_MASK) >> \ + HDA_PARAM_AUDIO_WIDGET_CAP_IN_AMP_SHIFT) +#define HDA_PARAM_AUDIO_WIDGET_CAP_STEREO(param) \ + (((param) & HDA_PARAM_AUDIO_WIDGET_CAP_STEREO_MASK) >> \ + HDA_PARAM_AUDIO_WIDGET_CAP_STEREO_SHIFT) + +#define HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_OUTPUT 0x0 +#define HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT 0x1 +#define HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER 0x2 +#define HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_SELECTOR 0x3 +#define HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX 0x4 +#define HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_POWER_WIDGET 0x5 +#define HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_VOLUME_WIDGET 0x6 +#define HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_BEEP_WIDGET 0x7 +#define HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_VENDOR_WIDGET 0xf + +/* Supported PCM Size, Rates */ + +#define HDA_PARAM_SUPP_PCM_SIZE_RATE 0x0a + +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_32BIT_MASK 0x00100000 +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_32BIT_SHIFT 20 +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_24BIT_MASK 0x00080000 +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_24BIT_SHIFT 19 +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_20BIT_MASK 0x00040000 +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_20BIT_SHIFT 18 +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_16BIT_MASK 0x00020000 +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_16BIT_SHIFT 17 +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_8BIT_MASK 0x00010000 +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_8BIT_SHIFT 16 +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_8KHZ_MASK 0x00000001 +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_8KHZ_SHIFT 0 +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_11KHZ_MASK 0x00000002 +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_11KHZ_SHIFT 1 +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_16KHZ_MASK 0x00000004 +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_16KHZ_SHIFT 2 +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_22KHZ_MASK 0x00000008 +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_22KHZ_SHIFT 3 +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_32KHZ_MASK 0x00000010 +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_32KHZ_SHIFT 4 +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_44KHZ_MASK 0x00000020 +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_44KHZ_SHIFT 5 +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_48KHZ_MASK 0x00000040 +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_48KHZ_SHIFT 6 +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_88KHZ_MASK 0x00000080 +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_88KHZ_SHIFT 7 +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_96KHZ_MASK 0x00000100 +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_96KHZ_SHIFT 8 +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_176KHZ_MASK 0x00000200 +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_176KHZ_SHIFT 9 +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_192KHZ_MASK 0x00000400 +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_192KHZ_SHIFT 10 +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_384KHZ_MASK 0x00000800 +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_384KHZ_SHIFT 11 + +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_32BIT(param) \ + (((param) & HDA_PARAM_SUPP_PCM_SIZE_RATE_32BIT_MASK) >> \ + HDA_PARAM_SUPP_PCM_SIZE_RATE_32BIT_SHIFT) +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_24BIT(param) \ + (((param) & HDA_PARAM_SUPP_PCM_SIZE_RATE_24BIT_MASK) >> \ + HDA_PARAM_SUPP_PCM_SIZE_RATE_24BIT_SHIFT) +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_20BIT(param) \ + (((param) & HDA_PARAM_SUPP_PCM_SIZE_RATE_20BIT_MASK) >> \ + HDA_PARAM_SUPP_PCM_SIZE_RATE_20BIT_SHIFT) +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_16BIT(param) \ + (((param) & HDA_PARAM_SUPP_PCM_SIZE_RATE_16BIT_MASK) >> \ + HDA_PARAM_SUPP_PCM_SIZE_RATE_16BIT_SHIFT) +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_8BIT(param) \ + (((param) & HDA_PARAM_SUPP_PCM_SIZE_RATE_8BIT_MASK) >> \ + HDA_PARAM_SUPP_PCM_SIZE_RATE_8BIT_SHIFT) +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_8KHZ(param) \ + (((param) & HDA_PARAM_SUPP_PCM_SIZE_RATE_8KHZ_MASK) >> \ + HDA_PARAM_SUPP_PCM_SIZE_RATE_8KHZ_SHIFT) +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_11KHZ(param) \ + (((param) & HDA_PARAM_SUPP_PCM_SIZE_RATE_11KHZ_MASK) >> \ + HDA_PARAM_SUPP_PCM_SIZE_RATE_11KHZ_SHIFT) +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_16KHZ(param) \ + (((param) & HDA_PARAM_SUPP_PCM_SIZE_RATE_16KHZ_MASK) >> \ + HDA_PARAM_SUPP_PCM_SIZE_RATE_16KHZ_SHIFT) +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_22KHZ(param) \ + (((param) & HDA_PARAM_SUPP_PCM_SIZE_RATE_22KHZ_MASK) >> \ + HDA_PARAM_SUPP_PCM_SIZE_RATE_22KHZ_SHIFT) +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_32KHZ(param) \ + (((param) & HDA_PARAM_SUPP_PCM_SIZE_RATE_32KHZ_MASK) >> \ + HDA_PARAM_SUPP_PCM_SIZE_RATE_32KHZ_SHIFT) +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_44KHZ(param) \ + (((param) & HDA_PARAM_SUPP_PCM_SIZE_RATE_44KHZ_MASK) >> \ + HDA_PARAM_SUPP_PCM_SIZE_RATE_44KHZ_SHIFT) +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_48KHZ(param) \ + (((param) & HDA_PARAM_SUPP_PCM_SIZE_RATE_48KHZ_MASK) >> \ + HDA_PARAM_SUPP_PCM_SIZE_RATE_48KHZ_SHIFT) +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_88KHZ(param) \ + (((param) & HDA_PARAM_SUPP_PCM_SIZE_RATE_88KHZ_MASK) >> \ + HDA_PARAM_SUPP_PCM_SIZE_RATE_88KHZ_SHIFT) +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_96KHZ(param) \ + (((param) & HDA_PARAM_SUPP_PCM_SIZE_RATE_96KHZ_MASK) >> \ + HDA_PARAM_SUPP_PCM_SIZE_RATE_96KHZ_SHIFT) +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_176KHZ(param) \ + (((param) & HDA_PARAM_SUPP_PCM_SIZE_RATE_176KHZ_MASK) >> \ + HDA_PARAM_SUPP_PCM_SIZE_RATE_176KHZ_SHIFT) +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_192KHZ(param) \ + (((param) & HDA_PARAM_SUPP_PCM_SIZE_RATE_192KHZ_MASK) >> \ + HDA_PARAM_SUPP_PCM_SIZE_RATE_192KHZ_SHIFT) +#define HDA_PARAM_SUPP_PCM_SIZE_RATE_384KHZ(param) \ + (((param) & HDA_PARAM_SUPP_PCM_SIZE_RATE_384KHZ_MASK) >> \ + HDA_PARAM_SUPP_PCM_SIZE_RATE_384KHZ_SHIFT) + +/* Supported Stream Formats */ +#define HDA_PARAM_SUPP_STREAM_FORMATS 0x0b + +#define HDA_PARAM_SUPP_STREAM_FORMATS_AC3_MASK 0x00000004 +#define HDA_PARAM_SUPP_STREAM_FORMATS_AC3_SHIFT 2 +#define HDA_PARAM_SUPP_STREAM_FORMATS_FLOAT32_MASK 0x00000002 +#define HDA_PARAM_SUPP_STREAM_FORMATS_FLOAT32_SHIFT 1 +#define HDA_PARAM_SUPP_STREAM_FORMATS_PCM_MASK 0x00000001 +#define HDA_PARAM_SUPP_STREAM_FORMATS_PCM_SHIFT 0 + +#define HDA_PARAM_SUPP_STREAM_FORMATS_AC3(param) \ + (((param) & HDA_PARAM_SUPP_STREAM_FORMATS_AC3_MASK) >> \ + HDA_PARAM_SUPP_STREAM_FORMATS_AC3_SHIFT) +#define HDA_PARAM_SUPP_STREAM_FORMATS_FLOAT32(param) \ + (((param) & HDA_PARAM_SUPP_STREAM_FORMATS_FLOAT32_MASK) >> \ + HDA_PARAM_SUPP_STREAM_FORMATS_FLOAT32_SHIFT) +#define HDA_PARAM_SUPP_STREAM_FORMATS_PCM(param) \ + (((param) & HDA_PARAM_SUPP_STREAM_FORMATS_PCM_MASK) >> \ + HDA_PARAM_SUPP_STREAM_FORMATS_PCM_SHIFT) + +/* Pin Capabilities */ +#define HDA_PARAM_PIN_CAP 0x0c + +#define HDA_PARAM_PIN_CAP_EAPD_CAP_MASK 0x00010000 +#define HDA_PARAM_PIN_CAP_EAPD_CAP_SHIFT 16 +#define HDA_PARAM_PIN_CAP_VREF_CTRL_MASK 0x0000ff00 +#define HDA_PARAM_PIN_CAP_VREF_CTRL_SHIFT 8 +#define HDA_PARAM_PIN_CAP_VREF_CTRL_100_MASK 0x00002000 +#define HDA_PARAM_PIN_CAP_VREF_CTRL_100_SHIFT 13 +#define HDA_PARAM_PIN_CAP_VREF_CTRL_80_MASK 0x00001000 +#define HDA_PARAM_PIN_CAP_VREF_CTRL_80_SHIFT 12 +#define HDA_PARAM_PIN_CAP_VREF_CTRL_GROUND_MASK 0x00000400 +#define HDA_PARAM_PIN_CAP_VREF_CTRL_GROUND_SHIFT 10 +#define HDA_PARAM_PIN_CAP_VREF_CTRL_50_MASK 0x00000200 +#define HDA_PARAM_PIN_CAP_VREF_CTRL_50_SHIFT 9 +#define HDA_PARAM_PIN_CAP_VREF_CTRL_HIZ_MASK 0x00000100 +#define HDA_PARAM_PIN_CAP_VREF_CTRL_HIZ_SHIFT 8 +#define HDA_PARAM_PIN_CAP_BALANCED_IO_PINS_MASK 0x00000040 +#define HDA_PARAM_PIN_CAP_BALANCED_IO_PINS_SHIFT 6 +#define HDA_PARAM_PIN_CAP_INPUT_CAP_MASK 0x00000020 +#define HDA_PARAM_PIN_CAP_INPUT_CAP_SHIFT 5 +#define HDA_PARAM_PIN_CAP_OUTPUT_CAP_MASK 0x00000010 +#define HDA_PARAM_PIN_CAP_OUTPUT_CAP_SHIFT 4 +#define HDA_PARAM_PIN_CAP_HEADPHONE_CAP_MASK 0x00000008 +#define HDA_PARAM_PIN_CAP_HEADPHONE_CAP_SHIFT 3 +#define HDA_PARAM_PIN_CAP_PRESENCE_DETECT_CAP_MASK 0x00000004 +#define HDA_PARAM_PIN_CAP_PRESENCE_DETECT_CAP_SHIFT 2 +#define HDA_PARAM_PIN_CAP_TRIGGER_REQD_MASK 0x00000002 +#define HDA_PARAM_PIN_CAP_TRIGGER_REQD_SHIFT 1 +#define HDA_PARAM_PIN_CAP_IMP_SENSE_CAP_MASK 0x00000001 +#define HDA_PARAM_PIN_CAP_IMP_SENSE_CAP_SHIFT 0 + +#define HDA_PARAM_PIN_CAP_EAPD_CAP(param) \ + (((param) & HDA_PARAM_PIN_CAP_EAPD_CAP_MASK) >> \ + HDA_PARAM_PIN_CAP_EAPD_CAP_SHIFT) +#define HDA_PARAM_PIN_CAP_VREF_CTRL(param) \ + (((param) & HDA_PARAM_PIN_CAP_VREF_CTRL_MASK) >> \ + HDA_PARAM_PIN_CAP_VREF_CTRL_SHIFT) +#define HDA_PARAM_PIN_CAP_VREF_CTRL_100(param) \ + (((param) & HDA_PARAM_PIN_CAP_VREF_CTRL_100_MASK) >> \ + HDA_PARAM_PIN_CAP_VREF_CTRL_100_SHIFT) +#define HDA_PARAM_PIN_CAP_VREF_CTRL_80(param) \ + (((param) & HDA_PARAM_PIN_CAP_VREF_CTRL_80_MASK) >> \ + HDA_PARAM_PIN_CAP_VREF_CTRL_80_SHIFT) +#define HDA_PARAM_PIN_CAP_VREF_CTRL_GROUND(param) \ + (((param) & HDA_PARAM_PIN_CAP_VREF_CTRL_GROUND_MASK) >> \ + HDA_PARAM_PIN_CAP_VREF_CTRL_GROUND_SHIFT) +#define HDA_PARAM_PIN_CAP_VREF_CTRL_50(param) \ + (((param) & HDA_PARAM_PIN_CAP_VREF_CTRL_50_MASK) >> \ + HDA_PARAM_PIN_CAP_VREF_CTRL_50_SHIFT) +#define HDA_PARAM_PIN_CAP_VREF_CTRL_HIZ(param) \ + (((param) & HDA_PARAM_PIN_CAP_VREF_CTRL_HIZ_MASK) >> \ + HDA_PARAM_PIN_CAP_VREF_CTRL_HIZ_SHIFT) +#define HDA_PARAM_PIN_CAP_BALANCED_IO_PINS(param) \ + (((param) & HDA_PARAM_PIN_CAP_BALANCED_IO_PINS_MASK) >> \ + HDA_PARAM_PIN_CAP_BALANCED_IO_PINS_SHIFT) +#define HDA_PARAM_PIN_CAP_INPUT_CAP(param) \ + (((param) & HDA_PARAM_PIN_CAP_INPUT_CAP_MASK) >> \ + HDA_PARAM_PIN_CAP_INPUT_CAP_SHIFT) +#define HDA_PARAM_PIN_CAP_OUTPUT_CAP(param) \ + (((param) & HDA_PARAM_PIN_CAP_OUTPUT_CAP_MASK) >> \ + HDA_PARAM_PIN_CAP_OUTPUT_CAP_SHIFT) +#define HDA_PARAM_PIN_CAP_HEADPHONE_CAP(param) \ + (((param) & HDA_PARAM_PIN_CAP_HEADPHONE_CAP_MASK) >> \ + HDA_PARAM_PIN_CAP_HEADPHONE_CAP_SHIFT) +#define HDA_PARAM_PIN_CAP_PRESENCE_DETECT_CAP(param) \ + (((param) & HDA_PARAM_PIN_CAP_PRESENCE_DETECT_CAP_MASK) >> \ + HDA_PARAM_PIN_CAP_PRESENCE_DETECT_CAP_MASK) +#define HDA_PARAM_PIN_CAP_TRIGGER_REQD(param) \ + (((param) & HDA_PARAM_PIN_CAP_TRIGGER_REQD_MASK) >> \ + HDA_PARAM_PIN_CAP_TRIGGER_REQD_SHIFT) +#define HDA_PARAM_PIN_CAP_IMP_SENSE_CAP(param) \ + (((param) & HDA_PARAM_PIN_CAP_IMP_SENSE_CAP_MASK) >> \ + HDA_PARAM_PIN_CAP_IMP_SENSE_CAP_SHIFT) + +/* Input Amplifier Capabilities */ +#define HDA_PARAM_INPUT_AMP_CAP 0x0d + +#define HDA_PARAM_INPUT_AMP_CAP_MUTE_CAP_MASK 0x80000000 +#define HDA_PARAM_INPUT_AMP_CAP_MUTE_CAP_SHIFT 31 +#define HDA_PARAM_INPUT_AMP_CAP_STEPSIZE_MASK 0x007f0000 +#define HDA_PARAM_INPUT_AMP_CAP_STEPSIZE_SHIFT 16 +#define HDA_PARAM_INPUT_AMP_CAP_NUMSTEPS_MASK 0x00007f00 +#define HDA_PARAM_INPUT_AMP_CAP_NUMSTEPS_SHIFT 8 +#define HDA_PARAM_INPUT_AMP_CAP_OFFSET_MASK 0x0000007f +#define HDA_PARAM_INPUT_AMP_CAP_OFFSET_SHIFT 0 + +#define HDA_PARAM_INPUT_AMP_CAP_MUTE_CAP(param) \ + (((param) & HDA_PARAM_INPUT_AMP_CAP_MUTE_CAP_MASK) >> \ + HDA_PARAM_INPUT_AMP_CAP_MUTE_CAP_SHIFT) +#define HDA_PARAM_INPUT_AMP_CAP_STEPSIZE(param) \ + (((param) & HDA_PARAM_INPUT_AMP_CAP_STEPSIZE_MASK) >> \ + HDA_PARAM_INPUT_AMP_CAP_STEPSIZE_SHIFT) +#define HDA_PARAM_INPUT_AMP_CAP_NUMSTEPS(param) \ + (((param) & HDA_PARAM_INPUT_AMP_CAP_NUMSTEPS_MASK) >> \ + HDA_PARAM_INPUT_AMP_CAP_NUMSTEPS_SHIFT) +#define HDA_PARAM_INPUT_AMP_CAP_OFFSET(param) \ + (((param) & HDA_PARAM_INPUT_AMP_CAP_OFFSET_MASK) >> \ + HDA_PARAM_INPUT_AMP_CAP_OFFSET_SHIFT) + +/* Output Amplifier Capabilities */ +#define HDA_PARAM_OUTPUT_AMP_CAP 0x12 + +#define HDA_PARAM_OUTPUT_AMP_CAP_MUTE_CAP_MASK 0x80000000 +#define HDA_PARAM_OUTPUT_AMP_CAP_MUTE_CAP_SHIFT 31 +#define HDA_PARAM_OUTPUT_AMP_CAP_STEPSIZE_MASK 0x007f0000 +#define HDA_PARAM_OUTPUT_AMP_CAP_STEPSIZE_SHIFT 16 +#define HDA_PARAM_OUTPUT_AMP_CAP_NUMSTEPS_MASK 0x00007f00 +#define HDA_PARAM_OUTPUT_AMP_CAP_NUMSTEPS_SHIFT 8 +#define HDA_PARAM_OUTPUT_AMP_CAP_OFFSET_MASK 0x0000007f +#define HDA_PARAM_OUTPUT_AMP_CAP_OFFSET_SHIFT 0 + +#define HDA_PARAM_OUTPUT_AMP_CAP_MUTE_CAP(param) \ + (((param) & HDA_PARAM_OUTPUT_AMP_CAP_MUTE_CAP_MASK) >> \ + HDA_PARAM_OUTPUT_AMP_CAP_MUTE_CAP_SHIFT) +#define HDA_PARAM_OUTPUT_AMP_CAP_STEPSIZE(param) \ + (((param) & HDA_PARAM_OUTPUT_AMP_CAP_STEPSIZE_MASK) >> \ + HDA_PARAM_OUTPUT_AMP_CAP_STEPSIZE_SHIFT) +#define HDA_PARAM_OUTPUT_AMP_CAP_NUMSTEPS(param) \ + (((param) & HDA_PARAM_OUTPUT_AMP_CAP_NUMSTEPS_MASK) >> \ + HDA_PARAM_OUTPUT_AMP_CAP_NUMSTEPS_SHIFT) +#define HDA_PARAM_OUTPUT_AMP_CAP_OFFSET(param) \ + (((param) & HDA_PARAM_OUTPUT_AMP_CAP_OFFSET_MASK) >> \ + HDA_PARAM_OUTPUT_AMP_CAP_OFFSET_SHIFT) + +/* Connection List Length */ +#define HDA_PARAM_CONN_LIST_LENGTH 0x0e + +#define HDA_PARAM_CONN_LIST_LENGTH_LONG_FORM_MASK 0x00000080 +#define HDA_PARAM_CONN_LIST_LENGTH_LONG_FORM_SHIFT 7 +#define HDA_PARAM_CONN_LIST_LENGTH_LIST_LENGTH_MASK 0x0000007f +#define HDA_PARAM_CONN_LIST_LENGTH_LIST_LENGTH_SHIFT 0 + +#define HDA_PARAM_CONN_LIST_LENGTH_LONG_FORM(param) \ + (((param) & HDA_PARAM_CONN_LIST_LENGTH_LONG_FORM_MASK) >> \ + HDA_PARAM_CONN_LIST_LENGTH_LONG_FORM_SHIFT) +#define HDA_PARAM_CONN_LIST_LENGTH_LIST_LENGTH(param) \ + (((param) & HDA_PARAM_CONN_LIST_LENGTH_LIST_LENGTH_MASK) >> \ + HDA_PARAM_CONN_LIST_LENGTH_LIST_LENGTH_SHIFT) + +/* Supported Power States */ +#define HDA_PARAM_SUPP_POWER_STATES 0x0f + +#define HDA_PARAM_SUPP_POWER_STATES_D3_MASK 0x00000008 +#define HDA_PARAM_SUPP_POWER_STATES_D3_SHIFT 3 +#define HDA_PARAM_SUPP_POWER_STATES_D2_MASK 0x00000004 +#define HDA_PARAM_SUPP_POWER_STATES_D2_SHIFT 2 +#define HDA_PARAM_SUPP_POWER_STATES_D1_MASK 0x00000002 +#define HDA_PARAM_SUPP_POWER_STATES_D1_SHIFT 1 +#define HDA_PARAM_SUPP_POWER_STATES_D0_MASK 0x00000001 +#define HDA_PARAM_SUPP_POWER_STATES_D0_SHIFT 0 + +#define HDA_PARAM_SUPP_POWER_STATES_D3(param) \ + (((param) & HDA_PARAM_SUPP_POWER_STATES_D3_MASK) >> \ + HDA_PARAM_SUPP_POWER_STATES_D3_SHIFT) +#define HDA_PARAM_SUPP_POWER_STATES_D2(param) \ + (((param) & HDA_PARAM_SUPP_POWER_STATES_D2_MASK) >> \ + HDA_PARAM_SUPP_POWER_STATES_D2_SHIFT) +#define HDA_PARAM_SUPP_POWER_STATES_D1(param) \ + (((param) & HDA_PARAM_SUPP_POWER_STATES_D1_MASK) >> \ + HDA_PARAM_SUPP_POWER_STATES_D1_SHIFT) +#define HDA_PARAM_SUPP_POWER_STATES_D0(param) \ + (((param) & HDA_PARAM_SUPP_POWER_STATES_D0_MASK) >> \ + HDA_PARAM_SUPP_POWER_STATES_D0_SHIFT) + +/* Processing Capabilities */ +#define HDA_PARAM_PROCESSING_CAP 0x10 + +#define HDA_PARAM_PROCESSING_CAP_NUMCOEFF_MASK 0x0000ff00 +#define HDA_PARAM_PROCESSING_CAP_NUMCOEFF_SHIFT 8 +#define HDA_PARAM_PROCESSING_CAP_BENIGN_MASK 0x00000001 +#define HDA_PARAM_PROCESSING_CAP_BENIGN_SHIFT 0 + +#define HDA_PARAM_PROCESSING_CAP_NUMCOEFF(param) \ + (((param) & HDA_PARAM_PROCESSING_CAP_NUMCOEFF_MASK) >> \ + HDA_PARAM_PROCESSING_CAP_NUMCOEFF_SHIFT) +#define HDA_PARAM_PROCESSING_CAP_BENIGN(param) \ + (((param) & HDA_PARAM_PROCESSING_CAP_BENIGN_MASK) >> \ + HDA_PARAM_PROCESSING_CAP_BENIGN_SHIFT) + +/* GPIO Count */ +#define HDA_PARAM_GPIO_COUNT 0x11 + +#define HDA_PARAM_GPIO_COUNT_GPI_WAKE_MASK 0x80000000 +#define HDA_PARAM_GPIO_COUNT_GPI_WAKE_SHIFT 31 +#define HDA_PARAM_GPIO_COUNT_GPI_UNSOL_MASK 0x40000000 +#define HDA_PARAM_GPIO_COUNT_GPI_UNSOL_SHIFT 30 +#define HDA_PARAM_GPIO_COUNT_NUM_GPI_MASK 0x00ff0000 +#define HDA_PARAM_GPIO_COUNT_NUM_GPI_SHIFT 16 +#define HDA_PARAM_GPIO_COUNT_NUM_GPO_MASK 0x0000ff00 +#define HDA_PARAM_GPIO_COUNT_NUM_GPO_SHIFT 8 +#define HDA_PARAM_GPIO_COUNT_NUM_GPIO_MASK 0x000000ff +#define HDA_PARAM_GPIO_COUNT_NUM_GPIO_SHIFT 0 + +#define HDA_PARAM_GPIO_COUNT_GPI_WAKE(param) \ + (((param) & HDA_PARAM_GPIO_COUNT_GPI_WAKE_MASK) >> \ + HDA_PARAM_GPIO_COUNT_GPI_WAKE_SHIFT) +#define HDA_PARAM_GPIO_COUNT_GPI_UNSOL(param) \ + (((param) & HDA_PARAM_GPIO_COUNT_GPI_UNSOL_MASK) >> \ + HDA_PARAM_GPIO_COUNT_GPI_UNSOL_SHIFT) +#define HDA_PARAM_GPIO_COUNT_NUM_GPI(param) \ + (((param) & HDA_PARAM_GPIO_COUNT_NUM_GPI_MASK) >> \ + HDA_PARAM_GPIO_COUNT_NUM_GPI_SHIFT) +#define HDA_PARAM_GPIO_COUNT_NUM_GPO(param) \ + (((param) & HDA_PARAM_GPIO_COUNT_NUM_GPO_MASK) >> \ + HDA_PARAM_GPIO_COUNT_NUM_GPO_SHIFT) +#define HDA_PARAM_GPIO_COUNT_NUM_GPIO(param) \ + (((param) & HDA_PARAM_GPIO_COUNT_NUM_GPIO_MASK) >> \ + HDA_PARAM_GPIO_COUNT_NUM_GPIO_SHIFT) + +/* Volume Knob Capabilities */ +#define HDA_PARAM_VOLUME_KNOB_CAP 0x13 + +#define HDA_PARAM_VOLUME_KNOB_CAP_DELTA_MASK 0x00000080 +#define HDA_PARAM_VOLUME_KNOB_CAP_DELTA_SHIFT 7 +#define HDA_PARAM_VOLUME_KNOB_CAP_NUM_STEPS_MASK 0x0000007f +#define HDA_PARAM_VOLUME_KNOB_CAP_NUM_STEPS_SHIFT 0 + +#define HDA_PARAM_VOLUME_KNOB_CAP_DELTA(param) \ + (((param) & HDA_PARAM_VOLUME_KNOB_CAP_DELTA_MASK) >> \ + HDA_PARAM_VOLUME_KNOB_CAP_DELTA_SHIFT) +#define HDA_PARAM_VOLUME_KNOB_CAP_NUM_STEPS(param) \ + (((param) & HDA_PARAM_VOLUME_KNOB_CAP_NUM_STEPS_MASK) >> \ + HDA_PARAM_VOLUME_KNOB_CAP_NUM_STEPS_SHIFT) + + +#define HDA_CONFIG_DEFAULTCONF_SEQUENCE_MASK 0x00000000f +#define HDA_CONFIG_DEFAULTCONF_ASSOCIATION_MASK 0x0000000f0 +#define HDA_CONFIG_DEFAULTCONF_MISC_MASK 0x000000f00 +#define HDA_CONFIG_DEFAULTCONF_COLOR_MASK 0x00000f000 +#define HDA_CONFIG_DEFAULTCONF_CONNECTION_TYPE_MASK 0x000f00000 +#define HDA_CONFIG_DEFAULTCONF_DEVICE_MASK 0x000f00000 +#define HDA_CONFIG_DEFAULTCONF_LOCATION_MASK 0x03f000000 +#define HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_MASK 0x0c0000000 + +#define HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_JACK (0<<30) +#define HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_NONE (1<<30) +#define HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_FIXED (2<<30) +#define HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_BOTH (3<<30) + +#define HDA_CONFIG_DEFAULTCONF_DEVICE_LINE_OUT (0<<20) +#define HDA_CONFIG_DEFAULTCONF_DEVICE_SPEAKER (1<<20) +#define HDA_CONFIG_DEFAULTCONF_DEVICE_HP_OUT (2<<20) +#define HDA_CONFIG_DEFAULTCONF_DEVICE_CD (3<<20) +#define HDA_CONFIG_DEFAULTCONF_DEVICE_SPDIF_OUT (4<<20) +#define HDA_CONFIG_DEFAULTCONF_DEVICE_DIGITAL_OTHER_OUT (5<<20) +#define HDA_CONFIG_DEFAULTCONF_DEVICE_MODEM_LINE (6<<20) +#define HDA_CONFIG_DEFAULTCONF_DEVICE_MODEM_HANDSET (7<<20) +#define HDA_CONFIG_DEFAULTCONF_DEVICE_LINE_IN (8<<20) +#define HDA_CONFIG_DEFAULTCONF_DEVICE_AUX (9<<20) +#define HDA_CONFIG_DEFAULTCONF_DEVICE_MIC_IN (10<<20) +#define HDA_CONFIG_DEFAULTCONF_DEVICE_TELEPHONY (11<<20) +#define HDA_CONFIG_DEFAULTCONF_DEVICE_SPDIF_IN (12<<20) +#define HDA_CONFIG_DEFAULTCONF_DEVICE_DIGITAL_OTHER_IN (13<<20) +#define HDA_CONFIG_DEFAULTCONF_DEVICE_OTHER (15<<20) + +#endif --- sys/dev/sound/pci/hda/hdac.c.orig Thu Jan 1 07:30:00 1970 +++ sys/dev/sound/pci/hda/hdac.c Thu Jul 12 12:04:19 2007 @@ -0,0 +1,6398 @@ +/*- + * Copyright (c) 2006 Stephane E. Potvin + * Copyright (c) 2006 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. + */ + +/* + * Intel High Definition Audio (Controller) driver for FreeBSD. Be advised + * that this driver still in its early stage, and possible of rewrite are + * pretty much guaranteed. There are supposedly several distinct parent/child + * busses to make this "perfect", but as for now and for the sake of + * simplicity, everything is gobble up within single source. + * + * List of subsys: + * 1) HDA Controller support + * 2) HDA Codecs support, which may include + * - HDA + * - Modem + * - HDMI + * 3) Widget parser - the real magic of why this driver works on so + * many hardwares with minimal vendor specific quirk. The original + * parser was written using Ruby and can be found at + * http://people.freebsd.org/~ariff/HDA/parser.rb . This crude + * ruby parser take the verbose dmesg dump as its input. Refer to + * http://www.microsoft.com/whdc/device/audio/default.mspx for various + * interesting documents, especially UAA (Universal Audio Architecture). + * 4) Possible vendor specific support. + * (snd_hda_intel, snd_hda_ati, etc..) + * + * Thanks to Ahmad Ubaidah Omar @ Defenxis Sdn. Bhd. for the + * Compaq V3000 with Conexant HDA. + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * * + * * This driver is a collaborative effort made by: * + * * * + * * Stephane E. Potvin * + * * Andrea Bittau * + * * Wesley Morgan * + * * Daniel Eischen * + * * Maxime Guillaud * + * * Ariff Abdullah * + * * * + * * ....and various people from freebsd-multimedia@FreeBSD.org * + * * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + */ + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include "mixer_if.h" + +#define HDA_DRV_TEST_REV "20070710_0047" +#define HDA_WIDGET_PARSER_REV 1 + +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/hda/hdac.c,v 1.44 2007/07/09 20:42:11 ariff Exp $"); + +#define HDA_BOOTVERBOSE(stmt) do { \ + if (bootverbose != 0 || snd_verbose > 3) { \ + stmt \ + } \ +} while(0) + +#if 1 +#undef HDAC_INTR_EXTRA +#define HDAC_INTR_EXTRA 1 +#endif + +#define hdac_lock(sc) snd_mtxlock((sc)->lock) +#define hdac_unlock(sc) snd_mtxunlock((sc)->lock) +#define hdac_lockassert(sc) snd_mtxassert((sc)->lock) +#define hdac_lockowned(sc) mtx_owned((sc)->lock) + +#if defined(__i386__) || defined(__amd64__) +#if __FreeBSD_version < 602110 || defined(SND_USE_LPMAP) +#include +#endif +#endif + +#ifdef _LPMAP_C_ +#define HDAC_BUS_DMA_NOCACHE 0 +#define HDAC_DMA_ATTR(sc, v, s, attr) do { \ + vm_offset_t va = (vm_offset_t)(v); \ + vm_size_t sz = (vm_size_t)(s); \ + if ((sc) != NULL && ((sc)->flags & HDAC_F_DMA_NOCACHE) && \ + va != 0 && sz != 0) \ + (void)lpmap_change_attr(va, sz, (attr)); \ +} while(0) +#else +#define HDAC_BUS_DMA_NOCACHE BUS_DMA_NOCACHE +#define HDAC_DMA_ATTR(...) +#endif + +#define HDA_FLAG_MATCH(fl, v) (((fl) & (v)) == (v)) +#define HDA_DEV_MATCH(fl, v) ((fl) == (v) || \ + (fl) == 0xffffffff || \ + (((fl) & 0xffff0000) == 0xffff0000 && \ + ((fl) & 0x0000ffff) == ((v) & 0x0000ffff)) || \ + (((fl) & 0x0000ffff) == 0x0000ffff && \ + ((fl) & 0xffff0000) == ((v) & 0xffff0000))) +#define HDA_MATCH_ALL 0xffffffff +#define HDAC_INVALID 0xffffffff + +/* Default controller / jack sense poll: 250ms */ +#define HDAC_POLL_INTERVAL max(hz >> 2, 1) + +/* + * Make room for possible 4096 playback/record channels, in 100 years to come. + */ +#define HDAC_TRIGGER_NONE 0x00000000 +#define HDAC_TRIGGER_PLAY 0x00000fff +#define HDAC_TRIGGER_REC 0x00fff000 +#define HDAC_TRIGGER_UNSOL 0x80000000 + +#define HDA_MODEL_CONSTRUCT(vendor, model) \ + (((uint32_t)(model) << 16) | ((vendor##_VENDORID) & 0xffff)) + +/* Controller models */ + +/* Intel */ +#define INTEL_VENDORID 0x8086 +#define HDA_INTEL_82801F HDA_MODEL_CONSTRUCT(INTEL, 0x2668) +#define HDA_INTEL_63XXESB HDA_MODEL_CONSTRUCT(INTEL, 0x269a) +#define HDA_INTEL_82801G HDA_MODEL_CONSTRUCT(INTEL, 0x27d8) +#define HDA_INTEL_82801H HDA_MODEL_CONSTRUCT(INTEL, 0x284b) +#define HDA_INTEL_82801I HDA_MODEL_CONSTRUCT(INTEL, 0x293e) +#define HDA_INTEL_ALL HDA_MODEL_CONSTRUCT(INTEL, 0xffff) + +/* Nvidia */ +#define NVIDIA_VENDORID 0x10de +#define HDA_NVIDIA_MCP51 HDA_MODEL_CONSTRUCT(NVIDIA, 0x026c) +#define HDA_NVIDIA_MCP55 HDA_MODEL_CONSTRUCT(NVIDIA, 0x0371) +#define HDA_NVIDIA_MCP61A HDA_MODEL_CONSTRUCT(NVIDIA, 0x03e4) +#define HDA_NVIDIA_MCP61B HDA_MODEL_CONSTRUCT(NVIDIA, 0x03f0) +#define HDA_NVIDIA_MCP65A HDA_MODEL_CONSTRUCT(NVIDIA, 0x044a) +#define HDA_NVIDIA_MCP65B HDA_MODEL_CONSTRUCT(NVIDIA, 0x044b) +#define HDA_NVIDIA_ALL HDA_MODEL_CONSTRUCT(NVIDIA, 0xffff) + +/* ATI */ +#define ATI_VENDORID 0x1002 +#define HDA_ATI_SB450 HDA_MODEL_CONSTRUCT(ATI, 0x437b) +#define HDA_ATI_SB600 HDA_MODEL_CONSTRUCT(ATI, 0x4383) +#define HDA_ATI_ALL HDA_MODEL_CONSTRUCT(ATI, 0xffff) + +/* VIA */ +#define VIA_VENDORID 0x1106 +#define HDA_VIA_VT82XX HDA_MODEL_CONSTRUCT(VIA, 0x3288) +#define HDA_VIA_ALL HDA_MODEL_CONSTRUCT(VIA, 0xffff) + +/* SiS */ +#define SIS_VENDORID 0x1039 +#define HDA_SIS_966 HDA_MODEL_CONSTRUCT(SIS, 0x7502) +#define HDA_SIS_ALL HDA_MODEL_CONSTRUCT(SIS, 0xffff) + +/* OEM/subvendors */ + +/* Intel */ +#define INTEL_D101GGC_SUBVENDOR HDA_MODEL_CONSTRUCT(INTEL, 0xd600) + +/* HP/Compaq */ +#define HP_VENDORID 0x103c +#define HP_V3000_SUBVENDOR HDA_MODEL_CONSTRUCT(HP, 0x30b5) +#define HP_NX7400_SUBVENDOR HDA_MODEL_CONSTRUCT(HP, 0x30a2) +#define HP_NX6310_SUBVENDOR HDA_MODEL_CONSTRUCT(HP, 0x30aa) +#define HP_NX6325_SUBVENDOR HDA_MODEL_CONSTRUCT(HP, 0x30b0) +#define HP_XW4300_SUBVENDOR HDA_MODEL_CONSTRUCT(HP, 0x3013) +#define HP_3010_SUBVENDOR HDA_MODEL_CONSTRUCT(HP, 0x3010) +#define HP_DV5000_SUBVENDOR HDA_MODEL_CONSTRUCT(HP, 0x30a5) +#define HP_ALL_SUBVENDOR HDA_MODEL_CONSTRUCT(HP, 0xffff) +/* What is wrong with XN 2563 anyway? (Got the picture ?) */ +#define HP_NX6325_SUBVENDORX 0x103c30b0 + +/* Dell */ +#define DELL_VENDORID 0x1028 +#define DELL_D820_SUBVENDOR HDA_MODEL_CONSTRUCT(DELL, 0x01cc) +#define DELL_I1300_SUBVENDOR HDA_MODEL_CONSTRUCT(DELL, 0x01c9) +#define DELL_XPSM1210_SUBVENDOR HDA_MODEL_CONSTRUCT(DELL, 0x01d7) +#define DELL_OPLX745_SUBVENDOR HDA_MODEL_CONSTRUCT(DELL, 0x01da) +#define DELL_ALL_SUBVENDOR HDA_MODEL_CONSTRUCT(DELL, 0xffff) + +/* Clevo */ +#define CLEVO_VENDORID 0x1558 +#define CLEVO_D900T_SUBVENDOR HDA_MODEL_CONSTRUCT(CLEVO, 0x0900) +#define CLEVO_ALL_SUBVENDOR HDA_MODEL_CONSTRUCT(CLEVO, 0xffff) + +/* Acer */ +#define ACER_VENDORID 0x1025 +#define ACER_A5050_SUBVENDOR HDA_MODEL_CONSTRUCT(ACER, 0x010f) +#define ACER_3681WXM_SUBVENDOR HDA_MODEL_CONSTRUCT(ACER, 0x0110) +#define ACER_ALL_SUBVENDOR HDA_MODEL_CONSTRUCT(ACER, 0xffff) + +/* Asus */ +#define ASUS_VENDORID 0x1043 +#define ASUS_M5200_SUBVENDOR HDA_MODEL_CONSTRUCT(ASUS, 0x1993) +#define ASUS_U5F_SUBVENDOR HDA_MODEL_CONSTRUCT(ASUS, 0x1263) +#define ASUS_A8JC_SUBVENDOR HDA_MODEL_CONSTRUCT(ASUS, 0x1153) +#define ASUS_P1AH2_SUBVENDOR HDA_MODEL_CONSTRUCT(ASUS, 0x81cb) +#define ASUS_A7M_SUBVENDOR HDA_MODEL_CONSTRUCT(ASUS, 0x1323) +#define ASUS_A7T_SUBVENDOR HDA_MODEL_CONSTRUCT(ASUS, 0x13c2) +#define ASUS_W6F_SUBVENDOR HDA_MODEL_CONSTRUCT(ASUS, 0x1263) +#define ASUS_W2J_SUBVENDOR HDA_MODEL_CONSTRUCT(ASUS, 0x1971) +#define ASUS_F3JC_SUBVENDOR HDA_MODEL_CONSTRUCT(ASUS, 0x1338) +#define ASUS_M2V_SUBVENDOR HDA_MODEL_CONSTRUCT(ASUS, 0x81e7) +#define ASUS_M2N_SUBVENDOR HDA_MODEL_CONSTRUCT(ASUS, 0x8234) +#define ASUS_M2NPVMX_SUBVENDOR HDA_MODEL_CONSTRUCT(ASUS, 0x81cb) +#define ASUS_P5BWD_SUBVENDOR HDA_MODEL_CONSTRUCT(ASUS, 0x81ec) +#define ASUS_A8NVMCSM_SUBVENDOR HDA_MODEL_CONSTRUCT(NVIDIA, 0xcb84) +#define ASUS_ALL_SUBVENDOR HDA_MODEL_CONSTRUCT(ASUS, 0xffff) + +/* IBM / Lenovo */ +#define IBM_VENDORID 0x1014 +#define IBM_M52_SUBVENDOR HDA_MODEL_CONSTRUCT(IBM, 0x02f6) +#define IBM_ALL_SUBVENDOR HDA_MODEL_CONSTRUCT(IBM, 0xffff) + +/* Lenovo */ +#define LENOVO_VENDORID 0x17aa +#define LENOVO_3KN100_SUBVENDOR HDA_MODEL_CONSTRUCT(LENOVO, 0x2066) +#define LENOVO_ALL_SUBVENDOR HDA_MODEL_CONSTRUCT(LENOVO, 0xffff) + +/* Samsung */ +#define SAMSUNG_VENDORID 0x144d +#define SAMSUNG_Q1_SUBVENDOR HDA_MODEL_CONSTRUCT(SAMSUNG, 0xc027) +#define SAMSUNG_ALL_SUBVENDOR HDA_MODEL_CONSTRUCT(SAMSUNG, 0xffff) + +/* Medion ? */ +#define MEDION_VENDORID 0x161f +#define MEDION_MD95257_SUBVENDOR HDA_MODEL_CONSTRUCT(MEDION, 0x203d) +#define MEDION_ALL_SUBVENDOR HDA_MODEL_CONSTRUCT(MEDION, 0xffff) + +/* + * Apple Intel MacXXXX seems using Sigmatel codec/vendor id + * instead of their own, which is beyond my comprehension + * (see HDA_CODEC_STAC9221 below). + */ +#define APPLE_INTEL_MAC 0x76808384 + +/* LG Electronics */ +#define LG_VENDORID 0x1854 +#define LG_LW20_SUBVENDOR HDA_MODEL_CONSTRUCT(LG, 0x0018) +#define LG_ALL_SUBVENDOR HDA_MODEL_CONSTRUCT(LG, 0xffff) + +/* Fujitsu Siemens */ +#define FS_VENDORID 0x1734 +#define FS_PA1510_SUBVENDOR HDA_MODEL_CONSTRUCT(FS, 0x10b8) +#define FS_ALL_SUBVENDOR HDA_MODEL_CONSTRUCT(FS, 0xffff) + +/* Toshiba */ +#define TOSHIBA_VENDORID 0x1179 +#define TOSHIBA_U200_SUBVENDOR HDA_MODEL_CONSTRUCT(TOSHIBA, 0x0001) +#define TOSHIBA_ALL_SUBVENDOR HDA_MODEL_CONSTRUCT(TOSHIBA, 0xffff) + +/* Micro-Star International (MSI) */ +#define MSI_VENDORID 0x1462 +#define MSI_MS1034_SUBVENDOR HDA_MODEL_CONSTRUCT(MSI, 0x0349) +#define MSI_ALL_SUBVENDOR HDA_MODEL_CONSTRUCT(MSI, 0xffff) + +/* Uniwill ? */ +#define UNIWILL_VENDORID 0x1584 +#define UNIWILL_9075_SUBVENDOR HDA_MODEL_CONSTRUCT(UNIWILL, 0x9075) +#define UNIWILL_9080_SUBVENDOR HDA_MODEL_CONSTRUCT(UNIWILL, 0x9080) + + +/* Misc constants.. */ +#define HDA_AMP_MUTE_DEFAULT (0xffffffff) +#define HDA_AMP_MUTE_NONE (0) +#define HDA_AMP_MUTE_LEFT (1 << 0) +#define HDA_AMP_MUTE_RIGHT (1 << 1) +#define HDA_AMP_MUTE_ALL (HDA_AMP_MUTE_LEFT | HDA_AMP_MUTE_RIGHT) + +#define HDA_AMP_LEFT_MUTED(v) ((v) & (HDA_AMP_MUTE_LEFT)) +#define HDA_AMP_RIGHT_MUTED(v) (((v) & HDA_AMP_MUTE_RIGHT) >> 1) + +#define HDA_DAC_PATH (1 << 0) +#define HDA_ADC_PATH (1 << 1) +#define HDA_ADC_RECSEL (1 << 2) + +#define HDA_DAC_LOCKED (1 << 3) +#define HDA_ADC_LOCKED (1 << 4) + +#define HDA_CTL_OUT (1 << 0) +#define HDA_CTL_IN (1 << 1) +#define HDA_CTL_BOTH (HDA_CTL_IN | HDA_CTL_OUT) + +#define HDA_GPIO_MAX 8 +/* 0 - 7 = GPIO , 8 = Flush */ +#define HDA_QUIRK_GPIO0 (1 << 0) +#define HDA_QUIRK_GPIO1 (1 << 1) +#define HDA_QUIRK_GPIO2 (1 << 2) +#define HDA_QUIRK_GPIO3 (1 << 3) +#define HDA_QUIRK_GPIO4 (1 << 4) +#define HDA_QUIRK_GPIO5 (1 << 5) +#define HDA_QUIRK_GPIO6 (1 << 6) +#define HDA_QUIRK_GPIO7 (1 << 7) +#define HDA_QUIRK_GPIOFLUSH (1 << 8) + +/* 9 - 25 = anything else */ +#define HDA_QUIRK_SOFTPCMVOL (1 << 9) +#define HDA_QUIRK_FIXEDRATE (1 << 10) +#define HDA_QUIRK_FORCESTEREO (1 << 11) +#define HDA_QUIRK_EAPDINV (1 << 12) +#define HDA_QUIRK_DMAPOS (1 << 13) + +/* 26 - 31 = vrefs */ +#define HDA_QUIRK_IVREF50 (1 << 26) +#define HDA_QUIRK_IVREF80 (1 << 27) +#define HDA_QUIRK_IVREF100 (1 << 28) +#define HDA_QUIRK_OVREF50 (1 << 29) +#define HDA_QUIRK_OVREF80 (1 << 30) +#define HDA_QUIRK_OVREF100 (1 << 31) + +#define HDA_QUIRK_IVREF (HDA_QUIRK_IVREF50 | HDA_QUIRK_IVREF80 | \ + HDA_QUIRK_IVREF100) +#define HDA_QUIRK_OVREF (HDA_QUIRK_OVREF50 | HDA_QUIRK_OVREF80 | \ + HDA_QUIRK_OVREF100) +#define HDA_QUIRK_VREF (HDA_QUIRK_IVREF | HDA_QUIRK_OVREF) + +#define SOUND_MASK_SKIP (1 << 30) +#define SOUND_MASK_DISABLE (1 << 31) + +#if __FreeBSD_version < 600000 +#define taskqueue_drain(...) +#endif + +static const struct { + char *key; + uint32_t value; +} hdac_quirks_tab[] = { + { "gpio0", HDA_QUIRK_GPIO0 }, + { "gpio1", HDA_QUIRK_GPIO1 }, + { "gpio2", HDA_QUIRK_GPIO2 }, + { "gpio3", HDA_QUIRK_GPIO3 }, + { "gpio4", HDA_QUIRK_GPIO4 }, + { "gpio5", HDA_QUIRK_GPIO5 }, + { "gpio6", HDA_QUIRK_GPIO6 }, + { "gpio7", HDA_QUIRK_GPIO7 }, + { "gpioflush", HDA_QUIRK_GPIOFLUSH }, + { "softpcmvol", HDA_QUIRK_SOFTPCMVOL }, + { "fixedrate", HDA_QUIRK_FIXEDRATE }, + { "forcestereo", HDA_QUIRK_FORCESTEREO }, + { "eapdinv", HDA_QUIRK_EAPDINV }, + { "dmapos", HDA_QUIRK_DMAPOS }, + { "ivref50", HDA_QUIRK_IVREF50 }, + { "ivref80", HDA_QUIRK_IVREF80 }, + { "ivref100", HDA_QUIRK_IVREF100 }, + { "ovref50", HDA_QUIRK_OVREF50 }, + { "ovref80", HDA_QUIRK_OVREF80 }, + { "ovref100", HDA_QUIRK_OVREF100 }, + { "ivref", HDA_QUIRK_IVREF }, + { "ovref", HDA_QUIRK_OVREF }, + { "vref", HDA_QUIRK_VREF }, +}; +#define HDAC_QUIRKS_TAB_LEN \ + (sizeof(hdac_quirks_tab) / sizeof(hdac_quirks_tab[0])) + +#define HDA_BDL_MIN 2 +#define HDA_BDL_MAX 256 +#define HDA_BDL_DEFAULT HDA_BDL_MIN + +#define HDA_BLK_MIN HDAC_DMA_ALIGNMENT +#define HDA_BLK_ALIGN (~(HDA_BLK_MIN - 1)) + +#define HDA_BUFSZ_MIN 4096 +#define HDA_BUFSZ_MAX 65536 +#define HDA_BUFSZ_DEFAULT 16384 + +#define HDA_PARSE_MAXDEPTH 10 + +#define HDAC_UNSOLTAG_EVENT_HP 0x00 +#define HDAC_UNSOLTAG_EVENT_TEST 0x01 + +MALLOC_DEFINE(M_HDAC, "hdac", "High Definition Audio Controller"); + +enum { + HDA_PARSE_MIXER, + HDA_PARSE_DIRECT +}; + +/* Default */ +static uint32_t hdac_fmt[] = { + AFMT_STEREO | AFMT_S16_LE, + 0 +}; + +static struct pcmchan_caps hdac_caps = {48000, 48000, hdac_fmt, 0}; + +static const struct { + uint32_t model; + char *desc; +} hdac_devices[] = { + { HDA_INTEL_82801F, "Intel 82801F" }, + { HDA_INTEL_63XXESB, "Intel 631x/632xESB" }, + { HDA_INTEL_82801G, "Intel 82801G" }, + { HDA_INTEL_82801H, "Intel 82801H" }, + { HDA_INTEL_82801I, "Intel 82801I" }, + { HDA_NVIDIA_MCP51, "NVidia MCP51" }, + { HDA_NVIDIA_MCP55, "NVidia MCP55" }, + { HDA_NVIDIA_MCP61A, "NVidia MCP61A" }, + { HDA_NVIDIA_MCP61B, "NVidia MCP61B" }, + { HDA_NVIDIA_MCP65A, "NVidia MCP65A" }, + { HDA_NVIDIA_MCP65B, "NVidia MCP65B" }, + { HDA_ATI_SB450, "ATI SB450" }, + { HDA_ATI_SB600, "ATI SB600" }, + { HDA_VIA_VT82XX, "VIA VT8251/8237A" }, + { HDA_SIS_966, "SiS 966" }, + /* Unknown */ + { HDA_INTEL_ALL, "Intel (Unknown)" }, + { HDA_NVIDIA_ALL, "NVidia (Unknown)" }, + { HDA_ATI_ALL, "ATI (Unknown)" }, + { HDA_VIA_ALL, "VIA (Unknown)" }, + { HDA_SIS_ALL, "SiS (Unknown)" }, +}; +#define HDAC_DEVICES_LEN (sizeof(hdac_devices) / sizeof(hdac_devices[0])) + +static const struct { + uint16_t vendor; + uint8_t reg; + uint8_t mask; + uint8_t enable; +} hdac_pcie_snoop[] = { + { INTEL_VENDORID, 0x00, 0x00, 0x00 }, + { ATI_VENDORID, 0x42, 0xf8, 0x02 }, + { NVIDIA_VENDORID, 0x4e, 0xf0, 0x0f }, +}; +#define HDAC_PCIESNOOP_LEN \ + (sizeof(hdac_pcie_snoop) / sizeof(hdac_pcie_snoop[0])) + +static const struct { + uint32_t rate; + int valid; + uint16_t base; + uint16_t mul; + uint16_t div; +} hda_rate_tab[] = { + { 8000, 1, 0x0000, 0x0000, 0x0500 }, /* (48000 * 1) / 6 */ + { 9600, 0, 0x0000, 0x0000, 0x0400 }, /* (48000 * 1) / 5 */ + { 12000, 0, 0x0000, 0x0000, 0x0300 }, /* (48000 * 1) / 4 */ + { 16000, 1, 0x0000, 0x0000, 0x0200 }, /* (48000 * 1) / 3 */ + { 18000, 0, 0x0000, 0x1000, 0x0700 }, /* (48000 * 3) / 8 */ + { 19200, 0, 0x0000, 0x0800, 0x0400 }, /* (48000 * 2) / 5 */ + { 24000, 0, 0x0000, 0x0000, 0x0100 }, /* (48000 * 1) / 2 */ + { 28800, 0, 0x0000, 0x1000, 0x0400 }, /* (48000 * 3) / 5 */ + { 32000, 1, 0x0000, 0x0800, 0x0200 }, /* (48000 * 2) / 3 */ + { 36000, 0, 0x0000, 0x1000, 0x0300 }, /* (48000 * 3) / 4 */ + { 38400, 0, 0x0000, 0x1800, 0x0400 }, /* (48000 * 4) / 5 */ + { 48000, 1, 0x0000, 0x0000, 0x0000 }, /* (48000 * 1) / 1 */ + { 64000, 0, 0x0000, 0x1800, 0x0200 }, /* (48000 * 4) / 3 */ + { 72000, 0, 0x0000, 0x1000, 0x0100 }, /* (48000 * 3) / 2 */ + { 96000, 1, 0x0000, 0x0800, 0x0000 }, /* (48000 * 2) / 1 */ + { 144000, 0, 0x0000, 0x1000, 0x0000 }, /* (48000 * 3) / 1 */ + { 192000, 1, 0x0000, 0x1800, 0x0000 }, /* (48000 * 4) / 1 */ + { 8820, 0, 0x4000, 0x0000, 0x0400 }, /* (44100 * 1) / 5 */ + { 11025, 1, 0x4000, 0x0000, 0x0300 }, /* (44100 * 1) / 4 */ + { 12600, 0, 0x4000, 0x0800, 0x0600 }, /* (44100 * 2) / 7 */ + { 14700, 0, 0x4000, 0x0000, 0x0200 }, /* (44100 * 1) / 3 */ + { 17640, 0, 0x4000, 0x0800, 0x0400 }, /* (44100 * 2) / 5 */ + { 18900, 0, 0x4000, 0x1000, 0x0600 }, /* (44100 * 3) / 7 */ + { 22050, 1, 0x4000, 0x0000, 0x0100 }, /* (44100 * 1) / 2 */ + { 25200, 0, 0x4000, 0x1800, 0x0600 }, /* (44100 * 4) / 7 */ + { 26460, 0, 0x4000, 0x1000, 0x0400 }, /* (44100 * 3) / 5 */ + { 29400, 0, 0x4000, 0x0800, 0x0200 }, /* (44100 * 2) / 3 */ + { 33075, 0, 0x4000, 0x1000, 0x0300 }, /* (44100 * 3) / 4 */ + { 35280, 0, 0x4000, 0x1800, 0x0400 }, /* (44100 * 4) / 5 */ + { 44100, 1, 0x4000, 0x0000, 0x0000 }, /* (44100 * 1) / 1 */ + { 58800, 0, 0x4000, 0x1800, 0x0200 }, /* (44100 * 4) / 3 */ + { 66150, 0, 0x4000, 0x1000, 0x0100 }, /* (44100 * 3) / 2 */ + { 88200, 1, 0x4000, 0x0800, 0x0000 }, /* (44100 * 2) / 1 */ + { 132300, 0, 0x4000, 0x1000, 0x0000 }, /* (44100 * 3) / 1 */ + { 176400, 1, 0x4000, 0x1800, 0x0000 }, /* (44100 * 4) / 1 */ +}; +#define HDA_RATE_TAB_LEN (sizeof(hda_rate_tab) / sizeof(hda_rate_tab[0])) + +/* All codecs you can eat... */ +#define HDA_CODEC_CONSTRUCT(vendor, id) \ + (((uint32_t)(vendor##_VENDORID) << 16) | ((id) & 0xffff)) + +/* Realtek */ +#define REALTEK_VENDORID 0x10ec +#define HDA_CODEC_ALC260 HDA_CODEC_CONSTRUCT(REALTEK, 0x0260) +#define HDA_CODEC_ALC262 HDA_CODEC_CONSTRUCT(REALTEK, 0x0262) +#define HDA_CODEC_ALC268 HDA_CODEC_CONSTRUCT(REALTEK, 0x0268) +#define HDA_CODEC_ALC660 HDA_CODEC_CONSTRUCT(REALTEK, 0x0660) +#define HDA_CODEC_ALC861 HDA_CODEC_CONSTRUCT(REALTEK, 0x0861) +#define HDA_CODEC_ALC861VD HDA_CODEC_CONSTRUCT(REALTEK, 0x0862) +#define HDA_CODEC_ALC880 HDA_CODEC_CONSTRUCT(REALTEK, 0x0880) +#define HDA_CODEC_ALC882 HDA_CODEC_CONSTRUCT(REALTEK, 0x0882) +#define HDA_CODEC_ALC883 HDA_CODEC_CONSTRUCT(REALTEK, 0x0883) +#define HDA_CODEC_ALC885 HDA_CODEC_CONSTRUCT(REALTEK, 0x0885) +#define HDA_CODEC_ALC888 HDA_CODEC_CONSTRUCT(REALTEK, 0x0888) +#define HDA_CODEC_ALCXXXX HDA_CODEC_CONSTRUCT(REALTEK, 0xffff) + +/* Analog Devices */ +#define ANALOGDEVICES_VENDORID 0x11d4 +#define HDA_CODEC_AD1981HD HDA_CODEC_CONSTRUCT(ANALOGDEVICES, 0x1981) +#define HDA_CODEC_AD1983 HDA_CODEC_CONSTRUCT(ANALOGDEVICES, 0x1983) +#define HDA_CODEC_AD1986A HDA_CODEC_CONSTRUCT(ANALOGDEVICES, 0x1986) +#define HDA_CODEC_AD1988 HDA_CODEC_CONSTRUCT(ANALOGDEVICES, 0x1988) +#define HDA_CODEC_AD1988B HDA_CODEC_CONSTRUCT(ANALOGDEVICES, 0x198b) +#define HDA_CODEC_ADXXXX HDA_CODEC_CONSTRUCT(ANALOGDEVICES, 0xffff) + +/* CMedia */ +#define CMEDIA_VENDORID 0x434d +#define HDA_CODEC_CMI9880 HDA_CODEC_CONSTRUCT(CMEDIA, 0x4980) +#define HDA_CODEC_CMIXXXX HDA_CODEC_CONSTRUCT(CMEDIA, 0xffff) + +/* Sigmatel */ +#define SIGMATEL_VENDORID 0x8384 +#define HDA_CODEC_STAC9221 HDA_CODEC_CONSTRUCT(SIGMATEL, 0x7680) +#define HDA_CODEC_STAC9221D HDA_CODEC_CONSTRUCT(SIGMATEL, 0x7683) +#define HDA_CODEC_STAC9220 HDA_CODEC_CONSTRUCT(SIGMATEL, 0x7690) +#define HDA_CODEC_STAC922XD HDA_CODEC_CONSTRUCT(SIGMATEL, 0x7681) +#define HDA_CODEC_STAC9227 HDA_CODEC_CONSTRUCT(SIGMATEL, 0x7618) +#define HDA_CODEC_STAC9271D HDA_CODEC_CONSTRUCT(SIGMATEL, 0x7627) +#define HDA_CODEC_STACXXXX HDA_CODEC_CONSTRUCT(SIGMATEL, 0xffff) + +/* + * Conexant + * + * Ok, the truth is, I don't have any idea at all whether + * it is "Venice" or "Waikiki" or other unnamed CXyadayada. The only + * place that tell me it is "Venice" is from its Windows driver INF. + * + * Venice - CX????? + * Waikiki - CX20551-22 + */ +#define CONEXANT_VENDORID 0x14f1 +#define HDA_CODEC_CXVENICE HDA_CODEC_CONSTRUCT(CONEXANT, 0x5045) +#define HDA_CODEC_CXWAIKIKI HDA_CODEC_CONSTRUCT(CONEXANT, 0x5047) +#define HDA_CODEC_CXXXXX HDA_CODEC_CONSTRUCT(CONEXANT, 0xffff) + +/* VIA */ +#define HDA_CODEC_VT1708_8 HDA_CODEC_CONSTRUCT(VIA, 0x1708) +#define HDA_CODEC_VT1708_9 HDA_CODEC_CONSTRUCT(VIA, 0x1709) +#define HDA_CODEC_VT1708_A HDA_CODEC_CONSTRUCT(VIA, 0x170a) +#define HDA_CODEC_VT1708_B HDA_CODEC_CONSTRUCT(VIA, 0x170b) +#define HDA_CODEC_VT1709_0 HDA_CODEC_CONSTRUCT(VIA, 0xe710) +#define HDA_CODEC_VT1709_1 HDA_CODEC_CONSTRUCT(VIA, 0xe711) +#define HDA_CODEC_VT1709_2 HDA_CODEC_CONSTRUCT(VIA, 0xe712) +#define HDA_CODEC_VT1709_3 HDA_CODEC_CONSTRUCT(VIA, 0xe713) +#define HDA_CODEC_VT1709_4 HDA_CODEC_CONSTRUCT(VIA, 0xe714) +#define HDA_CODEC_VT1709_5 HDA_CODEC_CONSTRUCT(VIA, 0xe715) +#define HDA_CODEC_VT1709_6 HDA_CODEC_CONSTRUCT(VIA, 0xe716) +#define HDA_CODEC_VT1709_7 HDA_CODEC_CONSTRUCT(VIA, 0xe717) +#define HDA_CODEC_VTXXXX HDA_CODEC_CONSTRUCT(VIA, 0xffff) + + +/* Codecs */ +static const struct { + uint32_t id; + char *name; +} hdac_codecs[] = { + { HDA_CODEC_ALC260, "Realtek ALC260" }, + { HDA_CODEC_ALC262, "Realtek ALC262" }, + { HDA_CODEC_ALC268, "Realtek ALC268" }, + { HDA_CODEC_ALC660, "Realtek ALC660" }, + { HDA_CODEC_ALC861, "Realtek ALC861" }, + { HDA_CODEC_ALC861VD, "Realtek ALC861-VD" }, + { HDA_CODEC_ALC880, "Realtek ALC880" }, + { HDA_CODEC_ALC882, "Realtek ALC882" }, + { HDA_CODEC_ALC883, "Realtek ALC883" }, + { HDA_CODEC_ALC885, "Realtek ALC885" }, + { HDA_CODEC_ALC888, "Realtek ALC888" }, + { HDA_CODEC_AD1981HD, "Analog Devices AD1981HD" }, + { HDA_CODEC_AD1983, "Analog Devices AD1983" }, + { HDA_CODEC_AD1986A, "Analog Devices AD1986A" }, + { HDA_CODEC_AD1988, "Analog Devices AD1988" }, + { HDA_CODEC_AD1988B, "Analog Devices AD1988B" }, + { HDA_CODEC_CMI9880, "CMedia CMI9880" }, + { HDA_CODEC_STAC9221, "Sigmatel STAC9221" }, + { HDA_CODEC_STAC9221D, "Sigmatel STAC9221D" }, + { HDA_CODEC_STAC9220, "Sigmatel STAC9220" }, + { HDA_CODEC_STAC922XD, "Sigmatel STAC9220D/9223D" }, + { HDA_CODEC_STAC9227, "Sigmatel STAC9227" }, + { HDA_CODEC_STAC9271D, "Sigmatel STAC9271D" }, + { HDA_CODEC_CXVENICE, "Conexant Venice" }, + { HDA_CODEC_CXWAIKIKI, "Conexant Waikiki" }, + { HDA_CODEC_VT1708_8, "VIA VT1708_8" }, + { HDA_CODEC_VT1708_9, "VIA VT1708_9" }, + { HDA_CODEC_VT1708_A, "VIA VT1708_A" }, + { HDA_CODEC_VT1708_B, "VIA VT1708_B" }, + { HDA_CODEC_VT1709_0, "VIA VT1709_0" }, + { HDA_CODEC_VT1709_1, "VIA VT1709_1" }, + { HDA_CODEC_VT1709_2, "VIA VT1709_2" }, + { HDA_CODEC_VT1709_3, "VIA VT1709_3" }, + { HDA_CODEC_VT1709_4, "VIA VT1709_4" }, + { HDA_CODEC_VT1709_5, "VIA VT1709_5" }, + { HDA_CODEC_VT1709_6, "VIA VT1709_6" }, + { HDA_CODEC_VT1709_7, "VIA VT1709_7" }, + /* Unknown codec */ + { HDA_CODEC_ALCXXXX, "Realtek (Unknown)" }, + { HDA_CODEC_ADXXXX, "Analog Devices (Unknown)" }, + { HDA_CODEC_CMIXXXX, "CMedia (Unknown)" }, + { HDA_CODEC_STACXXXX, "Sigmatel (Unknown)" }, + { HDA_CODEC_CXXXXX, "Conexant (Unknown)" }, + { HDA_CODEC_VTXXXX, "VIA (Unknown)" }, +}; +#define HDAC_CODECS_LEN (sizeof(hdac_codecs) / sizeof(hdac_codecs[0])) + +enum { + HDAC_HP_SWITCH_CTL, + HDAC_HP_SWITCH_CTRL, + HDAC_HP_SWITCH_DEBUG +}; + +static const struct { + uint32_t model; + uint32_t id; + int type; + int inverted; + int polling; + int execsense; + nid_t hpnid; + nid_t spkrnid[8]; + nid_t eapdnid; +} hdac_hp_switch[] = { + /* Specific OEM models */ + { HP_V3000_SUBVENDOR, HDA_CODEC_CXVENICE, HDAC_HP_SWITCH_CTL, + 0, 0, -1, 17, { 16, -1 }, 16 }, + /* { HP_XW4300_SUBVENDOR, HDA_CODEC_ALC260, HDAC_HP_SWITCH_CTL, + 0, 0, -1, 21, { 16, 17, -1 }, -1 } */ + /*{ HP_3010_SUBVENDOR, HDA_CODEC_ALC260, HDAC_HP_SWITCH_DEBUG, + 0, 1, 0, 16, { 15, 18, 19, 20, 21, -1 }, -1 },*/ + { HP_NX7400_SUBVENDOR, HDA_CODEC_AD1981HD, HDAC_HP_SWITCH_CTL, + 0, 0, -1, 6, { 5, -1 }, 5 }, + { HP_NX6310_SUBVENDOR, HDA_CODEC_AD1981HD, HDAC_HP_SWITCH_CTL, + 0, 0, -1, 6, { 5, -1 }, 5 }, + { HP_NX6325_SUBVENDOR, HDA_CODEC_AD1981HD, HDAC_HP_SWITCH_CTL, + 0, 0, -1, 6, { 5, -1 }, 5 }, + { TOSHIBA_U200_SUBVENDOR, HDA_CODEC_AD1981HD, HDAC_HP_SWITCH_CTL, + 0, 0, -1, 6, { 5, -1 }, -1 }, + { DELL_D820_SUBVENDOR, HDA_CODEC_STAC9220, HDAC_HP_SWITCH_CTRL, + 0, 0, -1, 13, { 14, -1 }, -1 }, + { DELL_I1300_SUBVENDOR, HDA_CODEC_STAC9220, HDAC_HP_SWITCH_CTRL, + 0, 0, -1, 13, { 14, -1 }, -1 }, + { DELL_OPLX745_SUBVENDOR, HDA_CODEC_AD1983, HDAC_HP_SWITCH_CTL, + 0, 0, -1, 6, { 5, 7, -1 }, -1 }, + { APPLE_INTEL_MAC, HDA_CODEC_STAC9221, HDAC_HP_SWITCH_CTRL, + 0, 0, -1, 10, { 13, -1 }, -1 }, + { LENOVO_3KN100_SUBVENDOR, HDA_CODEC_AD1986A, HDAC_HP_SWITCH_CTL, + 1, 0, -1, 26, { 27, -1 }, -1 }, + { LG_LW20_SUBVENDOR, HDA_CODEC_ALC880, HDAC_HP_SWITCH_CTL, + 0, 0, -1, 27, { 20, -1 }, -1 }, + { ACER_A5050_SUBVENDOR, HDA_CODEC_ALC883, HDAC_HP_SWITCH_CTL, + 0, 0, -1, 20, { 21, -1 }, -1 }, + { ACER_3681WXM_SUBVENDOR, HDA_CODEC_ALC883, HDAC_HP_SWITCH_CTL, + 0, 0, -1, 20, { 21, -1 }, -1 }, + { UNIWILL_9080_SUBVENDOR, HDA_CODEC_ALC883, HDAC_HP_SWITCH_CTL, + 0, 0, -1, 20, { 21, -1 }, -1 }, + { MSI_MS1034_SUBVENDOR, HDA_CODEC_ALC883, HDAC_HP_SWITCH_CTL, + 0, 0, -1, 20, { 27, -1 }, -1 }, + /* + * All models that at least come from the same vendor with + * simmilar codec. + */ + { HP_ALL_SUBVENDOR, HDA_CODEC_CXVENICE, HDAC_HP_SWITCH_CTL, + 0, 0, -1, 17, { 16, -1 }, 16 }, + { HP_ALL_SUBVENDOR, HDA_CODEC_AD1981HD, HDAC_HP_SWITCH_CTL, + 0, 0, -1, 6, { 5, -1 }, 5 }, + { TOSHIBA_ALL_SUBVENDOR, HDA_CODEC_AD1981HD, HDAC_HP_SWITCH_CTL, + 0, 0, -1, 6, { 5, -1 }, -1 }, + { DELL_ALL_SUBVENDOR, HDA_CODEC_STAC9220, HDAC_HP_SWITCH_CTRL, + 0, 0, -1, 13, { 14, -1 }, -1 }, + { LENOVO_ALL_SUBVENDOR, HDA_CODEC_AD1986A, HDAC_HP_SWITCH_CTL, + 1, 0, -1, 26, { 27, -1 }, -1 }, +#if 0 + { ACER_ALL_SUBVENDOR, HDA_CODEC_ALC883, HDAC_HP_SWITCH_CTL, + 0, 0, -1, 20, { 21, -1 }, -1 }, +#endif +}; +#define HDAC_HP_SWITCH_LEN \ + (sizeof(hdac_hp_switch) / sizeof(hdac_hp_switch[0])) + +static const struct { + uint32_t model; + uint32_t id; + nid_t eapdnid; + int hp_switch; +} hdac_eapd_switch[] = { + { HP_V3000_SUBVENDOR, HDA_CODEC_CXVENICE, 16, 1 }, + { HP_NX7400_SUBVENDOR, HDA_CODEC_AD1981HD, 5, 1 }, + { HP_NX6310_SUBVENDOR, HDA_CODEC_AD1981HD, 5, 1 }, +}; +#define HDAC_EAPD_SWITCH_LEN \ + (sizeof(hdac_eapd_switch) / sizeof(hdac_eapd_switch[0])) + +/**************************************************************************** + * Function prototypes + ****************************************************************************/ +static void hdac_intr_handler(void *); +static int hdac_reset(struct hdac_softc *); +static int hdac_get_capabilities(struct hdac_softc *); +static void hdac_dma_cb(void *, bus_dma_segment_t *, int, int); +static int hdac_dma_alloc(struct hdac_softc *, + struct hdac_dma *, bus_size_t); +static void hdac_dma_free(struct hdac_softc *, struct hdac_dma *); +static int hdac_mem_alloc(struct hdac_softc *); +static void hdac_mem_free(struct hdac_softc *); +static int hdac_irq_alloc(struct hdac_softc *); +static void hdac_irq_free(struct hdac_softc *); +static void hdac_corb_init(struct hdac_softc *); +static void hdac_rirb_init(struct hdac_softc *); +static void hdac_corb_start(struct hdac_softc *); +static void hdac_rirb_start(struct hdac_softc *); +static void hdac_scan_codecs(struct hdac_softc *); +static int hdac_probe_codec(struct hdac_codec *); +static struct hdac_devinfo *hdac_probe_function(struct hdac_codec *, nid_t); +static void hdac_add_child(struct hdac_softc *, struct hdac_devinfo *); + +static void hdac_attach2(void *); + +static uint32_t hdac_command_sendone_internal(struct hdac_softc *, + uint32_t, int); +static void hdac_command_send_internal(struct hdac_softc *, + struct hdac_command_list *, int); + +static int hdac_probe(device_t); +static int hdac_attach(device_t); +static int hdac_detach(device_t); +static void hdac_widget_connection_select(struct hdac_widget *, uint8_t); +static void hdac_audio_ctl_amp_set(struct hdac_audio_ctl *, + uint32_t, int, int); +static struct hdac_audio_ctl *hdac_audio_ctl_amp_get(struct hdac_devinfo *, + nid_t, int, int); +static void hdac_audio_ctl_amp_set_internal(struct hdac_softc *, + nid_t, nid_t, int, int, int, int, int, int); +static int hdac_audio_ctl_ossmixer_getnextdev(struct hdac_devinfo *); +static struct hdac_widget *hdac_widget_get(struct hdac_devinfo *, nid_t); + +static int hdac_rirb_flush(struct hdac_softc *sc); +static int hdac_unsolq_flush(struct hdac_softc *sc); + +#define hdac_command(a1, a2, a3) \ + hdac_command_sendone_internal(a1, a2, a3) + +#define hdac_codec_id(d) \ + ((uint32_t)((d == NULL) ? 0x00000000 : \ + ((((uint32_t)(d)->vendor_id & 0x0000ffff) << 16) | \ + ((uint32_t)(d)->device_id & 0x0000ffff)))) + +static char * +hdac_codec_name(struct hdac_devinfo *devinfo) +{ + uint32_t id; + int i; + + id = hdac_codec_id(devinfo); + + for (i = 0; i < HDAC_CODECS_LEN; i++) { + if (HDA_DEV_MATCH(hdac_codecs[i].id, id)) + return (hdac_codecs[i].name); + } + + return ((id == 0x00000000) ? "NULL Codec" : "Unknown Codec"); +} + +static char * +hdac_audio_ctl_ossmixer_mask2name(uint32_t devmask) +{ + static char *ossname[] = SOUND_DEVICE_NAMES; + static char *unknown = "???"; + int i; + + for (i = SOUND_MIXER_NRDEVICES - 1; i >= 0; i--) { + if (devmask & (1 << i)) + return (ossname[i]); + } + return (unknown); +} + +static void +hdac_audio_ctl_ossmixer_mask2allname(uint32_t mask, char *buf, size_t len) +{ + static char *ossname[] = SOUND_DEVICE_NAMES; + int i, first = 1; + + bzero(buf, len); + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { + if (mask & (1 << i)) { + if (first == 0) + strlcat(buf, ", ", len); + strlcat(buf, ossname[i], len); + first = 0; + } + } +} + +static struct hdac_audio_ctl * +hdac_audio_ctl_each(struct hdac_devinfo *devinfo, int *index) +{ + if (devinfo == NULL || + devinfo->node_type != HDA_PARAM_FCT_GRP_TYPE_NODE_TYPE_AUDIO || + index == NULL || devinfo->function.audio.ctl == NULL || + devinfo->function.audio.ctlcnt < 1 || + *index < 0 || *index >= devinfo->function.audio.ctlcnt) + return (NULL); + return (&devinfo->function.audio.ctl[(*index)++]); +} + +static struct hdac_audio_ctl * +hdac_audio_ctl_amp_get(struct hdac_devinfo *devinfo, nid_t nid, + int index, int cnt) +{ + struct hdac_audio_ctl *ctl, *retctl = NULL; + int i, at, atindex, found = 0; + + if (devinfo == NULL || devinfo->function.audio.ctl == NULL) + return (NULL); + + at = cnt; + if (at == 0) + at = 1; + else if (at < 0) + at = -1; + atindex = index; + if (atindex < 0) + atindex = -1; + + i = 0; + while ((ctl = hdac_audio_ctl_each(devinfo, &i)) != NULL) { + if (ctl->enable == 0 || ctl->widget == NULL) + continue; + if (!(ctl->widget->nid == nid && (atindex == -1 || + ctl->index == atindex))) + continue; + found++; + if (found == cnt) + return (ctl); + retctl = ctl; + } + + return ((at == -1) ? retctl : NULL); +} + +static void +hdac_hp_switch_handler(struct hdac_devinfo *devinfo) +{ + struct hdac_softc *sc; + struct hdac_widget *w; + struct hdac_audio_ctl *ctl; + uint32_t val, id, res; + int i = 0, j, timeout, forcemute; + nid_t cad; + + if (devinfo == NULL || devinfo->codec == NULL || + devinfo->codec->sc == NULL) + return; + + sc = devinfo->codec->sc; + cad = devinfo->codec->cad; + id = hdac_codec_id(devinfo); + for (i = 0; i < HDAC_HP_SWITCH_LEN; i++) { + if (HDA_DEV_MATCH(hdac_hp_switch[i].model, + sc->pci_subvendor) && + hdac_hp_switch[i].id == id) + break; + } + + if (i >= HDAC_HP_SWITCH_LEN) + return; + + forcemute = 0; + if (hdac_hp_switch[i].eapdnid != -1) { + w = hdac_widget_get(devinfo, hdac_hp_switch[i].eapdnid); + if (w != NULL && w->param.eapdbtl != HDAC_INVALID) + forcemute = (w->param.eapdbtl & + HDA_CMD_SET_EAPD_BTL_ENABLE_EAPD) ? 0 : 1; + } + + if (hdac_hp_switch[i].execsense != -1) + hdac_command(sc, + HDA_CMD_SET_PIN_SENSE(cad, hdac_hp_switch[i].hpnid, + hdac_hp_switch[i].execsense), cad); + + timeout = 10000; + do { + res = hdac_command(sc, + HDA_CMD_GET_PIN_SENSE(cad, hdac_hp_switch[i].hpnid), + cad); + if (hdac_hp_switch[i].execsense == -1 || res != 0x7fffffff) + break; + DELAY(10); + } while (--timeout != 0); + + HDA_BOOTVERBOSE( + device_printf(sc->dev, + "HDA_DEBUG: Pin sense: nid=%d timeout=%d res=0x%08x\n", + hdac_hp_switch[i].hpnid, timeout, res); + ); + + res = HDA_CMD_GET_PIN_SENSE_PRESENCE_DETECT(res); + res ^= hdac_hp_switch[i].inverted; + + switch (hdac_hp_switch[i].type) { + case HDAC_HP_SWITCH_CTL: + ctl = hdac_audio_ctl_amp_get(devinfo, + hdac_hp_switch[i].hpnid, 0, 1); + if (ctl != NULL) { + val = (res != 0 && forcemute == 0) ? + HDA_AMP_MUTE_NONE : HDA_AMP_MUTE_ALL; + if (val != ctl->muted) { + ctl->muted = val; + hdac_audio_ctl_amp_set(ctl, + HDA_AMP_MUTE_DEFAULT, ctl->left, + ctl->right); + } + } + for (j = 0; hdac_hp_switch[i].spkrnid[j] != -1; j++) { + ctl = hdac_audio_ctl_amp_get(devinfo, + hdac_hp_switch[i].spkrnid[j], 0, 1); + if (ctl == NULL) + continue; + val = (res != 0 || forcemute == 1) ? + HDA_AMP_MUTE_ALL : HDA_AMP_MUTE_NONE; + if (val == ctl->muted) + continue; + ctl->muted = val; + hdac_audio_ctl_amp_set(ctl, HDA_AMP_MUTE_DEFAULT, + ctl->left, ctl->right); + } + break; + case HDAC_HP_SWITCH_CTRL: + if (res != 0) { + /* HP in */ + w = hdac_widget_get(devinfo, hdac_hp_switch[i].hpnid); + if (w != NULL && w->type == + HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) { + if (forcemute == 0) + val = w->wclass.pin.ctrl | + HDA_CMD_SET_PIN_WIDGET_CTRL_OUT_ENABLE; + else + val = w->wclass.pin.ctrl & + ~HDA_CMD_SET_PIN_WIDGET_CTRL_OUT_ENABLE; + if (val != w->wclass.pin.ctrl) { + w->wclass.pin.ctrl = val; + hdac_command(sc, + HDA_CMD_SET_PIN_WIDGET_CTRL(cad, + w->nid, w->wclass.pin.ctrl), cad); + } + } + for (j = 0; hdac_hp_switch[i].spkrnid[j] != -1; j++) { + w = hdac_widget_get(devinfo, + hdac_hp_switch[i].spkrnid[j]); + if (w == NULL || w->type != + HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) + continue; + val = w->wclass.pin.ctrl & + ~HDA_CMD_SET_PIN_WIDGET_CTRL_OUT_ENABLE; + if (val == w->wclass.pin.ctrl) + continue; + w->wclass.pin.ctrl = val; + hdac_command(sc, HDA_CMD_SET_PIN_WIDGET_CTRL( + cad, w->nid, w->wclass.pin.ctrl), cad); + } + } else { + /* HP out */ + w = hdac_widget_get(devinfo, hdac_hp_switch[i].hpnid); + if (w != NULL && w->type == + HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) { + val = w->wclass.pin.ctrl & + ~HDA_CMD_SET_PIN_WIDGET_CTRL_OUT_ENABLE; + if (val != w->wclass.pin.ctrl) { + w->wclass.pin.ctrl = val; + hdac_command(sc, + HDA_CMD_SET_PIN_WIDGET_CTRL(cad, + w->nid, w->wclass.pin.ctrl), cad); + } + } + for (j = 0; hdac_hp_switch[i].spkrnid[j] != -1; j++) { + w = hdac_widget_get(devinfo, + hdac_hp_switch[i].spkrnid[j]); + if (w == NULL || w->type != + HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) + continue; + if (forcemute == 0) + val = w->wclass.pin.ctrl | + HDA_CMD_SET_PIN_WIDGET_CTRL_OUT_ENABLE; + else + val = w->wclass.pin.ctrl & + ~HDA_CMD_SET_PIN_WIDGET_CTRL_OUT_ENABLE; + if (val == w->wclass.pin.ctrl) + continue; + w->wclass.pin.ctrl = val; + hdac_command(sc, HDA_CMD_SET_PIN_WIDGET_CTRL( + cad, w->nid, w->wclass.pin.ctrl), cad); + } + } + break; + case HDAC_HP_SWITCH_DEBUG: + if (hdac_hp_switch[i].execsense != -1) + hdac_command(sc, + HDA_CMD_SET_PIN_SENSE(cad, hdac_hp_switch[i].hpnid, + hdac_hp_switch[i].execsense), cad); + res = hdac_command(sc, + HDA_CMD_GET_PIN_SENSE(cad, hdac_hp_switch[i].hpnid), cad); + device_printf(sc->dev, + "[ 0] HDA_DEBUG: Pin sense: nid=%d res=0x%08x\n", + hdac_hp_switch[i].hpnid, res); + for (j = 0; hdac_hp_switch[i].spkrnid[j] != -1; j++) { + w = hdac_widget_get(devinfo, + hdac_hp_switch[i].spkrnid[j]); + if (w == NULL || w->type != + HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) + continue; + if (hdac_hp_switch[i].execsense != -1) + hdac_command(sc, + HDA_CMD_SET_PIN_SENSE(cad, w->nid, + hdac_hp_switch[i].execsense), cad); + res = hdac_command(sc, + HDA_CMD_GET_PIN_SENSE(cad, w->nid), cad); + device_printf(sc->dev, + "[%2d] HDA_DEBUG: Pin sense: nid=%d res=0x%08x\n", + j + 1, w->nid, res); + } + break; + default: + break; + } +} + +static void +hdac_unsolicited_handler(struct hdac_codec *codec, uint32_t tag) +{ + struct hdac_softc *sc; + struct hdac_devinfo *devinfo = NULL; + device_t *devlist = NULL; + int devcount, i; + + if (codec == NULL || codec->sc == NULL) + return; + + sc = codec->sc; + + HDA_BOOTVERBOSE( + device_printf(sc->dev, "HDA_DEBUG: Unsol Tag: 0x%08x\n", tag); + ); + + device_get_children(sc->dev, &devlist, &devcount); + for (i = 0; devlist != NULL && i < devcount; i++) { + devinfo = (struct hdac_devinfo *)device_get_ivars(devlist[i]); + if (devinfo != NULL && devinfo->node_type == + HDA_PARAM_FCT_GRP_TYPE_NODE_TYPE_AUDIO && + devinfo->codec != NULL && + devinfo->codec->cad == codec->cad) { + break; + } else + devinfo = NULL; + } + if (devlist != NULL) + free(devlist, M_TEMP); + + if (devinfo == NULL) + return; + + switch (tag) { + case HDAC_UNSOLTAG_EVENT_HP: + hdac_hp_switch_handler(devinfo); + break; + case HDAC_UNSOLTAG_EVENT_TEST: + device_printf(sc->dev, "Unsol Test!\n"); + break; + default: + break; + } +} + +static int +hdac_stream_intr(struct hdac_softc *sc, struct hdac_chan *ch) +{ + /* XXX to be removed */ +#ifdef HDAC_INTR_EXTRA + uint32_t res; +#endif + + if (!(ch->flags & HDAC_CHN_RUNNING)) + return (0); + + /* XXX to be removed */ +#ifdef HDAC_INTR_EXTRA + res = HDAC_READ_1(&sc->mem, ch->off + HDAC_SDSTS); +#endif + + /* XXX to be removed */ +#ifdef HDAC_INTR_EXTRA + HDA_BOOTVERBOSE( + if (res & (HDAC_SDSTS_DESE | HDAC_SDSTS_FIFOE)) + device_printf(sc->dev, + "PCMDIR_%s intr triggered beyond stream boundary:" + "%08x\n", + (ch->dir == PCMDIR_PLAY) ? "PLAY" : "REC", res); + ); +#endif + + HDAC_WRITE_1(&sc->mem, ch->off + HDAC_SDSTS, + HDAC_SDSTS_DESE | HDAC_SDSTS_FIFOE | HDAC_SDSTS_BCIS ); + + /* XXX to be removed */ +#ifdef HDAC_INTR_EXTRA + if (res & HDAC_SDSTS_BCIS) { +#endif + return (1); + /* XXX to be removed */ +#ifdef HDAC_INTR_EXTRA + } +#endif + + return (0); +} + +/**************************************************************************** + * void hdac_intr_handler(void *) + * + * Interrupt handler. Processes interrupts received from the hdac. + ****************************************************************************/ +static void +hdac_intr_handler(void *context) +{ + struct hdac_softc *sc; + uint32_t intsts; + uint8_t rirbsts; + struct hdac_rirb *rirb_base; + uint32_t trigger; + + sc = (struct hdac_softc *)context; + + hdac_lock(sc); + if (sc->polling != 0) { + hdac_unlock(sc); + return; + } + + /* Do we have anything to do? */ + intsts = HDAC_READ_4(&sc->mem, HDAC_INTSTS); + if (!HDA_FLAG_MATCH(intsts, HDAC_INTSTS_GIS)) { + hdac_unlock(sc); + return; + } + + trigger = 0; + + /* Was this a controller interrupt? */ + if (HDA_FLAG_MATCH(intsts, HDAC_INTSTS_CIS)) { + rirb_base = (struct hdac_rirb *)sc->rirb_dma.dma_vaddr; + rirbsts = HDAC_READ_1(&sc->mem, HDAC_RIRBSTS); + /* Get as many responses that we can */ + while (HDA_FLAG_MATCH(rirbsts, HDAC_RIRBSTS_RINTFL)) { + HDAC_WRITE_1(&sc->mem, + HDAC_RIRBSTS, HDAC_RIRBSTS_RINTFL); + if (hdac_rirb_flush(sc) != 0) + trigger |= HDAC_TRIGGER_UNSOL; + rirbsts = HDAC_READ_1(&sc->mem, HDAC_RIRBSTS); + } + /* XXX to be removed */ + /* Clear interrupt and exit */ +#ifdef HDAC_INTR_EXTRA + HDAC_WRITE_4(&sc->mem, HDAC_INTSTS, HDAC_INTSTS_CIS); +#endif + } + + if (intsts & HDAC_INTSTS_SIS_MASK) { + if ((intsts & (1 << sc->num_iss)) && + hdac_stream_intr(sc, &sc->play) != 0) + trigger |= HDAC_TRIGGER_PLAY; + if ((intsts & (1 << 0)) && + hdac_stream_intr(sc, &sc->rec) != 0) + trigger |= HDAC_TRIGGER_REC; + /* XXX to be removed */ +#ifdef HDAC_INTR_EXTRA + HDAC_WRITE_4(&sc->mem, HDAC_INTSTS, intsts & + HDAC_INTSTS_SIS_MASK); +#endif + } + + hdac_unlock(sc); + + if (trigger & HDAC_TRIGGER_PLAY) + chn_intr(sc->play.c); + if (trigger & HDAC_TRIGGER_REC) + chn_intr(sc->rec.c); + if (trigger & HDAC_TRIGGER_UNSOL) + taskqueue_enqueue(taskqueue_thread, &sc->unsolq_task); +} + +/**************************************************************************** + * int hdac_reset(hdac_softc *) + * + * Reset the hdac to a quiescent and known state. + ****************************************************************************/ +static int +hdac_reset(struct hdac_softc *sc) +{ + uint32_t gctl; + int count, i; + + /* + * Stop all Streams DMA engine + */ + for (i = 0; i < sc->num_iss; i++) + HDAC_WRITE_4(&sc->mem, HDAC_ISDCTL(sc, i), 0x0); + for (i = 0; i < sc->num_oss; i++) + HDAC_WRITE_4(&sc->mem, HDAC_OSDCTL(sc, i), 0x0); + for (i = 0; i < sc->num_bss; i++) + HDAC_WRITE_4(&sc->mem, HDAC_BSDCTL(sc, i), 0x0); + + /* + * Stop Control DMA engines. + */ + HDAC_WRITE_1(&sc->mem, HDAC_CORBCTL, 0x0); + HDAC_WRITE_1(&sc->mem, HDAC_RIRBCTL, 0x0); + + /* + * Reset DMA position buffer. + */ + HDAC_WRITE_4(&sc->mem, HDAC_DPIBLBASE, 0x0); + HDAC_WRITE_4(&sc->mem, HDAC_DPIBUBASE, 0x0); + + /* + * Reset the controller. The reset must remain asserted for + * a minimum of 100us. + */ + gctl = HDAC_READ_4(&sc->mem, HDAC_GCTL); + HDAC_WRITE_4(&sc->mem, HDAC_GCTL, gctl & ~HDAC_GCTL_CRST); + count = 10000; + do { + gctl = HDAC_READ_4(&sc->mem, HDAC_GCTL); + if (!(gctl & HDAC_GCTL_CRST)) + break; + DELAY(10); + } while (--count); + if (gctl & HDAC_GCTL_CRST) { + device_printf(sc->dev, "Unable to put hdac in reset\n"); + return (ENXIO); + } + DELAY(100); + gctl = HDAC_READ_4(&sc->mem, HDAC_GCTL); + HDAC_WRITE_4(&sc->mem, HDAC_GCTL, gctl | HDAC_GCTL_CRST); + count = 10000; + do { + gctl = HDAC_READ_4(&sc->mem, HDAC_GCTL); + if (gctl & HDAC_GCTL_CRST) + break; + DELAY(10); + } while (--count); + if (!(gctl & HDAC_GCTL_CRST)) { + device_printf(sc->dev, "Device stuck in reset\n"); + return (ENXIO); + } + + /* + * Wait for codecs to finish their own reset sequence. The delay here + * should be of 250us but for some reasons, on it's not enough on my + * computer. Let's use twice as much as necessary to make sure that + * it's reset properly. + */ + DELAY(1000); + + return (0); +} + + +/**************************************************************************** + * int hdac_get_capabilities(struct hdac_softc *); + * + * Retreive the general capabilities of the hdac; + * Number of Input Streams + * Number of Output Streams + * Number of bidirectional Streams + * 64bit ready + * CORB and RIRB sizes + ****************************************************************************/ +static int +hdac_get_capabilities(struct hdac_softc *sc) +{ + uint16_t gcap; + uint8_t corbsize, rirbsize; + + gcap = HDAC_READ_2(&sc->mem, HDAC_GCAP); + sc->num_iss = HDAC_GCAP_ISS(gcap); + sc->num_oss = HDAC_GCAP_OSS(gcap); + sc->num_bss = HDAC_GCAP_BSS(gcap); + + sc->support_64bit = HDA_FLAG_MATCH(gcap, HDAC_GCAP_64OK); + + corbsize = HDAC_READ_1(&sc->mem, HDAC_CORBSIZE); + if ((corbsize & HDAC_CORBSIZE_CORBSZCAP_256) == + HDAC_CORBSIZE_CORBSZCAP_256) + sc->corb_size = 256; + else if ((corbsize & HDAC_CORBSIZE_CORBSZCAP_16) == + HDAC_CORBSIZE_CORBSZCAP_16) + sc->corb_size = 16; + else if ((corbsize & HDAC_CORBSIZE_CORBSZCAP_2) == + HDAC_CORBSIZE_CORBSZCAP_2) + sc->corb_size = 2; + else { + device_printf(sc->dev, "%s: Invalid corb size (%x)\n", + __func__, corbsize); + return (ENXIO); + } + + rirbsize = HDAC_READ_1(&sc->mem, HDAC_RIRBSIZE); + if ((rirbsize & HDAC_RIRBSIZE_RIRBSZCAP_256) == + HDAC_RIRBSIZE_RIRBSZCAP_256) + sc->rirb_size = 256; + else if ((rirbsize & HDAC_RIRBSIZE_RIRBSZCAP_16) == + HDAC_RIRBSIZE_RIRBSZCAP_16) + sc->rirb_size = 16; + else if ((rirbsize & HDAC_RIRBSIZE_RIRBSZCAP_2) == + HDAC_RIRBSIZE_RIRBSZCAP_2) + sc->rirb_size = 2; + else { + device_printf(sc->dev, "%s: Invalid rirb size (%x)\n", + __func__, rirbsize); + return (ENXIO); + } + + return (0); +} + + +/**************************************************************************** + * void hdac_dma_cb + * + * This function is called by bus_dmamap_load when the mapping has been + * established. We just record the physical address of the mapping into + * the struct hdac_dma passed in. + ****************************************************************************/ +static void +hdac_dma_cb(void *callback_arg, bus_dma_segment_t *segs, int nseg, int error) +{ + struct hdac_dma *dma; + + if (error == 0) { + dma = (struct hdac_dma *)callback_arg; + dma->dma_paddr = segs[0].ds_addr; + } +} + + +/**************************************************************************** + * int hdac_dma_alloc + * + * This function allocate and setup a dma region (struct hdac_dma). + * It must be freed by a corresponding hdac_dma_free. + ****************************************************************************/ +static int +hdac_dma_alloc(struct hdac_softc *sc, struct hdac_dma *dma, bus_size_t size) +{ + bus_size_t roundsz; + int result; + int lowaddr; + + roundsz = roundup2(size, HDAC_DMA_ALIGNMENT); + lowaddr = (sc->support_64bit) ? BUS_SPACE_MAXADDR : + BUS_SPACE_MAXADDR_32BIT; + bzero(dma, sizeof(*dma)); + + /* + * Create a DMA tag + */ + result = bus_dma_tag_create(NULL, /* parent */ + HDAC_DMA_ALIGNMENT, /* alignment */ + 0, /* boundary */ + lowaddr, /* lowaddr */ + BUS_SPACE_MAXADDR, /* highaddr */ + NULL, /* filtfunc */ + NULL, /* fistfuncarg */ + roundsz, /* maxsize */ + 1, /* nsegments */ + roundsz, /* maxsegsz */ + 0, /* flags */ + NULL, /* lockfunc */ + NULL, /* lockfuncarg */ + &dma->dma_tag); /* dmat */ + if (result != 0) { + device_printf(sc->dev, "%s: bus_dma_tag_create failed (%x)\n", + __func__, result); + goto hdac_dma_alloc_fail; + } + + /* + * Allocate DMA memory + */ + result = bus_dmamem_alloc(dma->dma_tag, (void **)&dma->dma_vaddr, + BUS_DMA_NOWAIT | BUS_DMA_ZERO | + ((sc->flags & HDAC_F_DMA_NOCACHE) ? HDAC_BUS_DMA_NOCACHE : 0), + &dma->dma_map); + if (result != 0) { + device_printf(sc->dev, "%s: bus_dmamem_alloc failed (%x)\n", + __func__, result); + goto hdac_dma_alloc_fail; + } + + HDAC_DMA_ATTR(sc, dma->dma_vaddr, roundsz, PAT_UNCACHEABLE); + dma->dma_size = roundsz; + + /* + * Map the memory + */ + result = bus_dmamap_load(dma->dma_tag, dma->dma_map, + (void *)dma->dma_vaddr, roundsz, hdac_dma_cb, (void *)dma, 0); + if (result != 0 || dma->dma_paddr == 0) { + if (result == 0) + result = ENOMEM; + device_printf(sc->dev, "%s: bus_dmamem_load failed (%x)\n", + __func__, result); + goto hdac_dma_alloc_fail; + } + + HDA_BOOTVERBOSE( + device_printf(sc->dev, "%s: size=%ju -> roundsz=%ju\n", + __func__, (uintmax_t)size, (uintmax_t)roundsz); + ); + + return (0); + +hdac_dma_alloc_fail: + hdac_dma_free(sc, dma); + + return (result); +} + + +/**************************************************************************** + * void hdac_dma_free(struct hdac_softc *, struct hdac_dma *) + * + * Free a struct dhac_dma that has been previously allocated via the + * hdac_dma_alloc function. + ****************************************************************************/ +static void +hdac_dma_free(struct hdac_softc *sc, struct hdac_dma *dma) +{ + if (dma->dma_map != NULL) { +#if 0 + /* Flush caches */ + bus_dmamap_sync(dma->dma_tag, dma->dma_map, + BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE); +#endif + bus_dmamap_unload(dma->dma_tag, dma->dma_map); + } + if (dma->dma_vaddr != NULL) { + HDAC_DMA_ATTR(sc, dma->dma_vaddr, dma->dma_size, + PAT_WRITE_BACK); + bus_dmamem_free(dma->dma_tag, dma->dma_vaddr, dma->dma_map); + dma->dma_vaddr = NULL; + } + dma->dma_map = NULL; + if (dma->dma_tag != NULL) { + bus_dma_tag_destroy(dma->dma_tag); + dma->dma_tag = NULL; + } + dma->dma_size = 0; +} + +/**************************************************************************** + * int hdac_mem_alloc(struct hdac_softc *) + * + * Allocate all the bus resources necessary to speak with the physical + * controller. + ****************************************************************************/ +static int +hdac_mem_alloc(struct hdac_softc *sc) +{ + struct hdac_mem *mem; + + mem = &sc->mem; + mem->mem_rid = PCIR_BAR(0); + mem->mem_res = bus_alloc_resource_any(sc->dev, SYS_RES_MEMORY, + &mem->mem_rid, RF_ACTIVE); + if (mem->mem_res == NULL) { + device_printf(sc->dev, + "%s: Unable to allocate memory resource\n", __func__); + return (ENOMEM); + } + mem->mem_tag = rman_get_bustag(mem->mem_res); + mem->mem_handle = rman_get_bushandle(mem->mem_res); + + return (0); +} + +/**************************************************************************** + * void hdac_mem_free(struct hdac_softc *) + * + * Free up resources previously allocated by hdac_mem_alloc. + ****************************************************************************/ +static void +hdac_mem_free(struct hdac_softc *sc) +{ + struct hdac_mem *mem; + + mem = &sc->mem; + if (mem->mem_res != NULL) + bus_release_resource(sc->dev, SYS_RES_MEMORY, mem->mem_rid, + mem->mem_res); + mem->mem_res = NULL; +} + +/**************************************************************************** + * int hdac_irq_alloc(struct hdac_softc *) + * + * Allocate and setup the resources necessary for interrupt handling. + ****************************************************************************/ +static int +hdac_irq_alloc(struct hdac_softc *sc) +{ + struct hdac_irq *irq; + int result; + + irq = &sc->irq; + irq->irq_rid = 0x0; + +#if !defined(_LPMAP_C_) && __FreeBSD_version >= 602106 + if ((sc->flags & HDAC_F_MSI) && + (result = pci_msi_count(sc->dev)) == 1 && + pci_alloc_msi(sc->dev, &result) == 0) + irq->irq_rid = 0x1; + else +#endif + sc->flags &= ~HDAC_F_MSI; + + irq->irq_res = bus_alloc_resource_any(sc->dev, SYS_RES_IRQ, + &irq->irq_rid, RF_SHAREABLE | RF_ACTIVE); + if (irq->irq_res == NULL) { + device_printf(sc->dev, "%s: Unable to allocate irq\n", + __func__); + goto hdac_irq_alloc_fail; + } + result = snd_setup_intr(sc->dev, irq->irq_res, INTR_MPSAFE, + hdac_intr_handler, sc, &irq->irq_handle); + if (result != 0) { + device_printf(sc->dev, + "%s: Unable to setup interrupt handler (%x)\n", + __func__, result); + goto hdac_irq_alloc_fail; + } + + return (0); + +hdac_irq_alloc_fail: + hdac_irq_free(sc); + + return (ENXIO); +} + +/**************************************************************************** + * void hdac_irq_free(struct hdac_softc *) + * + * Free up resources previously allocated by hdac_irq_alloc. + ****************************************************************************/ +static void +hdac_irq_free(struct hdac_softc *sc) +{ + struct hdac_irq *irq; + + irq = &sc->irq; + if (irq->irq_res != NULL && irq->irq_handle != NULL) + bus_teardown_intr(sc->dev, irq->irq_res, irq->irq_handle); + if (irq->irq_res != NULL) + bus_release_resource(sc->dev, SYS_RES_IRQ, irq->irq_rid, + irq->irq_res); +#if !defined(_LPMAP_C_) && __FreeBSD_version >= 602106 + if ((sc->flags & HDAC_F_MSI) && irq->irq_rid == 0x1) + pci_release_msi(sc->dev); +#endif + irq->irq_handle = NULL; + irq->irq_res = NULL; + irq->irq_rid = 0x0; +} + +/**************************************************************************** + * void hdac_corb_init(struct hdac_softc *) + * + * Initialize the corb registers for operations but do not start it up yet. + * The CORB engine must not be running when this function is called. + ****************************************************************************/ +static void +hdac_corb_init(struct hdac_softc *sc) +{ + uint8_t corbsize; + uint64_t corbpaddr; + + /* Setup the CORB size. */ + switch (sc->corb_size) { + case 256: + corbsize = HDAC_CORBSIZE_CORBSIZE(HDAC_CORBSIZE_CORBSIZE_256); + break; + case 16: + corbsize = HDAC_CORBSIZE_CORBSIZE(HDAC_CORBSIZE_CORBSIZE_16); + break; + case 2: + corbsize = HDAC_CORBSIZE_CORBSIZE(HDAC_CORBSIZE_CORBSIZE_2); + break; + default: + panic("%s: Invalid CORB size (%x)\n", __func__, sc->corb_size); + } + HDAC_WRITE_1(&sc->mem, HDAC_CORBSIZE, corbsize); + + /* Setup the CORB Address in the hdac */ + corbpaddr = (uint64_t)sc->corb_dma.dma_paddr; + HDAC_WRITE_4(&sc->mem, HDAC_CORBLBASE, (uint32_t)corbpaddr); + HDAC_WRITE_4(&sc->mem, HDAC_CORBUBASE, (uint32_t)(corbpaddr >> 32)); + + /* Set the WP and RP */ + sc->corb_wp = 0; + HDAC_WRITE_2(&sc->mem, HDAC_CORBWP, sc->corb_wp); + HDAC_WRITE_2(&sc->mem, HDAC_CORBRP, HDAC_CORBRP_CORBRPRST); + /* + * The HDA specification indicates that the CORBRPRST bit will always + * read as zero. Unfortunately, it seems that at least the 82801G + * doesn't reset the bit to zero, which stalls the corb engine. + * manually reset the bit to zero before continuing. + */ + HDAC_WRITE_2(&sc->mem, HDAC_CORBRP, 0x0); + + /* Enable CORB error reporting */ +#if 0 + HDAC_WRITE_1(&sc->mem, HDAC_CORBCTL, HDAC_CORBCTL_CMEIE); +#endif +} + +/**************************************************************************** + * void hdac_rirb_init(struct hdac_softc *) + * + * Initialize the rirb registers for operations but do not start it up yet. + * The RIRB engine must not be running when this function is called. + ****************************************************************************/ +static void +hdac_rirb_init(struct hdac_softc *sc) +{ + uint8_t rirbsize; + uint64_t rirbpaddr; + + /* Setup the RIRB size. */ + switch (sc->rirb_size) { + case 256: + rirbsize = HDAC_RIRBSIZE_RIRBSIZE(HDAC_RIRBSIZE_RIRBSIZE_256); + break; + case 16: + rirbsize = HDAC_RIRBSIZE_RIRBSIZE(HDAC_RIRBSIZE_RIRBSIZE_16); + break; + case 2: + rirbsize = HDAC_RIRBSIZE_RIRBSIZE(HDAC_RIRBSIZE_RIRBSIZE_2); + break; + default: + panic("%s: Invalid RIRB size (%x)\n", __func__, sc->rirb_size); + } + HDAC_WRITE_1(&sc->mem, HDAC_RIRBSIZE, rirbsize); + + /* Setup the RIRB Address in the hdac */ + rirbpaddr = (uint64_t)sc->rirb_dma.dma_paddr; + HDAC_WRITE_4(&sc->mem, HDAC_RIRBLBASE, (uint32_t)rirbpaddr); + HDAC_WRITE_4(&sc->mem, HDAC_RIRBUBASE, (uint32_t)(rirbpaddr >> 32)); + + /* Setup the WP and RP */ + sc->rirb_rp = 0; + HDAC_WRITE_2(&sc->mem, HDAC_RIRBWP, HDAC_RIRBWP_RIRBWPRST); + + if (sc->polling == 0) { + /* Setup the interrupt threshold */ + HDAC_WRITE_2(&sc->mem, HDAC_RINTCNT, sc->rirb_size / 2); + + /* Enable Overrun and response received reporting */ +#if 0 + HDAC_WRITE_1(&sc->mem, HDAC_RIRBCTL, + HDAC_RIRBCTL_RIRBOIC | HDAC_RIRBCTL_RINTCTL); +#else + HDAC_WRITE_1(&sc->mem, HDAC_RIRBCTL, HDAC_RIRBCTL_RINTCTL); +#endif + } + +#if 0 + /* + * Make sure that the Host CPU cache doesn't contain any dirty + * cache lines that falls in the rirb. If I understood correctly, it + * should be sufficient to do this only once as the rirb is purely + * read-only from now on. + */ + bus_dmamap_sync(sc->rirb_dma.dma_tag, sc->rirb_dma.dma_map, + BUS_DMASYNC_PREREAD); +#endif +} + +/**************************************************************************** + * void hdac_corb_start(hdac_softc *) + * + * Startup the corb DMA engine + ****************************************************************************/ +static void +hdac_corb_start(struct hdac_softc *sc) +{ + uint32_t corbctl; + + corbctl = HDAC_READ_1(&sc->mem, HDAC_CORBCTL); + corbctl |= HDAC_CORBCTL_CORBRUN; + HDAC_WRITE_1(&sc->mem, HDAC_CORBCTL, corbctl); +} + +/**************************************************************************** + * void hdac_rirb_start(hdac_softc *) + * + * Startup the rirb DMA engine + ****************************************************************************/ +static void +hdac_rirb_start(struct hdac_softc *sc) +{ + uint32_t rirbctl; + + rirbctl = HDAC_READ_1(&sc->mem, HDAC_RIRBCTL); + rirbctl |= HDAC_RIRBCTL_RIRBDMAEN; + HDAC_WRITE_1(&sc->mem, HDAC_RIRBCTL, rirbctl); +} + + +/**************************************************************************** + * void hdac_scan_codecs(struct hdac_softc *) + * + * Scan the bus for available codecs. + ****************************************************************************/ +static void +hdac_scan_codecs(struct hdac_softc *sc) +{ + struct hdac_codec *codec; + int i; + uint16_t statests; + + statests = HDAC_READ_2(&sc->mem, HDAC_STATESTS); + for (i = 0; i < HDAC_CODEC_MAX; i++) { + if (HDAC_STATESTS_SDIWAKE(statests, i)) { + /* We have found a codec. */ + codec = (struct hdac_codec *)malloc(sizeof(*codec), + M_HDAC, M_ZERO | M_NOWAIT); + if (codec == NULL) { + device_printf(sc->dev, + "Unable to allocate memory for codec\n"); + continue; + } + codec->commands = NULL; + codec->responses_received = 0; + codec->verbs_sent = 0; + codec->sc = sc; + codec->cad = i; + sc->codecs[i] = codec; + if (hdac_probe_codec(codec) != 0) + break; + } + } + /* All codecs have been probed, now try to attach drivers to them */ + /* bus_generic_attach(sc->dev); */ +} + +/**************************************************************************** + * void hdac_probe_codec(struct hdac_softc *, int) + * + * Probe a the given codec_id for available function groups. + ****************************************************************************/ +static int +hdac_probe_codec(struct hdac_codec *codec) +{ + struct hdac_softc *sc = codec->sc; + struct hdac_devinfo *devinfo; + uint32_t vendorid, revisionid, subnode; + int startnode; + int endnode; + int i; + nid_t cad = codec->cad; + + HDA_BOOTVERBOSE( + device_printf(sc->dev, "HDA_DEBUG: Probing codec: %d\n", cad); + ); + vendorid = hdac_command(sc, + HDA_CMD_GET_PARAMETER(cad, 0x0, HDA_PARAM_VENDOR_ID), + cad); + revisionid = hdac_command(sc, + HDA_CMD_GET_PARAMETER(cad, 0x0, HDA_PARAM_REVISION_ID), + cad); + subnode = hdac_command(sc, + HDA_CMD_GET_PARAMETER(cad, 0x0, HDA_PARAM_SUB_NODE_COUNT), + cad); + startnode = HDA_PARAM_SUB_NODE_COUNT_START(subnode); + endnode = startnode + HDA_PARAM_SUB_NODE_COUNT_TOTAL(subnode); + + HDA_BOOTVERBOSE( + device_printf(sc->dev, "HDA_DEBUG: \tstartnode=%d endnode=%d\n", + startnode, endnode); + ); + for (i = startnode; i < endnode; i++) { + devinfo = hdac_probe_function(codec, i); + if (devinfo != NULL) { + /* XXX Ignore other FG. */ + devinfo->vendor_id = + HDA_PARAM_VENDOR_ID_VENDOR_ID(vendorid); + devinfo->device_id = + HDA_PARAM_VENDOR_ID_DEVICE_ID(vendorid); + devinfo->revision_id = + HDA_PARAM_REVISION_ID_REVISION_ID(revisionid); + devinfo->stepping_id = + HDA_PARAM_REVISION_ID_STEPPING_ID(revisionid); + HDA_BOOTVERBOSE( + device_printf(sc->dev, + "HDA_DEBUG: \tFound AFG nid=%d " + "[startnode=%d endnode=%d]\n", + devinfo->nid, startnode, endnode); + ); + return (1); + } + } + + HDA_BOOTVERBOSE( + device_printf(sc->dev, "HDA_DEBUG: \tAFG not found\n"); + ); + return (0); +} + +static struct hdac_devinfo * +hdac_probe_function(struct hdac_codec *codec, nid_t nid) +{ + struct hdac_softc *sc = codec->sc; + struct hdac_devinfo *devinfo; + uint32_t fctgrptype; + nid_t cad = codec->cad; + + fctgrptype = HDA_PARAM_FCT_GRP_TYPE_NODE_TYPE(hdac_command(sc, + HDA_CMD_GET_PARAMETER(cad, nid, HDA_PARAM_FCT_GRP_TYPE), cad)); + + /* XXX For now, ignore other FG. */ + if (fctgrptype != HDA_PARAM_FCT_GRP_TYPE_NODE_TYPE_AUDIO) + return (NULL); + + devinfo = (struct hdac_devinfo *)malloc(sizeof(*devinfo), M_HDAC, + M_NOWAIT | M_ZERO); + if (devinfo == NULL) { + device_printf(sc->dev, "%s: Unable to allocate ivar\n", + __func__); + return (NULL); + } + + devinfo->nid = nid; + devinfo->node_type = fctgrptype; + devinfo->codec = codec; + + hdac_add_child(sc, devinfo); + + return (devinfo); +} + +static void +hdac_add_child(struct hdac_softc *sc, struct hdac_devinfo *devinfo) +{ + devinfo->dev = device_add_child(sc->dev, NULL, -1); + device_set_ivars(devinfo->dev, (void *)devinfo); + /* XXX - Print more information when booting verbose??? */ +} + +static void +hdac_widget_connection_parse(struct hdac_widget *w) +{ + struct hdac_softc *sc = w->devinfo->codec->sc; + uint32_t res; + int i, j, max, ents, entnum; + nid_t cad = w->devinfo->codec->cad; + nid_t nid = w->nid; + nid_t cnid, addcnid, prevcnid; + + w->nconns = 0; + + res = hdac_command(sc, + HDA_CMD_GET_PARAMETER(cad, nid, HDA_PARAM_CONN_LIST_LENGTH), cad); + + ents = HDA_PARAM_CONN_LIST_LENGTH_LIST_LENGTH(res); + + if (ents < 1) + return; + + entnum = HDA_PARAM_CONN_LIST_LENGTH_LONG_FORM(res) ? 2 : 4; + max = (sizeof(w->conns) / sizeof(w->conns[0])) - 1; + prevcnid = 0; + +#define CONN_RMASK(e) (1 << ((32 / (e)) - 1)) +#define CONN_NMASK(e) (CONN_RMASK(e) - 1) +#define CONN_RESVAL(r, e, n) ((r) >> ((32 / (e)) * (n))) +#define CONN_RANGE(r, e, n) (CONN_RESVAL(r, e, n) & CONN_RMASK(e)) +#define CONN_CNID(r, e, n) (CONN_RESVAL(r, e, n) & CONN_NMASK(e)) + + for (i = 0; i < ents; i += entnum) { + res = hdac_command(sc, + HDA_CMD_GET_CONN_LIST_ENTRY(cad, nid, i), cad); + for (j = 0; j < entnum; j++) { + cnid = CONN_CNID(res, entnum, j); + if (cnid == 0) { + if (w->nconns < ents) + device_printf(sc->dev, + "%s: nid=%d WARNING: zero cnid " + "entnum=%d j=%d index=%d " + "entries=%d found=%d res=0x%08x\n", + __func__, nid, entnum, j, i, + ents, w->nconns, res); + else + goto getconns_out; + } + if (cnid < w->devinfo->startnode || + cnid >= w->devinfo->endnode) { + HDA_BOOTVERBOSE( + device_printf(sc->dev, + "%s: GHOST: nid=%d j=%d " + "entnum=%d index=%d res=0x%08x\n", + __func__, nid, j, entnum, i, res); + ); + } + if (CONN_RANGE(res, entnum, j) == 0) + addcnid = cnid; + else if (prevcnid == 0 || prevcnid >= cnid) { + device_printf(sc->dev, + "%s: WARNING: Invalid child range " + "nid=%d index=%d j=%d entnum=%d " + "prevcnid=%d cnid=%d res=0x%08x\n", + __func__, nid, i, j, entnum, prevcnid, + cnid, res); + addcnid = cnid; + } else + addcnid = prevcnid + 1; + while (addcnid <= cnid) { + if (w->nconns > max) { + device_printf(sc->dev, + "%s: nid=%d: Adding %d: " + "Max connection reached! max=%d\n", + __func__, nid, addcnid, max + 1); + goto getconns_out; + } + w->conns[w->nconns++] = addcnid++; + } + prevcnid = cnid; + } + } + +getconns_out: + HDA_BOOTVERBOSE( + device_printf(sc->dev, + "HDA_DEBUG: %s: nid=%d entries=%d found=%d\n", + __func__, nid, ents, w->nconns); + ); + return; +} + +static uint32_t +hdac_widget_pin_getconfig(struct hdac_widget *w) +{ + struct hdac_softc *sc; + uint32_t config, orig, id; + nid_t cad, nid; + + sc = w->devinfo->codec->sc; + cad = w->devinfo->codec->cad; + nid = w->nid; + id = hdac_codec_id(w->devinfo); + + config = hdac_command(sc, + HDA_CMD_GET_CONFIGURATION_DEFAULT(cad, nid), + cad); + orig = config; + + /* + * XXX REWRITE!!!! Don't argue! + */ + if (id == HDA_CODEC_ALC880 && sc->pci_subvendor == LG_LW20_SUBVENDOR) { + switch (nid) { + case 26: + config &= ~HDA_CONFIG_DEFAULTCONF_DEVICE_MASK; + config |= HDA_CONFIG_DEFAULTCONF_DEVICE_LINE_IN; + break; + case 27: + config &= ~HDA_CONFIG_DEFAULTCONF_DEVICE_MASK; + config |= HDA_CONFIG_DEFAULTCONF_DEVICE_HP_OUT; + break; + default: + break; + } + } else if (id == HDA_CODEC_ALC880 && + (sc->pci_subvendor == CLEVO_D900T_SUBVENDOR || + sc->pci_subvendor == ASUS_M5200_SUBVENDOR)) { + /* + * Super broken BIOS + */ + switch (nid) { + case 20: + break; + case 21: + break; + case 22: + break; + case 23: + break; + case 24: /* MIC1 */ + config &= ~HDA_CONFIG_DEFAULTCONF_DEVICE_MASK; + config |= HDA_CONFIG_DEFAULTCONF_DEVICE_MIC_IN; + break; + case 25: /* XXX MIC2 */ + config &= ~HDA_CONFIG_DEFAULTCONF_DEVICE_MASK; + config |= HDA_CONFIG_DEFAULTCONF_DEVICE_MIC_IN; + break; + case 26: /* LINE1 */ + config &= ~HDA_CONFIG_DEFAULTCONF_DEVICE_MASK; + config |= HDA_CONFIG_DEFAULTCONF_DEVICE_LINE_IN; + break; + case 27: /* XXX LINE2 */ + config &= ~HDA_CONFIG_DEFAULTCONF_DEVICE_MASK; + config |= HDA_CONFIG_DEFAULTCONF_DEVICE_LINE_IN; + break; + case 28: /* CD */ + config &= ~HDA_CONFIG_DEFAULTCONF_DEVICE_MASK; + config |= HDA_CONFIG_DEFAULTCONF_DEVICE_CD; + break; + case 30: + break; + case 31: + break; + default: + break; + } + } else if (id == HDA_CODEC_ALC883 && + HDA_DEV_MATCH(ACER_ALL_SUBVENDOR, sc->pci_subvendor)) { + switch (nid) { + case 25: + config &= ~(HDA_CONFIG_DEFAULTCONF_DEVICE_MASK | + HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_MASK); + config |= (HDA_CONFIG_DEFAULTCONF_DEVICE_MIC_IN | + HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_FIXED); + break; + case 28: + config &= ~(HDA_CONFIG_DEFAULTCONF_DEVICE_MASK | + HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_MASK); + config |= (HDA_CONFIG_DEFAULTCONF_DEVICE_CD | + HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_FIXED); + break; + default: + break; + } + } else if (id == HDA_CODEC_CXVENICE && sc->pci_subvendor == + HP_V3000_SUBVENDOR) { + switch (nid) { + case 18: + config &= ~HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_MASK; + config |= HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_NONE; + break; + case 20: + config &= ~(HDA_CONFIG_DEFAULTCONF_DEVICE_MASK | + HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_MASK); + config |= (HDA_CONFIG_DEFAULTCONF_DEVICE_MIC_IN | + HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_FIXED); + break; + case 21: + config &= ~(HDA_CONFIG_DEFAULTCONF_DEVICE_MASK | + HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_MASK); + config |= (HDA_CONFIG_DEFAULTCONF_DEVICE_CD | + HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_FIXED); + break; + default: + break; + } + } else if (id == HDA_CODEC_CXWAIKIKI && sc->pci_subvendor == + HP_DV5000_SUBVENDOR) { + switch (nid) { + case 20: + case 21: + config &= ~HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_MASK; + config |= HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_NONE; + break; + default: + break; + } + } else if (id == HDA_CODEC_ALC861 && sc->pci_subvendor == + ASUS_W6F_SUBVENDOR) { + switch (nid) { + case 11: + config &= ~(HDA_CONFIG_DEFAULTCONF_DEVICE_MASK | + HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_MASK); + config |= (HDA_CONFIG_DEFAULTCONF_DEVICE_LINE_OUT | + HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_FIXED); + break; + case 15: + config &= ~(HDA_CONFIG_DEFAULTCONF_DEVICE_MASK | + HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_MASK); + config |= (HDA_CONFIG_DEFAULTCONF_DEVICE_HP_OUT | + HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_JACK); + break; + default: + break; + } + } else if (id == HDA_CODEC_ALC861 && sc->pci_subvendor == + UNIWILL_9075_SUBVENDOR) { + switch (nid) { + case 15: + config &= ~(HDA_CONFIG_DEFAULTCONF_DEVICE_MASK | + HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_MASK); + config |= (HDA_CONFIG_DEFAULTCONF_DEVICE_HP_OUT | + HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_JACK); + break; + default: + break; + } + } else if (id == HDA_CODEC_AD1986A && + (sc->pci_subvendor == ASUS_M2NPVMX_SUBVENDOR || + sc->pci_subvendor == ASUS_A8NVMCSM_SUBVENDOR)) { + switch (nid) { + case 28: /* LINE */ + config &= ~HDA_CONFIG_DEFAULTCONF_DEVICE_MASK; + config |= HDA_CONFIG_DEFAULTCONF_DEVICE_LINE_IN; + break; + case 29: /* MIC */ + config &= ~HDA_CONFIG_DEFAULTCONF_DEVICE_MASK; + config |= HDA_CONFIG_DEFAULTCONF_DEVICE_MIC_IN; + break; + default: + break; + } + } + + HDA_BOOTVERBOSE( + if (config != orig) + device_printf(sc->dev, + "HDA_DEBUG: Pin config nid=%u 0x%08x -> 0x%08x\n", + nid, orig, config); + ); + + return (config); +} + +static uint32_t +hdac_widget_pin_getcaps(struct hdac_widget *w) +{ + struct hdac_softc *sc; + uint32_t caps, orig, id; + nid_t cad, nid; + + sc = w->devinfo->codec->sc; + cad = w->devinfo->codec->cad; + nid = w->nid; + id = hdac_codec_id(w->devinfo); + + caps = hdac_command(sc, + HDA_CMD_GET_PARAMETER(cad, nid, HDA_PARAM_PIN_CAP), cad); + orig = caps; + + HDA_BOOTVERBOSE( + if (caps != orig) + device_printf(sc->dev, + "HDA_DEBUG: Pin caps nid=%u 0x%08x -> 0x%08x\n", + nid, orig, caps); + ); + + return (caps); +} + +static void +hdac_widget_pin_parse(struct hdac_widget *w) +{ + struct hdac_softc *sc = w->devinfo->codec->sc; + uint32_t config, pincap; + char *devstr, *connstr; + nid_t cad = w->devinfo->codec->cad; + nid_t nid = w->nid; + + config = hdac_widget_pin_getconfig(w); + w->wclass.pin.config = config; + + pincap = hdac_widget_pin_getcaps(w); + w->wclass.pin.cap = pincap; + + w->wclass.pin.ctrl = hdac_command(sc, + HDA_CMD_GET_PIN_WIDGET_CTRL(cad, nid), cad) & + ~(HDA_CMD_SET_PIN_WIDGET_CTRL_HPHN_ENABLE | + HDA_CMD_SET_PIN_WIDGET_CTRL_OUT_ENABLE | + HDA_CMD_SET_PIN_WIDGET_CTRL_IN_ENABLE | + HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE_MASK); + + if (HDA_PARAM_PIN_CAP_HEADPHONE_CAP(pincap)) + w->wclass.pin.ctrl |= HDA_CMD_SET_PIN_WIDGET_CTRL_HPHN_ENABLE; + if (HDA_PARAM_PIN_CAP_OUTPUT_CAP(pincap)) + w->wclass.pin.ctrl |= HDA_CMD_SET_PIN_WIDGET_CTRL_OUT_ENABLE; + if (HDA_PARAM_PIN_CAP_INPUT_CAP(pincap)) + w->wclass.pin.ctrl |= HDA_CMD_SET_PIN_WIDGET_CTRL_IN_ENABLE; + if (HDA_PARAM_PIN_CAP_EAPD_CAP(pincap)) { + w->param.eapdbtl = hdac_command(sc, + HDA_CMD_GET_EAPD_BTL_ENABLE(cad, nid), cad); + w->param.eapdbtl &= 0x7; + w->param.eapdbtl |= HDA_CMD_SET_EAPD_BTL_ENABLE_EAPD; + } else + w->param.eapdbtl = HDAC_INVALID; + + switch (config & HDA_CONFIG_DEFAULTCONF_DEVICE_MASK) { + case HDA_CONFIG_DEFAULTCONF_DEVICE_LINE_OUT: + devstr = "line out"; + break; + case HDA_CONFIG_DEFAULTCONF_DEVICE_SPEAKER: + devstr = "speaker"; + break; + case HDA_CONFIG_DEFAULTCONF_DEVICE_HP_OUT: + devstr = "headphones out"; + break; + case HDA_CONFIG_DEFAULTCONF_DEVICE_CD: + devstr = "CD"; + break; + case HDA_CONFIG_DEFAULTCONF_DEVICE_SPDIF_OUT: + devstr = "SPDIF out"; + break; + case HDA_CONFIG_DEFAULTCONF_DEVICE_DIGITAL_OTHER_OUT: + devstr = "digital (other) out"; + break; + case HDA_CONFIG_DEFAULTCONF_DEVICE_MODEM_LINE: + devstr = "modem, line side"; + break; + case HDA_CONFIG_DEFAULTCONF_DEVICE_MODEM_HANDSET: + devstr = "modem, handset side"; + break; + case HDA_CONFIG_DEFAULTCONF_DEVICE_LINE_IN: + devstr = "line in"; + break; + case HDA_CONFIG_DEFAULTCONF_DEVICE_AUX: + devstr = "AUX"; + break; + case HDA_CONFIG_DEFAULTCONF_DEVICE_MIC_IN: + devstr = "Mic in"; + break; + case HDA_CONFIG_DEFAULTCONF_DEVICE_TELEPHONY: + devstr = "telephony"; + break; + case HDA_CONFIG_DEFAULTCONF_DEVICE_SPDIF_IN: + devstr = "SPDIF in"; + break; + case HDA_CONFIG_DEFAULTCONF_DEVICE_DIGITAL_OTHER_IN: + devstr = "digital (other) in"; + break; + case HDA_CONFIG_DEFAULTCONF_DEVICE_OTHER: + devstr = "other"; + break; + default: + devstr = "unknown"; + break; + } + + switch (config & HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_MASK) { + case HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_JACK: + connstr = "jack"; + break; + case HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_NONE: + connstr = "none"; + break; + case HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_FIXED: + connstr = "fixed"; + break; + case HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_BOTH: + connstr = "jack / fixed"; + break; + default: + connstr = "unknown"; + break; + } + + strlcat(w->name, ": ", sizeof(w->name)); + strlcat(w->name, devstr, sizeof(w->name)); + strlcat(w->name, " (", sizeof(w->name)); + strlcat(w->name, connstr, sizeof(w->name)); + strlcat(w->name, ")", sizeof(w->name)); +} + +static void +hdac_widget_parse(struct hdac_widget *w) +{ + struct hdac_softc *sc = w->devinfo->codec->sc; + uint32_t wcap, cap; + char *typestr; + nid_t cad = w->devinfo->codec->cad; + nid_t nid = w->nid; + + wcap = hdac_command(sc, + HDA_CMD_GET_PARAMETER(cad, nid, HDA_PARAM_AUDIO_WIDGET_CAP), + cad); + w->param.widget_cap = wcap; + w->type = HDA_PARAM_AUDIO_WIDGET_CAP_TYPE(wcap); + + switch (w->type) { + case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_OUTPUT: + typestr = "audio output"; + break; + case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT: + typestr = "audio input"; + break; + case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER: + typestr = "audio mixer"; + break; + case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_SELECTOR: + typestr = "audio selector"; + break; + case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX: + typestr = "pin"; + break; + case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_POWER_WIDGET: + typestr = "power widget"; + break; + case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_VOLUME_WIDGET: + typestr = "volume widget"; + break; + case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_BEEP_WIDGET: + typestr = "beep widget"; + break; + case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_VENDOR_WIDGET: + typestr = "vendor widget"; + break; + default: + typestr = "unknown type"; + break; + } + + strlcpy(w->name, typestr, sizeof(w->name)); + + if (HDA_PARAM_AUDIO_WIDGET_CAP_POWER_CTRL(wcap)) { + hdac_command(sc, + HDA_CMD_SET_POWER_STATE(cad, nid, HDA_CMD_POWER_STATE_D0), + cad); + DELAY(1000); + } + + hdac_widget_connection_parse(w); + + if (HDA_PARAM_AUDIO_WIDGET_CAP_OUT_AMP(wcap)) { + if (HDA_PARAM_AUDIO_WIDGET_CAP_AMP_OVR(wcap)) + w->param.outamp_cap = + hdac_command(sc, + HDA_CMD_GET_PARAMETER(cad, nid, + HDA_PARAM_OUTPUT_AMP_CAP), cad); + else + w->param.outamp_cap = + w->devinfo->function.audio.outamp_cap; + } else + w->param.outamp_cap = 0; + + if (HDA_PARAM_AUDIO_WIDGET_CAP_IN_AMP(wcap)) { + if (HDA_PARAM_AUDIO_WIDGET_CAP_AMP_OVR(wcap)) + w->param.inamp_cap = + hdac_command(sc, + HDA_CMD_GET_PARAMETER(cad, nid, + HDA_PARAM_INPUT_AMP_CAP), cad); + else + w->param.inamp_cap = + w->devinfo->function.audio.inamp_cap; + } else + w->param.inamp_cap = 0; + + if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_OUTPUT || + w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT) { + if (HDA_PARAM_AUDIO_WIDGET_CAP_FORMAT_OVR(wcap)) { + cap = hdac_command(sc, + HDA_CMD_GET_PARAMETER(cad, nid, + HDA_PARAM_SUPP_STREAM_FORMATS), cad); + w->param.supp_stream_formats = (cap != 0) ? cap : + w->devinfo->function.audio.supp_stream_formats; + cap = hdac_command(sc, + HDA_CMD_GET_PARAMETER(cad, nid, + HDA_PARAM_SUPP_PCM_SIZE_RATE), cad); + w->param.supp_pcm_size_rate = (cap != 0) ? cap : + w->devinfo->function.audio.supp_pcm_size_rate; + } else { + w->param.supp_stream_formats = + w->devinfo->function.audio.supp_stream_formats; + w->param.supp_pcm_size_rate = + w->devinfo->function.audio.supp_pcm_size_rate; + } + } else { + w->param.supp_stream_formats = 0; + w->param.supp_pcm_size_rate = 0; + } + + if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) + hdac_widget_pin_parse(w); +} + +static struct hdac_widget * +hdac_widget_get(struct hdac_devinfo *devinfo, nid_t nid) +{ + if (devinfo == NULL || devinfo->widget == NULL || + nid < devinfo->startnode || nid >= devinfo->endnode) + return (NULL); + return (&devinfo->widget[nid - devinfo->startnode]); +} + +static __inline int +hda_poll_channel(struct hdac_chan *ch) +{ + uint32_t sz, delta; + volatile uint32_t ptr; + + if (!(ch->flags & HDAC_CHN_RUNNING)) + return (0); + + sz = ch->blksz * ch->blkcnt; + if (ch->dmapos != NULL) + ptr = *(ch->dmapos); + else + ptr = HDAC_READ_4(&ch->devinfo->codec->sc->mem, + ch->off + HDAC_SDLPIB); + 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 hda_chan_active(sc) (((sc)->play.flags | (sc)->rec.flags) & \ + HDAC_CHN_RUNNING) + +static void +hda_poll_callback(void *arg) +{ + struct hdac_softc *sc = arg; + uint32_t trigger; + + if (sc == NULL) + return; + + hdac_lock(sc); + if (sc->polling == 0 || hda_chan_active(sc) == 0) { + hdac_unlock(sc); + return; + } + + trigger = 0; + trigger |= (hda_poll_channel(&sc->play) != 0) ? HDAC_TRIGGER_PLAY : 0; + trigger |= (hda_poll_channel(&sc->rec)) != 0 ? HDAC_TRIGGER_REC : 0; + + /* XXX */ + callout_reset(&sc->poll_hda, 1/*sc->poll_ticks*/, + hda_poll_callback, sc); + + hdac_unlock(sc); + + if (trigger & HDAC_TRIGGER_PLAY) + chn_intr(sc->play.c); + if (trigger & HDAC_TRIGGER_REC) + chn_intr(sc->rec.c); +} + +static int +hdac_rirb_flush(struct hdac_softc *sc) +{ + struct hdac_rirb *rirb_base, *rirb; + struct hdac_codec *codec; + struct hdac_command_list *commands; + nid_t cad; + uint32_t resp; + uint8_t rirbwp; + int ret; + + rirb_base = (struct hdac_rirb *)sc->rirb_dma.dma_vaddr; + rirbwp = HDAC_READ_1(&sc->mem, HDAC_RIRBWP); +#if 0 + bus_dmamap_sync(sc->rirb_dma.dma_tag, sc->rirb_dma.dma_map, + BUS_DMASYNC_POSTREAD); +#endif + + ret = 0; + + while (sc->rirb_rp != rirbwp) { + sc->rirb_rp++; + sc->rirb_rp %= sc->rirb_size; + rirb = &rirb_base[sc->rirb_rp]; + cad = HDAC_RIRB_RESPONSE_EX_SDATA_IN(rirb->response_ex); + if (cad < 0 || cad >= HDAC_CODEC_MAX || + sc->codecs[cad] == NULL) + continue; + resp = rirb->response; + codec = sc->codecs[cad]; + commands = codec->commands; + if (rirb->response_ex & HDAC_RIRB_RESPONSE_EX_UNSOLICITED) { + sc->unsolq[sc->unsolq_wp++] = (cad << 16) | + ((resp >> 26) & 0xffff); + sc->unsolq_wp %= HDAC_UNSOLQ_MAX; + } else if (commands != NULL && commands->num_commands > 0 && + codec->responses_received < commands->num_commands) + commands->responses[codec->responses_received++] = + resp; + ret++; + } + + return (ret); +} + +static int +hdac_unsolq_flush(struct hdac_softc *sc) +{ + nid_t cad; + uint32_t tag; + int ret = 0; + + if (sc->unsolq_st == HDAC_UNSOLQ_READY) { + sc->unsolq_st = HDAC_UNSOLQ_BUSY; + while (sc->unsolq_rp != sc->unsolq_wp) { + cad = sc->unsolq[sc->unsolq_rp] >> 16; + tag = sc->unsolq[sc->unsolq_rp++] & 0xffff; + sc->unsolq_rp %= HDAC_UNSOLQ_MAX; + hdac_unsolicited_handler(sc->codecs[cad], tag); + ret++; + } + sc->unsolq_st = HDAC_UNSOLQ_READY; + } + + return (ret); +} + +static void +hdac_poll_callback(void *arg) +{ + struct hdac_softc *sc = arg; + if (sc == NULL) + return; + + hdac_lock(sc); + if (sc->polling == 0 || sc->poll_ival == 0) { + hdac_unlock(sc); + return; + } + if (hdac_rirb_flush(sc) != 0) + hdac_unsolq_flush(sc); + callout_reset(&sc->poll_hdac, sc->poll_ival, hdac_poll_callback, sc); + hdac_unlock(sc); +} + +static void +hdac_stream_stop(struct hdac_chan *ch) +{ + struct hdac_softc *sc = ch->devinfo->codec->sc; + uint32_t ctl; + + ctl = HDAC_READ_1(&sc->mem, ch->off + HDAC_SDCTL0); + ctl &= ~(HDAC_SDCTL_IOCE | HDAC_SDCTL_FEIE | HDAC_SDCTL_DEIE | + HDAC_SDCTL_RUN); + HDAC_WRITE_1(&sc->mem, ch->off + HDAC_SDCTL0, ctl); + + ch->flags &= ~HDAC_CHN_RUNNING; + + if (sc->polling != 0) { + int pollticks; + + if (hda_chan_active(sc) == 0) { + callout_stop(&sc->poll_hda); + sc->poll_ticks = 1; + } else { + if (sc->play.flags & HDAC_CHN_RUNNING) + ch = &sc->play; + else + ch = &sc->rec; + pollticks = ((uint64_t)hz * ch->blksz) / + ((uint64_t)sndbuf_getbps(ch->b) * + sndbuf_getspd(ch->b)); + pollticks >>= 2; + if (pollticks > hz) + pollticks = hz; + if (pollticks < 1) { + HDA_BOOTVERBOSE( + device_printf(sc->dev, + "%s: pollticks=%d < 1 !\n", + __func__, pollticks); + ); + pollticks = 1; + } + if (pollticks > sc->poll_ticks) { + HDA_BOOTVERBOSE( + device_printf(sc->dev, + "%s: pollticks %d -> %d\n", + __func__, sc->poll_ticks, + pollticks); + ); + sc->poll_ticks = pollticks; + callout_reset(&sc->poll_hda, 1, + hda_poll_callback, sc); + } + } + } else { + ctl = HDAC_READ_4(&sc->mem, HDAC_INTCTL); + ctl &= ~(1 << (ch->off >> 5)); + HDAC_WRITE_4(&sc->mem, HDAC_INTCTL, ctl); + } +} + +static void +hdac_stream_start(struct hdac_chan *ch) +{ + struct hdac_softc *sc = ch->devinfo->codec->sc; + uint32_t ctl; + + if (sc->polling != 0) { + int pollticks; + + pollticks = ((uint64_t)hz * ch->blksz) / + ((uint64_t)sndbuf_getbps(ch->b) * sndbuf_getspd(ch->b)); + pollticks >>= 2; + if (pollticks > hz) + pollticks = hz; + if (pollticks < 1) { + HDA_BOOTVERBOSE( + device_printf(sc->dev, + "%s: pollticks=%d < 1 !\n", + __func__, pollticks); + ); + pollticks = 1; + } + if (hda_chan_active(sc) == 0 || pollticks < sc->poll_ticks) { + HDA_BOOTVERBOSE( + if (hda_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_hda, 1, hda_poll_callback, + sc); + } + ctl = HDAC_READ_1(&sc->mem, ch->off + HDAC_SDCTL0); + ctl |= HDAC_SDCTL_RUN; + } else { + ctl = HDAC_READ_4(&sc->mem, HDAC_INTCTL); + ctl |= 1 << (ch->off >> 5); + HDAC_WRITE_4(&sc->mem, HDAC_INTCTL, ctl); + ctl = HDAC_READ_1(&sc->mem, ch->off + HDAC_SDCTL0); + ctl |= HDAC_SDCTL_IOCE | HDAC_SDCTL_FEIE | HDAC_SDCTL_DEIE | + HDAC_SDCTL_RUN; + } + HDAC_WRITE_1(&sc->mem, ch->off + HDAC_SDCTL0, ctl); + + ch->flags |= HDAC_CHN_RUNNING; +} + +static void +hdac_stream_reset(struct hdac_chan *ch) +{ + struct hdac_softc *sc = ch->devinfo->codec->sc; + int timeout = 1000; + int to = timeout; + uint32_t ctl; + + ctl = HDAC_READ_1(&sc->mem, ch->off + HDAC_SDCTL0); + ctl |= HDAC_SDCTL_SRST; + HDAC_WRITE_1(&sc->mem, ch->off + HDAC_SDCTL0, ctl); + do { + ctl = HDAC_READ_1(&sc->mem, ch->off + HDAC_SDCTL0); + if (ctl & HDAC_SDCTL_SRST) + break; + DELAY(10); + } while (--to); + if (!(ctl & HDAC_SDCTL_SRST)) { + device_printf(sc->dev, "timeout in reset\n"); + } + ctl &= ~HDAC_SDCTL_SRST; + HDAC_WRITE_1(&sc->mem, ch->off + HDAC_SDCTL0, ctl); + to = timeout; + do { + ctl = HDAC_READ_1(&sc->mem, ch->off + HDAC_SDCTL0); + if (!(ctl & HDAC_SDCTL_SRST)) + break; + DELAY(10); + } while (--to); + if (ctl & HDAC_SDCTL_SRST) + device_printf(sc->dev, "can't reset!\n"); +} + +static void +hdac_stream_setid(struct hdac_chan *ch) +{ + struct hdac_softc *sc = ch->devinfo->codec->sc; + uint32_t ctl; + + ctl = HDAC_READ_1(&sc->mem, ch->off + HDAC_SDCTL2); + ctl &= ~HDAC_SDCTL2_STRM_MASK; + ctl |= ch->sid << HDAC_SDCTL2_STRM_SHIFT; + HDAC_WRITE_1(&sc->mem, ch->off + HDAC_SDCTL2, ctl); +} + +static void +hdac_bdl_setup(struct hdac_chan *ch) +{ + struct hdac_softc *sc = ch->devinfo->codec->sc; + struct hdac_bdle *bdle; + uint64_t addr; + uint32_t blksz, blkcnt; + int i; + + addr = (uint64_t)sndbuf_getbufaddr(ch->b); + bdle = (struct hdac_bdle *)ch->bdl_dma.dma_vaddr; + + if (sc->polling != 0) { + blksz = ch->blksz * ch->blkcnt; + blkcnt = 1; + } else { + blksz = ch->blksz; + blkcnt = ch->blkcnt; + } + + for (i = 0; i < blkcnt; i++, bdle++) { + bdle->addrl = (uint32_t)addr; + bdle->addrh = (uint32_t)(addr >> 32); + bdle->len = blksz; + bdle->ioc = 1 ^ sc->polling; + addr += blksz; + } + + HDAC_WRITE_4(&sc->mem, ch->off + HDAC_SDCBL, blksz * blkcnt); + HDAC_WRITE_2(&sc->mem, ch->off + HDAC_SDLVI, blkcnt - 1); + addr = ch->bdl_dma.dma_paddr; + HDAC_WRITE_4(&sc->mem, ch->off + HDAC_SDBDPL, (uint32_t)addr); + HDAC_WRITE_4(&sc->mem, ch->off + HDAC_SDBDPU, (uint32_t)(addr >> 32)); + if (ch->dmapos != NULL && + !(HDAC_READ_4(&sc->mem, HDAC_DPIBLBASE) & 0x00000001)) { + addr = sc->pos_dma.dma_paddr; + HDAC_WRITE_4(&sc->mem, HDAC_DPIBLBASE, + ((uint32_t)addr & HDAC_DPLBASE_DPLBASE_MASK) | 0x00000001); + HDAC_WRITE_4(&sc->mem, HDAC_DPIBUBASE, (uint32_t)(addr >> 32)); + } +} + +static int +hdac_bdl_alloc(struct hdac_chan *ch) +{ + struct hdac_softc *sc = ch->devinfo->codec->sc; + int rc; + + rc = hdac_dma_alloc(sc, &ch->bdl_dma, + sizeof(struct hdac_bdle) * HDA_BDL_MAX); + if (rc) { + device_printf(sc->dev, "can't alloc bdl\n"); + return (rc); + } + + return (0); +} + +static void +hdac_audio_ctl_amp_set_internal(struct hdac_softc *sc, nid_t cad, nid_t nid, + int index, int lmute, int rmute, + int left, int right, int dir) +{ + uint16_t v = 0; + + if (sc == NULL) + return; + + if (left != right || lmute != rmute) { + v = (1 << (15 - dir)) | (1 << 13) | (index << 8) | + (lmute << 7) | left; + hdac_command(sc, + HDA_CMD_SET_AMP_GAIN_MUTE(cad, nid, v), cad); + v = (1 << (15 - dir)) | (1 << 12) | (index << 8) | + (rmute << 7) | right; + } else + v = (1 << (15 - dir)) | (3 << 12) | (index << 8) | + (lmute << 7) | left; + + hdac_command(sc, + HDA_CMD_SET_AMP_GAIN_MUTE(cad, nid, v), cad); +} + +static void +hdac_audio_ctl_amp_set(struct hdac_audio_ctl *ctl, uint32_t mute, + int left, int right) +{ + struct hdac_softc *sc; + nid_t nid, cad; + int lmute, rmute; + + if (ctl == NULL || ctl->widget == NULL || + ctl->widget->devinfo == NULL || + ctl->widget->devinfo->codec == NULL || + ctl->widget->devinfo->codec->sc == NULL) + return; + + sc = ctl->widget->devinfo->codec->sc; + cad = ctl->widget->devinfo->codec->cad; + nid = ctl->widget->nid; + + if (mute == HDA_AMP_MUTE_DEFAULT) { + lmute = HDA_AMP_LEFT_MUTED(ctl->muted); + rmute = HDA_AMP_RIGHT_MUTED(ctl->muted); + } else { + lmute = HDA_AMP_LEFT_MUTED(mute); + rmute = HDA_AMP_RIGHT_MUTED(mute); + } + + if (ctl->dir & HDA_CTL_OUT) + hdac_audio_ctl_amp_set_internal(sc, cad, nid, ctl->index, + lmute, rmute, left, right, 0); + if (ctl->dir & HDA_CTL_IN) + hdac_audio_ctl_amp_set_internal(sc, cad, nid, ctl->index, + lmute, rmute, left, right, 1); + ctl->left = left; + ctl->right = right; +} + +static void +hdac_widget_connection_select(struct hdac_widget *w, uint8_t index) +{ + if (w == NULL || w->nconns < 1 || index > (w->nconns - 1)) + return; + hdac_command(w->devinfo->codec->sc, + HDA_CMD_SET_CONNECTION_SELECT_CONTROL(w->devinfo->codec->cad, + w->nid, index), w->devinfo->codec->cad); + w->selconn = index; +} + + +/**************************************************************************** + * uint32_t hdac_command_sendone_internal + * + * Wrapper function that sends only one command to a given codec + ****************************************************************************/ +static uint32_t +hdac_command_sendone_internal(struct hdac_softc *sc, uint32_t verb, nid_t cad) +{ + struct hdac_command_list cl; + uint32_t response = HDAC_INVALID; + + if (!hdac_lockowned(sc)) + device_printf(sc->dev, "WARNING!!!! mtx not owned!!!!\n"); + cl.num_commands = 1; + cl.verbs = &verb; + cl.responses = &response; + + hdac_command_send_internal(sc, &cl, cad); + + return (response); +} + +/**************************************************************************** + * hdac_command_send_internal + * + * Send a command list to the codec via the corb. We queue as much verbs as + * we can and msleep on the codec. When the interrupt get the responses + * back from the rirb, it will wake us up so we can queue the remaining verbs + * if any. + ****************************************************************************/ +static void +hdac_command_send_internal(struct hdac_softc *sc, + struct hdac_command_list *commands, nid_t cad) +{ + struct hdac_codec *codec; + int corbrp; + uint32_t *corb; + int timeout; + int retry = 10; + struct hdac_rirb *rirb_base; + + if (sc == NULL || sc->codecs[cad] == NULL || commands == NULL || + commands->num_commands < 1) + return; + + codec = sc->codecs[cad]; + codec->commands = commands; + codec->responses_received = 0; + codec->verbs_sent = 0; + corb = (uint32_t *)sc->corb_dma.dma_vaddr; + rirb_base = (struct hdac_rirb *)sc->rirb_dma.dma_vaddr; + + do { + if (codec->verbs_sent != commands->num_commands) { + /* Queue as many verbs as possible */ + corbrp = HDAC_READ_2(&sc->mem, HDAC_CORBRP); +#if 0 + bus_dmamap_sync(sc->corb_dma.dma_tag, + sc->corb_dma.dma_map, BUS_DMASYNC_PREWRITE); +#endif + while (codec->verbs_sent != commands->num_commands && + ((sc->corb_wp + 1) % sc->corb_size) != corbrp) { + sc->corb_wp++; + sc->corb_wp %= sc->corb_size; + corb[sc->corb_wp] = + commands->verbs[codec->verbs_sent++]; + } + + /* Send the verbs to the codecs */ +#if 0 + bus_dmamap_sync(sc->corb_dma.dma_tag, + sc->corb_dma.dma_map, BUS_DMASYNC_POSTWRITE); +#endif + HDAC_WRITE_2(&sc->mem, HDAC_CORBWP, sc->corb_wp); + } + + timeout = 1000; + while (hdac_rirb_flush(sc) == 0 && --timeout) + DELAY(10); + } while ((codec->verbs_sent != commands->num_commands || + codec->responses_received != commands->num_commands) && --retry); + + if (retry == 0) + device_printf(sc->dev, + "%s: TIMEOUT numcmd=%d, sent=%d, received=%d\n", + __func__, commands->num_commands, codec->verbs_sent, + codec->responses_received); + + codec->commands = NULL; + codec->responses_received = 0; + codec->verbs_sent = 0; + + hdac_unsolq_flush(sc); +} + + +/**************************************************************************** + * Device Methods + ****************************************************************************/ + +/**************************************************************************** + * int hdac_probe(device_t) + * + * Probe for the presence of an hdac. If none is found, check for a generic + * match using the subclass of the device. + ****************************************************************************/ +static int +hdac_probe(device_t dev) +{ + int i, result; + uint32_t model; + uint16_t class, subclass; + char desc[64]; + + model = (uint32_t)pci_get_device(dev) << 16; + model |= (uint32_t)pci_get_vendor(dev) & 0x0000ffff; + class = pci_get_class(dev); + subclass = pci_get_subclass(dev); + + bzero(desc, sizeof(desc)); + result = ENXIO; + for (i = 0; i < HDAC_DEVICES_LEN; i++) { + if (hdac_devices[i].model == model) { + strlcpy(desc, hdac_devices[i].desc, sizeof(desc)); + result = BUS_PROBE_DEFAULT; + break; + } + if (HDA_DEV_MATCH(hdac_devices[i].model, model) && + class == PCIC_MULTIMEDIA && + subclass == PCIS_MULTIMEDIA_HDA) { + strlcpy(desc, hdac_devices[i].desc, sizeof(desc)); + result = BUS_PROBE_GENERIC; + break; + } + } + if (result == ENXIO && class == PCIC_MULTIMEDIA && + subclass == PCIS_MULTIMEDIA_HDA) { + strlcpy(desc, "Generic", sizeof(desc)); + result = BUS_PROBE_GENERIC; + } + if (result != ENXIO) { + strlcat(desc, " High Definition Audio Controller", + sizeof(desc)); + device_set_desc_copy(dev, desc); + } + + return (result); +} + +static void * +hdac_channel_init(kobj_t obj, void *data, struct snd_dbuf *b, + struct pcm_channel *c, int dir) +{ + struct hdac_devinfo *devinfo = data; + struct hdac_softc *sc = devinfo->codec->sc; + struct hdac_chan *ch; + + hdac_lock(sc); + if (dir == PCMDIR_PLAY) { + ch = &sc->play; + ch->off = (sc->num_iss + devinfo->function.audio.playcnt) << 5; + devinfo->function.audio.playcnt++; + } else { + ch = &sc->rec; + ch->off = devinfo->function.audio.reccnt << 5; + devinfo->function.audio.reccnt++; + } + if (devinfo->function.audio.quirks & HDA_QUIRK_FIXEDRATE) { + ch->caps.minspeed = ch->caps.maxspeed = 48000; + ch->pcmrates[0] = 48000; + ch->pcmrates[1] = 0; + } + if (sc->pos_dma.dma_vaddr != NULL) + ch->dmapos = (uint32_t *)(sc->pos_dma.dma_vaddr + + (sc->streamcnt * 8)); + else + ch->dmapos = NULL; + ch->sid = ++sc->streamcnt; + ch->dir = dir; + ch->b = b; + ch->c = c; + ch->devinfo = devinfo; + ch->blksz = sc->chan_size / sc->chan_blkcnt; + ch->blkcnt = sc->chan_blkcnt; + hdac_unlock(sc); + + if (hdac_bdl_alloc(ch) != 0) { + ch->blkcnt = 0; + return (NULL); + } + + if (sndbuf_alloc(ch->b, sc->chan_dmat, + (sc->flags & HDAC_F_DMA_NOCACHE) ? HDAC_BUS_DMA_NOCACHE : 0, + sc->chan_size) != 0) + return (NULL); + + HDAC_DMA_ATTR(sc, sndbuf_getbuf(ch->b), sndbuf_getmaxsize(ch->b), + PAT_UNCACHEABLE); + + return (ch); +} + +#ifdef _LPMAP_C_ +static int +hdac_channel_free(kobj_t obj, void *data) +{ + struct hdac_softc *sc; + struct hdac_chan *ch; + + ch = (struct hdac_chan *)data; + sc = (ch != NULL && ch->devinfo != NULL && ch->devinfo->codec != NULL) ? + ch->devinfo->codec->sc : NULL; + if (ch != NULL && sc != NULL) { + HDAC_DMA_ATTR(sc, sndbuf_getbuf(ch->b), + sndbuf_getmaxsize(ch->b), PAT_WRITE_BACK); + } + + return (1); +} +#endif + +static int +hdac_channel_setformat(kobj_t obj, void *data, uint32_t format) +{ + struct hdac_chan *ch = data; + int i; + + for (i = 0; ch->caps.fmtlist[i] != 0; i++) { + if (format == ch->caps.fmtlist[i]) { + ch->fmt = format; + return (0); + } + } + + return (EINVAL); +} + +static int +hdac_channel_setspeed(kobj_t obj, void *data, uint32_t speed) +{ + struct hdac_chan *ch = data; + uint32_t spd = 0, threshold; + int i; + + for (i = 0; ch->pcmrates[i] != 0; i++) { + spd = ch->pcmrates[i]; + threshold = spd + ((ch->pcmrates[i + 1] != 0) ? + ((ch->pcmrates[i + 1] - spd) >> 1) : 0); + if (speed < threshold) + break; + } + + if (spd == 0) /* impossible */ + ch->spd = 48000; + else + ch->spd = spd; + + return (ch->spd); +} + +static void +hdac_stream_setup(struct hdac_chan *ch) +{ + struct hdac_softc *sc = ch->devinfo->codec->sc; + int i; + nid_t cad = ch->devinfo->codec->cad; + uint16_t fmt; + + fmt = 0; + if (ch->fmt & AFMT_S16_LE) + fmt |= ch->bit16 << 4; + else if (ch->fmt & AFMT_S32_LE) + fmt |= ch->bit32 << 4; + else + fmt |= 1 << 4; + + for (i = 0; i < HDA_RATE_TAB_LEN; i++) { + if (hda_rate_tab[i].valid && ch->spd == hda_rate_tab[i].rate) { + fmt |= hda_rate_tab[i].base; + fmt |= hda_rate_tab[i].mul; + fmt |= hda_rate_tab[i].div; + break; + } + } + + if (ch->fmt & AFMT_STEREO) + fmt |= 1; + + HDAC_WRITE_2(&sc->mem, ch->off + HDAC_SDFMT, fmt); + + for (i = 0; ch->io[i] != -1; i++) { + HDA_BOOTVERBOSE( + device_printf(sc->dev, + "HDA_DEBUG: PCMDIR_%s: Stream setup nid=%d " + "fmt=0x%08x\n", + (ch->dir == PCMDIR_PLAY) ? "PLAY" : "REC", + ch->io[i], fmt); + ); + hdac_command(sc, + HDA_CMD_SET_CONV_FMT(cad, ch->io[i], fmt), cad); + hdac_command(sc, + HDA_CMD_SET_CONV_STREAM_CHAN(cad, ch->io[i], + ch->sid << 4), cad); + } +} + +static int +hdac_channel_setfragments(kobj_t obj, void *data, + uint32_t blksz, uint32_t blkcnt) +{ + struct hdac_chan *ch = data; + struct hdac_softc *sc = ch->devinfo->codec->sc; + + blksz &= HDA_BLK_ALIGN; + + if (blksz > (sndbuf_getmaxsize(ch->b) / HDA_BDL_MIN)) + blksz = sndbuf_getmaxsize(ch->b) / HDA_BDL_MIN; + if (blksz < HDA_BLK_MIN) + blksz = HDA_BLK_MIN; + if (blkcnt > HDA_BDL_MAX) + blkcnt = HDA_BDL_MAX; + if (blkcnt < HDA_BDL_MIN) + blkcnt = HDA_BDL_MIN; + + while ((blksz * blkcnt) > sndbuf_getmaxsize(ch->b)) { + if ((blkcnt >> 1) >= HDA_BDL_MIN) + blkcnt >>= 1; + else if ((blksz >> 1) >= HDA_BLK_MIN) + blksz >>= 1; + else + break; + } + + if ((sndbuf_getblksz(ch->b) != blksz || + sndbuf_getblkcnt(ch->b) != blkcnt) && + sndbuf_resize(ch->b, blkcnt, blksz) != 0) + device_printf(sc->dev, "%s: failed blksz=%u blkcnt=%u\n", + __func__, blksz, blkcnt); + + ch->blksz = sndbuf_getblksz(ch->b); + ch->blkcnt = sndbuf_getblkcnt(ch->b); + + return (1); +} + +static int +hdac_channel_setblocksize(kobj_t obj, void *data, uint32_t blksz) +{ + struct hdac_chan *ch = data; + struct hdac_softc *sc = ch->devinfo->codec->sc; + + hdac_channel_setfragments(obj, data, blksz, sc->chan_blkcnt); + + return (ch->blksz); +} + +static void +hdac_channel_stop(struct hdac_softc *sc, struct hdac_chan *ch) +{ + struct hdac_devinfo *devinfo = ch->devinfo; + nid_t cad = devinfo->codec->cad; + int i; + + hdac_stream_stop(ch); + + for (i = 0; ch->io[i] != -1; i++) { + hdac_command(sc, + HDA_CMD_SET_CONV_STREAM_CHAN(cad, ch->io[i], + 0), cad); + } +} + +static void +hdac_channel_start(struct hdac_softc *sc, struct hdac_chan *ch) +{ + ch->ptr = 0; + ch->prevptr = 0; + hdac_stream_stop(ch); + hdac_stream_reset(ch); + hdac_bdl_setup(ch); + hdac_stream_setid(ch); + hdac_stream_setup(ch); + hdac_stream_start(ch); +} + +static int +hdac_channel_trigger(kobj_t obj, void *data, int go) +{ + struct hdac_chan *ch = data; + struct hdac_softc *sc = ch->devinfo->codec->sc; + + if (!PCMTRIG_COMMON(go)) + return (0); + + hdac_lock(sc); + switch (go) { + case PCMTRIG_START: + hdac_channel_start(sc, ch); + break; + case PCMTRIG_STOP: + case PCMTRIG_ABORT: + hdac_channel_stop(sc, ch); + break; + default: + break; + } + hdac_unlock(sc); + + return (0); +} + +static int +hdac_channel_getptr(kobj_t obj, void *data) +{ + struct hdac_chan *ch = data; + struct hdac_softc *sc = ch->devinfo->codec->sc; + uint32_t ptr; + + hdac_lock(sc); + if (sc->polling != 0) + ptr = ch->ptr; + else if (ch->dmapos != NULL) + ptr = *(ch->dmapos); + else + ptr = HDAC_READ_4(&sc->mem, ch->off + HDAC_SDLPIB); + hdac_unlock(sc); + + /* + * Round to available space and force 128 bytes aligment. + */ + ptr %= ch->blksz * ch->blkcnt; + ptr &= HDA_BLK_ALIGN; + + return (ptr); +} + +static struct pcmchan_caps * +hdac_channel_getcaps(kobj_t obj, void *data) +{ + return (&((struct hdac_chan *)data)->caps); +} + +static kobj_method_t hdac_channel_methods[] = { + KOBJMETHOD(channel_init, hdac_channel_init), +#ifdef _LPMAP_C_ + KOBJMETHOD(channel_free, hdac_channel_free), +#endif + KOBJMETHOD(channel_setformat, hdac_channel_setformat), + KOBJMETHOD(channel_setspeed, hdac_channel_setspeed), + KOBJMETHOD(channel_setblocksize, hdac_channel_setblocksize), + KOBJMETHOD(channel_setfragments, hdac_channel_setfragments), + KOBJMETHOD(channel_trigger, hdac_channel_trigger), + KOBJMETHOD(channel_getptr, hdac_channel_getptr), + KOBJMETHOD(channel_getcaps, hdac_channel_getcaps), + { 0, 0 } +}; +CHANNEL_DECLARE(hdac_channel); + +static void +hdac_jack_poll_callback(void *arg) +{ + struct hdac_devinfo *devinfo = arg; + struct hdac_softc *sc; + + if (devinfo == NULL || devinfo->codec == NULL || + devinfo->codec->sc == NULL) + return; + sc = devinfo->codec->sc; + hdac_lock(sc); + if (sc->poll_ival == 0) { + hdac_unlock(sc); + return; + } + hdac_hp_switch_handler(devinfo); + callout_reset(&sc->poll_jack, sc->poll_ival, + hdac_jack_poll_callback, devinfo); + hdac_unlock(sc); +} + +static int +hdac_audio_ctl_ossmixer_init(struct snd_mixer *m) +{ + struct hdac_devinfo *devinfo = mix_getdevinfo(m); + struct hdac_softc *sc = devinfo->codec->sc; + struct hdac_widget *w, *cw; + struct hdac_audio_ctl *ctl; + uint32_t mask, recmask, id; + int i, j, softpcmvol; + nid_t cad; + + hdac_lock(sc); + + mask = 0; + recmask = 0; + + id = hdac_codec_id(devinfo); + cad = devinfo->codec->cad; + for (i = 0; i < HDAC_HP_SWITCH_LEN; i++) { + if (!(HDA_DEV_MATCH(hdac_hp_switch[i].model, + sc->pci_subvendor) && hdac_hp_switch[i].id == id)) + continue; + w = hdac_widget_get(devinfo, hdac_hp_switch[i].hpnid); + if (w == NULL || w->enable == 0 || w->type != + HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) + continue; + if (hdac_hp_switch[i].polling != 0) + callout_reset(&sc->poll_jack, 1, + hdac_jack_poll_callback, devinfo); + else if (HDA_PARAM_AUDIO_WIDGET_CAP_UNSOL_CAP(w->param.widget_cap)) + hdac_command(sc, + HDA_CMD_SET_UNSOLICITED_RESPONSE(cad, w->nid, + HDA_CMD_SET_UNSOLICITED_RESPONSE_ENABLE | + HDAC_UNSOLTAG_EVENT_HP), cad); + else + continue; + hdac_hp_switch_handler(devinfo); + HDA_BOOTVERBOSE( + device_printf(sc->dev, + "HDA_DEBUG: Enabling headphone/speaker " + "audio routing switching:\n"); + device_printf(sc->dev, + "HDA_DEBUG: \tindex=%d nid=%d " + "pci_subvendor=0x%08x " + "codec=0x%08x [%s]\n", + i, w->nid, sc->pci_subvendor, id, + (hdac_hp_switch[i].polling != 0) ? "POLL" : + "UNSOL"); + ); + break; + } + for (i = 0; i < HDAC_EAPD_SWITCH_LEN; i++) { + if (!(HDA_DEV_MATCH(hdac_eapd_switch[i].model, + sc->pci_subvendor) && + hdac_eapd_switch[i].id == id)) + continue; + w = hdac_widget_get(devinfo, hdac_eapd_switch[i].eapdnid); + if (w == NULL || w->enable == 0) + break; + if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX || + w->param.eapdbtl == HDAC_INVALID) + break; + mask |= SOUND_MASK_OGAIN; + break; + } + + for (i = devinfo->startnode; i < devinfo->endnode; i++) { + w = hdac_widget_get(devinfo, i); + if (w == NULL || w->enable == 0) + continue; + mask |= w->ctlflags; + if (!(w->pflags & HDA_ADC_RECSEL)) + continue; + for (j = 0; j < w->nconns; j++) { + cw = hdac_widget_get(devinfo, w->conns[j]); + if (cw == NULL || cw->enable == 0) + continue; + recmask |= cw->ctlflags; + } + } + + if (!(mask & SOUND_MASK_PCM)) { + softpcmvol = 1; + mask |= SOUND_MASK_PCM; + } else + softpcmvol = (devinfo->function.audio.quirks & + HDA_QUIRK_SOFTPCMVOL) ? 1 : 0; + + i = 0; + ctl = NULL; + while ((ctl = hdac_audio_ctl_each(devinfo, &i)) != NULL) { + if (ctl->widget == NULL || ctl->enable == 0) + continue; + if (!(ctl->ossmask & SOUND_MASK_PCM)) + continue; + if (ctl->step > 0) + break; + } + + if (softpcmvol == 1 || ctl == NULL) { + pcm_setflags(sc->dev, pcm_getflags(sc->dev) | SD_F_SOFTPCMVOL); + HDA_BOOTVERBOSE( + device_printf(sc->dev, + "HDA_DEBUG: %s Soft PCM volume\n", + (softpcmvol == 1) ? + "Forcing" : "Enabling"); + ); + i = 0; + /* + * XXX Temporary quirk for STAC9220, until the parser + * become smarter. + */ + if (id == HDA_CODEC_STAC9220) { + mask |= SOUND_MASK_VOLUME; + while ((ctl = hdac_audio_ctl_each(devinfo, &i)) != + NULL) { + if (ctl->widget == NULL || ctl->enable == 0) + continue; + if (ctl->widget->nid == 11 && ctl->index == 0) { + ctl->ossmask = SOUND_MASK_VOLUME; + ctl->ossval = 100 | (100 << 8); + } else + ctl->ossmask &= ~SOUND_MASK_VOLUME; + } + } else if (id == HDA_CODEC_STAC9221) { + mask |= SOUND_MASK_VOLUME; + while ((ctl = hdac_audio_ctl_each(devinfo, &i)) != + NULL) { + if (ctl->widget == NULL) + continue; + if (ctl->widget->type == + HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_OUTPUT && + ctl->index == 0 && (ctl->widget->nid == 2 || + ctl->widget->enable != 0)) { + ctl->enable = 1; + ctl->ossmask = SOUND_MASK_VOLUME; + ctl->ossval = 100 | (100 << 8); + } else if (ctl->enable == 0) + continue; + else + ctl->ossmask &= ~SOUND_MASK_VOLUME; + } + } else { + mix_setparentchild(m, SOUND_MIXER_VOLUME, + SOUND_MASK_PCM); + if (!(mask & SOUND_MASK_VOLUME)) + mix_setrealdev(m, SOUND_MIXER_VOLUME, + SOUND_MIXER_NONE); + while ((ctl = hdac_audio_ctl_each(devinfo, &i)) != + NULL) { + if (ctl->widget == NULL || ctl->enable == 0) + continue; + if (!HDA_FLAG_MATCH(ctl->ossmask, + SOUND_MASK_VOLUME | SOUND_MASK_PCM)) + continue; + if (!(ctl->mute == 1 && ctl->step == 0)) + ctl->enable = 0; + } + } + } + + recmask &= ~(SOUND_MASK_PCM | SOUND_MASK_RECLEV | SOUND_MASK_SPEAKER | + SOUND_MASK_BASS | SOUND_MASK_TREBLE | SOUND_MASK_IGAIN | + SOUND_MASK_OGAIN); + recmask &= (1 << SOUND_MIXER_NRDEVICES) - 1; + mask &= (1 << SOUND_MIXER_NRDEVICES) - 1; + + mix_setrecdevs(m, recmask); + mix_setdevs(m, mask); + + hdac_unlock(sc); + + return (0); +} + +static int +hdac_audio_ctl_ossmixer_set(struct snd_mixer *m, unsigned dev, + unsigned left, unsigned right) +{ + struct hdac_devinfo *devinfo = mix_getdevinfo(m); + struct hdac_softc *sc = devinfo->codec->sc; + struct hdac_widget *w; + struct hdac_audio_ctl *ctl; + uint32_t id, mute; + int lvol, rvol, mlvol, mrvol; + int i = 0; + + hdac_lock(sc); + if (dev == SOUND_MIXER_OGAIN) { + uint32_t orig; + /*if (left != right || !(left == 0 || left == 1)) { + hdac_unlock(sc); + return (-1); + }*/ + id = hdac_codec_id(devinfo); + for (i = 0; i < HDAC_EAPD_SWITCH_LEN; i++) { + if (HDA_DEV_MATCH(hdac_eapd_switch[i].model, + sc->pci_subvendor) && + hdac_eapd_switch[i].id == id) + break; + } + if (i >= HDAC_EAPD_SWITCH_LEN) { + hdac_unlock(sc); + return (-1); + } + w = hdac_widget_get(devinfo, hdac_eapd_switch[i].eapdnid); + if (w == NULL || + w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX || + w->param.eapdbtl == HDAC_INVALID) { + hdac_unlock(sc); + return (-1); + } + orig = w->param.eapdbtl; + if (left == 0) + w->param.eapdbtl &= ~HDA_CMD_SET_EAPD_BTL_ENABLE_EAPD; + else + w->param.eapdbtl |= HDA_CMD_SET_EAPD_BTL_ENABLE_EAPD; + if (orig != w->param.eapdbtl) { + uint32_t val; + + if (hdac_eapd_switch[i].hp_switch != 0) + hdac_hp_switch_handler(devinfo); + val = w->param.eapdbtl; + if (devinfo->function.audio.quirks & HDA_QUIRK_EAPDINV) + val ^= HDA_CMD_SET_EAPD_BTL_ENABLE_EAPD; + hdac_command(sc, + HDA_CMD_SET_EAPD_BTL_ENABLE(devinfo->codec->cad, + w->nid, val), devinfo->codec->cad); + } + hdac_unlock(sc); + return (left | (left << 8)); + } + if (dev == SOUND_MIXER_VOLUME) + devinfo->function.audio.mvol = left | (right << 8); + + mlvol = devinfo->function.audio.mvol & 0x7f; + mrvol = (devinfo->function.audio.mvol >> 8) & 0x7f; + lvol = 0; + rvol = 0; + + i = 0; + while ((ctl = hdac_audio_ctl_each(devinfo, &i)) != NULL) { + if (ctl->widget == NULL || ctl->enable == 0 || + !(ctl->ossmask & (1 << dev))) + continue; + switch (dev) { + case SOUND_MIXER_VOLUME: + lvol = ((ctl->ossval & 0x7f) * left) / 100; + lvol = (lvol * ctl->step) / 100; + rvol = (((ctl->ossval >> 8) & 0x7f) * right) / 100; + rvol = (rvol * ctl->step) / 100; + break; + default: + if (ctl->ossmask & SOUND_MASK_VOLUME) { + lvol = (left * mlvol) / 100; + lvol = (lvol * ctl->step) / 100; + rvol = (right * mrvol) / 100; + rvol = (rvol * ctl->step) / 100; + } else { + lvol = (left * ctl->step) / 100; + rvol = (right * ctl->step) / 100; + } + ctl->ossval = left | (right << 8); + break; + } + mute = 0; + if (ctl->step < 1) { + mute |= (left == 0) ? HDA_AMP_MUTE_LEFT : + (ctl->muted & HDA_AMP_MUTE_LEFT); + mute |= (right == 0) ? HDA_AMP_MUTE_RIGHT : + (ctl->muted & HDA_AMP_MUTE_RIGHT); + } else { + mute |= (lvol == 0) ? HDA_AMP_MUTE_LEFT : + (ctl->muted & HDA_AMP_MUTE_LEFT); + mute |= (rvol == 0) ? HDA_AMP_MUTE_RIGHT : + (ctl->muted & HDA_AMP_MUTE_RIGHT); + } + hdac_audio_ctl_amp_set(ctl, mute, lvol, rvol); + } + hdac_unlock(sc); + + return (left | (right << 8)); +} + +static int +hdac_audio_ctl_ossmixer_setrecsrc(struct snd_mixer *m, uint32_t src) +{ + struct hdac_devinfo *devinfo = mix_getdevinfo(m); + struct hdac_widget *w, *cw; + struct hdac_softc *sc = devinfo->codec->sc; + uint32_t ret = src, target; + int i, j; + + target = 0; + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { + if (src & (1 << i)) { + target = 1 << i; + break; + } + } + + hdac_lock(sc); + + for (i = devinfo->startnode; i < devinfo->endnode; i++) { + w = hdac_widget_get(devinfo, i); + if (w == NULL || w->enable == 0) + continue; + if (!(w->pflags & HDA_ADC_RECSEL)) + continue; + for (j = 0; j < w->nconns; j++) { + cw = hdac_widget_get(devinfo, w->conns[j]); + if (cw == NULL || cw->enable == 0) + continue; + if ((target == SOUND_MASK_VOLUME && + cw->type != + HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER) || + (target != SOUND_MASK_VOLUME && + cw->type == + HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER)) + continue; + if (cw->ctlflags & target) { + if (!(w->pflags & HDA_ADC_LOCKED)) + hdac_widget_connection_select(w, j); + ret = target; + j += w->nconns; + } + } + } + + hdac_unlock(sc); + + return (ret); +} + +static kobj_method_t hdac_audio_ctl_ossmixer_methods[] = { + KOBJMETHOD(mixer_init, hdac_audio_ctl_ossmixer_init), + KOBJMETHOD(mixer_set, hdac_audio_ctl_ossmixer_set), + KOBJMETHOD(mixer_setrecsrc, hdac_audio_ctl_ossmixer_setrecsrc), + { 0, 0 } +}; +MIXER_DECLARE(hdac_audio_ctl_ossmixer); + +static void +hdac_unsolq_task(void *context, int pending) +{ + struct hdac_softc *sc; + + sc = (struct hdac_softc *)context; + + hdac_lock(sc); + hdac_unsolq_flush(sc); + hdac_unlock(sc); +} + +/**************************************************************************** + * int hdac_attach(device_t) + * + * Attach the device into the kernel. Interrupts usually won't be enabled + * when this function is called. Setup everything that doesn't require + * interrupts and defer probing of codecs until interrupts are enabled. + ****************************************************************************/ +static int +hdac_attach(device_t dev) +{ + struct hdac_softc *sc; + int result; + int i; + uint16_t vendor; + uint8_t v; + + sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO); + sc->lock = snd_mtxcreate(device_get_nameunit(dev), HDAC_MTX_NAME); + sc->dev = dev; + sc->pci_subvendor = (uint32_t)pci_get_subdevice(sc->dev) << 16; + sc->pci_subvendor |= (uint32_t)pci_get_subvendor(sc->dev) & 0x0000ffff; + vendor = pci_get_vendor(dev); + + if (sc->pci_subvendor == HP_NX6325_SUBVENDORX) { + /* Screw nx6325 - subdevice/subvendor swapped */ + sc->pci_subvendor = HP_NX6325_SUBVENDOR; + } + + callout_init(&sc->poll_hda, CALLOUT_MPSAFE); + callout_init(&sc->poll_hdac, CALLOUT_MPSAFE); + callout_init(&sc->poll_jack, CALLOUT_MPSAFE); + + TASK_INIT(&sc->unsolq_task, 0, hdac_unsolq_task, sc); + + sc->poll_ticks = 1; + sc->poll_ival = HDAC_POLL_INTERVAL; + if (resource_int_value(device_get_name(dev), + device_get_unit(dev), "polling", &i) == 0 && i != 0) + sc->polling = 1; + else + sc->polling = 0; + + sc->chan_size = pcm_getbuffersize(dev, + HDA_BUFSZ_MIN, HDA_BUFSZ_DEFAULT, HDA_BUFSZ_MAX); + + if (resource_int_value(device_get_name(dev), + device_get_unit(dev), "blocksize", &i) == 0 && i > 0) { + i &= HDA_BLK_ALIGN; + if (i < HDA_BLK_MIN) + i = HDA_BLK_MIN; + sc->chan_blkcnt = sc->chan_size / i; + i = 0; + while (sc->chan_blkcnt >> i) + i++; + sc->chan_blkcnt = 1 << (i - 1); + if (sc->chan_blkcnt < HDA_BDL_MIN) + sc->chan_blkcnt = HDA_BDL_MIN; + else if (sc->chan_blkcnt > HDA_BDL_MAX) + sc->chan_blkcnt = HDA_BDL_MAX; + } else + sc->chan_blkcnt = HDA_BDL_DEFAULT; + + result = bus_dma_tag_create(NULL, /* parent */ + HDAC_DMA_ALIGNMENT, /* alignment */ + 0, /* boundary */ + BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ + BUS_SPACE_MAXADDR, /* highaddr */ + NULL, /* filtfunc */ + NULL, /* fistfuncarg */ + sc->chan_size, /* maxsize */ + 1, /* nsegments */ + sc->chan_size, /* maxsegsz */ + 0, /* flags */ + NULL, /* lockfunc */ + NULL, /* lockfuncarg */ + &sc->chan_dmat); /* dmat */ + if (result != 0) { + device_printf(dev, "%s: bus_dma_tag_create failed (%x)\n", + __func__, result); + snd_mtxfree(sc->lock); + free(sc, M_DEVBUF); + return (ENXIO); + } + + + sc->hdabus = NULL; + for (i = 0; i < HDAC_CODEC_MAX; i++) + sc->codecs[i] = NULL; + + pci_enable_busmaster(dev); + + if (vendor == INTEL_VENDORID) { + /* TCSEL -> TC0 */ + v = pci_read_config(dev, 0x44, 1); + pci_write_config(dev, 0x44, v & 0xf8, 1); + HDA_BOOTVERBOSE( + device_printf(dev, "TCSEL: 0x%02d -> 0x%02d\n", v, + pci_read_config(dev, 0x44, 1)); + ); + } + +#if !defined(_LPMAP_C_) && __FreeBSD_version >= 602106 + if (resource_int_value(device_get_name(dev), + device_get_unit(dev), "msi", &i) == 0 && i != 0 && + pci_msi_count(dev) == 1) + sc->flags |= HDAC_F_MSI; + else +#endif + sc->flags &= ~HDAC_F_MSI; + +#if defined(__i386__) || defined(__amd64__) + sc->flags |= HDAC_F_DMA_NOCACHE; + + if (resource_int_value(device_get_name(dev), + device_get_unit(dev), "snoop", &i) == 0 && i != 0) { +#else + sc->flags &= ~HDAC_F_DMA_NOCACHE; +#endif + /* + * Try to enable PCIe snoop to avoid messing around with + * uncacheable DMA attribute. Since PCIe snoop register + * config is pretty much vendor specific, there are no + * general solutions on how to enable it, forcing us (even + * Microsoft) to enable uncacheable or write combined DMA + * by default. + * + * http://msdn2.microsoft.com/en-us/library/ms790324.aspx + */ + for (i = 0; i < HDAC_PCIESNOOP_LEN; i++) { + if (hdac_pcie_snoop[i].vendor != vendor) + continue; + sc->flags &= ~HDAC_F_DMA_NOCACHE; + if (hdac_pcie_snoop[i].reg == 0x00) + break; + v = pci_read_config(dev, hdac_pcie_snoop[i].reg, 1); + if ((v & hdac_pcie_snoop[i].enable) == + hdac_pcie_snoop[i].enable) + break; + v &= hdac_pcie_snoop[i].mask; + v |= hdac_pcie_snoop[i].enable; + pci_write_config(dev, hdac_pcie_snoop[i].reg, v, 1); + v = pci_read_config(dev, hdac_pcie_snoop[i].reg, 1); + if ((v & hdac_pcie_snoop[i].enable) != + hdac_pcie_snoop[i].enable) { + HDA_BOOTVERBOSE( + device_printf(dev, + "WARNING: Failed to enable PCIe " + "snoop!\n"); + ); +#if defined(__i386__) || defined(__amd64__) + sc->flags |= HDAC_F_DMA_NOCACHE; +#endif + } + break; + } +#if defined(__i386__) || defined(__amd64__) + } +#endif + + HDA_BOOTVERBOSE( + device_printf(dev, "DMA Coherency: %s / vendor=0x%04x\n", + (sc->flags & HDAC_F_DMA_NOCACHE) ? + "Uncacheable" : "PCIe snoop", vendor); + ); + + /* Allocate resources */ + result = hdac_mem_alloc(sc); + if (result != 0) + goto hdac_attach_fail; + result = hdac_irq_alloc(sc); + if (result != 0) + goto hdac_attach_fail; + + /* Get Capabilities */ + result = hdac_get_capabilities(sc); + if (result != 0) + goto hdac_attach_fail; + + /* Allocate CORB and RIRB dma memory */ + result = hdac_dma_alloc(sc, &sc->corb_dma, + sc->corb_size * sizeof(uint32_t)); + if (result != 0) + goto hdac_attach_fail; + result = hdac_dma_alloc(sc, &sc->rirb_dma, + sc->rirb_size * sizeof(struct hdac_rirb)); + if (result != 0) + goto hdac_attach_fail; + + /* Quiesce everything */ + hdac_reset(sc); + + /* Initialize the CORB and RIRB */ + hdac_corb_init(sc); + hdac_rirb_init(sc); + + /* Defer remaining of initialization until interrupts are enabled */ + sc->intrhook.ich_func = hdac_attach2; + sc->intrhook.ich_arg = (void *)sc; + if (cold == 0 || config_intrhook_establish(&sc->intrhook) != 0) { + sc->intrhook.ich_func = NULL; + hdac_attach2((void *)sc); + } + + return (0); + +hdac_attach_fail: + hdac_irq_free(sc); + hdac_dma_free(sc, &sc->rirb_dma); + hdac_dma_free(sc, &sc->corb_dma); + hdac_mem_free(sc); + snd_mtxfree(sc->lock); + free(sc, M_DEVBUF); + + return (ENXIO); +} + +static void +hdac_audio_parse(struct hdac_devinfo *devinfo) +{ + struct hdac_softc *sc = devinfo->codec->sc; + struct hdac_widget *w; + uint32_t res; + int i; + nid_t cad, nid; + + cad = devinfo->codec->cad; + nid = devinfo->nid; + + hdac_command(sc, + HDA_CMD_SET_POWER_STATE(cad, nid, HDA_CMD_POWER_STATE_D0), cad); + + DELAY(100); + + res = hdac_command(sc, + HDA_CMD_GET_PARAMETER(cad , nid, HDA_PARAM_SUB_NODE_COUNT), cad); + + devinfo->nodecnt = HDA_PARAM_SUB_NODE_COUNT_TOTAL(res); + devinfo->startnode = HDA_PARAM_SUB_NODE_COUNT_START(res); + devinfo->endnode = devinfo->startnode + devinfo->nodecnt; + + res = hdac_command(sc, + HDA_CMD_GET_PARAMETER(cad , nid, HDA_PARAM_GPIO_COUNT), cad); + devinfo->function.audio.gpio = res; + + HDA_BOOTVERBOSE( + device_printf(sc->dev, " Vendor: 0x%08x\n", + devinfo->vendor_id); + device_printf(sc->dev, " Device: 0x%08x\n", + devinfo->device_id); + device_printf(sc->dev, " Revision: 0x%08x\n", + devinfo->revision_id); + device_printf(sc->dev, " Stepping: 0x%08x\n", + devinfo->stepping_id); + device_printf(sc->dev, "PCI Subvendor: 0x%08x\n", + sc->pci_subvendor); + device_printf(sc->dev, " Nodes: start=%d " + "endnode=%d total=%d\n", + devinfo->startnode, devinfo->endnode, devinfo->nodecnt); + device_printf(sc->dev, " CORB size: %d\n", sc->corb_size); + device_printf(sc->dev, " RIRB size: %d\n", sc->rirb_size); + device_printf(sc->dev, " Streams: ISS=%d OSS=%d BSS=%d\n", + sc->num_iss, sc->num_oss, sc->num_bss); + device_printf(sc->dev, " GPIO: 0x%08x\n", + devinfo->function.audio.gpio); + device_printf(sc->dev, " NumGPIO=%d NumGPO=%d " + "NumGPI=%d GPIWake=%d GPIUnsol=%d\n", + HDA_PARAM_GPIO_COUNT_NUM_GPIO(devinfo->function.audio.gpio), + HDA_PARAM_GPIO_COUNT_NUM_GPO(devinfo->function.audio.gpio), + HDA_PARAM_GPIO_COUNT_NUM_GPI(devinfo->function.audio.gpio), + HDA_PARAM_GPIO_COUNT_GPI_WAKE(devinfo->function.audio.gpio), + HDA_PARAM_GPIO_COUNT_GPI_UNSOL(devinfo->function.audio.gpio)); + ); + + res = hdac_command(sc, + HDA_CMD_GET_PARAMETER(cad, nid, HDA_PARAM_SUPP_STREAM_FORMATS), + cad); + devinfo->function.audio.supp_stream_formats = res; + + res = hdac_command(sc, + HDA_CMD_GET_PARAMETER(cad, nid, HDA_PARAM_SUPP_PCM_SIZE_RATE), + cad); + devinfo->function.audio.supp_pcm_size_rate = res; + + res = hdac_command(sc, + HDA_CMD_GET_PARAMETER(cad, nid, HDA_PARAM_OUTPUT_AMP_CAP), + cad); + devinfo->function.audio.outamp_cap = res; + + res = hdac_command(sc, + HDA_CMD_GET_PARAMETER(cad, nid, HDA_PARAM_INPUT_AMP_CAP), + cad); + devinfo->function.audio.inamp_cap = res; + + if (devinfo->nodecnt > 0) + devinfo->widget = (struct hdac_widget *)malloc( + sizeof(*(devinfo->widget)) * devinfo->nodecnt, M_HDAC, + M_NOWAIT | M_ZERO); + else + devinfo->widget = NULL; + + if (devinfo->widget == NULL) { + device_printf(sc->dev, "unable to allocate widgets!\n"); + devinfo->endnode = devinfo->startnode; + devinfo->nodecnt = 0; + return; + } + + for (i = devinfo->startnode; i < devinfo->endnode; i++) { + w = hdac_widget_get(devinfo, i); + if (w == NULL) + device_printf(sc->dev, "Ghost widget! nid=%d!\n", i); + else { + w->devinfo = devinfo; + w->nid = i; + w->enable = 1; + w->selconn = -1; + w->pflags = 0; + w->ctlflags = 0; + w->param.eapdbtl = HDAC_INVALID; + hdac_widget_parse(w); + } + } +} + +static void +hdac_audio_ctl_parse(struct hdac_devinfo *devinfo) +{ + struct hdac_softc *sc = devinfo->codec->sc; + struct hdac_audio_ctl *ctls; + struct hdac_widget *w, *cw; + int i, j, cnt, max, ocap, icap; + int mute, offset, step, size; + + /* XXX This is redundant */ + max = 0; + for (i = devinfo->startnode; i < devinfo->endnode; i++) { + w = hdac_widget_get(devinfo, i); + if (w == NULL || w->enable == 0) + continue; + if (w->param.outamp_cap != 0) + max++; + if (w->param.inamp_cap != 0) { + switch (w->type) { + case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_SELECTOR: + case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER: + for (j = 0; j < w->nconns; j++) { + cw = hdac_widget_get(devinfo, + w->conns[j]); + if (cw == NULL || cw->enable == 0) + continue; + max++; + } + break; + default: + max++; + break; + } + } + } + + devinfo->function.audio.ctlcnt = max; + + if (max < 1) + return; + + ctls = (struct hdac_audio_ctl *)malloc( + sizeof(*ctls) * max, M_HDAC, M_ZERO | M_NOWAIT); + + if (ctls == NULL) { + /* Blekh! */ + device_printf(sc->dev, "unable to allocate ctls!\n"); + devinfo->function.audio.ctlcnt = 0; + return; + } + + cnt = 0; + for (i = devinfo->startnode; cnt < max && i < devinfo->endnode; i++) { + if (cnt >= max) { + device_printf(sc->dev, "%s: Ctl overflow!\n", + __func__); + break; + } + w = hdac_widget_get(devinfo, i); + if (w == NULL || w->enable == 0) + continue; + ocap = w->param.outamp_cap; + icap = w->param.inamp_cap; + if (ocap != 0) { + mute = HDA_PARAM_OUTPUT_AMP_CAP_MUTE_CAP(ocap); + step = HDA_PARAM_OUTPUT_AMP_CAP_NUMSTEPS(ocap); + size = HDA_PARAM_OUTPUT_AMP_CAP_STEPSIZE(ocap); + offset = HDA_PARAM_OUTPUT_AMP_CAP_OFFSET(ocap); + /*if (offset > step) { + HDA_BOOTVERBOSE( + device_printf(sc->dev, + "HDA_DEBUG: BUGGY outamp: nid=%d " + "[offset=%d > step=%d]\n", + w->nid, offset, step); + ); + offset = step; + }*/ + ctls[cnt].enable = 1; + ctls[cnt].widget = w; + ctls[cnt].mute = mute; + ctls[cnt].step = step; + ctls[cnt].size = size; + ctls[cnt].offset = offset; + ctls[cnt].left = offset; + ctls[cnt].right = offset; + ctls[cnt++].dir = HDA_CTL_OUT; + } + + if (icap != 0) { + mute = HDA_PARAM_OUTPUT_AMP_CAP_MUTE_CAP(icap); + step = HDA_PARAM_OUTPUT_AMP_CAP_NUMSTEPS(icap); + size = HDA_PARAM_OUTPUT_AMP_CAP_STEPSIZE(icap); + offset = HDA_PARAM_OUTPUT_AMP_CAP_OFFSET(icap); + /*if (offset > step) { + HDA_BOOTVERBOSE( + device_printf(sc->dev, + "HDA_DEBUG: BUGGY inamp: nid=%d " + "[offset=%d > step=%d]\n", + w->nid, offset, step); + ); + offset = step; + }*/ + switch (w->type) { + case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_SELECTOR: + case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER: + for (j = 0; j < w->nconns; j++) { + if (cnt >= max) { + device_printf(sc->dev, + "%s: Ctl overflow!\n", + __func__); + break; + } + cw = hdac_widget_get(devinfo, + w->conns[j]); + if (cw == NULL || cw->enable == 0) + continue; + ctls[cnt].enable = 1; + ctls[cnt].widget = w; + ctls[cnt].childwidget = cw; + ctls[cnt].index = j; + ctls[cnt].mute = mute; + ctls[cnt].step = step; + ctls[cnt].size = size; + ctls[cnt].offset = offset; + ctls[cnt].left = offset; + ctls[cnt].right = offset; + ctls[cnt++].dir = HDA_CTL_IN; + } + break; + default: + if (cnt >= max) { + device_printf(sc->dev, + "%s: Ctl overflow!\n", + __func__); + break; + } + ctls[cnt].enable = 1; + ctls[cnt].widget = w; + ctls[cnt].mute = mute; + ctls[cnt].step = step; + ctls[cnt].size = size; + ctls[cnt].offset = offset; + ctls[cnt].left = offset; + ctls[cnt].right = offset; + ctls[cnt++].dir = HDA_CTL_IN; + break; + } + } + } + + devinfo->function.audio.ctl = ctls; +} + +static const struct { + uint32_t model; + uint32_t id; + uint32_t set, unset; +} hdac_quirks[] = { + /* + * XXX Force stereo quirk. Monoural recording / playback + * on few codecs (especially ALC880) seems broken or + * perhaps unsupported. + */ + { HDA_MATCH_ALL, HDA_MATCH_ALL, + HDA_QUIRK_FORCESTEREO | HDA_QUIRK_IVREF, 0 }, + { ACER_ALL_SUBVENDOR, HDA_MATCH_ALL, + HDA_QUIRK_GPIO0, 0 }, + { ASUS_M5200_SUBVENDOR, HDA_CODEC_ALC880, + HDA_QUIRK_GPIO0, 0 }, + { ASUS_A7M_SUBVENDOR, HDA_CODEC_ALC880, + HDA_QUIRK_GPIO0, 0 }, + { ASUS_A7T_SUBVENDOR, HDA_CODEC_ALC882, + HDA_QUIRK_GPIO0, 0 }, + { ASUS_W2J_SUBVENDOR, HDA_CODEC_ALC882, + HDA_QUIRK_GPIO0, 0 }, + { ASUS_U5F_SUBVENDOR, HDA_CODEC_AD1986A, + HDA_QUIRK_EAPDINV, 0 }, + { ASUS_A8JC_SUBVENDOR, HDA_CODEC_AD1986A, + HDA_QUIRK_EAPDINV, 0 }, + { ASUS_F3JC_SUBVENDOR, HDA_CODEC_ALC861, + HDA_QUIRK_OVREF, 0 }, + { ASUS_W6F_SUBVENDOR, HDA_CODEC_ALC861, + HDA_QUIRK_OVREF, 0 }, + { UNIWILL_9075_SUBVENDOR, HDA_CODEC_ALC861, + HDA_QUIRK_OVREF, 0 }, + /*{ ASUS_M2N_SUBVENDOR, HDA_CODEC_AD1988, + HDA_QUIRK_IVREF80, HDA_QUIRK_IVREF50 | HDA_QUIRK_IVREF100 },*/ + { MEDION_MD95257_SUBVENDOR, HDA_CODEC_ALC880, + HDA_QUIRK_GPIO1, 0 }, + { LENOVO_3KN100_SUBVENDOR, HDA_CODEC_AD1986A, + HDA_QUIRK_EAPDINV, 0 }, + { SAMSUNG_Q1_SUBVENDOR, HDA_CODEC_AD1986A, + HDA_QUIRK_EAPDINV, 0 }, + { APPLE_INTEL_MAC, HDA_CODEC_STAC9221, + HDA_QUIRK_GPIO0 | HDA_QUIRK_GPIO1, 0 }, + { HDA_MATCH_ALL, HDA_CODEC_AD1988, + HDA_QUIRK_IVREF80, HDA_QUIRK_IVREF50 | HDA_QUIRK_IVREF100 }, + { HDA_MATCH_ALL, HDA_CODEC_AD1988B, + HDA_QUIRK_IVREF80, HDA_QUIRK_IVREF50 | HDA_QUIRK_IVREF100 }, + { HDA_MATCH_ALL, HDA_CODEC_CXVENICE, + 0, HDA_QUIRK_FORCESTEREO }, + { HDA_MATCH_ALL, HDA_CODEC_STACXXXX, + HDA_QUIRK_SOFTPCMVOL, 0 } +}; +#define HDAC_QUIRKS_LEN (sizeof(hdac_quirks) / sizeof(hdac_quirks[0])) + +static void +hdac_vendor_patch_parse(struct hdac_devinfo *devinfo) +{ + struct hdac_widget *w; + struct hdac_audio_ctl *ctl; + uint32_t id, subvendor; + int i; + + id = hdac_codec_id(devinfo); + subvendor = devinfo->codec->sc->pci_subvendor; + + /* + * Quirks + */ + for (i = 0; i < HDAC_QUIRKS_LEN; i++) { + if (!(HDA_DEV_MATCH(hdac_quirks[i].model, subvendor) && + HDA_DEV_MATCH(hdac_quirks[i].id, id))) + continue; + if (hdac_quirks[i].set != 0) + devinfo->function.audio.quirks |= + hdac_quirks[i].set; + if (hdac_quirks[i].unset != 0) + devinfo->function.audio.quirks &= + ~(hdac_quirks[i].unset); + } + + switch (id) { + case HDA_CODEC_ALC260: + for (i = devinfo->startnode; i < devinfo->endnode; i++) { + w = hdac_widget_get(devinfo, i); + if (w == NULL || w->enable == 0) + continue; + if (w->type != + HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT) + continue; + if (w->nid != 5) + w->enable = 0; + } + if (subvendor == HP_XW4300_SUBVENDOR) { + ctl = hdac_audio_ctl_amp_get(devinfo, 16, 0, 1); + if (ctl != NULL && ctl->widget != NULL) { + ctl->ossmask = SOUND_MASK_SPEAKER; + ctl->widget->ctlflags |= SOUND_MASK_SPEAKER; + } + ctl = hdac_audio_ctl_amp_get(devinfo, 17, 0, 1); + if (ctl != NULL && ctl->widget != NULL) { + ctl->ossmask = SOUND_MASK_SPEAKER; + ctl->widget->ctlflags |= SOUND_MASK_SPEAKER; + } + } else if (subvendor == HP_3010_SUBVENDOR) { + ctl = hdac_audio_ctl_amp_get(devinfo, 17, 0, 1); + if (ctl != NULL && ctl->widget != NULL) { + ctl->ossmask = SOUND_MASK_SPEAKER; + ctl->widget->ctlflags |= SOUND_MASK_SPEAKER; + } + ctl = hdac_audio_ctl_amp_get(devinfo, 21, 0, 1); + if (ctl != NULL && ctl->widget != NULL) { + ctl->ossmask = SOUND_MASK_SPEAKER; + ctl->widget->ctlflags |= SOUND_MASK_SPEAKER; + } + } + break; + case HDA_CODEC_ALC861: + ctl = hdac_audio_ctl_amp_get(devinfo, 21, 2, 1); + if (ctl != NULL) + ctl->muted = HDA_AMP_MUTE_ALL; + break; + case HDA_CODEC_ALC880: + for (i = devinfo->startnode; i < devinfo->endnode; i++) { + w = hdac_widget_get(devinfo, i); + if (w == NULL || w->enable == 0) + continue; + if (w->type == + HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT && + w->nid != 9 && w->nid != 29) { + w->enable = 0; + } else if (w->type != + HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_BEEP_WIDGET && + w->nid == 29) { + w->type = + HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_BEEP_WIDGET; + w->param.widget_cap &= + ~HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_MASK; + w->param.widget_cap |= + HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_BEEP_WIDGET << + HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_SHIFT; + strlcpy(w->name, "beep widget", sizeof(w->name)); + } + } + break; + case HDA_CODEC_ALC883: + /* + * nid: 24/25 = External (jack) or Internal (fixed) Mic. + * Clear vref cap for jack connectivity. + */ + w = hdac_widget_get(devinfo, 24); + if (w != NULL && w->enable != 0 && w->type == + HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX && + (w->wclass.pin.config & + HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_MASK) == + HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_JACK) + w->wclass.pin.cap &= ~( + HDA_PARAM_PIN_CAP_VREF_CTRL_100_MASK | + HDA_PARAM_PIN_CAP_VREF_CTRL_80_MASK | + HDA_PARAM_PIN_CAP_VREF_CTRL_50_MASK); + w = hdac_widget_get(devinfo, 25); + if (w != NULL && w->enable != 0 && w->type == + HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX && + (w->wclass.pin.config & + HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_MASK) == + HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_JACK) + w->wclass.pin.cap &= ~( + HDA_PARAM_PIN_CAP_VREF_CTRL_100_MASK | + HDA_PARAM_PIN_CAP_VREF_CTRL_80_MASK | + HDA_PARAM_PIN_CAP_VREF_CTRL_50_MASK); + /* + * nid: 26 = Line-in, leave it alone. + */ + break; + case HDA_CODEC_AD1981HD: + w = hdac_widget_get(devinfo, 11); + if (w != NULL && w->enable != 0 && w->nconns > 3) + w->selconn = 3; + if (subvendor == IBM_M52_SUBVENDOR) { + ctl = hdac_audio_ctl_amp_get(devinfo, 7, 0, 1); + if (ctl != NULL) + ctl->ossmask = SOUND_MASK_SPEAKER; + } + break; + case HDA_CODEC_AD1986A: + for (i = devinfo->startnode; i < devinfo->endnode; i++) { + w = hdac_widget_get(devinfo, i); + if (w == NULL || w->enable == 0) + continue; + if (w->type != + HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_OUTPUT) + continue; + if (w->nid != 3) + w->enable = 0; + } + if (subvendor == ASUS_M2NPVMX_SUBVENDOR || + subvendor == ASUS_A8NVMCSM_SUBVENDOR) { + /* nid 28 is mic, nid 29 is line-in */ + w = hdac_widget_get(devinfo, 15); + if (w != NULL) + w->selconn = 2; + w = hdac_widget_get(devinfo, 16); + if (w != NULL) + w->selconn = 1; + } + break; + case HDA_CODEC_AD1988: + case HDA_CODEC_AD1988B: + /*w = hdac_widget_get(devinfo, 12); + if (w != NULL) { + w->selconn = 1; + w->pflags |= HDA_ADC_LOCKED; + } + w = hdac_widget_get(devinfo, 13); + if (w != NULL) { + w->selconn = 4; + w->pflags |= HDA_ADC_LOCKED; + } + w = hdac_widget_get(devinfo, 14); + if (w != NULL) { + w->selconn = 2; + w->pflags |= HDA_ADC_LOCKED; + }*/ + ctl = hdac_audio_ctl_amp_get(devinfo, 57, 0, 1); + if (ctl != NULL) { + ctl->ossmask = SOUND_MASK_IGAIN; + ctl->widget->ctlflags |= SOUND_MASK_IGAIN; + } + ctl = hdac_audio_ctl_amp_get(devinfo, 58, 0, 1); + if (ctl != NULL) { + ctl->ossmask = SOUND_MASK_IGAIN; + ctl->widget->ctlflags |= SOUND_MASK_IGAIN; + } + ctl = hdac_audio_ctl_amp_get(devinfo, 60, 0, 1); + if (ctl != NULL) { + ctl->ossmask = SOUND_MASK_IGAIN; + ctl->widget->ctlflags |= SOUND_MASK_IGAIN; + } + ctl = hdac_audio_ctl_amp_get(devinfo, 32, 0, 1); + if (ctl != NULL) { + ctl->ossmask = SOUND_MASK_MIC | SOUND_MASK_VOLUME; + ctl->widget->ctlflags |= SOUND_MASK_MIC; + } + ctl = hdac_audio_ctl_amp_get(devinfo, 32, 4, 1); + if (ctl != NULL) { + ctl->ossmask = SOUND_MASK_MIC | SOUND_MASK_VOLUME; + ctl->widget->ctlflags |= SOUND_MASK_MIC; + } + ctl = hdac_audio_ctl_amp_get(devinfo, 32, 1, 1); + if (ctl != NULL) { + ctl->ossmask = SOUND_MASK_LINE | SOUND_MASK_VOLUME; + ctl->widget->ctlflags |= SOUND_MASK_LINE; + } + ctl = hdac_audio_ctl_amp_get(devinfo, 32, 7, 1); + if (ctl != NULL) { + ctl->ossmask = SOUND_MASK_SPEAKER | SOUND_MASK_VOLUME; + ctl->widget->ctlflags |= SOUND_MASK_SPEAKER; + } + break; + case HDA_CODEC_STAC9221: + /* + * Dell XPS M1210 need all DACs for each output jacks + */ + if (subvendor == DELL_XPSM1210_SUBVENDOR) + break; + for (i = devinfo->startnode; i < devinfo->endnode; i++) { + w = hdac_widget_get(devinfo, i); + if (w == NULL || w->enable == 0) + continue; + if (w->type != + HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_OUTPUT) + continue; + if (w->nid != 2) + w->enable = 0; + } + break; + case HDA_CODEC_STAC9221D: + for (i = devinfo->startnode; i < devinfo->endnode; i++) { + w = hdac_widget_get(devinfo, i); + if (w == NULL || w->enable == 0) + continue; + if (w->type == + HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT && + w->nid != 6) + w->enable = 0; + + } + break; + case HDA_CODEC_STAC9227: + w = hdac_widget_get(devinfo, 8); + if (w != NULL) + w->enable = 0; + w = hdac_widget_get(devinfo, 9); + if (w != NULL) + w->enable = 0; + break; + case HDA_CODEC_CXWAIKIKI: + if (subvendor == HP_DV5000_SUBVENDOR) { + w = hdac_widget_get(devinfo, 27); + if (w != NULL) + w->enable = 0; + } + ctl = hdac_audio_ctl_amp_get(devinfo, 16, 0, 1); + if (ctl != NULL) + ctl->ossmask = SOUND_MASK_SKIP; + ctl = hdac_audio_ctl_amp_get(devinfo, 25, 0, 1); + if (ctl != NULL && ctl->childwidget != NULL && + ctl->childwidget->enable != 0) { + ctl->ossmask = SOUND_MASK_PCM | SOUND_MASK_VOLUME; + ctl->childwidget->ctlflags |= SOUND_MASK_PCM; + } + ctl = hdac_audio_ctl_amp_get(devinfo, 25, 1, 1); + if (ctl != NULL && ctl->childwidget != NULL && + ctl->childwidget->enable != 0) { + ctl->ossmask = SOUND_MASK_LINE | SOUND_MASK_VOLUME; + ctl->childwidget->ctlflags |= SOUND_MASK_LINE; + } + ctl = hdac_audio_ctl_amp_get(devinfo, 25, 2, 1); + if (ctl != NULL && ctl->childwidget != NULL && + ctl->childwidget->enable != 0) { + ctl->ossmask = SOUND_MASK_MIC | SOUND_MASK_VOLUME; + ctl->childwidget->ctlflags |= SOUND_MASK_MIC; + } + ctl = hdac_audio_ctl_amp_get(devinfo, 26, 0, 1); + if (ctl != NULL) { + ctl->ossmask = SOUND_MASK_SKIP; + /* XXX mixer \=rec mic broken.. why?!? */ + /* ctl->widget->ctlflags |= SOUND_MASK_MIC; */ + } + break; + default: + break; + } +} + +static int +hdac_audio_ctl_ossmixer_getnextdev(struct hdac_devinfo *devinfo) +{ + int *dev = &devinfo->function.audio.ossidx; + + while (*dev < SOUND_MIXER_NRDEVICES) { + switch (*dev) { + case SOUND_MIXER_VOLUME: + case SOUND_MIXER_BASS: + case SOUND_MIXER_TREBLE: + case SOUND_MIXER_PCM: + case SOUND_MIXER_SPEAKER: + case SOUND_MIXER_LINE: + case SOUND_MIXER_MIC: + case SOUND_MIXER_CD: + case SOUND_MIXER_RECLEV: + case SOUND_MIXER_IGAIN: + case SOUND_MIXER_OGAIN: /* reserved for EAPD switch */ + (*dev)++; + break; + default: + return (*dev)++; + break; + } + } + + return (-1); +} + +static int +hdac_widget_find_dac_path(struct hdac_devinfo *devinfo, nid_t nid, int depth) +{ + struct hdac_widget *w; + int i, ret = 0; + + if (depth > HDA_PARSE_MAXDEPTH) + return (0); + w = hdac_widget_get(devinfo, nid); + if (w == NULL || w->enable == 0) + return (0); + switch (w->type) { + case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_OUTPUT: + w->pflags |= HDA_DAC_PATH; + ret = 1; + break; + case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER: + case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_SELECTOR: + for (i = 0; i < w->nconns; i++) { + if (hdac_widget_find_dac_path(devinfo, + w->conns[i], depth + 1) != 0) { + if (w->selconn == -1) + w->selconn = i; + ret = 1; + w->pflags |= HDA_DAC_PATH; + } + } + break; + default: + break; + } + return (ret); +} + +static int +hdac_widget_find_adc_path(struct hdac_devinfo *devinfo, nid_t nid, int depth) +{ + struct hdac_widget *w; + int i, conndev, ret = 0; + + if (depth > HDA_PARSE_MAXDEPTH) + return (0); + w = hdac_widget_get(devinfo, nid); + if (w == NULL || w->enable == 0) + return (0); + switch (w->type) { + case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT: + case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_SELECTOR: + case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER: + for (i = 0; i < w->nconns; i++) { + if (hdac_widget_find_adc_path(devinfo, w->conns[i], + depth + 1) != 0) { + if (w->selconn == -1) + w->selconn = i; + w->pflags |= HDA_ADC_PATH; + ret = 1; + } + } + break; + case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX: + conndev = w->wclass.pin.config & + HDA_CONFIG_DEFAULTCONF_DEVICE_MASK; + if (HDA_PARAM_PIN_CAP_INPUT_CAP(w->wclass.pin.cap) && + (conndev == HDA_CONFIG_DEFAULTCONF_DEVICE_CD || + conndev == HDA_CONFIG_DEFAULTCONF_DEVICE_LINE_IN || + conndev == HDA_CONFIG_DEFAULTCONF_DEVICE_MIC_IN)) { + w->pflags |= HDA_ADC_PATH; + ret = 1; + } + break; + /*case HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER: + if (w->pflags & HDA_DAC_PATH) { + w->pflags |= HDA_ADC_PATH; + ret = 1; + } + break;*/ + default: + break; + } + return (ret); +} + +static uint32_t +hdac_audio_ctl_outamp_build(struct hdac_devinfo *devinfo, + nid_t nid, nid_t pnid, int index, int depth) +{ + struct hdac_widget *w, *pw; + struct hdac_audio_ctl *ctl; + uint32_t fl = 0; + int i, ossdev, conndev, strategy; + + if (depth > HDA_PARSE_MAXDEPTH) + return (0); + + w = hdac_widget_get(devinfo, nid); + if (w == NULL || w->enable == 0) + return (0); + + pw = hdac_widget_get(devinfo, pnid); + strategy = devinfo->function.audio.parsing_strategy; + + if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER + || w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_SELECTOR) { + for (i = 0; i < w->nconns; i++) { + fl |= hdac_audio_ctl_outamp_build(devinfo, w->conns[i], + w->nid, i, depth + 1); + } + w->ctlflags |= fl; + return (fl); + } else if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_OUTPUT && + (w->pflags & HDA_DAC_PATH)) { + i = 0; + while ((ctl = hdac_audio_ctl_each(devinfo, &i)) != NULL) { + if (ctl->enable == 0 || ctl->widget == NULL) + continue; + /* XXX This should be compressed! */ + if (((ctl->widget->nid == w->nid) || + (ctl->widget->nid == pnid && ctl->index == index && + (ctl->dir & HDA_CTL_IN)) || + (ctl->widget->nid == pnid && pw != NULL && + pw->type == + HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_SELECTOR && + (pw->nconns < 2 || pw->selconn == index || + pw->selconn == -1) && + (ctl->dir & HDA_CTL_OUT)) || + (strategy == HDA_PARSE_DIRECT && + ctl->widget->nid == w->nid)) && + !(ctl->ossmask & ~SOUND_MASK_VOLUME)) { + /*if (pw != NULL && pw->selconn == -1) + pw->selconn = index; + fl |= SOUND_MASK_VOLUME; + fl |= SOUND_MASK_PCM; + ctl->ossmask |= SOUND_MASK_VOLUME; + ctl->ossmask |= SOUND_MASK_PCM; + ctl->ossdev = SOUND_MIXER_PCM;*/ + if (!(w->ctlflags & SOUND_MASK_PCM) || + (pw != NULL && + !(pw->ctlflags & SOUND_MASK_PCM))) { + fl |= SOUND_MASK_VOLUME; + fl |= SOUND_MASK_PCM; + ctl->ossmask |= SOUND_MASK_VOLUME; + ctl->ossmask |= SOUND_MASK_PCM; + ctl->ossdev = SOUND_MIXER_PCM; + w->ctlflags |= SOUND_MASK_VOLUME; + w->ctlflags |= SOUND_MASK_PCM; + if (pw != NULL) { + if (pw->selconn == -1) + pw->selconn = index; + pw->ctlflags |= + SOUND_MASK_VOLUME; + pw->ctlflags |= + SOUND_MASK_PCM; + } + } + } + } + w->ctlflags |= fl; + return (fl); + } else if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX && + HDA_PARAM_PIN_CAP_INPUT_CAP(w->wclass.pin.cap) && + (w->pflags & HDA_ADC_PATH)) { + conndev = w->wclass.pin.config & + HDA_CONFIG_DEFAULTCONF_DEVICE_MASK; + i = 0; + while ((ctl = hdac_audio_ctl_each(devinfo, &i)) != NULL) { + if (ctl->enable == 0 || ctl->widget == NULL) + continue; + /* XXX This should be compressed! */ + if (((ctl->widget->nid == pnid && ctl->index == index && + (ctl->dir & HDA_CTL_IN)) || + (ctl->widget->nid == pnid && pw != NULL && + pw->type == + HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_SELECTOR && + (pw->nconns < 2 || pw->selconn == index || + pw->selconn == -1) && + (ctl->dir & HDA_CTL_OUT)) || + (strategy == HDA_PARSE_DIRECT && + ctl->widget->nid == w->nid)) && + !(ctl->ossmask & ~SOUND_MASK_VOLUME)) { + if (pw != NULL && pw->selconn == -1) + pw->selconn = index; + ossdev = 0; + switch (conndev) { + case HDA_CONFIG_DEFAULTCONF_DEVICE_MIC_IN: + ossdev = SOUND_MIXER_MIC; + break; + case HDA_CONFIG_DEFAULTCONF_DEVICE_LINE_IN: + ossdev = SOUND_MIXER_LINE; + break; + case HDA_CONFIG_DEFAULTCONF_DEVICE_CD: + ossdev = SOUND_MIXER_CD; + break; + default: + ossdev = + hdac_audio_ctl_ossmixer_getnextdev( + devinfo); + if (ossdev < 0) + ossdev = 0; + break; + } + if (strategy == HDA_PARSE_MIXER) { + fl |= SOUND_MASK_VOLUME; + ctl->ossmask |= SOUND_MASK_VOLUME; + } + fl |= 1 << ossdev; + ctl->ossmask |= 1 << ossdev; + ctl->ossdev = ossdev; + } + } + w->ctlflags |= fl; + return (fl); + } else if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_BEEP_WIDGET) { + i = 0; + while ((ctl = hdac_audio_ctl_each(devinfo, &i)) != NULL) { + if (ctl->enable == 0 || ctl->widget == NULL) + continue; + /* XXX This should be compressed! */ + if (((ctl->widget->nid == pnid && ctl->index == index && + (ctl->dir & HDA_CTL_IN)) || + (ctl->widget->nid == pnid && pw != NULL && + pw->type == + HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_SELECTOR && + (pw->nconns < 2 || pw->selconn == index || + pw->selconn == -1) && + (ctl->dir & HDA_CTL_OUT)) || + (strategy == HDA_PARSE_DIRECT && + ctl->widget->nid == w->nid)) && + !(ctl->ossmask & ~SOUND_MASK_VOLUME)) { + if (pw != NULL && pw->selconn == -1) + pw->selconn = index; + fl |= SOUND_MASK_VOLUME; + fl |= SOUND_MASK_SPEAKER; + ctl->ossmask |= SOUND_MASK_VOLUME; + ctl->ossmask |= SOUND_MASK_SPEAKER; + ctl->ossdev = SOUND_MIXER_SPEAKER; + } + } + w->ctlflags |= fl; + return (fl); + } + return (0); +} + +static uint32_t +hdac_audio_ctl_inamp_build(struct hdac_devinfo *devinfo, nid_t nid, int depth) +{ + struct hdac_widget *w, *cw; + struct hdac_audio_ctl *ctl; + uint32_t fl; + int i; + + if (depth > HDA_PARSE_MAXDEPTH) + return (0); + + w = hdac_widget_get(devinfo, nid); + if (w == NULL || w->enable == 0) + return (0); + /*if (!(w->pflags & HDA_ADC_PATH)) + return (0); + if (!(w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT || + w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_SELECTOR)) + return (0);*/ + i = 0; + while ((ctl = hdac_audio_ctl_each(devinfo, &i)) != NULL) { + if (ctl->enable == 0 || ctl->widget == NULL) + continue; + if (ctl->widget->nid == nid) { + ctl->ossmask |= SOUND_MASK_RECLEV; + w->ctlflags |= SOUND_MASK_RECLEV; + return (SOUND_MASK_RECLEV); + } + } + for (i = 0; i < w->nconns; i++) { + cw = hdac_widget_get(devinfo, w->conns[i]); + if (cw == NULL || cw->enable == 0) + continue; + if (cw->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_SELECTOR) + continue; + fl = hdac_audio_ctl_inamp_build(devinfo, cw->nid, depth + 1); + if (fl != 0) { + cw->ctlflags |= fl; + w->ctlflags |= fl; + return (fl); + } + } + return (0); +} + +static int +hdac_audio_ctl_recsel_build(struct hdac_devinfo *devinfo, nid_t nid, int depth) +{ + struct hdac_widget *w, *cw; + int i, child = 0; + + if (depth > HDA_PARSE_MAXDEPTH) + return (0); + + w = hdac_widget_get(devinfo, nid); + if (w == NULL || w->enable == 0) + return (0); + /*if (!(w->pflags & HDA_ADC_PATH)) + return (0); + if (!(w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT || + w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_SELECTOR)) + return (0);*/ + /* XXX weak! */ + for (i = 0; i < w->nconns; i++) { + cw = hdac_widget_get(devinfo, w->conns[i]); + if (cw == NULL) + continue; + if (++child > 1) { + w->pflags |= HDA_ADC_RECSEL; + return (1); + } + } + for (i = 0; i < w->nconns; i++) { + if (hdac_audio_ctl_recsel_build(devinfo, + w->conns[i], depth + 1) != 0) + return (1); + } + return (0); +} + +static int +hdac_audio_build_tree_strategy(struct hdac_devinfo *devinfo) +{ + struct hdac_widget *w, *cw; + int i, j, conndev, found_dac = 0; + int strategy; + + strategy = devinfo->function.audio.parsing_strategy; + + for (i = devinfo->startnode; i < devinfo->endnode; i++) { + w = hdac_widget_get(devinfo, i); + if (w == NULL || w->enable == 0) + continue; + if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) + continue; + if (!HDA_PARAM_PIN_CAP_OUTPUT_CAP(w->wclass.pin.cap)) + continue; + conndev = w->wclass.pin.config & + HDA_CONFIG_DEFAULTCONF_DEVICE_MASK; + if (!(conndev == HDA_CONFIG_DEFAULTCONF_DEVICE_HP_OUT || + conndev == HDA_CONFIG_DEFAULTCONF_DEVICE_SPEAKER || + conndev == HDA_CONFIG_DEFAULTCONF_DEVICE_LINE_OUT)) + continue; + for (j = 0; j < w->nconns; j++) { + cw = hdac_widget_get(devinfo, w->conns[j]); + if (cw == NULL || cw->enable == 0) + continue; + if (strategy == HDA_PARSE_MIXER && !(cw->type == + HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER || + cw->type == + HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_SELECTOR)) + continue; + if (hdac_widget_find_dac_path(devinfo, cw->nid, 0) + != 0) { + if (w->selconn == -1) + w->selconn = j; + w->pflags |= HDA_DAC_PATH; + found_dac++; + } + } + } + + return (found_dac); +} + +static void +hdac_audio_build_tree(struct hdac_devinfo *devinfo) +{ + struct hdac_widget *w; + struct hdac_audio_ctl *ctl; + int i, j, dacs, strategy; + + /* Construct DAC path */ + strategy = HDA_PARSE_MIXER; + devinfo->function.audio.parsing_strategy = strategy; + HDA_BOOTVERBOSE( + device_printf(devinfo->codec->sc->dev, + "HDA_DEBUG: HWiP: HDA Widget Parser - Revision %d\n", + HDA_WIDGET_PARSER_REV); + ); + dacs = hdac_audio_build_tree_strategy(devinfo); + if (dacs == 0) { + HDA_BOOTVERBOSE( + device_printf(devinfo->codec->sc->dev, + "HDA_DEBUG: HWiP: 0 DAC path found! " + "Retrying parser " + "using HDA_PARSE_DIRECT strategy.\n"); + ); + strategy = HDA_PARSE_DIRECT; + devinfo->function.audio.parsing_strategy = strategy; + dacs = hdac_audio_build_tree_strategy(devinfo); + } + + HDA_BOOTVERBOSE( + device_printf(devinfo->codec->sc->dev, + "HDA_DEBUG: HWiP: Found %d DAC path using HDA_PARSE_%s " + "strategy.\n", + dacs, (strategy == HDA_PARSE_MIXER) ? "MIXER" : "DIRECT"); + ); + + /* Construct ADC path */ + for (i = devinfo->startnode; i < devinfo->endnode; i++) { + w = hdac_widget_get(devinfo, i); + if (w == NULL || w->enable == 0) + continue; + if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT) + continue; + (void)hdac_widget_find_adc_path(devinfo, w->nid, 0); + } + + /* Output mixers */ + for (i = devinfo->startnode; i < devinfo->endnode; i++) { + w = hdac_widget_get(devinfo, i); + if (w == NULL || w->enable == 0) + continue; + if ((strategy == HDA_PARSE_MIXER && + (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER || + w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_SELECTOR) + && (w->pflags & HDA_DAC_PATH)) || + (strategy == HDA_PARSE_DIRECT && (w->pflags & + (HDA_DAC_PATH | HDA_ADC_PATH)))) { + w->ctlflags |= hdac_audio_ctl_outamp_build(devinfo, + w->nid, devinfo->startnode - 1, 0, 0); + } else if (w->type == + HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_BEEP_WIDGET) { + j = 0; + while ((ctl = hdac_audio_ctl_each(devinfo, &j)) != + NULL) { + if (ctl->enable == 0 || ctl->widget == NULL) + continue; + if (ctl->widget->nid != w->nid) + continue; + ctl->ossmask |= SOUND_MASK_VOLUME; + ctl->ossmask |= SOUND_MASK_SPEAKER; + ctl->ossdev = SOUND_MIXER_SPEAKER; + w->ctlflags |= SOUND_MASK_VOLUME; + w->ctlflags |= SOUND_MASK_SPEAKER; + } + } + } + + /* Input mixers (rec) */ + for (i = devinfo->startnode; i < devinfo->endnode; i++) { + w = hdac_widget_get(devinfo, i); + if (w == NULL || w->enable == 0) + continue; + if (!(w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT && + w->pflags & HDA_ADC_PATH)) + continue; + hdac_audio_ctl_inamp_build(devinfo, w->nid, 0); + hdac_audio_ctl_recsel_build(devinfo, w->nid, 0); + } +} + +#define HDA_COMMIT_CONN (1 << 0) +#define HDA_COMMIT_CTRL (1 << 1) +#define HDA_COMMIT_EAPD (1 << 2) +#define HDA_COMMIT_GPIO (1 << 3) +#define HDA_COMMIT_MISC (1 << 4) +#define HDA_COMMIT_ALL (HDA_COMMIT_CONN | HDA_COMMIT_CTRL | \ + HDA_COMMIT_EAPD | HDA_COMMIT_GPIO | HDA_COMMIT_MISC) + +static void +hdac_audio_commit(struct hdac_devinfo *devinfo, uint32_t cfl) +{ + struct hdac_softc *sc = devinfo->codec->sc; + struct hdac_widget *w; + nid_t cad; + int i; + + if (!(cfl & HDA_COMMIT_ALL)) + return; + + cad = devinfo->codec->cad; + + if ((cfl & HDA_COMMIT_MISC)) { + if (sc->pci_subvendor == APPLE_INTEL_MAC) + hdac_command(sc, HDA_CMD_12BIT(cad, devinfo->nid, + 0x7e7, 0), cad); + } + + if (cfl & HDA_COMMIT_GPIO) { + uint32_t gdata, gmask, gdir; + int commitgpio, numgpio; + + gdata = 0; + gmask = 0; + gdir = 0; + commitgpio = 0; + + numgpio = HDA_PARAM_GPIO_COUNT_NUM_GPIO( + devinfo->function.audio.gpio); + + if (devinfo->function.audio.quirks & HDA_QUIRK_GPIOFLUSH) + commitgpio = (numgpio > 0) ? 1 : 0; + else { + for (i = 0; i < numgpio && i < HDA_GPIO_MAX; i++) { + if (!(devinfo->function.audio.quirks & + (1 << i))) + continue; + if (commitgpio == 0) { + commitgpio = 1; + HDA_BOOTVERBOSE( + gdata = hdac_command(sc, + HDA_CMD_GET_GPIO_DATA(cad, + devinfo->nid), cad); + gmask = hdac_command(sc, + HDA_CMD_GET_GPIO_ENABLE_MASK(cad, + devinfo->nid), cad); + gdir = hdac_command(sc, + HDA_CMD_GET_GPIO_DIRECTION(cad, + devinfo->nid), cad); + device_printf(sc->dev, + "GPIO init: data=0x%08x " + "mask=0x%08x dir=0x%08x\n", + gdata, gmask, gdir); + gdata = 0; + gmask = 0; + gdir = 0; + ); + } + gdata |= 1 << i; + gmask |= 1 << i; + gdir |= 1 << i; + } + } + + if (commitgpio != 0) { + HDA_BOOTVERBOSE( + device_printf(sc->dev, + "GPIO commit: data=0x%08x mask=0x%08x " + "dir=0x%08x\n", + gdata, gmask, gdir); + ); + hdac_command(sc, + HDA_CMD_SET_GPIO_ENABLE_MASK(cad, devinfo->nid, + gmask), cad); + hdac_command(sc, + HDA_CMD_SET_GPIO_DIRECTION(cad, devinfo->nid, + gdir), cad); + hdac_command(sc, + HDA_CMD_SET_GPIO_DATA(cad, devinfo->nid, + gdata), cad); + } + } + + for (i = 0; i < devinfo->nodecnt; i++) { + w = &devinfo->widget[i]; + if (w == NULL || w->enable == 0) + continue; + if (cfl & HDA_COMMIT_CONN) { + if (w->selconn == -1) + w->selconn = 0; + if (w->nconns > 0) + hdac_widget_connection_select(w, w->selconn); + } + if ((cfl & HDA_COMMIT_CTRL) && + w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) { + uint32_t pincap; + + pincap = w->wclass.pin.cap; + + if ((w->pflags & (HDA_DAC_PATH | HDA_ADC_PATH)) == + (HDA_DAC_PATH | HDA_ADC_PATH)) + device_printf(sc->dev, "WARNING: node %d " + "participate both for DAC/ADC!\n", w->nid); + if (w->pflags & HDA_DAC_PATH) { + w->wclass.pin.ctrl &= + ~HDA_CMD_SET_PIN_WIDGET_CTRL_IN_ENABLE; + if ((w->wclass.pin.config & + HDA_CONFIG_DEFAULTCONF_DEVICE_MASK) != + HDA_CONFIG_DEFAULTCONF_DEVICE_HP_OUT) + w->wclass.pin.ctrl &= + ~HDA_CMD_SET_PIN_WIDGET_CTRL_HPHN_ENABLE; + if ((devinfo->function.audio.quirks & HDA_QUIRK_OVREF100) && + HDA_PARAM_PIN_CAP_VREF_CTRL_100(pincap)) + w->wclass.pin.ctrl |= + HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE( + HDA_CMD_PIN_WIDGET_CTRL_VREF_ENABLE_100); + else if ((devinfo->function.audio.quirks & HDA_QUIRK_OVREF80) && + HDA_PARAM_PIN_CAP_VREF_CTRL_80(pincap)) + w->wclass.pin.ctrl |= + HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE( + HDA_CMD_PIN_WIDGET_CTRL_VREF_ENABLE_80); + else if ((devinfo->function.audio.quirks & HDA_QUIRK_OVREF50) && + HDA_PARAM_PIN_CAP_VREF_CTRL_50(pincap)) + w->wclass.pin.ctrl |= + HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE( + HDA_CMD_PIN_WIDGET_CTRL_VREF_ENABLE_50); + } else if (w->pflags & HDA_ADC_PATH) { + w->wclass.pin.ctrl &= + ~(HDA_CMD_SET_PIN_WIDGET_CTRL_OUT_ENABLE | + HDA_CMD_SET_PIN_WIDGET_CTRL_HPHN_ENABLE); + if ((devinfo->function.audio.quirks & HDA_QUIRK_IVREF100) && + HDA_PARAM_PIN_CAP_VREF_CTRL_100(pincap)) + w->wclass.pin.ctrl |= + HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE( + HDA_CMD_PIN_WIDGET_CTRL_VREF_ENABLE_100); + else if ((devinfo->function.audio.quirks & HDA_QUIRK_IVREF80) && + HDA_PARAM_PIN_CAP_VREF_CTRL_80(pincap)) + w->wclass.pin.ctrl |= + HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE( + HDA_CMD_PIN_WIDGET_CTRL_VREF_ENABLE_80); + else if ((devinfo->function.audio.quirks & HDA_QUIRK_IVREF50) && + HDA_PARAM_PIN_CAP_VREF_CTRL_50(pincap)) + w->wclass.pin.ctrl |= + HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE( + HDA_CMD_PIN_WIDGET_CTRL_VREF_ENABLE_50); + } else + w->wclass.pin.ctrl &= ~( + HDA_CMD_SET_PIN_WIDGET_CTRL_HPHN_ENABLE | + HDA_CMD_SET_PIN_WIDGET_CTRL_OUT_ENABLE | + HDA_CMD_SET_PIN_WIDGET_CTRL_IN_ENABLE | + HDA_CMD_SET_PIN_WIDGET_CTRL_VREF_ENABLE_MASK); + hdac_command(sc, + HDA_CMD_SET_PIN_WIDGET_CTRL(cad, w->nid, + w->wclass.pin.ctrl), cad); + } + if ((cfl & HDA_COMMIT_EAPD) && + w->param.eapdbtl != HDAC_INVALID) { + uint32_t val; + + val = w->param.eapdbtl; + if (devinfo->function.audio.quirks & + HDA_QUIRK_EAPDINV) + val ^= HDA_CMD_SET_EAPD_BTL_ENABLE_EAPD; + hdac_command(sc, + HDA_CMD_SET_EAPD_BTL_ENABLE(cad, w->nid, + val), cad); + + } + DELAY(1000); + } +} + +static void +hdac_audio_ctl_commit(struct hdac_devinfo *devinfo) +{ + struct hdac_softc *sc = devinfo->codec->sc; + struct hdac_audio_ctl *ctl; + int i; + + devinfo->function.audio.mvol = 100 | (100 << 8); + i = 0; + while ((ctl = hdac_audio_ctl_each(devinfo, &i)) != NULL) { + if (ctl->enable == 0 || ctl->widget == NULL) { + HDA_BOOTVERBOSE( + device_printf(sc->dev, "[%2d] Ctl nid=%d", + i, (ctl->widget != NULL) ? + ctl->widget->nid : -1); + if (ctl->childwidget != NULL) + printf(" childnid=%d", + ctl->childwidget->nid); + if (ctl->widget == NULL) + printf(" NULL WIDGET!"); + printf(" DISABLED\n"); + ); + continue; + } + HDA_BOOTVERBOSE( + if (ctl->ossmask == 0) { + device_printf(sc->dev, "[%2d] Ctl nid=%d", + i, ctl->widget->nid); + if (ctl->childwidget != NULL) + printf(" childnid=%d", + ctl->childwidget->nid); + printf(" Bind to NONE\n"); + } + ); + if (ctl->step > 0) { + ctl->ossval = (ctl->left * 100) / ctl->step; + ctl->ossval |= ((ctl->right * 100) / ctl->step) << 8; + } else + ctl->ossval = 0; + hdac_audio_ctl_amp_set(ctl, HDA_AMP_MUTE_DEFAULT, + ctl->left, ctl->right); + } +} + +static int +hdac_pcmchannel_setup(struct hdac_devinfo *devinfo, int dir) +{ + struct hdac_chan *ch; + struct hdac_widget *w; + uint32_t cap, fmtcap, pcmcap, path; + int i, type, ret, max; + + if (dir == PCMDIR_PLAY) { + type = HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_OUTPUT; + ch = &devinfo->codec->sc->play; + path = HDA_DAC_PATH; + } else { + type = HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT; + ch = &devinfo->codec->sc->rec; + path = HDA_ADC_PATH; + } + + ch->caps = hdac_caps; + ch->caps.fmtlist = ch->fmtlist; + ch->bit16 = 1; + ch->bit32 = 0; + ch->pcmrates[0] = 48000; + ch->pcmrates[1] = 0; + + ret = 0; + fmtcap = devinfo->function.audio.supp_stream_formats; + pcmcap = devinfo->function.audio.supp_pcm_size_rate; + max = (sizeof(ch->io) / sizeof(ch->io[0])) - 1; + + for (i = devinfo->startnode; i < devinfo->endnode && ret < max; i++) { + w = hdac_widget_get(devinfo, i); + if (w == NULL || w->enable == 0 || w->type != type || + !(w->pflags & path)) + continue; + cap = w->param.widget_cap; + /*if (HDA_PARAM_AUDIO_WIDGET_CAP_DIGITAL(cap)) + continue;*/ + if (!HDA_PARAM_AUDIO_WIDGET_CAP_STEREO(cap)) + continue; + cap = w->param.supp_stream_formats; + /*if (HDA_PARAM_SUPP_STREAM_FORMATS_AC3(cap)) { + } + if (HDA_PARAM_SUPP_STREAM_FORMATS_FLOAT32(cap)) { + }*/ + if (!HDA_PARAM_SUPP_STREAM_FORMATS_PCM(cap)) + continue; + if (ret == 0) { + fmtcap = w->param.supp_stream_formats; + pcmcap = w->param.supp_pcm_size_rate; + } else { + fmtcap &= w->param.supp_stream_formats; + pcmcap &= w->param.supp_pcm_size_rate; + } + ch->io[ret++] = i; + } + ch->io[ret] = -1; + + ch->supp_stream_formats = fmtcap; + ch->supp_pcm_size_rate = pcmcap; + + /* + * 8bit = 0 + * 16bit = 1 + * 20bit = 2 + * 24bit = 3 + * 32bit = 4 + */ + if (ret > 0) { + cap = pcmcap; + if (HDA_PARAM_SUPP_PCM_SIZE_RATE_16BIT(cap)) + ch->bit16 = 1; + else if (HDA_PARAM_SUPP_PCM_SIZE_RATE_8BIT(cap)) + ch->bit16 = 0; + if (HDA_PARAM_SUPP_PCM_SIZE_RATE_32BIT(cap)) + ch->bit32 = 4; + else if (HDA_PARAM_SUPP_PCM_SIZE_RATE_24BIT(cap)) + ch->bit32 = 3; + else if (HDA_PARAM_SUPP_PCM_SIZE_RATE_20BIT(cap)) + ch->bit32 = 2; + i = 0; + if (!(devinfo->function.audio.quirks & HDA_QUIRK_FORCESTEREO)) + ch->fmtlist[i++] = AFMT_S16_LE; + ch->fmtlist[i++] = AFMT_S16_LE | AFMT_STEREO; + if (ch->bit32 > 0) { + if (!(devinfo->function.audio.quirks & + HDA_QUIRK_FORCESTEREO)) + ch->fmtlist[i++] = AFMT_S32_LE; + ch->fmtlist[i++] = AFMT_S32_LE | AFMT_STEREO; + } + ch->fmtlist[i] = 0; + i = 0; + if (HDA_PARAM_SUPP_PCM_SIZE_RATE_8KHZ(cap)) + ch->pcmrates[i++] = 8000; + if (HDA_PARAM_SUPP_PCM_SIZE_RATE_11KHZ(cap)) + ch->pcmrates[i++] = 11025; + if (HDA_PARAM_SUPP_PCM_SIZE_RATE_16KHZ(cap)) + ch->pcmrates[i++] = 16000; + if (HDA_PARAM_SUPP_PCM_SIZE_RATE_22KHZ(cap)) + ch->pcmrates[i++] = 22050; + if (HDA_PARAM_SUPP_PCM_SIZE_RATE_32KHZ(cap)) + ch->pcmrates[i++] = 32000; + if (HDA_PARAM_SUPP_PCM_SIZE_RATE_44KHZ(cap)) + ch->pcmrates[i++] = 44100; + /* if (HDA_PARAM_SUPP_PCM_SIZE_RATE_48KHZ(cap)) */ + ch->pcmrates[i++] = 48000; + if (HDA_PARAM_SUPP_PCM_SIZE_RATE_88KHZ(cap)) + ch->pcmrates[i++] = 88200; + if (HDA_PARAM_SUPP_PCM_SIZE_RATE_96KHZ(cap)) + ch->pcmrates[i++] = 96000; + if (HDA_PARAM_SUPP_PCM_SIZE_RATE_176KHZ(cap)) + ch->pcmrates[i++] = 176400; + if (HDA_PARAM_SUPP_PCM_SIZE_RATE_192KHZ(cap)) + ch->pcmrates[i++] = 192000; + /* if (HDA_PARAM_SUPP_PCM_SIZE_RATE_384KHZ(cap)) */ + ch->pcmrates[i] = 0; + if (i > 0) { + ch->caps.minspeed = ch->pcmrates[0]; + ch->caps.maxspeed = ch->pcmrates[i - 1]; + } + } + + return (ret); +} + +static void +hdac_dump_ctls(struct hdac_devinfo *devinfo, const char *banner, uint32_t flag) +{ + struct hdac_audio_ctl *ctl; + struct hdac_softc *sc = devinfo->codec->sc; + int i; + uint32_t fl = 0; + + + if (flag == 0) { + fl = SOUND_MASK_VOLUME | SOUND_MASK_PCM | + SOUND_MASK_CD | SOUND_MASK_LINE | SOUND_MASK_RECLEV | + SOUND_MASK_MIC | SOUND_MASK_SPEAKER | SOUND_MASK_OGAIN; + } + + i = 0; + while ((ctl = hdac_audio_ctl_each(devinfo, &i)) != NULL) { + if (ctl->enable == 0 || ctl->widget == NULL || + ctl->widget->enable == 0 || (ctl->ossmask & + (SOUND_MASK_SKIP | SOUND_MASK_DISABLE))) + continue; + if ((flag == 0 && (ctl->ossmask & ~fl)) || + (flag != 0 && (ctl->ossmask & flag))) { + if (banner != NULL) { + device_printf(sc->dev, "\n"); + device_printf(sc->dev, "%s\n", banner); + } + goto hdac_ctl_dump_it_all; + } + } + + return; + +hdac_ctl_dump_it_all: + i = 0; + while ((ctl = hdac_audio_ctl_each(devinfo, &i)) != NULL) { + if (ctl->enable == 0 || ctl->widget == NULL || + ctl->widget->enable == 0) + continue; + if (!((flag == 0 && (ctl->ossmask & ~fl)) || + (flag != 0 && (ctl->ossmask & flag)))) + continue; + if (flag == 0) { + device_printf(sc->dev, "\n"); + device_printf(sc->dev, "Unknown Ctl (OSS: %s)\n", + hdac_audio_ctl_ossmixer_mask2name(ctl->ossmask)); + } + device_printf(sc->dev, " |\n"); + device_printf(sc->dev, " +- nid: %2d index: %2d ", + ctl->widget->nid, ctl->index); + if (ctl->childwidget != NULL) + printf("(nid: %2d) ", ctl->childwidget->nid); + else + printf(" "); + printf("mute: %d step: %3d size: %3d off: %3d dir=0x%x ossmask=0x%08x\n", + ctl->mute, ctl->step, ctl->size, ctl->offset, ctl->dir, + ctl->ossmask); + } +} + +static void +hdac_dump_audio_formats(struct hdac_softc *sc, uint32_t fcap, uint32_t pcmcap) +{ + uint32_t cap; + + cap = fcap; + if (cap != 0) { + device_printf(sc->dev, " Stream cap: 0x%08x\n", cap); + device_printf(sc->dev, " Format:"); + if (HDA_PARAM_SUPP_STREAM_FORMATS_AC3(cap)) + printf(" AC3"); + if (HDA_PARAM_SUPP_STREAM_FORMATS_FLOAT32(cap)) + printf(" FLOAT32"); + if (HDA_PARAM_SUPP_STREAM_FORMATS_PCM(cap)) + printf(" PCM"); + printf("\n"); + } + cap = pcmcap; + if (cap != 0) { + device_printf(sc->dev, " PCM cap: 0x%08x\n", cap); + device_printf(sc->dev, " PCM size:"); + if (HDA_PARAM_SUPP_PCM_SIZE_RATE_8BIT(cap)) + printf(" 8"); + if (HDA_PARAM_SUPP_PCM_SIZE_RATE_16BIT(cap)) + printf(" 16"); + if (HDA_PARAM_SUPP_PCM_SIZE_RATE_20BIT(cap)) + printf(" 20"); + if (HDA_PARAM_SUPP_PCM_SIZE_RATE_24BIT(cap)) + printf(" 24"); + if (HDA_PARAM_SUPP_PCM_SIZE_RATE_32BIT(cap)) + printf(" 32"); + printf("\n"); + device_printf(sc->dev, " PCM rate:"); + if (HDA_PARAM_SUPP_PCM_SIZE_RATE_8KHZ(cap)) + printf(" 8"); + if (HDA_PARAM_SUPP_PCM_SIZE_RATE_11KHZ(cap)) + printf(" 11"); + if (HDA_PARAM_SUPP_PCM_SIZE_RATE_16KHZ(cap)) + printf(" 16"); + if (HDA_PARAM_SUPP_PCM_SIZE_RATE_22KHZ(cap)) + printf(" 22"); + if (HDA_PARAM_SUPP_PCM_SIZE_RATE_32KHZ(cap)) + printf(" 32"); + if (HDA_PARAM_SUPP_PCM_SIZE_RATE_44KHZ(cap)) + printf(" 44"); + printf(" 48"); + if (HDA_PARAM_SUPP_PCM_SIZE_RATE_88KHZ(cap)) + printf(" 88"); + if (HDA_PARAM_SUPP_PCM_SIZE_RATE_96KHZ(cap)) + printf(" 96"); + if (HDA_PARAM_SUPP_PCM_SIZE_RATE_176KHZ(cap)) + printf(" 176"); + if (HDA_PARAM_SUPP_PCM_SIZE_RATE_192KHZ(cap)) + printf(" 192"); + printf("\n"); + } +} + +static void +hdac_dump_pin(struct hdac_softc *sc, struct hdac_widget *w) +{ + uint32_t pincap, wcap; + + pincap = w->wclass.pin.cap; + wcap = w->param.widget_cap; + + device_printf(sc->dev, " Pin cap: 0x%08x\n", pincap); + device_printf(sc->dev, " "); + if (HDA_PARAM_PIN_CAP_IMP_SENSE_CAP(pincap)) + printf(" ISC"); + if (HDA_PARAM_PIN_CAP_TRIGGER_REQD(pincap)) + printf(" TRQD"); + if (HDA_PARAM_PIN_CAP_PRESENCE_DETECT_CAP(pincap)) + printf(" PDC"); + if (HDA_PARAM_PIN_CAP_HEADPHONE_CAP(pincap)) + printf(" HP"); + if (HDA_PARAM_PIN_CAP_OUTPUT_CAP(pincap)) + printf(" OUT"); + if (HDA_PARAM_PIN_CAP_INPUT_CAP(pincap)) + printf(" IN"); + if (HDA_PARAM_PIN_CAP_BALANCED_IO_PINS(pincap)) + printf(" BAL"); + if (HDA_PARAM_PIN_CAP_VREF_CTRL(pincap)) { + printf(" VREF["); + if (HDA_PARAM_PIN_CAP_VREF_CTRL_50(pincap)) + printf(" 50"); + if (HDA_PARAM_PIN_CAP_VREF_CTRL_80(pincap)) + printf(" 80"); + if (HDA_PARAM_PIN_CAP_VREF_CTRL_100(pincap)) + printf(" 100"); + if (HDA_PARAM_PIN_CAP_VREF_CTRL_GROUND(pincap)) + printf(" GROUND"); + if (HDA_PARAM_PIN_CAP_VREF_CTRL_HIZ(pincap)) + printf(" HIZ"); + printf(" ]"); + } + if (HDA_PARAM_PIN_CAP_EAPD_CAP(pincap)) + printf(" EAPD"); + if (HDA_PARAM_AUDIO_WIDGET_CAP_UNSOL_CAP(wcap)) + printf(" : UNSOL"); + printf("\n"); + device_printf(sc->dev, " Pin config: 0x%08x\n", + w->wclass.pin.config); + device_printf(sc->dev, " Pin control: 0x%08x", w->wclass.pin.ctrl); + if (w->wclass.pin.ctrl & HDA_CMD_SET_PIN_WIDGET_CTRL_HPHN_ENABLE) + printf(" HP"); + if (w->wclass.pin.ctrl & HDA_CMD_SET_PIN_WIDGET_CTRL_IN_ENABLE) + printf(" IN"); + if (w->wclass.pin.ctrl & HDA_CMD_SET_PIN_WIDGET_CTRL_OUT_ENABLE) + printf(" OUT"); + printf("\n"); +} + +static void +hdac_dump_amp(struct hdac_softc *sc, uint32_t cap, char *banner) +{ + device_printf(sc->dev, " %s amp: 0x%08x\n", banner, cap); + device_printf(sc->dev, " " + "mute=%d step=%d size=%d offset=%d\n", + HDA_PARAM_OUTPUT_AMP_CAP_MUTE_CAP(cap), + HDA_PARAM_OUTPUT_AMP_CAP_NUMSTEPS(cap), + HDA_PARAM_OUTPUT_AMP_CAP_STEPSIZE(cap), + HDA_PARAM_OUTPUT_AMP_CAP_OFFSET(cap)); +} + +static void +hdac_dump_nodes(struct hdac_devinfo *devinfo) +{ + struct hdac_softc *sc = devinfo->codec->sc; + struct hdac_widget *w, *cw; + int i, j; + + device_printf(sc->dev, "\n"); + device_printf(sc->dev, "Default Parameter\n"); + device_printf(sc->dev, "-----------------\n"); + hdac_dump_audio_formats(sc, + devinfo->function.audio.supp_stream_formats, + devinfo->function.audio.supp_pcm_size_rate); + device_printf(sc->dev, " IN amp: 0x%08x\n", + devinfo->function.audio.inamp_cap); + device_printf(sc->dev, " OUT amp: 0x%08x\n", + devinfo->function.audio.outamp_cap); + for (i = devinfo->startnode; i < devinfo->endnode; i++) { + w = hdac_widget_get(devinfo, i); + if (w == NULL) { + device_printf(sc->dev, "Ghost widget nid=%d\n", i); + continue; + } + device_printf(sc->dev, "\n"); + device_printf(sc->dev, " nid: %d [%s]%s\n", w->nid, + HDA_PARAM_AUDIO_WIDGET_CAP_DIGITAL(w->param.widget_cap) ? + "DIGITAL" : "ANALOG", + (w->enable == 0) ? " [DISABLED]" : ""); + device_printf(sc->dev, " name: %s\n", w->name); + device_printf(sc->dev, " widget_cap: 0x%08x\n", + w->param.widget_cap); + device_printf(sc->dev, " Parse flags: 0x%08x\n", + w->pflags); + device_printf(sc->dev, " Ctl flags: 0x%08x\n", + w->ctlflags); + if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_OUTPUT || + w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_INPUT) { + hdac_dump_audio_formats(sc, + w->param.supp_stream_formats, + w->param.supp_pcm_size_rate); + } else if (w->type == + HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) + hdac_dump_pin(sc, w); + if (w->param.eapdbtl != HDAC_INVALID) + device_printf(sc->dev, " EAPD: 0x%08x\n", + w->param.eapdbtl); + if (HDA_PARAM_AUDIO_WIDGET_CAP_OUT_AMP(w->param.widget_cap) && + w->param.outamp_cap != 0) + hdac_dump_amp(sc, w->param.outamp_cap, "Output"); + if (HDA_PARAM_AUDIO_WIDGET_CAP_IN_AMP(w->param.widget_cap) && + w->param.inamp_cap != 0) + hdac_dump_amp(sc, w->param.inamp_cap, " Input"); + device_printf(sc->dev, " connections: %d\n", w->nconns); + for (j = 0; j < w->nconns; j++) { + cw = hdac_widget_get(devinfo, w->conns[j]); + device_printf(sc->dev, " |\n"); + device_printf(sc->dev, " + <- nid=%d [%s]", + w->conns[j], (cw == NULL) ? "GHOST!" : cw->name); + if (cw == NULL) + printf(" [UNKNOWN]"); + else if (cw->enable == 0) + printf(" [DISABLED]"); + if (w->nconns > 1 && w->selconn == j && w->type != + HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER) + printf(" (selected)"); + printf("\n"); + } + } + +} + +static int +hdac_dump_dac_internal(struct hdac_devinfo *devinfo, nid_t nid, int depth) +{ + struct hdac_widget *w, *cw; + struct hdac_softc *sc = devinfo->codec->sc; + int i; + + if (depth > HDA_PARSE_MAXDEPTH) + return (0); + + w = hdac_widget_get(devinfo, nid); + if (w == NULL || w->enable == 0 || !(w->pflags & HDA_DAC_PATH)) + return (0); + + if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) { + device_printf(sc->dev, "\n"); + device_printf(sc->dev, " nid=%d [%s]\n", w->nid, w->name); + device_printf(sc->dev, " ^\n"); + device_printf(sc->dev, " |\n"); + device_printf(sc->dev, " +-----<------+\n"); + } else { + device_printf(sc->dev, " ^\n"); + device_printf(sc->dev, " |\n"); + device_printf(sc->dev, " "); + printf(" nid=%d [%s]\n", w->nid, w->name); + } + + if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_OUTPUT) { + return (1); + } else if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_MIXER) { + for (i = 0; i < w->nconns; i++) { + cw = hdac_widget_get(devinfo, w->conns[i]); + if (cw == NULL || cw->enable == 0 || cw->type == + HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) + continue; + if (hdac_dump_dac_internal(devinfo, cw->nid, + depth + 1) != 0) + return (1); + } + } else if ((w->type == + HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_AUDIO_SELECTOR || + w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) && + w->selconn > -1 && w->selconn < w->nconns) { + if (hdac_dump_dac_internal(devinfo, w->conns[w->selconn], + depth + 1) != 0) + return (1); + } + + return (0); +} + +static void +hdac_dump_dac(struct hdac_devinfo *devinfo) +{ + struct hdac_widget *w; + struct hdac_softc *sc = devinfo->codec->sc; + int i, printed = 0; + + for (i = devinfo->startnode; i < devinfo->endnode; i++) { + w = hdac_widget_get(devinfo, i); + if (w == NULL || w->enable == 0) + continue; + if (w->type != HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX || + !(w->pflags & HDA_DAC_PATH)) + continue; + if (printed == 0) { + printed = 1; + device_printf(sc->dev, "\n"); + device_printf(sc->dev, "Playback path:\n"); + } + hdac_dump_dac_internal(devinfo, w->nid, 0); + } +} + +static void +hdac_dump_adc(struct hdac_devinfo *devinfo) +{ + struct hdac_widget *w, *cw; + struct hdac_softc *sc = devinfo->codec->sc; + int i, j; + int printed = 0; + char ossdevs[256]; + + for (i = devinfo->startnode; i < devinfo->endnode; i++) { + w = hdac_widget_get(devinfo, i); + if (w == NULL || w->enable == 0) + continue; + if (!(w->pflags & HDA_ADC_RECSEL)) + continue; + if (printed == 0) { + printed = 1; + device_printf(sc->dev, "\n"); + device_printf(sc->dev, "Recording sources:\n"); + } + device_printf(sc->dev, "\n"); + device_printf(sc->dev, " nid=%d [%s]\n", w->nid, w->name); + for (j = 0; j < w->nconns; j++) { + cw = hdac_widget_get(devinfo, w->conns[j]); + if (cw == NULL || cw->enable == 0) + continue; + hdac_audio_ctl_ossmixer_mask2allname(cw->ctlflags, + ossdevs, sizeof(ossdevs)); + device_printf(sc->dev, " |\n"); + device_printf(sc->dev, " + <- nid=%d [%s]", + cw->nid, cw->name); + if (strlen(ossdevs) > 0) { + printf(" [recsrc: %s]", ossdevs); + } + printf("\n"); + } + } +} + +static void +hdac_dump_pcmchannels(struct hdac_softc *sc, int pcnt, int rcnt) +{ + nid_t *nids; + + if (pcnt > 0) { + device_printf(sc->dev, "\n"); + device_printf(sc->dev, " PCM Playback: %d\n", pcnt); + hdac_dump_audio_formats(sc, sc->play.supp_stream_formats, + sc->play.supp_pcm_size_rate); + device_printf(sc->dev, " DAC:"); + for (nids = sc->play.io; *nids != -1; nids++) + printf(" %d", *nids); + printf("\n"); + } + + if (rcnt > 0) { + device_printf(sc->dev, "\n"); + device_printf(sc->dev, " PCM Record: %d\n", rcnt); + hdac_dump_audio_formats(sc, sc->play.supp_stream_formats, + sc->rec.supp_pcm_size_rate); + device_printf(sc->dev, " ADC:"); + for (nids = sc->rec.io; *nids != -1; nids++) + printf(" %d", *nids); + printf("\n"); + } +} + +static void +hdac_release_resources(struct hdac_softc *sc) +{ + struct hdac_devinfo *devinfo = NULL; + device_t *devlist = NULL; + int i, devcount; + + if (sc == NULL) + return; + + hdac_lock(sc); + sc->polling = 0; + sc->poll_ival = 0; + callout_stop(&sc->poll_hda); + callout_stop(&sc->poll_hdac); + callout_stop(&sc->poll_jack); + hdac_reset(sc); + hdac_unlock(sc); + taskqueue_drain(taskqueue_thread, &sc->unsolq_task); + callout_drain(&sc->poll_hda); + callout_drain(&sc->poll_hdac); + callout_drain(&sc->poll_jack); + + hdac_irq_free(sc); + + device_get_children(sc->dev, &devlist, &devcount); + for (i = 0; devlist != NULL && i < devcount; i++) { + devinfo = (struct hdac_devinfo *)device_get_ivars(devlist[i]); + if (devinfo == NULL) + continue; + if (devinfo->widget != NULL) + free(devinfo->widget, M_HDAC); + if (devinfo->node_type == + HDA_PARAM_FCT_GRP_TYPE_NODE_TYPE_AUDIO && + devinfo->function.audio.ctl != NULL) + free(devinfo->function.audio.ctl, M_HDAC); + free(devinfo, M_HDAC); + device_delete_child(sc->dev, devlist[i]); + } + if (devlist != NULL) + free(devlist, M_TEMP); + + for (i = 0; i < HDAC_CODEC_MAX; i++) { + if (sc->codecs[i] != NULL) + free(sc->codecs[i], M_HDAC); + sc->codecs[i] = NULL; + } + + hdac_dma_free(sc, &sc->pos_dma); + hdac_dma_free(sc, &sc->rirb_dma); + hdac_dma_free(sc, &sc->corb_dma); + if (sc->play.blkcnt > 0) + hdac_dma_free(sc, &sc->play.bdl_dma); + if (sc->rec.blkcnt > 0) + hdac_dma_free(sc, &sc->rec.bdl_dma); + if (sc->chan_dmat != NULL) { + bus_dma_tag_destroy(sc->chan_dmat); + sc->chan_dmat = NULL; + } + hdac_mem_free(sc); + snd_mtxfree(sc->lock); + free(sc, M_DEVBUF); +} + +/* This function surely going to make its way into upper level someday. */ +static void +hdac_config_fetch(struct hdac_softc *sc, uint32_t *on, uint32_t *off) +{ + const char *res = NULL; + int i = 0, j, k, len, inv; + + if (on != NULL) + *on = 0; + if (off != NULL) + *off = 0; + if (sc == NULL) + return; + if (resource_string_value(device_get_name(sc->dev), + device_get_unit(sc->dev), "config", &res) != 0) + return; + if (!(res != NULL && strlen(res) > 0)) + return; + HDA_BOOTVERBOSE( + device_printf(sc->dev, "HDA_DEBUG: HDA Config:"); + ); + for (;;) { + while (res[i] != '\0' && + (res[i] == ',' || isspace(res[i]) != 0)) + i++; + if (res[i] == '\0') { + HDA_BOOTVERBOSE( + printf("\n"); + ); + return; + } + j = i; + while (res[j] != '\0' && + !(res[j] == ',' || isspace(res[j]) != 0)) + j++; + len = j - i; + if (len > 2 && strncmp(res + i, "no", 2) == 0) + inv = 2; + else + inv = 0; + for (k = 0; len > inv && k < HDAC_QUIRKS_TAB_LEN; k++) { + if (strncmp(res + i + inv, + hdac_quirks_tab[k].key, len - inv) != 0) + continue; + if (len - inv != strlen(hdac_quirks_tab[k].key)) + break; + HDA_BOOTVERBOSE( + printf(" %s%s", (inv != 0) ? "no" : "", + hdac_quirks_tab[k].key); + ); + if (inv == 0 && on != NULL) + *on |= hdac_quirks_tab[k].value; + else if (inv != 0 && off != NULL) + *off |= hdac_quirks_tab[k].value; + break; + } + i = j; + } +} + +#ifdef SND_DYNSYSCTL +static int +sysctl_hdac_polling(SYSCTL_HANDLER_ARGS) +{ + struct hdac_softc *sc; + struct hdac_devinfo *devinfo; + device_t dev; + uint32_t ctl; + int err, val; + + dev = oidp->oid_arg1; + devinfo = pcm_getdevinfo(dev); + if (devinfo == NULL || devinfo->codec == NULL || + devinfo->codec->sc == NULL) + return (EINVAL); + sc = devinfo->codec->sc; + hdac_lock(sc); + val = sc->polling; + hdac_unlock(sc); + err = sysctl_handle_int(oidp, &val, 0, req); + + if (err != 0 || req->newptr == NULL) + return (err); + if (val < 0 || val > 1) + return (EINVAL); + + hdac_lock(sc); + if (val != sc->polling) { + if (hda_chan_active(sc) != 0) + err = EBUSY; + else if (val == 0) { + callout_stop(&sc->poll_hdac); + hdac_unlock(sc); + callout_drain(&sc->poll_hdac); + hdac_lock(sc); + HDAC_WRITE_2(&sc->mem, HDAC_RINTCNT, + sc->rirb_size / 2); + ctl = HDAC_READ_1(&sc->mem, HDAC_RIRBCTL); + ctl |= HDAC_RIRBCTL_RINTCTL; + HDAC_WRITE_1(&sc->mem, HDAC_RIRBCTL, ctl); + HDAC_WRITE_4(&sc->mem, HDAC_INTCTL, + HDAC_INTCTL_CIE | HDAC_INTCTL_GIE); + sc->polling = 0; + DELAY(1000); + } else { + HDAC_WRITE_4(&sc->mem, HDAC_INTCTL, 0); + HDAC_WRITE_2(&sc->mem, HDAC_RINTCNT, 0); + ctl = HDAC_READ_1(&sc->mem, HDAC_RIRBCTL); + ctl &= ~HDAC_RIRBCTL_RINTCTL; + HDAC_WRITE_1(&sc->mem, HDAC_RIRBCTL, ctl); + hdac_unlock(sc); + taskqueue_drain(taskqueue_thread, &sc->unsolq_task); + hdac_lock(sc); + callout_reset(&sc->poll_hdac, 1, hdac_poll_callback, + sc); + sc->polling = 1; + DELAY(1000); + } + } + hdac_unlock(sc); + + return (err); +} + +static int +sysctl_hdac_polling_interval(SYSCTL_HANDLER_ARGS) +{ + struct hdac_softc *sc; + struct hdac_devinfo *devinfo; + device_t dev; + int err, val; + + dev = oidp->oid_arg1; + devinfo = pcm_getdevinfo(dev); + if (devinfo == NULL || devinfo->codec == NULL || + devinfo->codec->sc == NULL) + return (EINVAL); + sc = devinfo->codec->sc; + hdac_lock(sc); + val = ((uint64_t)sc->poll_ival * 1000) / hz; + hdac_unlock(sc); + err = sysctl_handle_int(oidp, &val, 0, req); + + if (err != 0 || req->newptr == NULL) + return (err); + + if (val < 1) + val = 1; + if (val > 5000) + val = 5000; + val = ((uint64_t)val * hz) / 1000; + if (val < 1) + val = 1; + if (val > (hz * 5)) + val = hz * 5; + + hdac_lock(sc); + sc->poll_ival = val; + hdac_unlock(sc); + + return (err); +} + +#ifdef SND_DEBUG +static int +sysctl_hdac_pindump(SYSCTL_HANDLER_ARGS) +{ + struct hdac_softc *sc; + struct hdac_devinfo *devinfo; + struct hdac_widget *w; + device_t dev; + uint32_t res, pincap, timeout; + int i, err, val; + nid_t cad; + + dev = oidp->oid_arg1; + devinfo = pcm_getdevinfo(dev); + if (devinfo == NULL || devinfo->codec == NULL || + devinfo->codec->sc == NULL) + return (EINVAL); + val = 0; + err = sysctl_handle_int(oidp, &val, 0, req); + if (err != 0 || req->newptr == NULL || val == 0) + return (err); + sc = devinfo->codec->sc; + cad = devinfo->codec->cad; + hdac_lock(sc); + device_printf(dev, "HDAC Dump AFG [nid=%d]:\n", devinfo->nid); + for (i = devinfo->startnode; i < devinfo->endnode; i++) { + w = hdac_widget_get(devinfo, i); + if (w == NULL || w->type != + HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX) + continue; + pincap = w->wclass.pin.cap; + if ((HDA_PARAM_PIN_CAP_IMP_SENSE_CAP(pincap) || + HDA_PARAM_PIN_CAP_PRESENCE_DETECT_CAP(pincap)) && + HDA_PARAM_PIN_CAP_TRIGGER_REQD(pincap)) { + timeout = 10000; + hdac_command(sc, + HDA_CMD_SET_PIN_SENSE(cad, w->nid, 0), cad); + do { + res = hdac_command(sc, + HDA_CMD_GET_PIN_SENSE(cad, w->nid), cad); + if (res != 0x7fffffff) + break; + DELAY(10); + } while (--timeout != 0); + } else { + timeout = -1; + res = hdac_command(sc, HDA_CMD_GET_PIN_SENSE(cad, + w->nid), cad); + } + device_printf(dev, + "PIN_SENSE: nid=%-3d timeout=%d res=0x%08x [%s]\n", + w->nid, timeout, res, + (w->enable == 0) ? "DISABLED" : "ENABLED"); + } + device_printf(dev, + "NumGPIO=%d NumGPO=%d NumGPI=%d GPIWake=%d GPIUnsol=%d\n", + HDA_PARAM_GPIO_COUNT_NUM_GPIO(devinfo->function.audio.gpio), + HDA_PARAM_GPIO_COUNT_NUM_GPO(devinfo->function.audio.gpio), + HDA_PARAM_GPIO_COUNT_NUM_GPI(devinfo->function.audio.gpio), + HDA_PARAM_GPIO_COUNT_GPI_WAKE(devinfo->function.audio.gpio), + HDA_PARAM_GPIO_COUNT_GPI_UNSOL(devinfo->function.audio.gpio)); + if (HDA_PARAM_GPIO_COUNT_NUM_GPI(devinfo->function.audio.gpio) > 0) { + device_printf(dev, " GPI:"); + res = hdac_command(sc, + HDA_CMD_GET_GPI_DATA(cad, devinfo->nid), cad); + printf(" data=0x%08x", res); + res = hdac_command(sc, + HDA_CMD_GET_GPI_WAKE_ENABLE_MASK(cad, devinfo->nid), + cad); + printf(" wake=0x%08x", res); + res = hdac_command(sc, + HDA_CMD_GET_GPI_UNSOLICITED_ENABLE_MASK(cad, devinfo->nid), + cad); + printf(" unsol=0x%08x", res); + res = hdac_command(sc, + HDA_CMD_GET_GPI_STICKY_MASK(cad, devinfo->nid), cad); + printf(" sticky=0x%08x\n", res); + } + if (HDA_PARAM_GPIO_COUNT_NUM_GPO(devinfo->function.audio.gpio) > 0) { + device_printf(dev, " GPO:"); + res = hdac_command(sc, + HDA_CMD_GET_GPO_DATA(cad, devinfo->nid), cad); + printf(" data=0x%08x\n", res); + } + if (HDA_PARAM_GPIO_COUNT_NUM_GPIO(devinfo->function.audio.gpio) > 0) { + device_printf(dev, "GPI0:"); + res = hdac_command(sc, + HDA_CMD_GET_GPIO_DATA(cad, devinfo->nid), cad); + printf(" data=0x%08x", res); + res = hdac_command(sc, + HDA_CMD_GET_GPIO_ENABLE_MASK(cad, devinfo->nid), cad); + printf(" enable=0x%08x", res); + res = hdac_command(sc, + HDA_CMD_GET_GPIO_DIRECTION(cad, devinfo->nid), cad); + printf(" direction=0x%08x\n", res); + res = hdac_command(sc, + HDA_CMD_GET_GPIO_WAKE_ENABLE_MASK(cad, devinfo->nid), cad); + device_printf(dev, " wake=0x%08x", res); + res = hdac_command(sc, + HDA_CMD_GET_GPIO_UNSOLICITED_ENABLE_MASK(cad, devinfo->nid), + cad); + printf(" unsol=0x%08x", res); + res = hdac_command(sc, + HDA_CMD_GET_GPIO_STICKY_MASK(cad, devinfo->nid), cad); + printf(" sticky=0x%08x\n", res); + } + hdac_unlock(sc); + return (0); +} +#endif +#endif + +static void +hdac_attach2(void *arg) +{ + struct hdac_softc *sc; + struct hdac_widget *w; + struct hdac_audio_ctl *ctl; + uint32_t quirks_on, quirks_off; + int pcnt, rcnt; + int i; + char status[SND_STATUSLEN]; + device_t *devlist = NULL; + int devcount; + struct hdac_devinfo *devinfo = NULL; + + sc = (struct hdac_softc *)arg; + + hdac_config_fetch(sc, &quirks_on, &quirks_off); + + HDA_BOOTVERBOSE( + device_printf(sc->dev, "HDA_DEBUG: HDA Config: on=0x%08x off=0x%08x\n", + quirks_on, quirks_off); + ); + + hdac_lock(sc); + + /* Remove ourselves from the config hooks */ + if (sc->intrhook.ich_func != NULL) { + config_intrhook_disestablish(&sc->intrhook); + sc->intrhook.ich_func = NULL; + } + + /* Start the corb and rirb engines */ + HDA_BOOTVERBOSE( + device_printf(sc->dev, "HDA_DEBUG: Starting CORB Engine...\n"); + ); + hdac_corb_start(sc); + HDA_BOOTVERBOSE( + device_printf(sc->dev, "HDA_DEBUG: Starting RIRB Engine...\n"); + ); + hdac_rirb_start(sc); + + HDA_BOOTVERBOSE( + device_printf(sc->dev, + "HDA_DEBUG: Enabling controller interrupt...\n"); + ); + if (sc->polling == 0) + HDAC_WRITE_4(&sc->mem, HDAC_INTCTL, + HDAC_INTCTL_CIE | HDAC_INTCTL_GIE); + HDAC_WRITE_4(&sc->mem, HDAC_GCTL, HDAC_READ_4(&sc->mem, HDAC_GCTL) | + HDAC_GCTL_UNSOL); + + DELAY(1000); + + HDA_BOOTVERBOSE( + device_printf(sc->dev, "HDA_DEBUG: Scanning HDA codecs...\n"); + ); + hdac_scan_codecs(sc); + + device_get_children(sc->dev, &devlist, &devcount); + for (i = 0; devlist != NULL && i < devcount; i++) { + devinfo = (struct hdac_devinfo *)device_get_ivars(devlist[i]); + if (devinfo != NULL && devinfo->node_type == + HDA_PARAM_FCT_GRP_TYPE_NODE_TYPE_AUDIO) { + break; + } else + devinfo = NULL; + } + if (devlist != NULL) + free(devlist, M_TEMP); + + if (devinfo == NULL) { + hdac_unlock(sc); + device_printf(sc->dev, "Audio Function Group not found!\n"); + hdac_release_resources(sc); + return; + } + + HDA_BOOTVERBOSE( + device_printf(sc->dev, + "HDA_DEBUG: Parsing AFG nid=%d cad=%d\n", + devinfo->nid, devinfo->codec->cad); + ); + hdac_audio_parse(devinfo); + HDA_BOOTVERBOSE( + device_printf(sc->dev, "HDA_DEBUG: Parsing Ctls...\n"); + ); + hdac_audio_ctl_parse(devinfo); + HDA_BOOTVERBOSE( + device_printf(sc->dev, "HDA_DEBUG: Parsing vendor patch...\n"); + ); + hdac_vendor_patch_parse(devinfo); + if (quirks_on != 0) + devinfo->function.audio.quirks |= quirks_on; + if (quirks_off != 0) + devinfo->function.audio.quirks &= ~quirks_off; + + /* XXX Disable all DIGITAL path. */ + for (i = devinfo->startnode; i < devinfo->endnode; i++) { + w = hdac_widget_get(devinfo, i); + if (w == NULL) + continue; + if (HDA_PARAM_AUDIO_WIDGET_CAP_DIGITAL(w->param.widget_cap)) { + w->enable = 0; + continue; + } + /* XXX Disable useless pin ? */ + if (w->type == HDA_PARAM_AUDIO_WIDGET_CAP_TYPE_PIN_COMPLEX && + (w->wclass.pin.config & + HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_MASK) == + HDA_CONFIG_DEFAULTCONF_CONNECTIVITY_NONE) + w->enable = 0; + } + i = 0; + while ((ctl = hdac_audio_ctl_each(devinfo, &i)) != NULL) { + if (ctl->widget == NULL) + continue; + if (ctl->ossmask & SOUND_MASK_DISABLE) + ctl->enable = 0; + w = ctl->widget; + if (w->enable == 0 || + HDA_PARAM_AUDIO_WIDGET_CAP_DIGITAL(w->param.widget_cap)) + ctl->enable = 0; + w = ctl->childwidget; + if (w == NULL) + continue; + if (w->enable == 0 || + HDA_PARAM_AUDIO_WIDGET_CAP_DIGITAL(w->param.widget_cap)) + ctl->enable = 0; + } + + HDA_BOOTVERBOSE( + device_printf(sc->dev, "HDA_DEBUG: Building AFG tree...\n"); + ); + hdac_audio_build_tree(devinfo); + + i = 0; + while ((ctl = hdac_audio_ctl_each(devinfo, &i)) != NULL) { + if (ctl->ossmask & (SOUND_MASK_SKIP | SOUND_MASK_DISABLE)) + ctl->ossmask = 0; + } + HDA_BOOTVERBOSE( + device_printf(sc->dev, "HDA_DEBUG: AFG commit...\n"); + ); + hdac_audio_commit(devinfo, HDA_COMMIT_ALL); + HDA_BOOTVERBOSE( + device_printf(sc->dev, "HDA_DEBUG: Ctls commit...\n"); + ); + hdac_audio_ctl_commit(devinfo); + + HDA_BOOTVERBOSE( + device_printf(sc->dev, "HDA_DEBUG: PCMDIR_PLAY setup...\n"); + ); + pcnt = hdac_pcmchannel_setup(devinfo, PCMDIR_PLAY); + HDA_BOOTVERBOSE( + device_printf(sc->dev, "HDA_DEBUG: PCMDIR_REC setup...\n"); + ); + rcnt = hdac_pcmchannel_setup(devinfo, PCMDIR_REC); + + hdac_unlock(sc); + HDA_BOOTVERBOSE( + device_printf(sc->dev, + "HDA_DEBUG: OSS mixer initialization...\n"); + ); + + /* + * There is no point of return after this. If the driver failed, + * so be it. Let the detach procedure do all the cleanup. + */ + if (mixer_init(sc->dev, &hdac_audio_ctl_ossmixer_class, devinfo) != 0) + device_printf(sc->dev, "Can't register mixer\n"); + + if (pcnt > 0) + pcnt = 1; + if (rcnt > 0) + rcnt = 1; + + HDA_BOOTVERBOSE( + device_printf(sc->dev, + "HDA_DEBUG: Registering PCM channels...\n"); + ); + if (pcm_register(sc->dev, devinfo, pcnt, rcnt) != 0) + device_printf(sc->dev, "Can't register PCM\n"); + + sc->registered++; + + if ((devinfo->function.audio.quirks & HDA_QUIRK_DMAPOS) && + hdac_dma_alloc(sc, &sc->pos_dma, + (sc->num_iss + sc->num_oss + sc->num_bss) * 8) != 0) { + HDA_BOOTVERBOSE( + device_printf(sc->dev, + "Failed to allocate DMA pos buffer (non-fatal)\n"); + ); + } + + for (i = 0; i < pcnt; i++) + pcm_addchan(sc->dev, PCMDIR_PLAY, &hdac_channel_class, devinfo); + for (i = 0; i < rcnt; i++) + pcm_addchan(sc->dev, PCMDIR_REC, &hdac_channel_class, devinfo); + +#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_hdac_polling, "I", "Enable polling mode"); + SYSCTL_ADD_PROC(device_get_sysctl_ctx(sc->dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(sc->dev)), OID_AUTO, + "polling_interval", CTLTYPE_INT | CTLFLAG_RW, sc->dev, + sizeof(sc->dev), sysctl_hdac_polling_interval, "I", + "Controller/Jack Sense polling interval (1-1000 ms)"); +#ifdef SND_DEBUG + SYSCTL_ADD_PROC(device_get_sysctl_ctx(sc->dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(sc->dev)), OID_AUTO, + "pindump", CTLTYPE_INT | CTLFLAG_RW, sc->dev, sizeof(sc->dev), + sysctl_hdac_pindump, "I", "Dump pin states/data"); +#endif +#endif + + snprintf(status, SND_STATUSLEN, "at memory 0x%lx irq %ld %s [%s]", + rman_get_start(sc->mem.mem_res), rman_get_start(sc->irq.irq_res), + PCM_KLDSTRING(snd_hda), HDA_DRV_TEST_REV); + pcm_setstatus(sc->dev, status); + device_printf(sc->dev, "\n", hdac_codec_name(devinfo)); + HDA_BOOTVERBOSE( + device_printf(sc->dev, "\n", + hdac_codec_id(devinfo)); + ); + device_printf(sc->dev, "\n", + HDA_DRV_TEST_REV); + + HDA_BOOTVERBOSE( + if (devinfo->function.audio.quirks != 0) { + device_printf(sc->dev, "\n"); + device_printf(sc->dev, "HDA config/quirks:"); + for (i = 0; i < HDAC_QUIRKS_TAB_LEN; i++) { + if ((devinfo->function.audio.quirks & + hdac_quirks_tab[i].value) == + hdac_quirks_tab[i].value) + printf(" %s", hdac_quirks_tab[i].key); + } + printf("\n"); + } + device_printf(sc->dev, "\n"); + device_printf(sc->dev, "+-------------------+\n"); + device_printf(sc->dev, "| DUMPING HDA NODES |\n"); + device_printf(sc->dev, "+-------------------+\n"); + hdac_dump_nodes(devinfo); + device_printf(sc->dev, "\n"); + device_printf(sc->dev, "+------------------------+\n"); + device_printf(sc->dev, "| DUMPING HDA AMPLIFIERS |\n"); + device_printf(sc->dev, "+------------------------+\n"); + device_printf(sc->dev, "\n"); + i = 0; + while ((ctl = hdac_audio_ctl_each(devinfo, &i)) != NULL) { + device_printf(sc->dev, "%3d: nid=%d", i, + (ctl->widget != NULL) ? ctl->widget->nid : -1); + if (ctl->childwidget != NULL) + printf(" cnid=%d", ctl->childwidget->nid); + printf(" dir=0x%x index=%d " + "ossmask=0x%08x ossdev=%d%s\n", + ctl->dir, ctl->index, + ctl->ossmask, ctl->ossdev, + (ctl->enable == 0) ? " [DISABLED]" : ""); + } + device_printf(sc->dev, "\n"); + device_printf(sc->dev, "+-----------------------------------+\n"); + device_printf(sc->dev, "| DUMPING HDA AUDIO/VOLUME CONTROLS |\n"); + device_printf(sc->dev, "+-----------------------------------+\n"); + hdac_dump_ctls(devinfo, "Master Volume (OSS: vol)", SOUND_MASK_VOLUME); + hdac_dump_ctls(devinfo, "PCM Volume (OSS: pcm)", SOUND_MASK_PCM); + hdac_dump_ctls(devinfo, "CD Volume (OSS: cd)", SOUND_MASK_CD); + hdac_dump_ctls(devinfo, "Microphone Volume (OSS: mic)", SOUND_MASK_MIC); + hdac_dump_ctls(devinfo, "Line-in Volume (OSS: line)", SOUND_MASK_LINE); + hdac_dump_ctls(devinfo, "Recording Level (OSS: rec)", SOUND_MASK_RECLEV); + hdac_dump_ctls(devinfo, "Speaker/Beep (OSS: speaker)", SOUND_MASK_SPEAKER); + hdac_dump_ctls(devinfo, NULL, 0); + hdac_dump_dac(devinfo); + hdac_dump_adc(devinfo); + device_printf(sc->dev, "\n"); + device_printf(sc->dev, "+--------------------------------------+\n"); + device_printf(sc->dev, "| DUMPING PCM Playback/Record Channels |\n"); + device_printf(sc->dev, "+--------------------------------------+\n"); + hdac_dump_pcmchannels(sc, pcnt, rcnt); + ); + + if (sc->polling != 0) { + hdac_lock(sc); + callout_reset(&sc->poll_hdac, 1, hdac_poll_callback, sc); + hdac_unlock(sc); + } +} + +/**************************************************************************** + * int hdac_detach(device_t) + * + * Detach and free up resources utilized by the hdac device. + ****************************************************************************/ +static int +hdac_detach(device_t dev) +{ + struct hdac_softc *sc = NULL; + struct hdac_devinfo *devinfo = NULL; + int err; + + devinfo = (struct hdac_devinfo *)pcm_getdevinfo(dev); + if (devinfo != NULL && devinfo->codec != NULL) + sc = devinfo->codec->sc; + if (sc == NULL) + return (0); + + if (sc->registered > 0) { + err = pcm_unregister(dev); + if (err != 0) + return (err); + } + + hdac_release_resources(sc); + + return (0); +} + +static device_method_t hdac_methods[] = { + /* device interface */ + DEVMETHOD(device_probe, hdac_probe), + DEVMETHOD(device_attach, hdac_attach), + DEVMETHOD(device_detach, hdac_detach), + { 0, 0 } +}; + +static driver_t hdac_driver = { + "pcm", + hdac_methods, + PCM_SOFTC_SIZE, +}; + +DRIVER_MODULE(snd_hda, pci, hdac_driver, pcm_devclass, 0, 0); +MODULE_DEPEND(snd_hda, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); +MODULE_VERSION(snd_hda, 1); --- sys/dev/sound/pci/hda/hdac.h.orig Thu Jan 1 07:30:00 1970 +++ sys/dev/sound/pci/hda/hdac.h Thu Jul 12 12:04:19 2007 @@ -0,0 +1,69 @@ +/*- + * Copyright (c) 2006 Stephane E. Potvin + * 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/hda/hdac.h,v 1.1 2006/10/01 11:12:59 ariff Exp $ + */ + +#ifndef _HDAC_H_ +#define _HDAC_H_ + + +#if 0 +/**************************************************************************** + * Miscellanious defines + ****************************************************************************/ + +/**************************************************************************** + * Helper Macros + ****************************************************************************/ + +/**************************************************************************** + * Simplified Accessors for HDA devices + ****************************************************************************/ +enum hdac_device_ivars { + HDAC_IVAR_CODEC_ID, + HDAC_IVAR_NODE_ID, + HDAC_IVAR_VENDOR_ID, + HDAC_IVAR_DEVICE_ID, + HDAC_IVAR_REVISION_ID, + HDAC_IVAR_STEPPING_ID, + HDAC_IVAR_NODE_TYPE, +}; + +#define HDAC_ACCESSOR(var, ivar, type) \ + __BUS_ACCESSOR(hdac, var, HDAC, ivar, type) + +HDAC_ACCESSOR(codec_id, CODEC_ID, uint8_t); +HDAC_ACCESSOR(node_id, NODE_ID, uint8_t); +HDAC_ACCESSOR(vendor_id, VENDOR_ID, uint16_t); +HDAC_ACCESSOR(device_id, DEVICE_ID, uint16_t); +HDAC_ACCESSOR(revision_id, REVISION_ID, uint8_t); +HDAC_ACCESSOR(stepping_id, STEPPING_ID, uint8_t); +HDAC_ACCESSOR(node_type, NODE_TYPE, uint8_t); +#endif + +#define PCIS_MULTIMEDIA_HDA 0x03 + +#endif --- sys/dev/sound/pci/hda/hdac_private.h.orig Thu Jan 1 07:30:00 1970 +++ sys/dev/sound/pci/hda/hdac_private.h Thu Jul 12 12:04:19 2007 @@ -0,0 +1,358 @@ +/*- + * Copyright (c) 2006 Stephane E. Potvin + * 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/hda/hdac_private.h,v 1.8 2007/07/09 20:42:11 ariff Exp $ + */ + +#ifndef _HDAC_PRIVATE_H_ +#define _HDAC_PRIVATE_H_ + + +/**************************************************************************** + * Miscellaneous defines + ****************************************************************************/ +#define HDAC_DMA_ALIGNMENT 128 +#define HDAC_CODEC_MAX 16 + +#define HDAC_MTX_NAME "hdac driver mutex" + +/**************************************************************************** + * Helper Macros + ****************************************************************************/ +#define HDAC_READ_1(mem, offset) \ + bus_space_read_1((mem)->mem_tag, (mem)->mem_handle, (offset)) +#define HDAC_READ_2(mem, offset) \ + bus_space_read_2((mem)->mem_tag, (mem)->mem_handle, (offset)) +#define HDAC_READ_4(mem, offset) \ + bus_space_read_4((mem)->mem_tag, (mem)->mem_handle, (offset)) +#define HDAC_WRITE_1(mem, offset, value) \ + bus_space_write_1((mem)->mem_tag, (mem)->mem_handle, (offset), (value)) +#define HDAC_WRITE_2(mem, offset, value) \ + bus_space_write_2((mem)->mem_tag, (mem)->mem_handle, (offset), (value)) +#define HDAC_WRITE_4(mem, offset, value) \ + bus_space_write_4((mem)->mem_tag, (mem)->mem_handle, (offset), (value)) + +#define HDAC_ISDCTL(sc, n) (_HDAC_ISDCTL((n), (sc)->num_iss, (sc)->num_oss)) +#define HDAC_ISDSTS(sc, n) (_HDAC_ISDSTS((n), (sc)->num_iss, (sc)->num_oss)) +#define HDAC_ISDPICB(sc, n) (_HDAC_ISDPICB((n), (sc)->num_iss, (sc)->num_oss)) +#define HDAC_ISDCBL(sc, n) (_HDAC_ISDCBL((n), (sc)->num_iss, (sc)->num_oss)) +#define HDAC_ISDLVI(sc, n) (_HDAC_ISDLVI((n), (sc)->num_iss, (sc)->num_oss)) +#define HDAC_ISDFIFOD(sc, n) (_HDAC_ISDFIFOD((n), (sc)->num_iss, (sc)->num_oss)) +#define HDAC_ISDFMT(sc, n) (_HDAC_ISDFMT((n), (sc)->num_iss, (sc)->num_oss)) +#define HDAC_ISDBDPL(sc, n) (_HDAC_ISDBDPL((n), (sc)->num_iss, (sc)->num_oss)) +#define HDAC_ISDBDPU(sc, n) (_HDAC_ISDBDPU((n), (sc)->num_iss, (sc)->num_oss)) + +#define HDAC_OSDCTL(sc, n) (_HDAC_OSDCTL((n), (sc)->num_iss, (sc)->num_oss)) +#define HDAC_OSDSTS(sc, n) (_HDAC_OSDSTS((n), (sc)->num_iss, (sc)->num_oss)) +#define HDAC_OSDPICB(sc, n) (_HDAC_OSDPICB((n), (sc)->num_iss, (sc)->num_oss)) +#define HDAC_OSDCBL(sc, n) (_HDAC_OSDCBL((n), (sc)->num_iss, (sc)->num_oss)) +#define HDAC_OSDLVI(sc, n) (_HDAC_OSDLVI((n), (sc)->num_iss, (sc)->num_oss)) +#define HDAC_OSDFIFOD(sc, n) (_HDAC_OSDFIFOD((n), (sc)->num_iss, (sc)->num_oss)) +#define HDAC_OSDBDPL(sc, n) (_HDAC_OSDBDPL((n), (sc)->num_iss, (sc)->num_oss)) +#define HDAC_OSDBDPU(sc, n) (_HDAC_OSDBDPU((n), (sc)->num_iss, (sc)->num_oss)) + +#define HDAC_BSDCTL(sc, n) (_HDAC_BSDCTL((n), (sc)->num_iss, (sc)->num_oss)) +#define HDAC_BSDSTS(sc, n) (_HDAC_BSDSTS((n), (sc)->num_iss, (sc)->num_oss)) +#define HDAC_BSDPICB(sc, n) (_HDAC_BSDPICB((n), (sc)->num_iss, (sc)->num_oss)) +#define HDAC_BSDCBL(sc, n) (_HDAC_BSDCBL((n), (sc)->num_iss, (sc)->num_oss)) +#define HDAC_BSDLVI(sc, n) (_HDAC_BSDLVI((n), (sc)->num_iss, (sc)->num_oss)) +#define HDAC_BSDFIFOD(sc, n) (_HDAC_BSDFIFOD((n), (sc)->num_iss, (sc)->num_oss)) +#define HDAC_BSDBDPL(sc, n) (_HDAC_BSDBDPL((n), (sc)->num_iss, (sc)->num_oss)) +#define HDAC_BSDBDPU(sc, n) (_HDAC_BSDBDPU((n), (sc)->num_iss, (sc)->num_oss)) + + +/**************************************************************************** + * Custom hdac malloc type + ****************************************************************************/ +MALLOC_DECLARE(M_HDAC); + +/**************************************************************************** + * struct hdac_mem + * + * Holds the resources necessary to describe the physical memory associated + * with the device. + ****************************************************************************/ +struct hdac_mem { + struct resource *mem_res; + int mem_rid; + bus_space_tag_t mem_tag; + bus_space_handle_t mem_handle; +}; + +/**************************************************************************** + * struct hdac_irq + * + * Holds the resources necessary to describe the irq associated with the + * device. + ****************************************************************************/ +struct hdac_irq { + struct resource *irq_res; + int irq_rid; + void *irq_handle; +}; + +/**************************************************************************** + * struct hdac_dma + * + * This structure is used to hold all the information to manage the dma + * states. + ****************************************************************************/ +struct hdac_dma { + bus_dma_tag_t dma_tag; + bus_dmamap_t dma_map; + bus_addr_t dma_paddr; + bus_size_t dma_size; + caddr_t dma_vaddr; +}; + +/**************************************************************************** + * struct hdac_rirb + * + * Hold a response from a verb sent to a codec received via the rirb. + ****************************************************************************/ +struct hdac_rirb { + uint32_t response; + uint32_t response_ex; +}; + +#define HDAC_RIRB_RESPONSE_EX_SDATA_IN_MASK 0x0000000f +#define HDAC_RIRB_RESPONSE_EX_SDATA_IN_OFFSET 0 +#define HDAC_RIRB_RESPONSE_EX_UNSOLICITED 0x00000010 + +#define HDAC_RIRB_RESPONSE_EX_SDATA_IN(response_ex) \ + (((response_ex) & HDAC_RIRB_RESPONSE_EX_SDATA_IN_MASK) >> \ + HDAC_RIRB_RESPONSE_EX_SDATA_IN_OFFSET) + +/**************************************************************************** + * struct hdac_command_list + * + * This structure holds the list of verbs that are to be sent to the codec + * via the corb and the responses received via the rirb. It's allocated by + * the codec driver and is owned by it. + ****************************************************************************/ +struct hdac_command_list { + int num_commands; + uint32_t *verbs; + uint32_t *responses; +}; + +typedef int nid_t; + +struct hdac_softc; +/**************************************************************************** + * struct hdac_codec + * + ****************************************************************************/ +struct hdac_codec { + int verbs_sent; + int responses_received; + nid_t cad; + struct hdac_command_list *commands; + struct hdac_softc *sc; +}; + +struct hdac_bdle { + volatile uint32_t addrl; + volatile uint32_t addrh; + volatile uint32_t len; + volatile uint32_t ioc; +} __packed; + +#define HDA_MAX_CONNS 32 +#define HDA_MAX_NAMELEN 32 + +struct hdac_devinfo; + +struct hdac_widget { + nid_t nid; + int type; + int enable; + int nconns, selconn; + uint32_t pflags, ctlflags; + nid_t conns[HDA_MAX_CONNS]; + char name[HDA_MAX_NAMELEN]; + struct hdac_devinfo *devinfo; + struct { + uint32_t widget_cap; + uint32_t outamp_cap; + uint32_t inamp_cap; + uint32_t supp_stream_formats; + uint32_t supp_pcm_size_rate; + uint32_t eapdbtl; + int outpath; + } param; + union { + struct { + uint32_t config; + uint32_t cap; + uint32_t ctrl; + } pin; + } wclass; +}; + +struct hdac_audio_ctl { + struct hdac_widget *widget, *childwidget; + int enable; + int index; + int mute, step, size, offset; + int left, right; + uint32_t muted; + int ossdev; + uint32_t dir, ossmask, ossval; +}; + +/**************************************************************************** + * struct hdac_devinfo + * + * Holds all the parameters of a given codec function group. This is stored + * in the ivar of each child of the hdac bus + ****************************************************************************/ +struct hdac_devinfo { + device_t dev; + uint16_t vendor_id; + uint16_t device_id; + uint8_t revision_id; + uint8_t stepping_id; + uint8_t node_type; + nid_t nid; + nid_t startnode, endnode; + int nodecnt; + struct hdac_codec *codec; + struct hdac_widget *widget; + union { + struct { + uint32_t outamp_cap; + uint32_t inamp_cap; + uint32_t supp_stream_formats; + uint32_t supp_pcm_size_rate; + int ctlcnt, pcnt, rcnt; + struct hdac_audio_ctl *ctl; + uint32_t mvol; + uint32_t quirks; + uint32_t gpio; + int ossidx; + int playcnt, reccnt; + int parsing_strategy; + } audio; + /* XXX undefined: modem, hdmi. */ + } function; +}; + +#define HDAC_CHN_RUNNING 0x00000001 +#define HDAC_CHN_SUSPEND 0x00000002 + +struct hdac_chan { + struct snd_dbuf *b; + struct pcm_channel *c; + struct pcmchan_caps caps; + struct hdac_devinfo *devinfo; + struct hdac_dma bdl_dma; + uint32_t spd, fmt, fmtlist[8], pcmrates[16]; + uint32_t supp_stream_formats, supp_pcm_size_rate; + uint32_t ptr, prevptr, blkcnt, blksz; + uint32_t *dmapos; + uint32_t flags; + int dir; + int off; + int sid; + int bit16, bit32; + nid_t io[16]; +}; + +/**************************************************************************** + * struct hdac_softc + * + * This structure holds the current state of the hdac driver. + ****************************************************************************/ + +#define HDAC_F_DMA_NOCACHE 0x00000001 +#define HDAC_F_MSI 0x00000002 + +struct hdac_softc { + device_t dev; + device_t hdabus; + struct mtx *lock; + + struct intr_config_hook intrhook; + + struct hdac_mem mem; + struct hdac_irq irq; + uint32_t pci_subvendor; + + uint32_t flags; + + int num_iss; + int num_oss; + int num_bss; + int support_64bit; + int streamcnt; + + int corb_size; + struct hdac_dma corb_dma; + int corb_wp; + + int rirb_size; + struct hdac_dma rirb_dma; + int rirb_rp; + + struct hdac_dma pos_dma; + + struct hdac_chan play, rec; + bus_dma_tag_t chan_dmat; + int chan_size; + int chan_blkcnt; + + /* + * Polling + */ + int polling; + int poll_ticks; + int poll_ival; + struct callout poll_hda; + struct callout poll_hdac; + struct callout poll_jack; + + struct task unsolq_task; + +#define HDAC_UNSOLQ_MAX 64 +#define HDAC_UNSOLQ_READY 0 +#define HDAC_UNSOLQ_BUSY 1 + int unsolq_rp; + int unsolq_wp; + int unsolq_st; + uint32_t unsolq[HDAC_UNSOLQ_MAX]; + + struct hdac_codec *codecs[HDAC_CODEC_MAX]; + + int registered; +}; + +/**************************************************************************** + * struct hdac_command flags + ****************************************************************************/ +#define HDAC_COMMAND_FLAG_WAITOK 0x0000 +#define HDAC_COMMAND_FLAG_NOWAIT 0x0001 + +#endif --- sys/dev/sound/pci/hda/hdac_reg.h.orig Thu Jan 1 07:30:00 1970 +++ sys/dev/sound/pci/hda/hdac_reg.h Thu Jul 12 12:04:19 2007 @@ -0,0 +1,266 @@ +/*- + * Copyright (c) 2006 Stephane E. Potvin + * 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/hda/hdac_reg.h,v 1.1 2006/10/01 11:12:59 ariff Exp $ + */ + +#ifndef _HDAC_REG_H_ +#define _HDAC_REG_H_ + +/**************************************************************************** + * HDA Controller Register Set + ****************************************************************************/ +#define HDAC_GCAP 0x00 /* 2 - Global Capabilities*/ +#define HDAC_VMIN 0x02 /* 1 - Minor Version */ +#define HDAC_VMAJ 0x03 /* 1 - Major Version */ +#define HDAC_OUTPAY 0x04 /* 2 - Output Payload Capability */ +#define HDAC_INPAY 0x06 /* 2 - Input Payload Capability */ +#define HDAC_GCTL 0x08 /* 4 - Global Control */ +#define HDAC_WAKEEN 0x0c /* 2 - Wake Enable */ +#define HDAC_STATESTS 0x0e /* 2 - State Change Status */ +#define HDAC_GSTS 0x10 /* 2 - Global Status */ +#define HDAC_OUTSTRMPAY 0x18 /* 2 - Output Stream Payload Capability */ +#define HDAC_INSTRMPAY 0x1a /* 2 - Input Stream Payload Capability */ +#define HDAC_INTCTL 0x20 /* 4 - Interrupt Control */ +#define HDAC_INTSTS 0x24 /* 4 - Interrupt Status */ +#define HDAC_WALCLK 0x30 /* 4 - Wall Clock Counter */ +#define HDAC_SSYNC 0x38 /* 4 - Stream Synchronization */ +#define HDAC_CORBLBASE 0x40 /* 4 - CORB Lower Base Address */ +#define HDAC_CORBUBASE 0x44 /* 4 - CORB Upper Base Address */ +#define HDAC_CORBWP 0x48 /* 2 - CORB Write Pointer */ +#define HDAC_CORBRP 0x4a /* 2 - CORB Read Pointer */ +#define HDAC_CORBCTL 0x4c /* 1 - CORB Control */ +#define HDAC_CORBSTS 0x4d /* 1 - CORB Status */ +#define HDAC_CORBSIZE 0x4e /* 1 - CORB Size */ +#define HDAC_RIRBLBASE 0x50 /* 4 - RIRB Lower Base Address */ +#define HDAC_RIRBUBASE 0x54 /* 4 - RIRB Upper Base Address */ +#define HDAC_RIRBWP 0x58 /* 2 - RIRB Write Pointer */ +#define HDAC_RINTCNT 0x5a /* 2 - Response Interrupt Count */ +#define HDAC_RIRBCTL 0x5c /* 1 - RIRB Control */ +#define HDAC_RIRBSTS 0x5d /* 1 - RIRB Status */ +#define HDAC_RIRBSIZE 0x5e /* 1 - RIRB Size */ +#define HDAC_ICOI 0x60 /* 4 - Immediate Command Output Interface */ +#define HDAC_ICII 0x64 /* 4 - Immediate Command Input Interface */ +#define HDAC_ICIS 0x68 /* 2 - Immediate Command Status */ +#define HDAC_DPIBLBASE 0x70 /* 4 - DMA Position Buffer Lower Base */ +#define HDAC_DPIBUBASE 0x74 /* 4 - DMA Position Buffer Upper Base */ +#define HDAC_SDCTL0 0x80 /* 3 - Stream Descriptor Control */ +#define HDAC_SDCTL1 0x81 /* 3 - Stream Descriptor Control */ +#define HDAC_SDCTL2 0x82 /* 3 - Stream Descriptor Control */ +#define HDAC_SDSTS 0x83 /* 1 - Stream Descriptor Status */ +#define HDAC_SDLPIB 0x84 /* 4 - Link Position in Buffer */ +#define HDAC_SDCBL 0x88 /* 4 - Cyclic Buffer Length */ +#define HDAC_SDLVI 0x8C /* 2 - Last Valid Index */ +#define HDAC_SDFIFOS 0x90 /* 2 - FIFOS */ +#define HDAC_SDFMT 0x92 /* 2 - fmt */ +#define HDAC_SDBDPL 0x98 /* 4 - Buffer Descriptor Pointer Lower Base */ +#define HDAC_SDBDPU 0x9C /* 4 - Buffer Descriptor Pointer Upper Base */ + +#define _HDAC_ISDOFFSET(n, iss, oss) (0x80 + ((n) * 0x20)) +#define _HDAC_ISDCTL(n, iss, oss) (0x00 + _HDAC_ISDOFFSET(n, iss, oss)) +#define _HDAC_ISDSTS(n, iss, oss) (0x03 + _HDAC_ISDOFFSET(n, iss, oss)) +#define _HDAC_ISDPICB(n, iss, oss) (0x04 + _HDAC_ISDOFFSET(n, iss, oss)) +#define _HDAC_ISDCBL(n, iss, oss) (0x08 + _HDAC_ISDOFFSET(n, iss, oss)) +#define _HDAC_ISDLVI(n, iss, oss) (0x0c + _HDAC_ISDOFFSET(n, iss, oss)) +#define _HDAC_ISDFIFOD(n, iss, oss) (0x10 + _HDAC_ISDOFFSET(n, iss, oss)) +#define _HDAC_ISDFMT(n, iss, oss) (0x12 + _HDAC_ISDOFFSET(n, iss, oss)) +#define _HDAC_ISDBDPL(n, iss, oss) (0x18 + _HDAC_ISDOFFSET(n, iss, oss)) +#define _HDAC_ISDBDPU(n, iss, oss) (0x1c + _HDAC_ISDOFFSET(n, iss, oss)) + +#define _HDAC_OSDOFFSET(n, iss, oss) (0x80 + ((iss) * 0x20) + ((n) * 0x20)) +#define _HDAC_OSDCTL(n, iss, oss) (0x00 + _HDAC_OSDOFFSET(n, iss, oss)) +#define _HDAC_OSDSTS(n, iss, oss) (0x03 + _HDAC_OSDOFFSET(n, iss, oss)) +#define _HDAC_OSDPICB(n, iss, oss) (0x04 + _HDAC_OSDOFFSET(n, iss, oss)) +#define _HDAC_OSDCBL(n, iss, oss) (0x08 + _HDAC_OSDOFFSET(n, iss, oss)) +#define _HDAC_OSDLVI(n, iss, oss) (0x0c + _HDAC_OSDOFFSET(n, iss, oss)) +#define _HDAC_OSDFIFOD(n, iss, oss) (0x10 + _HDAC_OSDOFFSET(n, iss, oss)) +#define _HDAC_OSDFMT(n, iss, oss) (0x12 + _HDAC_OSDOFFSET(n, iss, oss)) +#define _HDAC_OSDBDPL(n, iss, oss) (0x18 + _HDAC_OSDOFFSET(n, iss, oss)) +#define _HDAC_OSDBDPU(n, iss, oss) (0x1c + _HDAC_OSDOFFSET(n, iss, oss)) + +#define _HDAC_BSDOFFSET(n, iss, oss) (0x80 + ((iss) * 0x20) + ((oss) * 0x20) + ((n) * 0x20)) +#define _HDAC_BSDCTL(n, iss, oss) (0x00 + _HDAC_BSDOFFSET(n, iss, oss)) +#define _HDAC_BSDSTS(n, iss, oss) (0x03 + _HDAC_BSDOFFSET(n, iss, oss)) +#define _HDAC_BSDPICB(n, iss, oss) (0x04 + _HDAC_BSDOFFSET(n, iss, oss)) +#define _HDAC_BSDCBL(n, iss, oss) (0x08 + _HDAC_BSDOFFSET(n, iss, oss)) +#define _HDAC_BSDLVI(n, iss, oss) (0x0c + _HDAC_BSDOFFSET(n, iss, oss)) +#define _HDAC_BSDFIFOD(n, iss, oss) (0x10 + _HDAC_BSDOFFSET(n, iss, oss)) +#define _HDAC_BSDFMT(n, iss, oss) (0x12 + _HDAC_BSDOFFSET(n, iss, oss)) +#define _HDAC_BSDBDPL(n, iss, oss) (0x18 + _HDAC_BSDOFFSET(n, iss, oss)) +#define _HDAC_BSDBDBU(n, iss, oss) (0x1c + _HDAC_BSDOFFSET(n, iss, oss)) + +/**************************************************************************** + * HDA Controller Register Fields + ****************************************************************************/ + +/* GCAP - Global Capabilities */ +#define HDAC_GCAP_64OK 0x0001 +#define HDAC_GCAP_NSDO_MASK 0x0006 +#define HDAC_GCAP_NSDO_SHIFT 1 +#define HDAC_GCAP_BSS_MASK 0x00f8 +#define HDAC_GCAP_BSS_SHIFT 3 +#define HDAC_GCAP_ISS_MASK 0x0f00 +#define HDAC_GCAP_ISS_SHIFT 8 +#define HDAC_GCAP_OSS_MASK 0xf000 +#define HDAC_GCAP_OSS_SHIFT 12 + +#define HDAC_GCAP_NSDO_1SDO 0x00 +#define HDAC_GCAP_NSDO_2SDO 0x02 +#define HDAC_GCAP_NSDO_4SDO 0x04 + +#define HDAC_GCAP_BSS(gcap) \ + (((gcap) & HDAC_GCAP_BSS_MASK) >> HDAC_GCAP_BSS_SHIFT) +#define HDAC_GCAP_ISS(gcap) \ + (((gcap) & HDAC_GCAP_ISS_MASK) >> HDAC_GCAP_ISS_SHIFT) +#define HDAC_GCAP_OSS(gcap) \ + (((gcap) & HDAC_GCAP_OSS_MASK) >> HDAC_GCAP_OSS_SHIFT) + +/* GCTL - Global Control */ +#define HDAC_GCTL_CRST 0x00000001 +#define HDAC_GCTL_FCNTRL 0x00000002 +#define HDAC_GCTL_UNSOL 0x00000100 + +/* WAKEEN - Wake Enable */ +#define HDAC_WAKEEN_SDIWEN_MASK 0x7fff +#define HDAC_WAKEEN_SDIWEN_SHIFT 0 + +/* STATESTS - State Change Status */ +#define HDAC_STATESTS_SDIWAKE_MASK 0x7fff +#define HDAC_STATESTS_SDIWAKE_SHIFT 0 + +#define HDAC_STATESTS_SDIWAKE(statests, n) \ + (((((statests) & HDAC_STATESTS_SDIWAKE_MASK) >> \ + HDAC_STATESTS_SDIWAKE_SHIFT) >> (n)) & 0x0001) + +/* GSTS - Global Status */ +#define HDAC_GSTS_FSTS 0x0002 + +/* INTCTL - Interrut Control */ +#define HDAC_INTCTL_SIE_MASK 0x3fffffff +#define HDAC_INTCTL_SIE_SHIFT 0 +#define HDAC_INTCTL_CIE 0x40000000 +#define HDAC_INTCTL_GIE 0x80000000 + +/* INTSTS - Interrupt Status */ +#define HDAC_INTSTS_SIS_MASK 0x3fffffff +#define HDAC_INTSTS_SIS_SHIFT 0 +#define HDAC_INTSTS_CIS 0x40000000 +#define HDAC_INTSTS_GIS 0x80000000 + +/* SSYNC - Stream Synchronization */ +#define HDAC_SSYNC_SSYNC_MASK 0x3fffffff +#define HDAC_SSYNC_SSYNC_SHIFT 0 + +/* CORBWP - CORB Write Pointer */ +#define HDAC_CORBWP_CORBWP_MASK 0x00ff +#define HDAC_CORBWP_CORBWP_SHIFT 0 + +/* CORBRP - CORB Read Pointer */ +#define HDAC_CORBRP_CORBRP_MASK 0x00ff +#define HDAC_CORBRP_CORBRP_SHIFT 0 +#define HDAC_CORBRP_CORBRPRST 0x8000 + +/* CORBCTL - CORB Control */ +#define HDAC_CORBCTL_CMEIE 0x01 +#define HDAC_CORBCTL_CORBRUN 0x02 + +/* CORBSTS - CORB Status */ +#define HDAC_CORBSTS_CMEI 0x01 + +/* CORBSIZE - CORB Size */ +#define HDAC_CORBSIZE_CORBSIZE_MASK 0x03 +#define HDAC_CORBSIZE_CORBSIZE_SHIFT 0 +#define HDAC_CORBSIZE_CORBSZCAP_MASK 0xf0 +#define HDAC_CORBSIZE_CORBSZCAP_SHIFT 4 + +#define HDAC_CORBSIZE_CORBSIZE_2 0x00 +#define HDAC_CORBSIZE_CORBSIZE_16 0x01 +#define HDAC_CORBSIZE_CORBSIZE_256 0x02 + +#define HDAC_CORBSIZE_CORBSZCAP_2 0x10 +#define HDAC_CORBSIZE_CORBSZCAP_16 0x20 +#define HDAC_CORBSIZE_CORBSZCAP_256 0x40 + +#define HDAC_CORBSIZE_CORBSIZE(corbsize) \ + (((corbsize) & HDAC_CORBSIZE_CORBSIZE_MASK) >> HDAC_CORBSIZE_CORBSIZE_SHIFT) + +/* RIRBWP - RIRB Write Pointer */ +#define HDAC_RIRBWP_RIRBWP_MASK 0x00ff +#define HDAC_RIRBWP_RIRBWP_SHIFT 0 +#define HDAC_RIRBWP_RIRBWPRST 0x8000 + +/* RINTCTN - Response Interrupt Count */ +#define HDAC_RINTCNT_MASK 0x00ff +#define HDAC_RINTCNT_SHIFT 0 + +/* RIRBCTL - RIRB Control */ +#define HDAC_RIRBCTL_RINTCTL 0x01 +#define HDAC_RIRBCTL_RIRBDMAEN 0x02 +#define HDAC_RIRBCTL_RIRBOIC 0x04 + +/* RIRBSTS - RIRB Status */ +#define HDAC_RIRBSTS_RINTFL 0x01 +#define HDAC_RIRBSTS_RIRBOIS 0x04 + +/* RIRBSIZE - RIRB Size */ +#define HDAC_RIRBSIZE_RIRBSIZE_MASK 0x03 +#define HDAC_RIRBSIZE_RIRBSIZE_SHIFT 0 +#define HDAC_RIRBSIZE_RIRBSZCAP_MASK 0xf0 +#define HDAC_RIRBSIZE_RIRBSZCAP_SHIFT 4 + +#define HDAC_RIRBSIZE_RIRBSIZE_2 0x00 +#define HDAC_RIRBSIZE_RIRBSIZE_16 0x01 +#define HDAC_RIRBSIZE_RIRBSIZE_256 0x02 + +#define HDAC_RIRBSIZE_RIRBSZCAP_2 0x10 +#define HDAC_RIRBSIZE_RIRBSZCAP_16 0x20 +#define HDAC_RIRBSIZE_RIRBSZCAP_256 0x40 + +#define HDAC_RIRBSIZE_RIRBSIZE(rirbsize) \ + (((rirbsize) & HDAC_RIRBSIZE_RIRBSIZE_MASK) >> HDAC_RIRBSIZE_RIRBSIZE_SHIFT) + +/* DPLBASE - DMA Position Lower Base Address */ +#define HDAC_DPLBASE_DPLBASE_MASK 0xffffff80 +#define HDAC_DPLBASE_DPLBASE_SHIFT 7 +#define HDAC_DPLBASE_DPLBASE_DMAPBE 0x00000001 + +/* SDCTL - Stream Descriptor Control */ +#define HDAC_SDCTL_SRST 0x000001 +#define HDAC_SDCTL_RUN 0x000002 +#define HDAC_SDCTL_IOCE 0x000004 +#define HDAC_SDCTL_FEIE 0x000008 +#define HDAC_SDCTL_DEIE 0x000010 +#define HDAC_SDCTL_STRIPE_MASK 0x030000 +#define HDAC_SDCTL_STRIPE_SHIFT 16 +#define HDAC_SDCTL_TP 0x040000 +#define HDAC_SDCTL_DIR 0x080000 +#define HDAC_SDCTL2_STRM_MASK 0xf0 +#define HDAC_SDCTL2_STRM_SHIFT 4 + +#define HDAC_SDSTS_DESE (1 << 4) +#define HDAC_SDSTS_FIFOE (1 << 3) +#define HDAC_SDSTS_BCIS (1 << 2) + +#endif --- sys/dev/sound/pci/ich.c.orig Sat May 28 17:41:40 2005 +++ sys/dev/sound/pci/ich.c Thu Jul 12 12:04:19 2007 @@ -32,36 +32,152 @@ #include #include -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/ich.c,v 1.42.2.6 2005/05/28 09:41:40 tanimura Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/ich.c,v 1.78 2007/07/11 14:27:45 ariff Exp $"); /* -------------------------------------------------------------------- */ -#define ICH_TIMEOUT 1000 /* semaphore timeout polling count */ -#define ICH_DTBL_LENGTH 32 -#define ICH_DEFAULT_BUFSZ 16384 -#define ICH_MAX_BUFSZ 65536 - -#define SIS7012ID 0x70121039 /* SiS 7012 needs special handling */ -#define ICH4ID 0x24c58086 /* ICH4 needs special handling too */ -#define ICH5ID 0x24d58086 /* ICH5 needs to be treated as ICH4 */ -#define I6300ESBID 0x25a68086 /* 6300ESB needs to be treated as ICH4 */ -#define ICH6ID 0x266e8086 /* ICH6 needs to be treated as ICH4 */ +#define ICH_TIMEOUT 1000 /* semaphore timeout polling count */ +#define ICH_DTBL_LENGTH 32 +#define ICH_DEFAULT_BUFSZ 16384 +#define ICH_MAX_BUFSZ 65536 +#define ICH_MIN_BUFSZ 4096 +#define ICH_DEFAULT_BLKCNT 2 +#define ICH_MAX_BLKCNT 32 +#define ICH_MIN_BLKCNT 2 +#define ICH_MIN_BLKSZ 64 + +#define INTEL_VENDORID 0x8086 +#define SIS_VENDORID 0x1039 +#define NVIDIA_VENDORID 0x10de +#define AMD_VENDORID 0x1022 + +#define INTEL_82440MX 0x7195 +#define INTEL_82801AA 0x2415 +#define INTEL_82801AB 0x2425 +#define INTEL_82801BA 0x2445 +#define INTEL_82801CA 0x2485 +#define INTEL_82801DB 0x24c5 /* ICH4 needs special handling */ +#define INTEL_82801EB 0x24d5 /* ICH5 needs to be treated as ICH4 */ +#define INTEL_6300ESB 0x25a6 /* 6300ESB needs to be treated as ICH4 */ +#define INTEL_82801FB 0x266e /* ICH6 needs to be treated as ICH4 */ +#define INTEL_82801GB 0x27de /* ICH7 needs to be treated as ICH4 */ +#define SIS_7012 0x7012 /* SiS 7012 needs special handling */ +#define NVIDIA_NFORCE 0x01b1 +#define NVIDIA_NFORCE2 0x006a +#define NVIDIA_NFORCE2_400 0x008a +#define NVIDIA_NFORCE3 0x00da +#define NVIDIA_NFORCE3_250 0x00ea +#define NVIDIA_NFORCE4 0x0059 +#define NVIDIA_NFORCE_410_MCP 0x026b +#define NVIDIA_NFORCE4_MCP 0x003a +#define AMD_768 0x7445 +#define AMD_8111 0x746d + +#define ICH_LOCK(sc) snd_mtxlock((sc)->ich_lock) +#define ICH_UNLOCK(sc) snd_mtxunlock((sc)->ich_lock) +#define ICH_LOCK_ASSERT(sc) snd_mtxassert((sc)->ich_lock) + +#if 0 +#define ICH_DEBUG(stmt) do { \ + stmt \ +} while(0) +#else +#define ICH_DEBUG(...) +#endif + +#define ICH_CALIBRATE_DONE (1 << 0) +#define ICH_IGNORE_PCR (1 << 1) +#define ICH_IGNORE_RESET (1 << 2) +#define ICH_FIXED_RATE (1 << 3) +#define ICH_DMA_NOCACHE (1 << 4) +#define ICH_HIGH_LATENCY (1 << 5) + +#if defined(__i386__) || defined(__amd64__) +#if __FreeBSD_version < 602110 || defined(SND_USE_LPMAP) +#include +#endif +#endif + +#ifdef _LPMAP_C_ +#define ICH_BUS_DMA_NOCACHE 0 +#define ICH_DMA_ATTR(sc, v, s, attr) do { \ + vm_offset_t va = (vm_offset_t)(v); \ + vm_size_t sz = (vm_size_t)(s); \ + if ((sc) != NULL && ((sc)->flags & ICH_DMA_NOCACHE) && \ + va != 0 && sz != 0) \ + (void)lpmap_change_attr(va, sz, (attr)); \ +} while(0) +#else +#define ICH_BUS_DMA_NOCACHE BUS_DMA_NOCACHE +#define ICH_DMA_ATTR(...) +#endif + +static const struct ich_type { + uint16_t vendor; + uint16_t devid; + uint32_t options; +#define PROBE_LOW 0x01 + char *name; +} ich_devs[] = { + { INTEL_VENDORID, INTEL_82440MX, 0, + "Intel 440MX" }, + { INTEL_VENDORID, INTEL_82801AA, 0, + "Intel ICH (82801AA)" }, + { INTEL_VENDORID, INTEL_82801AB, 0, + "Intel ICH (82801AB)" }, + { INTEL_VENDORID, INTEL_82801BA, 0, + "Intel ICH2 (82801BA)" }, + { INTEL_VENDORID, INTEL_82801CA, 0, + "Intel ICH3 (82801CA)" }, + { INTEL_VENDORID, INTEL_82801DB, PROBE_LOW, + "Intel ICH4 (82801DB)" }, + { INTEL_VENDORID, INTEL_82801EB, PROBE_LOW, + "Intel ICH5 (82801EB)" }, + { INTEL_VENDORID, INTEL_6300ESB, PROBE_LOW, + "Intel 6300ESB" }, + { INTEL_VENDORID, INTEL_82801FB, PROBE_LOW, + "Intel ICH6 (82801FB)" }, + { INTEL_VENDORID, INTEL_82801GB, PROBE_LOW, + "Intel ICH7 (82801GB)" }, + { SIS_VENDORID, SIS_7012, 0, + "SiS 7012" }, + { NVIDIA_VENDORID, NVIDIA_NFORCE, 0, + "nVidia nForce" }, + { NVIDIA_VENDORID, NVIDIA_NFORCE2, 0, + "nVidia nForce2" }, + { NVIDIA_VENDORID, NVIDIA_NFORCE2_400, 0, + "nVidia nForce2 400" }, + { NVIDIA_VENDORID, NVIDIA_NFORCE3, 0, + "nVidia nForce3" }, + { NVIDIA_VENDORID, NVIDIA_NFORCE3_250, 0, + "nVidia nForce3 250" }, + { NVIDIA_VENDORID, NVIDIA_NFORCE4, 0, + "nVidia nForce4" }, + { NVIDIA_VENDORID, NVIDIA_NFORCE_410_MCP, 0, + "nVidia nForce 410 MCP" }, + { NVIDIA_VENDORID, NVIDIA_NFORCE4_MCP, 0, + "nVidia nForce 4 MCP" }, + { AMD_VENDORID, AMD_768, 0, + "AMD-768" }, + { AMD_VENDORID, AMD_8111, 0, + "AMD-8111" } +}; /* buffer descriptor */ struct ich_desc { - volatile u_int32_t buffer; - volatile u_int32_t length; + volatile uint32_t buffer; + volatile uint32_t length; }; struct sc_info; /* channel registers */ struct sc_chinfo { - u_int32_t num:8, run:1, run_save:1; - u_int32_t blksz, blkcnt, spd; - u_int32_t regbase, spdreg; - u_int32_t imask; - u_int32_t civ; + uint32_t num:8, run:1, run_save:1; + uint32_t blksz, blkcnt, spd; + uint32_t regbase, spdreg; + uint32_t imask; + uint32_t civ; struct snd_dbuf *buffer; struct pcm_channel *channel; @@ -75,14 +191,14 @@ struct sc_info { device_t dev; int hasvra, hasvrm, hasmic; - unsigned int chnum, bufsz; + unsigned int chnum, bufsz, blkcnt; int sample_size, swap_reg; struct resource *nambar, *nabmbar, *irq; int regtype, nambarid, nabmbarid, irqid; bus_space_tag_t nambart, nabmbart; bus_space_handle_t nambarh, nabmbarh; - bus_dma_tag_t dmat; + bus_dma_tag_t dmat, chan_dmat; bus_dmamap_t dtmap; void *ih; @@ -90,14 +206,18 @@ struct sc_chinfo ch[3]; int ac97rate; struct ich_desc *dtbl; + unsigned int dtbl_size; bus_addr_t desc_addr; struct intr_config_hook intrhook; - int use_intrhook; + uint16_t vendor; + uint16_t devid; + uint32_t flags; + struct mtx *ich_lock; }; /* -------------------------------------------------------------------- */ -static u_int32_t ich_fmt[] = { +static uint32_t ich_fmt[] = { AFMT_STEREO | AFMT_S16_LE, 0 }; @@ -106,23 +226,23 @@ /* -------------------------------------------------------------------- */ /* Hardware */ -static u_int32_t +static __inline uint32_t ich_rd(struct sc_info *sc, int regno, int size) { switch (size) { case 1: - return bus_space_read_1(sc->nabmbart, sc->nabmbarh, regno); + return (bus_space_read_1(sc->nabmbart, sc->nabmbarh, regno)); case 2: - return bus_space_read_2(sc->nabmbart, sc->nabmbarh, regno); + return (bus_space_read_2(sc->nabmbart, sc->nabmbarh, regno)); case 4: - return bus_space_read_4(sc->nabmbart, sc->nabmbarh, regno); + return (bus_space_read_4(sc->nabmbart, sc->nabmbarh, regno)); default: - return 0xffffffff; + return (0xffffffff); } } -static void -ich_wr(struct sc_info *sc, int regno, u_int32_t data, int size) +static __inline void +ich_wr(struct sc_info *sc, int regno, uint32_t data, int size) { switch (size) { case 1: @@ -141,17 +261,20 @@ static int ich_waitcd(void *devinfo) { - int i; - u_int32_t data; struct sc_info *sc = (struct sc_info *)devinfo; + uint32_t data; + int i; for (i = 0; i < ICH_TIMEOUT; i++) { data = ich_rd(sc, ICH_REG_ACC_SEMA, 1); if ((data & 0x01) == 0) - return 0; + return (0); + DELAY(1); } + if ((sc->flags & ICH_IGNORE_PCR) != 0) + return (0); device_printf(sc->dev, "CODEC semaphore timeout\n"); - return ETIMEDOUT; + return (ETIMEDOUT); } static int @@ -162,11 +285,11 @@ regno &= 0xff; ich_waitcd(sc); - return bus_space_read_2(sc->nambart, sc->nambarh, regno); + return (bus_space_read_2(sc->nambart, sc->nambarh, regno)); } static int -ich_wrcd(kobj_t obj, void *devinfo, int regno, u_int16_t data) +ich_wrcd(kobj_t obj, void *devinfo, int regno, uint16_t data) { struct sc_info *sc = (struct sc_info *)devinfo; @@ -174,7 +297,7 @@ ich_waitcd(sc); bus_space_write_2(sc->nambart, sc->nambarh, regno, data); - return 0; + return (0); } static kobj_method_t ich_ac97_methods[] = { @@ -190,15 +313,19 @@ static void ich_filldtbl(struct sc_chinfo *ch) { - u_int32_t base; + struct sc_info *sc = ch->parent; + uint32_t base; int i; base = sndbuf_getbufaddr(ch->buffer); - ch->blkcnt = sndbuf_getsize(ch->buffer) / ch->blksz; - if (ch->blkcnt != 2 && ch->blkcnt != 4 && ch->blkcnt != 8 && ch->blkcnt != 16 && ch->blkcnt != 32) { - ch->blkcnt = 2; - ch->blksz = sndbuf_getsize(ch->buffer) / ch->blkcnt; - } + if ((ch->blksz * ch->blkcnt) > sndbuf_getmaxsize(ch->buffer)) + ch->blksz = sndbuf_getmaxsize(ch->buffer) / ch->blkcnt; + if ((sndbuf_getblksz(ch->buffer) != ch->blksz || + sndbuf_getblkcnt(ch->buffer) != ch->blkcnt) && + sndbuf_resize(ch->buffer, ch->blkcnt, ch->blksz) != 0) + device_printf(sc->dev, "%s: failed blksz=%u blkcnt=%u\n", + __func__, ch->blksz, ch->blkcnt); + ch->blksz = sndbuf_getblksz(ch->buffer); for (i = 0; i < ICH_DTBL_LENGTH; i++) { ch->dtbl[i].buffer = base + (ch->blksz * (i % ch->blkcnt)); @@ -219,19 +346,35 @@ else if (num == 2) regbase = ICH_REG_MC_BASE; else - return ENXIO; + return (ENXIO); ich_wr(sc, regbase + ICH_REG_X_CR, 0, 1); +#if 1 + /* This may result in no sound output on NForce 2 MBs, see PR 73987 */ DELAY(100); +#else + (void)ich_rd(sc, regbase + ICH_REG_X_CR, 1); +#endif ich_wr(sc, regbase + ICH_REG_X_CR, ICH_X_CR_RR, 1); for (i = 0; i < ICH_TIMEOUT; i++) { cr = ich_rd(sc, regbase + ICH_REG_X_CR, 1); if (cr == 0) - return 0; + return (0); + DELAY(1); + } + + if (sc->flags & ICH_IGNORE_RESET) + return (0); +#if 0 + else if (sc->vendor == NVIDIA_VENDORID) { + sc->flags |= ICH_IGNORE_RESET; + device_printf(sc->dev, "ignoring reset failure!\n"); + return (0); } +#endif device_printf(sc->dev, "cannot reset channel %d\n", num); - return ENXIO; + return (ENXIO); } /* -------------------------------------------------------------------- */ @@ -244,6 +387,7 @@ struct sc_chinfo *ch; unsigned int num; + ICH_LOCK(sc); num = sc->chnum++; ch = &sc->ch[num]; ch->num = num; @@ -252,85 +396,150 @@ ch->parent = sc; ch->run = 0; ch->dtbl = sc->dtbl + (ch->num * ICH_DTBL_LENGTH); - ch->desc_addr = sc->desc_addr + (ch->num * ICH_DTBL_LENGTH) * - sizeof(struct ich_desc); - ch->blkcnt = 2; + ch->desc_addr = sc->desc_addr + + (ch->num * ICH_DTBL_LENGTH * sizeof(struct ich_desc)); + ch->blkcnt = sc->blkcnt; ch->blksz = sc->bufsz / ch->blkcnt; switch(ch->num) { case 0: /* play */ KASSERT(dir == PCMDIR_PLAY, ("wrong direction")); ch->regbase = ICH_REG_PO_BASE; - ch->spdreg = sc->hasvra? AC97_REGEXT_FDACRATE : 0; + ch->spdreg = (sc->hasvra) ? AC97_REGEXT_FDACRATE : 0; ch->imask = ICH_GLOB_STA_POINT; break; case 1: /* record */ KASSERT(dir == PCMDIR_REC, ("wrong direction")); ch->regbase = ICH_REG_PI_BASE; - ch->spdreg = sc->hasvra? AC97_REGEXT_LADCRATE : 0; + ch->spdreg = (sc->hasvra) ? AC97_REGEXT_LADCRATE : 0; ch->imask = ICH_GLOB_STA_PIINT; break; case 2: /* mic */ KASSERT(dir == PCMDIR_REC, ("wrong direction")); ch->regbase = ICH_REG_MC_BASE; - ch->spdreg = sc->hasvrm? AC97_REGEXT_MADCRATE : 0; + ch->spdreg = (sc->hasvrm) ? AC97_REGEXT_MADCRATE : 0; ch->imask = ICH_GLOB_STA_MINT; break; default: - return NULL; + return (NULL); } - if (sndbuf_alloc(ch->buffer, sc->dmat, sc->bufsz) != 0) - return NULL; + if (sc->flags & ICH_FIXED_RATE) + ch->spdreg = 0; + + ICH_UNLOCK(sc); + if (sndbuf_alloc(ch->buffer, sc->chan_dmat, + ((sc->flags & ICH_DMA_NOCACHE) ? ICH_BUS_DMA_NOCACHE : 0), + sc->bufsz) != 0) + return (NULL); + + ICH_DMA_ATTR(sc, sndbuf_getbuf(ch->buffer), + sndbuf_getmaxsize(ch->buffer), PAT_UNCACHEABLE); - ich_wr(sc, ch->regbase + ICH_REG_X_BDBAR, (u_int32_t)(ch->desc_addr), 4); + ICH_LOCK(sc); + ich_wr(sc, ch->regbase + ICH_REG_X_BDBAR, (uint32_t)(ch->desc_addr), 4); + ICH_UNLOCK(sc); - return ch; + return (ch); } +#ifdef _LPMAP_C_ static int -ichchan_setformat(kobj_t obj, void *data, u_int32_t format) +ichchan_free(kobj_t obj, void *data) { - return 0; + struct sc_chinfo *ch; + struct sc_info *sc; + + ch = (struct sc_chinfo *)data; + sc = (ch != NULL) ? ch->parent : NULL; + if (ch != NULL && sc != NULL) { + ICH_DMA_ATTR(sc, sndbuf_getbuf(ch->buffer), + sndbuf_getmaxsize(ch->buffer), PAT_WRITE_BACK); + } + + return (1); } +#endif static int -ichchan_setspeed(kobj_t obj, void *data, u_int32_t speed) +ichchan_setformat(kobj_t obj, void *data, uint32_t format) +{ + + ICH_DEBUG( + struct sc_chinfo *ch = data; + struct sc_info *sc = ch->parent; + if (!(sc->flags & ICH_CALIBRATE_DONE)) + device_printf(sc->dev, + "WARNING: %s() called before calibration!\n", + __func__); + ); + + return (0); +} + +static int +ichchan_setspeed(kobj_t obj, void *data, uint32_t speed) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; + ICH_DEBUG( + if (!(sc->flags & ICH_CALIBRATE_DONE)) + device_printf(sc->dev, + "WARNING: %s() called before calibration!\n", + __func__); + ); + if (ch->spdreg) { - int r; + int r, ac97rate; + + ICH_LOCK(sc); if (sc->ac97rate <= 32000 || sc->ac97rate >= 64000) sc->ac97rate = 48000; - r = (speed * 48000) / sc->ac97rate; + ac97rate = sc->ac97rate; + ICH_UNLOCK(sc); + r = (speed * 48000) / ac97rate; /* - * Cast the return value of ac97_setrate() to u_int so that + * Cast the return value of ac97_setrate() to uint64 so that * the math don't overflow into the negative range. */ - ch->spd = ((u_int)ac97_setrate(sc->codec, ch->spdreg, r) * - sc->ac97rate) / 48000; + ch->spd = ((uint64_t)ac97_setrate(sc->codec, ch->spdreg, r) * + ac97rate) / 48000; } else { ch->spd = 48000; } - return ch->spd; + return (ch->spd); } static int -ichchan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) +ichchan_setblocksize(kobj_t obj, void *data, uint32_t blocksize) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; + ICH_DEBUG( + if (!(sc->flags & ICH_CALIBRATE_DONE)) + device_printf(sc->dev, + "WARNING: %s() called before calibration!\n", + __func__); + ); + + if (sc->flags & ICH_HIGH_LATENCY) + blocksize = sndbuf_getmaxsize(ch->buffer) / ch->blkcnt; + + if (blocksize < ICH_MIN_BLKSZ) + blocksize = ICH_MIN_BLKSZ; + blocksize &= ~(ICH_MIN_BLKSZ - 1); ch->blksz = blocksize; ich_filldtbl(ch); + ICH_LOCK(sc); ich_wr(sc, ch->regbase + ICH_REG_X_LVI, ch->blkcnt - 1, 1); + ICH_UNLOCK(sc); - return ch->blksz; + return (ch->blksz); } static int @@ -339,19 +548,32 @@ struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; + ICH_DEBUG( + if (!(sc->flags & ICH_CALIBRATE_DONE)) + device_printf(sc->dev, + "WARNING: %s() called before calibration!\n", + __func__); + ); + switch (go) { case PCMTRIG_START: ch->run = 1; - ich_wr(sc, ch->regbase + ICH_REG_X_BDBAR, (u_int32_t)(ch->desc_addr), 4); + ICH_LOCK(sc); + ich_wr(sc, ch->regbase + ICH_REG_X_BDBAR, (uint32_t)(ch->desc_addr), 4); ich_wr(sc, ch->regbase + ICH_REG_X_CR, ICH_X_CR_RPBM | ICH_X_CR_LVBIE | ICH_X_CR_IOCE, 1); + ICH_UNLOCK(sc); break; - + case PCMTRIG_STOP: case PCMTRIG_ABORT: + ICH_LOCK(sc); ich_resetchan(sc, ch->num); + ICH_UNLOCK(sc); ch->run = 0; break; + default: + break; } - return 0; + return (0); } static int @@ -359,13 +581,22 @@ { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; - u_int32_t pos; + uint32_t pos; + ICH_DEBUG( + if (!(sc->flags & ICH_CALIBRATE_DONE)) + device_printf(sc->dev, + "WARNING: %s() called before calibration!\n", + __func__); + ); + + ICH_LOCK(sc); ch->civ = ich_rd(sc, ch->regbase + ICH_REG_X_CIV, 1) % ch->blkcnt; + ICH_UNLOCK(sc); pos = ch->civ * ch->blksz; - return pos; + return (pos); } static struct pcmchan_caps * @@ -373,11 +604,23 @@ { struct sc_chinfo *ch = data; - return ch->spdreg? &ich_vrcaps : &ich_caps; + ICH_DEBUG( + struct sc_info *sc = ch->parent; + + if (!(sc->flags & ICH_CALIBRATE_DONE)) + device_printf(ch->parent->dev, + "WARNING: %s() called before calibration!\n", + __func__); + ); + + return ((ch->spdreg) ? &ich_vrcaps : &ich_caps); } static kobj_method_t ichchan_methods[] = { KOBJMETHOD(channel_init, ichchan_init), +#ifdef _LPMAP_C_ + KOBJMETHOD(channel_free, ichchan_free), +#endif KOBJMETHOD(channel_setformat, ichchan_setformat), KOBJMETHOD(channel_setspeed, ichchan_setspeed), KOBJMETHOD(channel_setblocksize, ichchan_setblocksize), @@ -396,9 +639,18 @@ { struct sc_info *sc = (struct sc_info *)p; struct sc_chinfo *ch; - u_int32_t cbi, lbi, lvi, st, gs; + uint32_t cbi, lbi, lvi, st, gs; int i; + ICH_LOCK(sc); + + ICH_DEBUG( + if (!(sc->flags & ICH_CALIBRATE_DONE)) + device_printf(sc->dev, + "WARNING: %s() called before calibration!\n", + __func__); + ); + gs = ich_rd(sc, ICH_REG_GLOB_STA, 4) & ICH_GLOB_STA_IMASK; if (gs & (ICH_GLOB_STA_PRES | ICH_GLOB_STA_SRES)) { /* Clear resume interrupt(s) - nothing doing with them */ @@ -412,13 +664,16 @@ continue; gs &= ~ch->imask; st = ich_rd(sc, ch->regbase + - (sc->swap_reg ? ICH_REG_X_PICB : ICH_REG_X_SR), + ((sc->swap_reg) ? ICH_REG_X_PICB : ICH_REG_X_SR), 2); st &= ICH_X_SR_FIFOE | ICH_X_SR_BCIS | ICH_X_SR_LVBCI; if (st & (ICH_X_SR_BCIS | ICH_X_SR_LVBCI)) { /* block complete - update buffer */ - if (ch->run) + if (ch->run) { + ICH_UNLOCK(sc); chn_intr(ch->channel); + ICH_LOCK(sc); + } lvi = ich_rd(sc, ch->regbase + ICH_REG_X_LVI, 1); cbi = ch->civ % ch->blkcnt; if (cbi == 0) @@ -436,9 +691,10 @@ } /* clear status bit */ ich_wr(sc, ch->regbase + - (sc->swap_reg ? ICH_REG_X_PICB : ICH_REG_X_SR), + ((sc->swap_reg) ? ICH_REG_X_PICB : ICH_REG_X_SR), st, 2); } + ICH_UNLOCK(sc); if (gs != 0) { device_printf(sc->dev, "Unhandled interrupt, gs_intr = %x\n", gs); @@ -454,13 +710,33 @@ ich_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: this should move 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, "ac97rate", CTLFLAG_RW, &sc->ac97rate, 48000, "AC97 link rate (default = 48000)"); #endif /* SND_DYNSYSCTL */ - return 0; + return (0); +} + +static void +ich_setstatus(struct sc_info *sc) +{ + char status[SND_STATUSLEN]; + + snprintf(status, SND_STATUSLEN, + "at io 0x%lx, 0x%lx irq %ld bufsz %u %s", + rman_get_start(sc->nambar), rman_get_start(sc->nabmbar), + rman_get_start(sc->irq), sc->bufsz,PCM_KLDSTRING(snd_ich)); + + if (bootverbose && (sc->flags & ICH_DMA_NOCACHE)) + device_printf(sc->dev, + "PCI Master abort workaround enabled\n"); + + pcm_setstatus(sc->dev, status); } /* -------------------------------------------------------------------- */ @@ -468,20 +744,23 @@ * function of the ac97 codec initialization code (to be investigated). */ -static -void ich_calibrate(void *arg) +static void +ich_calibrate(void *arg) { struct sc_info *sc; struct sc_chinfo *ch; struct timeval t1, t2; - u_int8_t ociv, nciv; - u_int32_t wait_us, actual_48k_rate, bytes; + uint8_t ociv, nciv; + uint32_t wait_us, actual_48k_rate, oblkcnt; sc = (struct sc_info *)arg; + ICH_LOCK(sc); ch = &sc->ch[1]; - if (sc->use_intrhook) + if (sc->intrhook.ich_func != NULL) { config_intrhook_disestablish(&sc->intrhook); + sc->intrhook.ich_func = NULL; + } /* * Grab audio from input for fixed interval and compare how @@ -492,8 +771,13 @@ KASSERT(ch->regbase == ICH_REG_PI_BASE, ("wrong direction")); - bytes = sndbuf_getsize(ch->buffer) / 2; - ichchan_setblocksize(0, ch, bytes); + oblkcnt = ch->blkcnt; + ch->blkcnt = 2; + sc->flags |= ICH_CALIBRATE_DONE; + ICH_UNLOCK(sc); + ichchan_setblocksize(0, ch, sndbuf_getmaxsize(ch->buffer) >> 1); + ICH_LOCK(sc); + sc->flags &= ~ICH_CALIBRATE_DONE; /* * our data format is stereo, 16 bit so each sample is 4 bytes. @@ -510,20 +794,19 @@ /* prepare */ ociv = ich_rd(sc, ch->regbase + ICH_REG_X_CIV, 1); nciv = ociv; - ich_wr(sc, ch->regbase + ICH_REG_X_BDBAR, (u_int32_t)(ch->desc_addr), 4); + ich_wr(sc, ch->regbase + ICH_REG_X_BDBAR, (uint32_t)(ch->desc_addr), 4); /* start */ microtime(&t1); ich_wr(sc, ch->regbase + ICH_REG_X_CR, ICH_X_CR_RPBM, 1); /* wait */ - while (nciv == ociv) { + do { microtime(&t2); if (t2.tv_sec - t1.tv_sec > 1) break; nciv = ich_rd(sc, ch->regbase + ICH_REG_X_CIV, 1); - } - microtime(&t2); + } while (nciv == ociv); /* stop */ ich_wr(sc, ch->regbase + ICH_REG_X_CR, 0, 1); @@ -531,16 +814,24 @@ /* reset */ DELAY(100); ich_wr(sc, ch->regbase + ICH_REG_X_CR, ICH_X_CR_RR, 1); + ch->blkcnt = oblkcnt; /* turn time delta into us */ wait_us = ((t2.tv_sec - t1.tv_sec) * 1000000) + t2.tv_usec - t1.tv_usec; if (nciv == ociv) { device_printf(sc->dev, "ac97 link rate calibration timed out after %d us\n", wait_us); + sc->flags |= ICH_CALIBRATE_DONE; + ICH_UNLOCK(sc); + ich_setstatus(sc); return; } - actual_48k_rate = (bytes * 250000) / wait_us; + /* Just in case the timecounter screwed. It is possible, really. */ + if (wait_us > 0) + actual_48k_rate = ((uint64_t)ch->blksz * 250000) / wait_us; + else + actual_48k_rate = 48000; if (actual_48k_rate < 47500 || actual_48k_rate > 48500) { sc->ac97rate = actual_48k_rate; @@ -554,6 +845,10 @@ printf(", will use %d Hz", sc->ac97rate); printf("\n"); } + sc->flags |= ICH_CALIBRATE_DONE; + ICH_UNLOCK(sc); + + ich_setstatus(sc); return; } @@ -572,8 +867,7 @@ static int ich_init(struct sc_info *sc) { - u_int32_t stat; - int sz; + uint32_t stat; ich_wr(sc, ICH_REG_GLOB_CNT, ICH_GLOB_CTL_COLD, 4); DELAY(600000); @@ -581,134 +875,71 @@ if ((stat & ICH_GLOB_STA_PCR) == 0) { /* ICH4/ICH5 may fail when busmastering is enabled. Continue */ - if ((pci_get_devid(sc->dev) != ICH4ID) && - (pci_get_devid(sc->dev) != ICH5ID) && - (pci_get_devid(sc->dev) != I6300ESBID) && - (pci_get_devid(sc->dev) != ICH6ID)) { - return ENXIO; + if (sc->vendor == INTEL_VENDORID && ( + sc->devid == INTEL_82801DB || sc->devid == INTEL_82801EB || + sc->devid == INTEL_6300ESB || sc->devid == INTEL_82801FB || + sc->devid == INTEL_82801GB)) { + sc->flags |= ICH_IGNORE_PCR; + device_printf(sc->dev, "primary codec not ready!\n"); } } +#if 0 ich_wr(sc, ICH_REG_GLOB_CNT, ICH_GLOB_CTL_COLD | ICH_GLOB_CTL_PRES, 4); +#else + ich_wr(sc, ICH_REG_GLOB_CNT, ICH_GLOB_CTL_COLD, 4); +#endif if (ich_resetchan(sc, 0) || ich_resetchan(sc, 1)) - return ENXIO; + return (ENXIO); if (sc->hasmic && ich_resetchan(sc, 2)) - return ENXIO; - - if (bus_dmamem_alloc(sc->dmat, (void **)&sc->dtbl, BUS_DMA_NOWAIT, &sc->dtmap)) - return ENOSPC; - - sz = sizeof(struct ich_desc) * ICH_DTBL_LENGTH * 3; - if (bus_dmamap_load(sc->dmat, sc->dtmap, sc->dtbl, sz, ich_setmap, sc, 0)) { - bus_dmamem_free(sc->dmat, (void **)&sc->dtbl, sc->dtmap); - return ENOSPC; - } + return (ENXIO); - return 0; + return (0); } static int ich_pci_probe(device_t dev) { - switch(pci_get_devid(dev)) { - case 0x71958086: - device_set_desc(dev, "Intel 443MX"); - return 0; - - case 0x24158086: - device_set_desc(dev, "Intel ICH (82801AA)"); - return 0; - - case 0x24258086: - device_set_desc(dev, "Intel ICH (82801AB)"); - return 0; - - case 0x24458086: - device_set_desc(dev, "Intel ICH2 (82801BA)"); - return 0; - - case 0x24858086: - device_set_desc(dev, "Intel ICH3 (82801CA)"); - return 0; - - case ICH4ID: - device_set_desc(dev, "Intel ICH4 (82801DB)"); - return -1000; /* allow a better driver to override us */ - - case ICH5ID: - device_set_desc(dev, "Intel ICH5 (82801EB)"); - return -1000; /* allow a better driver to override us */ - - case I6300ESBID: - device_set_desc(dev, "Intel 6300ESB"); - return -1000; /* allow a better driver to override us */ - - case ICH6ID: - device_set_desc(dev, "Intel ICH6 (82801FB)"); - return -1000; /* allow a better driver to override us */ - - case SIS7012ID: - device_set_desc(dev, "SiS 7012"); - return 0; - - case 0x01b110de: - device_set_desc(dev, "nVidia nForce"); - return 0; - - case 0x006a10de: - device_set_desc(dev, "nVidia nForce2"); - return 0; - - case 0x008a10de: - device_set_desc(dev, "nVidia nForce2 400"); - return 0; - - case 0x00da10de: - device_set_desc(dev, "nVidia nForce3"); - return 0; - - case 0x00ea10de: - device_set_desc(dev, "nVidia nForce3 250"); - return 0; - - case 0x005910de: - device_set_desc(dev, "nVidia nForce4"); - return 0; - - case 0x74451022: - device_set_desc(dev, "AMD-768"); - return 0; - - case 0x746d1022: - device_set_desc(dev, "AMD-8111"); - return 0; + int i; + uint16_t devid, vendor; - default: - return ENXIO; + vendor = pci_get_vendor(dev); + devid = pci_get_device(dev); + for (i = 0; i < sizeof(ich_devs)/sizeof(ich_devs[0]); i++) { + if (vendor == ich_devs[i].vendor && + devid == ich_devs[i].devid) { + device_set_desc(dev, ich_devs[i].name); + /* allow a better driver to override us */ + if ((ich_devs[i].options & PROBE_LOW) != 0) + return (BUS_PROBE_LOW_PRIORITY); + return (BUS_PROBE_DEFAULT); + } } + return (ENXIO); } static int ich_pci_attach(device_t dev) { - u_int16_t extcaps; + uint32_t subdev; + uint16_t extcaps; + uint16_t devid, vendor; struct sc_info *sc; - char status[SND_STATUSLEN]; + int i; - if ((sc = malloc(sizeof(*sc), M_DEVBUF, M_NOWAIT)) == NULL) { - device_printf(dev, "cannot allocate softc\n"); - return ENXIO; - } - - bzero(sc, sizeof(*sc)); + sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO); + sc->ich_lock = snd_mtxcreate(device_get_nameunit(dev), "snd_ich softc"); sc->dev = dev; + vendor = sc->vendor = pci_get_vendor(dev); + devid = sc->devid = pci_get_device(dev); + subdev = (pci_get_subdevice(dev) << 16) | pci_get_subvendor(dev); /* * The SiS 7012 register set isn't quite like the standard ich. * There really should be a general "quirks" mechanism. */ - if (pci_get_devid(dev) == SIS7012ID) { + if (vendor == SIS_VENDORID && devid == SIS_7012) { sc->swap_reg = 1; sc->sample_size = 1; } else { @@ -717,15 +948,13 @@ } /* - * By default, ich4 has NAMBAR and NABMBAR i/o spaces as - * read-only. Need to enable "legacy support", by poking into - * pci config space. The driver should use MMBAR and MBBAR, - * but doing so will mess things up here. ich4 has enough new - * features it warrants it's own driver. + * Intel 440MX Errata #36 + * - AC97 Soft Audio and Soft Modem Master Abort Errata + * + * http://www.intel.com/design/chipsets/specupdt/245051.htm */ - if (pci_get_devid(dev) == ICH4ID) { - pci_write_config(dev, PCIR_ICH_LEGACY, ICH_LEGACY_ENABLE, 1); - } + if (vendor == INTEL_VENDORID && devid == INTEL_82440MX) + sc->flags |= ICH_DMA_NOCACHE; /* * Enable bus master. On ich4/5 this may prevent the detection of @@ -733,12 +962,20 @@ */ pci_enable_busmaster(dev); - if (pci_get_devid(dev) == ICH5ID || - pci_get_devid(dev) == I6300ESBID || - pci_get_devid(dev) == ICH6ID) { + /* + * By default, ich4 has NAMBAR and NABMBAR i/o spaces as + * read-only. Need to enable "legacy support", by poking into + * pci config space. The driver should use MMBAR and MBBAR, + * but doing so will mess things up here. ich4 has enough new + * features it warrants it's own driver. + */ + if (vendor == INTEL_VENDORID && (devid == INTEL_82801DB || + devid == INTEL_82801EB || devid == INTEL_6300ESB || + devid == INTEL_82801FB || devid == INTEL_82801GB)) { sc->nambarid = PCIR_MMBAR; sc->nabmbarid = PCIR_MBBAR; sc->regtype = SYS_RES_MEMORY; + pci_write_config(dev, PCIR_ICH_LEGACY, ICH_LEGACY_ENABLE, 1); } else { sc->nambarid = PCIR_NAMBAR; sc->nabmbarid = PCIR_NABMBAR; @@ -760,18 +997,38 @@ sc->nabmbart = rman_get_bustag(sc->nabmbar); sc->nabmbarh = rman_get_bushandle(sc->nabmbar); - sc->bufsz = pcm_getbuffersize(dev, 4096, ICH_DEFAULT_BUFSZ, ICH_MAX_BUFSZ); - if (bus_dma_tag_create(NULL, 8, 0, BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, - NULL, NULL, sc->bufsz, 1, 0x3ffff, 0, - busdma_lock_mutex, &Giant, &sc->dmat) != 0) { - device_printf(dev, "unable to create dma tag\n"); - goto bad; - } + sc->bufsz = pcm_getbuffersize(dev, + ICH_MIN_BUFSZ, ICH_DEFAULT_BUFSZ, ICH_MAX_BUFSZ); + + if (resource_int_value(device_get_name(dev), + device_get_unit(dev), "blocksize", &i) == 0 && i > 0) { + sc->blkcnt = sc->bufsz / i; + i = 0; + while (sc->blkcnt >> i) + i++; + sc->blkcnt = 1 << (i - 1); + if (sc->blkcnt < ICH_MIN_BLKCNT) + sc->blkcnt = ICH_MIN_BLKCNT; + else if (sc->blkcnt > ICH_MAX_BLKCNT) + sc->blkcnt = ICH_MAX_BLKCNT; + } else + sc->blkcnt = ICH_DEFAULT_BLKCNT; + + if (resource_int_value(device_get_name(dev), + device_get_unit(dev), "highlatency", &i) == 0 && i != 0) { + sc->flags |= ICH_HIGH_LATENCY; + sc->blkcnt = ICH_MIN_BLKCNT; + } + + if (resource_int_value(device_get_name(dev), + device_get_unit(dev), "fixedrate", &i) == 0 && i != 0) + sc->flags |= ICH_FIXED_RATE; 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, ich_intr, sc, &sc->ih)) { + RF_ACTIVE | RF_SHAREABLE); + if (!sc->irq || snd_setup_intr(dev, sc->irq, INTR_MPSAFE, ich_intr, + sc, &sc->ih)) { device_printf(dev, "unable to map interrupt\n"); goto bad; } @@ -784,6 +1041,29 @@ sc->codec = AC97_CREATE(dev, sc, ich_ac97); if (sc->codec == NULL) goto bad; + + /* + * Turn on inverted external amplifier sense flags for few + * 'special' boards. + */ + switch (subdev) { + case 0x202f161f: /* Gateway 7326GZ */ + case 0x203a161f: /* Gateway 4028GZ */ + case 0x204c161f: /* Kvazar-Micro Senator 3592XT */ + case 0x8144104d: /* Sony VAIO PCG-TR* */ + case 0x8197104d: /* Sony S1XP */ + case 0x81c0104d: /* Sony VAIO type T */ + case 0x81c5104d: /* Sony VAIO VGN B1VP/B1XP */ + case 0x3089103c: /* Compaq Presario B3800 */ + case 0x309a103c: /* HP Compaq nx4300 */ + case 0x82131033: /* NEC VersaPro VJ10F/BH */ + case 0x82be1033: /* NEC VersaPro VJ12F/CH */ + ac97_setflags(sc->codec, ac97_getflags(sc->codec) | AC97_F_EAPD_INV); + break; + default: + break; + } + mixer_init(dev, ac97_getmixerclass(), sc->codec); /* check and set VRA function */ @@ -793,7 +1073,37 @@ sc->hasmic = ac97_getcaps(sc->codec) & AC97_CAP_MICCHANNEL; ac97_setextmode(sc->codec, sc->hasvra | sc->hasvrm); - if (pcm_register(dev, sc, 1, sc->hasmic? 2 : 1)) + sc->dtbl_size = sizeof(struct ich_desc) * ICH_DTBL_LENGTH * + ((sc->hasmic) ? 3 : 2); + + /* BDL tag */ + if (bus_dma_tag_create(bus_get_dma_tag(dev), 8, 0, + BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL, + sc->dtbl_size, 1, 0x3ffff, 0, NULL, NULL, &sc->dmat) != 0) { + device_printf(dev, "unable to create dma tag\n"); + goto bad; + } + + /* PCM channel tag */ + if (bus_dma_tag_create(bus_get_dma_tag(dev), ICH_MIN_BLKSZ, 0, + BUS_SPACE_MAXADDR_32BIT, BUS_SPACE_MAXADDR, NULL, NULL, + sc->bufsz, 1, 0x3ffff, 0, NULL, NULL, &sc->chan_dmat) != 0) { + device_printf(dev, "unable to create dma tag\n"); + goto bad; + } + + if (bus_dmamem_alloc(sc->dmat, (void **)&sc->dtbl, BUS_DMA_NOWAIT | + ((sc->flags & ICH_DMA_NOCACHE) ? ICH_BUS_DMA_NOCACHE : 0), + &sc->dtmap)) + goto bad; + + ICH_DMA_ATTR(sc, sc->dtbl, sc->dtbl_size, PAT_UNCACHEABLE); + + if (bus_dmamap_load(sc->dmat, sc->dtmap, sc->dtbl, sc->dtbl_size, + ich_setmap, sc, 0)) + goto bad; + + if (pcm_register(dev, sc, 1, (sc->hasmic) ? 2 : 1)) goto bad; pcm_addchan(dev, PCMDIR_PLAY, &ichchan_class, sc); /* play */ @@ -801,23 +1111,22 @@ if (sc->hasmic) pcm_addchan(dev, PCMDIR_REC, &ichchan_class, sc); /* record mic */ - snprintf(status, SND_STATUSLEN, "at io 0x%lx, 0x%lx irq %ld bufsz %u %s", - rman_get_start(sc->nambar), rman_get_start(sc->nabmbar), rman_get_start(sc->irq), sc->bufsz,PCM_KLDSTRING(snd_ich)); - - pcm_setstatus(dev, status); - - ich_initsys(sc); + if (sc->flags & ICH_FIXED_RATE) { + sc->flags |= ICH_CALIBRATE_DONE; + ich_setstatus(sc); + } else { + ich_initsys(sc); - sc->intrhook.ich_func = ich_calibrate; - sc->intrhook.ich_arg = sc; - sc->use_intrhook = 1; - if (config_intrhook_establish(&sc->intrhook) != 0) { - device_printf(dev, "Cannot establish calibration hook, will calibrate now\n"); - sc->use_intrhook = 0; - ich_calibrate(sc); + sc->intrhook.ich_func = ich_calibrate; + sc->intrhook.ich_arg = sc; + if (cold == 0 || + config_intrhook_establish(&sc->intrhook) != 0) { + sc->intrhook.ich_func = NULL; + ich_calibrate(sc); + } } - return 0; + return (0); bad: if (sc->codec) @@ -832,8 +1141,20 @@ if (sc->nabmbar) bus_release_resource(dev, sc->regtype, sc->nabmbarid, sc->nabmbar); + if (sc->dtmap) + bus_dmamap_unload(sc->dmat, sc->dtmap); + if (sc->dtbl) { + ICH_DMA_ATTR(sc, sc->dtbl, sc->dtbl_size, PAT_WRITE_BACK); + bus_dmamem_free(sc->dmat, sc->dtbl, sc->dtmap); + } + if (sc->chan_dmat) + bus_dma_tag_destroy(sc->chan_dmat); + if (sc->dmat) + bus_dma_tag_destroy(sc->dmat); + if (sc->ich_lock) + snd_mtxfree(sc->ich_lock); free(sc, M_DEVBUF); - return ENXIO; + return (ENXIO); } static int @@ -844,16 +1165,21 @@ r = pcm_unregister(dev); if (r) - return r; + return (r); sc = pcm_getdevinfo(dev); bus_teardown_intr(dev, sc->irq, sc->ih); bus_release_resource(dev, SYS_RES_IRQ, sc->irqid, sc->irq); bus_release_resource(dev, sc->regtype, sc->nambarid, sc->nambar); bus_release_resource(dev, sc->regtype, sc->nabmbarid, sc->nabmbar); + bus_dmamap_unload(sc->dmat, sc->dtmap); + ICH_DMA_ATTR(sc, sc->dtbl, sc->dtbl_size, PAT_WRITE_BACK); + bus_dmamem_free(sc->dmat, sc->dtbl, sc->dtmap); + bus_dma_tag_destroy(sc->chan_dmat); bus_dma_tag_destroy(sc->dmat); + snd_mtxfree(sc->ich_lock); free(sc, M_DEVBUF); - return 0; + return (0); } static void @@ -885,13 +1211,17 @@ int i; sc = pcm_getdevinfo(dev); + ICH_LOCK(sc); for (i = 0 ; i < 3; i++) { sc->ch[i].run_save = sc->ch[i].run; if (sc->ch[i].run) { + ICH_UNLOCK(sc); ichchan_trigger(0, &sc->ch[i], PCMTRIG_ABORT); + ICH_LOCK(sc); } } - return 0; + ICH_UNLOCK(sc); + return (0); } static int @@ -908,17 +1238,20 @@ pci_enable_io(dev, SYS_RES_MEMORY); pci_enable_busmaster(dev); + ICH_LOCK(sc); /* Reinit audio device */ if (ich_init(sc) == -1) { device_printf(dev, "unable to reinitialize the card\n"); - return ENXIO; + ICH_UNLOCK(sc); + return (ENXIO); } /* Reinit mixer */ ich_pci_codec_reset(sc); + ICH_UNLOCK(sc); ac97_setextmode(sc->codec, sc->hasvra | sc->hasvrm); if (mixer_reinit(dev) == -1) { device_printf(dev, "unable to reinitialize the mixer\n"); - return ENXIO; + return (ENXIO); } /* Re-start DMA engines */ for (i = 0 ; i < 3; i++) { @@ -929,7 +1262,7 @@ ichchan_trigger(0, ch, PCMTRIG_START); } } - return 0; + return (0); } static device_method_t ich_methods[] = { --- sys/dev/sound/pci/ich.h.orig Sun Jan 30 09:00:04 2005 +++ sys/dev/sound/pci/ich.h Thu Jan 6 09:43:19 2005 @@ -24,7 +24,7 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THEPOSSIBILITY OF * SUCH DAMAGE. * - * $FreeBSD: src/sys/dev/sound/pci/ich.h,v 1.3.4.1 2005/01/30 01:00:04 imp Exp $ + * $FreeBSD: src/sys/dev/sound/pci/ich.h,v 1.4 2005/01/06 01:43:19 imp Exp $ */ #define PCIR_NAMBAR 0x10 --- sys/dev/sound/pci/maestro.c.orig Sun Jan 30 09:00:04 2005 +++ sys/dev/sound/pci/maestro.c Thu Jul 12 12:04:19 2007 @@ -23,7 +23,7 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $Id: maestro.c,v 1.18 2003/07/01 15:52:01 scottl Exp $ + * maestro.c,v 1.23.2.1 2003/10/03 18:21:38 taku Exp */ /* @@ -42,6 +42,9 @@ * were looked at by * Munehiro Matsuda , * who brought patches based on the Linux driver with some simplification. + * + * Hardware volume controller was implemented by + * John Baldwin . */ #include @@ -51,9 +54,7 @@ #include -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/maestro.c,v 1.23.2.2 2005/01/30 01:00:04 imp Exp $"); - -#define inline __inline +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/maestro.c,v 1.36 2007/06/17 06:10:42 ariff Exp $"); /* * PCI IDs of supported chips: @@ -70,319 +71,510 @@ #define NEC_SUBID1 0x80581033 /* Taken from Linux driver */ #define NEC_SUBID2 0x803c1033 /* NEC VersaProNX VA26D */ -#ifndef AGG_MAXPLAYCH +#ifdef AGG_MAXPLAYCH +# if AGG_MAXPLAYCH > 4 +# undef AGG_MAXPLAYCH +# define AGG_MAXPLAYCH 4 +# endif +#else # define AGG_MAXPLAYCH 4 #endif #define AGG_DEFAULT_BUFSZ 0x4000 /* 0x1000, but gets underflows */ +/* compatibility */ +#if __FreeBSD_version < 500000 +# define critical_enter() disable_intr() +# define critical_exit() enable_intr() +#endif + +#ifndef PCIR_BAR +#define PCIR_BAR(x) (PCIR_MAPS + (x) * 4) +#endif + + /* ----------------------------- * Data structures. */ struct agg_chinfo { + /* parent softc */ struct agg_info *parent; + + /* FreeBSD newpcm related */ struct pcm_channel *channel; struct snd_dbuf *buffer; - bus_addr_t offset; - u_int32_t blocksize; + + /* OS independent */ + bus_addr_t phys; /* channel buffer physical address */ + bus_addr_t base; /* channel buffer segment base */ + u_int32_t blklen; /* DMA block length in WORDs */ + u_int32_t buflen; /* channel buffer length in WORDs */ u_int32_t speed; - int dir; - u_int num; - u_int16_t aputype; - u_int16_t wcreg_tpl; + unsigned num : 3; + unsigned stereo : 1; + unsigned qs16 : 1; /* quantum size is 16bit */ + unsigned us : 1; /* in unsigned format */ +}; + +struct agg_rchinfo { + /* parent softc */ + struct agg_info *parent; + + /* FreeBSD newpcm related */ + struct pcm_channel *channel; + struct snd_dbuf *buffer; + + /* OS independent */ + bus_addr_t phys; /* channel buffer physical address */ + bus_addr_t base; /* channel buffer segment base */ + u_int32_t blklen; /* DMA block length in WORDs */ + u_int32_t buflen; /* channel buffer length in WORDs */ + u_int32_t speed; + unsigned : 3; + unsigned stereo : 1; + bus_addr_t srcphys; + int16_t *src; /* stereo peer buffer */ + int16_t *sink; /* channel buffer pointer */ + volatile u_int32_t hwptr; /* ready point in 16bit sample */ }; struct agg_info { + /* FreeBSD newbus related */ device_t dev; + + /* I wonder whether bus_space_* are in common in *BSD... */ struct resource *reg; int regid; - bus_space_tag_t st; bus_space_handle_t sh; - bus_dma_tag_t parent_dmat; struct resource *irq; int irqid; void *ih; - u_int8_t *stat; - bus_addr_t baseaddr; + bus_dma_tag_t buf_dmat; + bus_dma_tag_t stat_dmat; + /* FreeBSD SMPng related */ +#ifdef USING_MUTEX + struct mtx lock; /* mutual exclusion */ +#endif + /* FreeBSD newpcm related */ struct ac97_info *codec; - struct mtx *lock; - unsigned int bufsz; - u_int playchns, active; + /* OS independent */ + u_int8_t *stat; /* status buffer pointer */ + bus_addr_t phys; /* status buffer physical address */ + unsigned int bufsz; /* channel buffer size in bytes */ + u_int playchns; + volatile u_int active; struct agg_chinfo pch[AGG_MAXPLAYCH]; - struct agg_chinfo rch; + struct agg_rchinfo rch; + volatile u_int8_t curpwr; /* current power status: D[0-3] */ }; -static inline void ringbus_setdest(struct agg_info*, int, int); -static inline u_int16_t wp_rdreg(struct agg_info*, u_int16_t); -static inline void wp_wrreg(struct agg_info*, u_int16_t, u_int16_t); -static inline u_int16_t wp_rdapu(struct agg_info*, int, u_int16_t); -static inline void wp_wrapu(struct agg_info*, int, u_int16_t, u_int16_t); -static inline void wp_settimer(struct agg_info*, u_int); -static inline void wp_starttimer(struct agg_info*); -static inline void wp_stoptimer(struct agg_info*); - -static inline u_int16_t wc_rdreg(struct agg_info*, u_int16_t); -static inline void wc_wrreg(struct agg_info*, u_int16_t, u_int16_t); -static inline u_int16_t wc_rdchctl(struct agg_info*, int); -static inline void wc_wrchctl(struct agg_info*, int, u_int16_t); - -static inline void agg_power(struct agg_info*, int); - -static void agg_init(struct agg_info*); - -static void aggch_start_dac(struct agg_chinfo*); -static void aggch_stop_dac(struct agg_chinfo*); - -static inline void suppress_jitter(struct agg_chinfo*); - -static inline u_int calc_timer_freq(struct agg_chinfo*); -static void set_timer(struct agg_info*); - -static void agg_intr(void *); -static int agg_probe(device_t); -static int agg_attach(device_t); -static int agg_detach(device_t); -static int agg_suspend(device_t); -static int agg_resume(device_t); -static int agg_shutdown(device_t); +/* ----------------------------- + * Sysctls for debug. + */ +static unsigned int powerstate_active = PCI_POWERSTATE_D1; +#ifdef MAESTRO_AGGRESSIVE_POWERSAVE +static unsigned int powerstate_idle = PCI_POWERSTATE_D2; +#else +static unsigned int powerstate_idle = PCI_POWERSTATE_D1; +#endif +static unsigned int powerstate_init = PCI_POWERSTATE_D2; + +/* XXX: this should move to a device specific sysctl dev.pcm.X.debug.Y via + device_get_sysctl_*() as discussed on multimedia@ in msg-id + <861wujij2q.fsf@xps.des.no> */ +SYSCTL_NODE(_debug, OID_AUTO, maestro, CTLFLAG_RD, 0, ""); +SYSCTL_UINT(_debug_maestro, OID_AUTO, powerstate_active, CTLFLAG_RW, + &powerstate_active, 0, "The Dx power state when active (0-1)"); +SYSCTL_UINT(_debug_maestro, OID_AUTO, powerstate_idle, CTLFLAG_RW, + &powerstate_idle, 0, "The Dx power state when idle (0-2)"); +SYSCTL_UINT(_debug_maestro, OID_AUTO, powerstate_init, CTLFLAG_RW, + &powerstate_init, 0, + "The Dx power state prior to the first use (0-2)"); + + +/* ----------------------------- + * Prototypes + */ + +static void agg_sleep(struct agg_info*, const char *wmesg, int msec); + +static __inline u_int32_t agg_rd(struct agg_info*, int, int size); +static __inline void agg_wr(struct agg_info*, int, u_int32_t data, + int size); +static int agg_rdcodec(struct agg_info*, int); +static int agg_wrcodec(struct agg_info*, int, u_int32_t); + +static void ringbus_setdest(struct agg_info*, int, int); + +static u_int16_t wp_rdreg(struct agg_info*, u_int16_t); +static void wp_wrreg(struct agg_info*, u_int16_t, u_int16_t); +static u_int16_t wp_rdapu(struct agg_info*, unsigned, u_int16_t); +static void wp_wrapu(struct agg_info*, unsigned, u_int16_t, u_int16_t); +static void wp_settimer(struct agg_info*, u_int); +static void wp_starttimer(struct agg_info*); +static void wp_stoptimer(struct agg_info*); + +#if 0 +static u_int16_t wc_rdreg(struct agg_info*, u_int16_t); +#endif +static void wc_wrreg(struct agg_info*, u_int16_t, u_int16_t); +#if 0 +static u_int16_t wc_rdchctl(struct agg_info*, int); +#endif +static void wc_wrchctl(struct agg_info*, int, u_int16_t); + +static void agg_stopclock(struct agg_info*, int part, int st); + +static void agg_initcodec(struct agg_info*); +static void agg_init(struct agg_info*); +static void agg_power(struct agg_info*, int); + +static void aggch_start_dac(struct agg_chinfo*); +static void aggch_stop_dac(struct agg_chinfo*); +static void aggch_start_adc(struct agg_rchinfo*); +static void aggch_stop_adc(struct agg_rchinfo*); +static void aggch_feed_adc_stereo(struct agg_rchinfo*); +static void aggch_feed_adc_mono(struct agg_rchinfo*); + +#ifdef AGG_JITTER_CORRECTION +static void suppress_jitter(struct agg_chinfo*); +static void suppress_rec_jitter(struct agg_rchinfo*); +#endif + +static void set_timer(struct agg_info*); + +static void agg_intr(void *); +static int agg_probe(device_t); +static int agg_attach(device_t); +static int agg_detach(device_t); +static int agg_suspend(device_t); +static int agg_resume(device_t); +static int agg_shutdown(device_t); + +static void *dma_malloc(bus_dma_tag_t, u_int32_t, bus_addr_t*); +static void dma_free(bus_dma_tag_t, void *); -static void *dma_malloc(struct agg_info*, u_int32_t, bus_addr_t*); -static void dma_free(struct agg_info*, void *); /* ----------------------------- * Subsystems. */ -/* Codec/Ringbus */ +/* locking */ +#define agg_lock(sc) snd_mtxlock(&((sc)->lock)) +#define agg_unlock(sc) snd_mtxunlock(&((sc)->lock)) -/* -------------------------------------------------------------------- */ +static void +agg_sleep(struct agg_info *sc, const char *wmesg, int msec) +{ + int timo; -static u_int32_t -agg_ac97_init(kobj_t obj, void *sc) + timo = msec * hz / 1000; + if (timo == 0) + timo = 1; +#ifdef USING_MUTEX + msleep(sc, &sc->lock, PWAIT, wmesg, timo); +#else + tsleep(sc, PWAIT, wmesg, timo); +#endif +} + + +/* I/O port */ + +static __inline u_int32_t +agg_rd(struct agg_info *sc, int regno, int size) { - struct agg_info *ess = 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); + default: + return ~(u_int32_t)0; + } +} - return (bus_space_read_1(ess->st, ess->sh, PORT_CODEC_STAT) & CODEC_STAT_MASK)? 0 : 1; +#define AGG_RD(sc, regno, size) \ + bus_space_read_##size( \ + ((struct agg_info*)(sc))->st, \ + ((struct agg_info*)(sc))->sh, (regno)) + +static __inline void +agg_wr(struct agg_info *sc, int regno, u_int32_t data, int size) +{ + 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; + } } +#define AGG_WR(sc, regno, data, size) \ + bus_space_write_##size( \ + ((struct agg_info*)(sc))->st, \ + ((struct agg_info*)(sc))->sh, (regno), (data)) + +/* -------------------------------------------------------------------- */ + +/* Codec/Ringbus */ + static int -agg_rdcodec(kobj_t obj, void *sc, int regno) +agg_codec_wait4idle(struct agg_info *ess) { - struct agg_info *ess = sc; - unsigned t; + unsigned t = 26; - /* We have to wait for a SAFE time to write addr/data */ - for (t = 0; t < 20; t++) { - if ((bus_space_read_1(ess->st, ess->sh, PORT_CODEC_STAT) - & CODEC_STAT_MASK) != CODEC_STAT_PROGLESS) - break; + while (AGG_RD(ess, PORT_CODEC_STAT, 1) & CODEC_STAT_MASK) { + if (--t == 0) + return EBUSY; DELAY(2); /* 20.8us / 13 */ } - if (t == 20) + return 0; +} + + +static int +agg_rdcodec(struct agg_info *ess, int regno) +{ + int ret; + + /* We have to wait for a SAFE time to write addr/data */ + if (agg_codec_wait4idle(ess)) { + /* Timed out. No read performed. */ device_printf(ess->dev, "agg_rdcodec() PROGLESS timed out.\n"); + return -1; + } - bus_space_write_1(ess->st, ess->sh, PORT_CODEC_CMD, - CODEC_CMD_READ | regno); - DELAY(21); /* AC97 cycle = 20.8usec */ + AGG_WR(ess, PORT_CODEC_CMD, CODEC_CMD_READ | regno, 1); + /*DELAY(21); * AC97 cycle = 20.8usec */ /* Wait for data retrieve */ - for (t = 0; t < 20; t++) { - if ((bus_space_read_1(ess->st, ess->sh, PORT_CODEC_STAT) - & CODEC_STAT_MASK) == CODEC_STAT_RW_DONE) - break; - DELAY(2); /* 20.8us / 13 */ - } - if (t == 20) - /* Timed out, but perform dummy read. */ + if (!agg_codec_wait4idle(ess)) { + ret = AGG_RD(ess, PORT_CODEC_REG, 2); + } else { + /* Timed out. No read performed. */ device_printf(ess->dev, "agg_rdcodec() RW_DONE timed out.\n"); + ret = -1; + } - return bus_space_read_2(ess->st, ess->sh, PORT_CODEC_REG); + return ret; } static int -agg_wrcodec(kobj_t obj, void *sc, int regno, u_int32_t data) +agg_wrcodec(struct agg_info *ess, int regno, u_int32_t data) { - unsigned t; - struct agg_info *ess = sc; - /* We have to wait for a SAFE time to write addr/data */ - for (t = 0; t < 20; t++) { - if ((bus_space_read_1(ess->st, ess->sh, PORT_CODEC_STAT) - & CODEC_STAT_MASK) != CODEC_STAT_PROGLESS) - break; - DELAY(2); /* 20.8us / 13 */ - } - if (t == 20) { + if (agg_codec_wait4idle(ess)) { /* Timed out. Abort writing. */ device_printf(ess->dev, "agg_wrcodec() PROGLESS timed out.\n"); return -1; } - bus_space_write_2(ess->st, ess->sh, PORT_CODEC_REG, data); - bus_space_write_1(ess->st, ess->sh, PORT_CODEC_CMD, - CODEC_CMD_WRITE | regno); + AGG_WR(ess, PORT_CODEC_REG, data, 2); + AGG_WR(ess, PORT_CODEC_CMD, CODEC_CMD_WRITE | regno, 1); + + /* Wait for write completion */ + if (agg_codec_wait4idle(ess)) { + /* Timed out. */ + device_printf(ess->dev, "agg_wrcodec() RW_DONE timed out.\n"); + return -1; + } return 0; } -static kobj_method_t agg_ac97_methods[] = { - KOBJMETHOD(ac97_init, agg_ac97_init), - KOBJMETHOD(ac97_read, agg_rdcodec), - KOBJMETHOD(ac97_write, agg_wrcodec), - { 0, 0 } -}; -AC97_DECLARE(agg_ac97); - -/* -------------------------------------------------------------------- */ - -static inline void +static void ringbus_setdest(struct agg_info *ess, int src, int dest) { u_int32_t data; - data = bus_space_read_4(ess->st, ess->sh, PORT_RINGBUS_CTRL); + data = AGG_RD(ess, PORT_RINGBUS_CTRL, 4); data &= ~(0xfU << src); data |= (0xfU & dest) << src; - bus_space_write_4(ess->st, ess->sh, PORT_RINGBUS_CTRL, data); + AGG_WR(ess, PORT_RINGBUS_CTRL, data, 4); } +/* -------------------------------------------------------------------- */ + /* Wave Processor */ -static inline u_int16_t +static u_int16_t wp_rdreg(struct agg_info *ess, u_int16_t reg) { - bus_space_write_2(ess->st, ess->sh, PORT_DSP_INDEX, reg); - return bus_space_read_2(ess->st, ess->sh, PORT_DSP_DATA); + AGG_WR(ess, PORT_DSP_INDEX, reg, 2); + return AGG_RD(ess, PORT_DSP_DATA, 2); } -static inline void +static void wp_wrreg(struct agg_info *ess, u_int16_t reg, u_int16_t data) { - bus_space_write_2(ess->st, ess->sh, PORT_DSP_INDEX, reg); - bus_space_write_2(ess->st, ess->sh, PORT_DSP_DATA, data); + AGG_WR(ess, PORT_DSP_INDEX, reg, 2); + AGG_WR(ess, PORT_DSP_DATA, data, 2); } -static inline void -apu_setindex(struct agg_info *ess, u_int16_t reg) +static int +wp_wait_data(struct agg_info *ess, u_int16_t data) { - int t; + unsigned t = 0; - wp_wrreg(ess, WPREG_CRAM_PTR, reg); - /* Sometimes WP fails to set apu register index. */ - for (t = 0; t < 1000; t++) { - if (bus_space_read_2(ess->st, ess->sh, PORT_DSP_DATA) == reg) - break; - bus_space_write_2(ess->st, ess->sh, PORT_DSP_DATA, reg); + while (AGG_RD(ess, PORT_DSP_DATA, 2) != data) { + if (++t == 1000) { + return EAGAIN; + } + AGG_WR(ess, PORT_DSP_DATA, data, 2); } - if (t == 1000) - device_printf(ess->dev, "apu_setindex() timed out.\n"); + + return 0; } -static inline u_int16_t -wp_rdapu(struct agg_info *ess, int ch, u_int16_t reg) +static u_int16_t +wp_rdapu(struct agg_info *ess, unsigned ch, u_int16_t reg) { - u_int16_t ret; - - apu_setindex(ess, ((unsigned)ch << 4) + reg); - ret = wp_rdreg(ess, WPREG_DATA_PORT); - return ret; + wp_wrreg(ess, WPREG_CRAM_PTR, reg | (ch << 4)); + if (wp_wait_data(ess, reg | (ch << 4)) != 0) + device_printf(ess->dev, "wp_rdapu() indexing timed out.\n"); + return wp_rdreg(ess, WPREG_DATA_PORT); } -static inline void -wp_wrapu(struct agg_info *ess, int ch, u_int16_t reg, u_int16_t data) +static void +wp_wrapu(struct agg_info *ess, unsigned ch, u_int16_t reg, u_int16_t data) { - int t; - - apu_setindex(ess, ((unsigned)ch << 4) + reg); - wp_wrreg(ess, WPREG_DATA_PORT, data); - for (t = 0; t < 1000; t++) { - if (bus_space_read_2(ess->st, ess->sh, PORT_DSP_DATA) == data) - break; - bus_space_write_2(ess->st, ess->sh, PORT_DSP_DATA, data); + wp_wrreg(ess, WPREG_CRAM_PTR, reg | (ch << 4)); + if (wp_wait_data(ess, reg | (ch << 4)) == 0) { + wp_wrreg(ess, WPREG_DATA_PORT, data); + if (wp_wait_data(ess, data) != 0) + device_printf(ess->dev, + "wp_wrapu() write timed out.\n"); + } else { + device_printf(ess->dev, "wp_wrapu() indexing timed out.\n"); } - if (t == 1000) - device_printf(ess->dev, "wp_wrapu() timed out.\n"); } -static inline void -wp_settimer(struct agg_info *ess, u_int freq) +static void +apu_setparam(struct agg_info *ess, int apuch, + u_int32_t wpwa, u_int16_t size, int16_t pan, u_int dv) { - u_int clock = 48000 << 2; - u_int prescale = 0, divide = (freq != 0) ? (clock / freq) : ~0; + wp_wrapu(ess, apuch, APUREG_WAVESPACE, (wpwa >> 8) & APU_64KPAGE_MASK); + wp_wrapu(ess, apuch, APUREG_CURPTR, wpwa); + wp_wrapu(ess, apuch, APUREG_ENDPTR, wpwa + size); + wp_wrapu(ess, apuch, APUREG_LOOPLEN, size); + wp_wrapu(ess, apuch, APUREG_ROUTING, 0); + wp_wrapu(ess, apuch, APUREG_AMPLITUDE, 0xf000); + wp_wrapu(ess, apuch, APUREG_POSITION, 0x8f00 + | (APU_RADIUS_MASK & (RADIUS_CENTERCIRCLE << APU_RADIUS_SHIFT)) + | (APU_PAN_MASK & ((pan + PAN_FRONT) << APU_PAN_SHIFT))); + wp_wrapu(ess, apuch, APUREG_FREQ_LOBYTE, + APU_plus6dB | ((dv & 0xff) << APU_FREQ_LOBYTE_SHIFT)); + wp_wrapu(ess, apuch, APUREG_FREQ_HIWORD, dv >> 8); +} - RANGE(divide, 4, 32 << 8); +static void +wp_settimer(struct agg_info *ess, u_int divide) +{ + u_int prescale = 0; - for (; divide > 32 << 1; divide >>= 1) + RANGE(divide, 2, 32 << 7); + + for (; divide > 32; divide >>= 1) { prescale++; - divide = (divide + 1) >> 1; + divide++; + } for (; prescale < 7 && divide > 2 && !(divide & 1); divide >>= 1) prescale++; wp_wrreg(ess, WPREG_TIMER_ENABLE, 0); - wp_wrreg(ess, WPREG_TIMER_FREQ, + wp_wrreg(ess, WPREG_TIMER_FREQ, 0x9000 | (prescale << WP_TIMER_FREQ_PRESCALE_SHIFT) | (divide - 1)); wp_wrreg(ess, WPREG_TIMER_ENABLE, 1); } -static inline void +static void wp_starttimer(struct agg_info *ess) { + AGG_WR(ess, PORT_INT_STAT, 1, 2); + AGG_WR(ess, PORT_HOSTINT_CTRL, HOSTINT_CTRL_DSOUND_INT_ENABLED + | AGG_RD(ess, PORT_HOSTINT_CTRL, 2), 2); wp_wrreg(ess, WPREG_TIMER_START, 1); } -static inline void +static void wp_stoptimer(struct agg_info *ess) { + AGG_WR(ess, PORT_HOSTINT_CTRL, ~HOSTINT_CTRL_DSOUND_INT_ENABLED + & AGG_RD(ess, PORT_HOSTINT_CTRL, 2), 2); + AGG_WR(ess, PORT_INT_STAT, 1, 2); wp_wrreg(ess, WPREG_TIMER_START, 0); - bus_space_write_2(ess->st, ess->sh, PORT_INT_STAT, 1); } +/* -------------------------------------------------------------------- */ + /* WaveCache */ -static inline u_int16_t +#if 0 +static u_int16_t wc_rdreg(struct agg_info *ess, u_int16_t reg) { - bus_space_write_2(ess->st, ess->sh, PORT_WAVCACHE_INDEX, reg); - return bus_space_read_2(ess->st, ess->sh, PORT_WAVCACHE_DATA); + AGG_WR(ess, PORT_WAVCACHE_INDEX, reg, 2); + return AGG_RD(ess, PORT_WAVCACHE_DATA, 2); } +#endif -static inline void +static void wc_wrreg(struct agg_info *ess, u_int16_t reg, u_int16_t data) { - bus_space_write_2(ess->st, ess->sh, PORT_WAVCACHE_INDEX, reg); - bus_space_write_2(ess->st, ess->sh, PORT_WAVCACHE_DATA, data); + AGG_WR(ess, PORT_WAVCACHE_INDEX, reg, 2); + AGG_WR(ess, PORT_WAVCACHE_DATA, data, 2); } -static inline u_int16_t +#if 0 +static u_int16_t wc_rdchctl(struct agg_info *ess, int ch) { return wc_rdreg(ess, ch << 3); } +#endif -static inline void +static void wc_wrchctl(struct agg_info *ess, int ch, u_int16_t data) { wc_wrreg(ess, ch << 3, data); } -/* Power management */ +/* -------------------------------------------------------------------- */ -static inline void -agg_power(struct agg_info *ess, int status) +/* Power management */ +static void +agg_stopclock(struct agg_info *ess, int part, int st) { - u_int8_t data; + u_int32_t data; - data = pci_read_config(ess->dev, CONF_PM_PTR, 1); - if (pci_read_config(ess->dev, data, 1) == PPMI_CID) - pci_write_config(ess->dev, data + PM_CTRL, status, 1); + data = pci_read_config(ess->dev, CONF_ACPI_STOPCLOCK, 4); + if (part < 16) { + if (st == PCI_POWERSTATE_D1) + data &= ~(1 << part); + else + data |= (1 << part); + if (st == PCI_POWERSTATE_D1 || st == PCI_POWERSTATE_D2) + data |= (0x10000 << part); + else + data &= ~(0x10000 << part); + pci_write_config(ess->dev, CONF_ACPI_STOPCLOCK, data, 4); + } } @@ -390,51 +582,43 @@ * Controller. */ -static inline void +static void agg_initcodec(struct agg_info* ess) { u_int16_t data; - if (bus_space_read_4(ess->st, ess->sh, PORT_RINGBUS_CTRL) - & RINGBUS_CTRL_ACLINK_ENABLED) { - bus_space_write_4(ess->st, ess->sh, PORT_RINGBUS_CTRL, 0); + if (AGG_RD(ess, PORT_RINGBUS_CTRL, 4) & RINGBUS_CTRL_ACLINK_ENABLED) { + AGG_WR(ess, PORT_RINGBUS_CTRL, 0, 4); DELAY(104); /* 20.8us * (4 + 1) */ } /* XXX - 2nd codec should be looked at. */ - bus_space_write_4(ess->st, ess->sh, PORT_RINGBUS_CTRL, - RINGBUS_CTRL_AC97_SWRESET); + AGG_WR(ess, PORT_RINGBUS_CTRL, RINGBUS_CTRL_AC97_SWRESET, 4); DELAY(2); - bus_space_write_4(ess->st, ess->sh, PORT_RINGBUS_CTRL, - RINGBUS_CTRL_ACLINK_ENABLED); - DELAY(21); - - agg_rdcodec(NULL, ess, 0); - if (bus_space_read_1(ess->st, ess->sh, PORT_CODEC_STAT) - & CODEC_STAT_MASK) { - bus_space_write_4(ess->st, ess->sh, PORT_RINGBUS_CTRL, 0); + AGG_WR(ess, PORT_RINGBUS_CTRL, RINGBUS_CTRL_ACLINK_ENABLED, 4); + DELAY(50); + + if (agg_rdcodec(ess, 0) < 0) { + AGG_WR(ess, PORT_RINGBUS_CTRL, 0, 4); DELAY(21); /* Try cold reset. */ device_printf(ess->dev, "will perform cold reset.\n"); - data = bus_space_read_2(ess->st, ess->sh, PORT_GPIO_DIR); + data = AGG_RD(ess, PORT_GPIO_DIR, 2); if (pci_read_config(ess->dev, 0x58, 2) & 1) data |= 0x10; - data |= 0x009 & - ~bus_space_read_2(ess->st, ess->sh, PORT_GPIO_DATA); - bus_space_write_2(ess->st, ess->sh, PORT_GPIO_MASK, 0xff6); - bus_space_write_2(ess->st, ess->sh, PORT_GPIO_DIR, - data | 0x009); - bus_space_write_2(ess->st, ess->sh, PORT_GPIO_DATA, 0x000); + data |= 0x009 & ~AGG_RD(ess, PORT_GPIO_DATA, 2); + AGG_WR(ess, PORT_GPIO_MASK, 0xff6, 2); + AGG_WR(ess, PORT_GPIO_DIR, data | 0x009, 2); + AGG_WR(ess, PORT_GPIO_DATA, 0x000, 2); DELAY(2); - bus_space_write_2(ess->st, ess->sh, PORT_GPIO_DATA, 0x001); + AGG_WR(ess, PORT_GPIO_DATA, 0x001, 2); DELAY(1); - bus_space_write_2(ess->st, ess->sh, PORT_GPIO_DATA, 0x009); - DELAY(500000); - bus_space_write_2(ess->st, ess->sh, PORT_GPIO_DIR, data); + AGG_WR(ess, PORT_GPIO_DATA, 0x009, 2); + agg_sleep(ess, "agginicd", 500); + AGG_WR(ess, PORT_GPIO_DIR, data, 2); DELAY(84); /* 20.8us * 4 */ - bus_space_write_4(ess->st, ess->sh, PORT_RINGBUS_CTRL, - RINGBUS_CTRL_ACLINK_ENABLED); - DELAY(21); + AGG_WR(ess, PORT_RINGBUS_CTRL, RINGBUS_CTRL_ACLINK_ENABLED, 4); + DELAY(50); } } @@ -455,47 +639,93 @@ * Prefer PCI timing rather than that of ISA. * Don't swap L/R. */ data = pci_read_config(ess->dev, CONF_MAESTRO, 4); + data |= MAESTRO_PMC; data |= MAESTRO_CHIBUS | MAESTRO_POSTEDWRITE | MAESTRO_DMA_PCITIMING; data &= ~MAESTRO_SWAP_LR; pci_write_config(ess->dev, CONF_MAESTRO, data, 4); + /* Turn off unused parts if necessary. */ + /* consult CONF_MAESTRO. */ + if (data & MAESTRO_SPDIF) + agg_stopclock(ess, ACPI_PART_SPDIF, PCI_POWERSTATE_D2); + else + agg_stopclock(ess, ACPI_PART_SPDIF, PCI_POWERSTATE_D1); + if (data & MAESTRO_HWVOL) + agg_stopclock(ess, ACPI_PART_HW_VOL, PCI_POWERSTATE_D3); + else + agg_stopclock(ess, ACPI_PART_HW_VOL, PCI_POWERSTATE_D1); + + /* parts that never be used */ + agg_stopclock(ess, ACPI_PART_978, PCI_POWERSTATE_D1); + agg_stopclock(ess, ACPI_PART_DAA, PCI_POWERSTATE_D1); + agg_stopclock(ess, ACPI_PART_GPIO, PCI_POWERSTATE_D1); + agg_stopclock(ess, ACPI_PART_SB, PCI_POWERSTATE_D1); + agg_stopclock(ess, ACPI_PART_FM, PCI_POWERSTATE_D1); + agg_stopclock(ess, ACPI_PART_MIDI, PCI_POWERSTATE_D1); + agg_stopclock(ess, ACPI_PART_GAME_PORT, PCI_POWERSTATE_D1); + + /* parts that will be used only when play/recording */ + agg_stopclock(ess, ACPI_PART_WP, PCI_POWERSTATE_D2); + + /* parts that should always be turned on */ + agg_stopclock(ess, ACPI_PART_CODEC_CLOCK, PCI_POWERSTATE_D3); + agg_stopclock(ess, ACPI_PART_GLUE, PCI_POWERSTATE_D3); + agg_stopclock(ess, ACPI_PART_PCI_IF, PCI_POWERSTATE_D3); + agg_stopclock(ess, ACPI_PART_RINGBUS, PCI_POWERSTATE_D3); + /* Reset direct sound. */ - bus_space_write_2(ess->st, ess->sh, PORT_HOSTINT_CTRL, - HOSTINT_CTRL_DSOUND_RESET); - DELAY(10000); /* XXX - too long? */ - bus_space_write_2(ess->st, ess->sh, PORT_HOSTINT_CTRL, 0); - DELAY(10000); - - /* Enable direct sound interruption and hardware volume control. */ - bus_space_write_2(ess->st, ess->sh, PORT_HOSTINT_CTRL, - HOSTINT_CTRL_DSOUND_INT_ENABLED | HOSTINT_CTRL_HWVOL_ENABLED); + AGG_WR(ess, PORT_HOSTINT_CTRL, HOSTINT_CTRL_SOFT_RESET, 2); + DELAY(100); + AGG_WR(ess, PORT_HOSTINT_CTRL, 0, 2); + DELAY(100); + AGG_WR(ess, PORT_HOSTINT_CTRL, HOSTINT_CTRL_DSOUND_RESET, 2); + DELAY(100); + AGG_WR(ess, PORT_HOSTINT_CTRL, 0, 2); + DELAY(100); + + /* Enable hardware volume control interruption. */ + if (data & MAESTRO_HWVOL) /* XXX - why not use device flags? */ + AGG_WR(ess, PORT_HOSTINT_CTRL,HOSTINT_CTRL_HWVOL_ENABLED, 2); /* Setup Wave Processor. */ /* Enable WaveCache, set DMA base address. */ wp_wrreg(ess, WPREG_WAVE_ROMRAM, WP_WAVE_VIRTUAL_ENABLED | WP_WAVE_DRAM_ENABLED); - bus_space_write_2(ess->st, ess->sh, PORT_WAVCACHE_CTRL, - WAVCACHE_ENABLED | WAVCACHE_WTSIZE_4MB); + wp_wrreg(ess, WPREG_CRAM_DATA, 0); + + AGG_WR(ess, PORT_WAVCACHE_CTRL, + WAVCACHE_ENABLED | WAVCACHE_WTSIZE_2MB | WAVCACHE_SGC_32_47, 2); for (data = WAVCACHE_PCMBAR; data < WAVCACHE_PCMBAR + 4; data++) - wc_wrreg(ess, data, ess->baseaddr >> WAVCACHE_BASEADDR_SHIFT); + wc_wrreg(ess, data, ess->phys >> WAVCACHE_BASEADDR_SHIFT); /* Setup Codec/Ringbus. */ agg_initcodec(ess); - bus_space_write_4(ess->st, ess->sh, PORT_RINGBUS_CTRL, - RINGBUS_CTRL_RINGBUS_ENABLED | RINGBUS_CTRL_ACLINK_ENABLED); + AGG_WR(ess, PORT_RINGBUS_CTRL, + RINGBUS_CTRL_RINGBUS_ENABLED | RINGBUS_CTRL_ACLINK_ENABLED, 4); - wp_wrreg(ess, WPREG_BASE, 0x8500); /* Parallel I/O */ + wp_wrreg(ess, 0x08, 0xB004); + wp_wrreg(ess, 0x09, 0x001B); + wp_wrreg(ess, 0x0A, 0x8000); + wp_wrreg(ess, 0x0B, 0x3F37); + wp_wrreg(ess, WPREG_BASE, 0x8598); /* Parallel I/O */ + wp_wrreg(ess, WPREG_BASE + 1, 0x7632); ringbus_setdest(ess, RINGBUS_SRC_ADC, RINGBUS_DEST_STEREO | RINGBUS_DEST_DSOUND_IN); ringbus_setdest(ess, RINGBUS_SRC_DSOUND, RINGBUS_DEST_STEREO | RINGBUS_DEST_DAC); + /* Enable S/PDIF if necessary. */ + if (pci_read_config(ess->dev, CONF_MAESTRO, 4) & MAESTRO_SPDIF) + /* XXX - why not use device flags? */ + AGG_WR(ess, PORT_RINGBUS_CTRL_B, RINGBUS_CTRL_SPDIF | + AGG_RD(ess, PORT_RINGBUS_CTRL_B, 1), 1); + /* Setup ASSP. Needed for Dell Inspiron 7500? */ - bus_space_write_1(ess->st, ess->sh, PORT_ASSP_CTRL_B, 0x00); - bus_space_write_1(ess->st, ess->sh, PORT_ASSP_CTRL_A, 0x03); - bus_space_write_1(ess->st, ess->sh, PORT_ASSP_CTRL_C, 0x00); + AGG_WR(ess, PORT_ASSP_CTRL_B, 0x00, 1); + AGG_WR(ess, PORT_ASSP_CTRL_A, 0x03, 1); + AGG_WR(ess, PORT_ASSP_CTRL_C, 0x00, 1); /* * Setup GPIO. @@ -507,131 +737,489 @@ case NEC_SUBID2: /* Matthew Braithwaite reported that * NEC Versa LX doesn't need GPIO operation. */ - bus_space_write_2(ess->st, ess->sh, PORT_GPIO_MASK, 0x9ff); - bus_space_write_2(ess->st, ess->sh, PORT_GPIO_DIR, - bus_space_read_2(ess->st, ess->sh, PORT_GPIO_DIR) | 0x600); - bus_space_write_2(ess->st, ess->sh, PORT_GPIO_DATA, 0x200); + AGG_WR(ess, PORT_GPIO_MASK, 0x9ff, 2); + AGG_WR(ess, PORT_GPIO_DIR, + AGG_RD(ess, PORT_GPIO_DIR, 2) | 0x600, 2); + AGG_WR(ess, PORT_GPIO_DATA, 0x200, 2); break; } } +/* Deals power state transition. Must be called with softc->lock held. */ +static void +agg_power(struct agg_info *ess, int status) +{ + u_int8_t lastpwr; + + lastpwr = ess->curpwr; + if (lastpwr == status) + return; + + switch (status) { + case PCI_POWERSTATE_D0: + case PCI_POWERSTATE_D1: + switch (lastpwr) { + case PCI_POWERSTATE_D2: + pci_set_powerstate(ess->dev, status); + /* Turn on PCM-related parts. */ + agg_wrcodec(ess, AC97_REG_POWER, 0); + DELAY(100); +#if 0 + if ((agg_rdcodec(ess, AC97_REG_POWER) & 3) != 3) + device_printf(ess->dev, + "warning: codec not ready.\n"); +#endif + AGG_WR(ess, PORT_RINGBUS_CTRL, + (AGG_RD(ess, PORT_RINGBUS_CTRL, 4) + & ~RINGBUS_CTRL_ACLINK_ENABLED) + | RINGBUS_CTRL_RINGBUS_ENABLED, 4); + DELAY(50); + AGG_WR(ess, PORT_RINGBUS_CTRL, + AGG_RD(ess, PORT_RINGBUS_CTRL, 4) + | RINGBUS_CTRL_ACLINK_ENABLED, 4); + break; + case PCI_POWERSTATE_D3: + /* Initialize. */ + pci_set_powerstate(ess->dev, PCI_POWERSTATE_D0); + DELAY(100); + agg_init(ess); + /* FALLTHROUGH */ + case PCI_POWERSTATE_D0: + case PCI_POWERSTATE_D1: + pci_set_powerstate(ess->dev, status); + break; + } + break; + case PCI_POWERSTATE_D2: + switch (lastpwr) { + case PCI_POWERSTATE_D3: + /* Initialize. */ + pci_set_powerstate(ess->dev, PCI_POWERSTATE_D0); + DELAY(100); + agg_init(ess); + /* FALLTHROUGH */ + case PCI_POWERSTATE_D0: + case PCI_POWERSTATE_D1: + /* Turn off PCM-related parts. */ + AGG_WR(ess, PORT_RINGBUS_CTRL, + AGG_RD(ess, PORT_RINGBUS_CTRL, 4) + & ~RINGBUS_CTRL_RINGBUS_ENABLED, 4); + DELAY(100); + agg_wrcodec(ess, AC97_REG_POWER, 0x300); + DELAY(100); + break; + } + pci_set_powerstate(ess->dev, status); + break; + case PCI_POWERSTATE_D3: + /* Entirely power down. */ + agg_wrcodec(ess, AC97_REG_POWER, 0xdf00); + DELAY(100); + AGG_WR(ess, PORT_RINGBUS_CTRL, 0, 4); + /*DELAY(1);*/ + if (lastpwr != PCI_POWERSTATE_D2) + wp_stoptimer(ess); + AGG_WR(ess, PORT_HOSTINT_CTRL, 0, 2); + AGG_WR(ess, PORT_HOSTINT_STAT, 0xff, 1); + pci_set_powerstate(ess->dev, status); + break; + default: + /* Invalid power state; let it ignored. */ + status = lastpwr; + break; + } + + ess->curpwr = status; +} + +/* -------------------------------------------------------------------- */ + /* Channel controller. */ static void aggch_start_dac(struct agg_chinfo *ch) { - bus_addr_t wpwa = APU_USE_SYSMEM | (ch->offset >> 9); - u_int size = ch->parent->bufsz >> 1; - u_int speed = ch->speed; - bus_addr_t offset = ch->offset >> 1; - u_int cp = 0; - u_int16_t apuch = ch->num << 1; - u_int dv; - int pan = 0; - - switch (ch->aputype) { - case APUTYPE_16BITSTEREO: - wpwa >>= 1; - size >>= 1; - offset >>= 1; - cp >>= 1; - /* FALLTHROUGH */ - case APUTYPE_8BITSTEREO: - pan = 8; - apuch++; - break; - case APUTYPE_8BITLINEAR: - speed >>= 1; - break; + bus_addr_t wpwa; + u_int32_t speed; + u_int16_t size, apuch, wtbar, wcreg, aputype; + u_int dv; + int pan; + + speed = ch->speed; + wpwa = (ch->phys - ch->base) >> 1; + wtbar = 0xc & (wpwa >> WPWA_WTBAR_SHIFT(2)); + wcreg = (ch->phys - 16) & WAVCACHE_CHCTL_ADDRTAG_MASK; + size = ch->buflen; + apuch = (ch->num << 1) | 32; + pan = PAN_RIGHT - PAN_FRONT; + + if (ch->stereo) { + wcreg |= WAVCACHE_CHCTL_STEREO; + if (ch->qs16) { + aputype = APUTYPE_16BITSTEREO; + wpwa >>= 1; + size >>= 1; + pan = -pan; + } else + aputype = APUTYPE_8BITSTEREO; + } else { + pan = 0; + if (ch->qs16) + aputype = APUTYPE_16BITLINEAR; + else { + aputype = APUTYPE_8BITLINEAR; + speed >>= 1; + } } + if (ch->us) + wcreg |= WAVCACHE_CHCTL_U8; + + if (wtbar > 8) + wtbar = (wtbar >> 1) + 4; dv = (((speed % 48000) << 16) + 24000) / 48000 + ((speed / 48000) << 16); - do { - wp_wrapu(ch->parent, apuch, APUREG_WAVESPACE, wpwa & 0xff00); - wp_wrapu(ch->parent, apuch, APUREG_CURPTR, offset + cp); - wp_wrapu(ch->parent, apuch, APUREG_ENDPTR, offset + size); - wp_wrapu(ch->parent, apuch, APUREG_LOOPLEN, size); - wp_wrapu(ch->parent, apuch, APUREG_AMPLITUDE, 0xe800); - wp_wrapu(ch->parent, apuch, APUREG_POSITION, 0x8f00 - | (RADIUS_CENTERCIRCLE << APU_RADIUS_SHIFT) - | ((PAN_FRONT + pan) << APU_PAN_SHIFT)); - wp_wrapu(ch->parent, apuch, APUREG_FREQ_LOBYTE, APU_plus6dB - | ((dv & 0xff) << APU_FREQ_LOBYTE_SHIFT)); - wp_wrapu(ch->parent, apuch, APUREG_FREQ_HIWORD, dv >> 8); - - if (ch->aputype == APUTYPE_16BITSTEREO) - wpwa |= APU_STEREO >> 1; - pan = -pan; - } while (pan < 0 && apuch--); - - wc_wrchctl(ch->parent, apuch, ch->wcreg_tpl); - wc_wrchctl(ch->parent, apuch + 1, ch->wcreg_tpl); - - wp_wrapu(ch->parent, apuch, APUREG_APUTYPE, - (ch->aputype << APU_APUTYPE_SHIFT) | APU_DMA_ENABLED | 0xf); - if (ch->wcreg_tpl & WAVCACHE_CHCTL_STEREO) + agg_lock(ch->parent); + agg_power(ch->parent, powerstate_active); + + wc_wrreg(ch->parent, WAVCACHE_WTBAR + wtbar, + ch->base >> WAVCACHE_BASEADDR_SHIFT); + wc_wrreg(ch->parent, WAVCACHE_WTBAR + wtbar + 1, + ch->base >> WAVCACHE_BASEADDR_SHIFT); + if (wtbar < 8) { + wc_wrreg(ch->parent, WAVCACHE_WTBAR + wtbar + 2, + ch->base >> WAVCACHE_BASEADDR_SHIFT); + wc_wrreg(ch->parent, WAVCACHE_WTBAR + wtbar + 3, + ch->base >> WAVCACHE_BASEADDR_SHIFT); + } + wc_wrchctl(ch->parent, apuch, wcreg); + wc_wrchctl(ch->parent, apuch + 1, wcreg); + + apu_setparam(ch->parent, apuch, wpwa, size, pan, dv); + if (ch->stereo) { + if (ch->qs16) + wpwa |= (WPWA_STEREO >> 1); + apu_setparam(ch->parent, apuch + 1, wpwa, size, -pan, dv); + + critical_enter(); + wp_wrapu(ch->parent, apuch, APUREG_APUTYPE, + (aputype << APU_APUTYPE_SHIFT) | APU_DMA_ENABLED | 0xf); wp_wrapu(ch->parent, apuch + 1, APUREG_APUTYPE, - (ch->aputype << APU_APUTYPE_SHIFT) | APU_DMA_ENABLED | 0xf); + (aputype << APU_APUTYPE_SHIFT) | APU_DMA_ENABLED | 0xf); + critical_exit(); + } else { + wp_wrapu(ch->parent, apuch, APUREG_APUTYPE, + (aputype << APU_APUTYPE_SHIFT) | APU_DMA_ENABLED | 0xf); + } + + /* to mark that this channel is ready for intr. */ + ch->parent->active |= (1 << ch->num); + + set_timer(ch->parent); + wp_starttimer(ch->parent); + agg_unlock(ch->parent); } static void aggch_stop_dac(struct agg_chinfo *ch) { - wp_wrapu(ch->parent, (ch->num << 1), APUREG_APUTYPE, + agg_lock(ch->parent); + + /* to mark that this channel no longer needs further intrs. */ + ch->parent->active &= ~(1 << ch->num); + + wp_wrapu(ch->parent, (ch->num << 1) | 32, APUREG_APUTYPE, APUTYPE_INACTIVE << APU_APUTYPE_SHIFT); - wp_wrapu(ch->parent, (ch->num << 1) + 1, APUREG_APUTYPE, + wp_wrapu(ch->parent, (ch->num << 1) | 33, APUREG_APUTYPE, APUTYPE_INACTIVE << APU_APUTYPE_SHIFT); + + if (ch->parent->active) { + set_timer(ch->parent); + wp_starttimer(ch->parent); + } else { + wp_stoptimer(ch->parent); + agg_power(ch->parent, powerstate_idle); + } + agg_unlock(ch->parent); +} + +static void +aggch_start_adc(struct agg_rchinfo *ch) +{ + bus_addr_t wpwa, wpwa2; + u_int16_t wcreg, wcreg2; + u_int dv; + int pan; + + /* speed > 48000 not cared */ + dv = ((ch->speed << 16) + 24000) / 48000; + + /* RATECONV doesn't seem to like dv == 0x10000. */ + if (dv == 0x10000) + dv--; + + if (ch->stereo) { + wpwa = (ch->srcphys - ch->base) >> 1; + wpwa2 = (ch->srcphys + ch->parent->bufsz/2 - ch->base) >> 1; + wcreg = (ch->srcphys - 16) & WAVCACHE_CHCTL_ADDRTAG_MASK; + wcreg2 = (ch->base - 16) & WAVCACHE_CHCTL_ADDRTAG_MASK; + pan = PAN_LEFT - PAN_FRONT; + } else { + wpwa = (ch->phys - ch->base) >> 1; + wpwa2 = (ch->srcphys - ch->base) >> 1; + wcreg = (ch->phys - 16) & WAVCACHE_CHCTL_ADDRTAG_MASK; + wcreg2 = (ch->base - 16) & WAVCACHE_CHCTL_ADDRTAG_MASK; + pan = 0; + } + + agg_lock(ch->parent); + + ch->hwptr = 0; + agg_power(ch->parent, powerstate_active); + + /* Invalidate WaveCache. */ + wc_wrchctl(ch->parent, 0, wcreg | WAVCACHE_CHCTL_STEREO); + wc_wrchctl(ch->parent, 1, wcreg | WAVCACHE_CHCTL_STEREO); + wc_wrchctl(ch->parent, 2, wcreg2 | WAVCACHE_CHCTL_STEREO); + wc_wrchctl(ch->parent, 3, wcreg2 | WAVCACHE_CHCTL_STEREO); + + /* Load APU registers. */ + /* APU #0 : Sample rate converter for left/center. */ + apu_setparam(ch->parent, 0, WPWA_USE_SYSMEM | wpwa, + ch->buflen >> ch->stereo, 0, dv); + wp_wrapu(ch->parent, 0, APUREG_AMPLITUDE, 0); + wp_wrapu(ch->parent, 0, APUREG_ROUTING, 2 << APU_DATASRC_A_SHIFT); + + /* APU #1 : Sample rate converter for right. */ + apu_setparam(ch->parent, 1, WPWA_USE_SYSMEM | wpwa2, + ch->buflen >> ch->stereo, 0, dv); + wp_wrapu(ch->parent, 1, APUREG_AMPLITUDE, 0); + wp_wrapu(ch->parent, 1, APUREG_ROUTING, 3 << APU_DATASRC_A_SHIFT); + + /* APU #2 : Input mixer for left. */ + apu_setparam(ch->parent, 2, WPWA_USE_SYSMEM | 0, + ch->parent->bufsz >> 2, pan, 0x10000); + wp_wrapu(ch->parent, 2, APUREG_AMPLITUDE, 0); + wp_wrapu(ch->parent, 2, APUREG_EFFECT_GAIN, 0xf0); + wp_wrapu(ch->parent, 2, APUREG_ROUTING, 0x15 << APU_DATASRC_A_SHIFT); + + /* APU #3 : Input mixer for right. */ + apu_setparam(ch->parent, 3, WPWA_USE_SYSMEM | (ch->parent->bufsz >> 2), + ch->parent->bufsz >> 2, -pan, 0x10000); + wp_wrapu(ch->parent, 3, APUREG_AMPLITUDE, 0); + wp_wrapu(ch->parent, 3, APUREG_EFFECT_GAIN, 0xf0); + wp_wrapu(ch->parent, 3, APUREG_ROUTING, 0x14 << APU_DATASRC_A_SHIFT); + + /* to mark this channel ready for intr. */ + ch->parent->active |= (1 << ch->parent->playchns); + + /* start adc */ + critical_enter(); + wp_wrapu(ch->parent, 0, APUREG_APUTYPE, + (APUTYPE_RATECONV << APU_APUTYPE_SHIFT) | APU_DMA_ENABLED | 0xf); + wp_wrapu(ch->parent, 1, APUREG_APUTYPE, + (APUTYPE_RATECONV << APU_APUTYPE_SHIFT) | APU_DMA_ENABLED | 0xf); + wp_wrapu(ch->parent, 2, APUREG_APUTYPE, + (APUTYPE_INPUTMIXER << APU_APUTYPE_SHIFT) | 0xf); + wp_wrapu(ch->parent, 3, APUREG_APUTYPE, + (APUTYPE_INPUTMIXER << APU_APUTYPE_SHIFT) | 0xf); + critical_exit(); + + set_timer(ch->parent); + wp_starttimer(ch->parent); + agg_unlock(ch->parent); +} + +static void +aggch_stop_adc(struct agg_rchinfo *ch) +{ + int apuch; + + agg_lock(ch->parent); + + /* to mark that this channel no longer needs further intrs. */ + ch->parent->active &= ~(1 << ch->parent->playchns); + + for (apuch = 0; apuch < 4; apuch++) + wp_wrapu(ch->parent, apuch, APUREG_APUTYPE, + APUTYPE_INACTIVE << APU_APUTYPE_SHIFT); + + if (ch->parent->active) { + set_timer(ch->parent); + wp_starttimer(ch->parent); + } else { + wp_stoptimer(ch->parent); + agg_power(ch->parent, powerstate_idle); + } + agg_unlock(ch->parent); +} + +/* + * Feed from L/R channel of ADC to destination with stereo interleaving. + * This function expects n not overwrapping the buffer boundary. + * Note that n is measured in sample unit. + * + * XXX - this function works in 16bit stereo format only. + */ +static void +interleave(int16_t *l, int16_t *r, int16_t *p, unsigned n) +{ + int16_t *end; + + for (end = l + n; l < end; ) { + *p++ = *l++; + *p++ = *r++; + } +} + +static void +aggch_feed_adc_stereo(struct agg_rchinfo *ch) +{ + unsigned cur, last; + int16_t *src2; + + agg_lock(ch->parent); + cur = wp_rdapu(ch->parent, 0, APUREG_CURPTR); + agg_unlock(ch->parent); + cur -= 0xffff & ((ch->srcphys - ch->base) >> 1); + last = ch->hwptr; + src2 = ch->src + ch->parent->bufsz/4; + + if (cur < last) { + interleave(ch->src + last, src2 + last, + ch->sink + 2*last, ch->buflen/2 - last); + interleave(ch->src, src2, + ch->sink, cur); + } else if (cur > last) + interleave(ch->src + last, src2 + last, + ch->sink + 2*last, cur - last); + ch->hwptr = cur; +} + +/* + * Feed from R channel of ADC and mixdown to destination L/center. + * This function expects n not overwrapping the buffer boundary. + * Note that n is measured in sample unit. + * + * XXX - this function works in 16bit monoral format only. + */ +static void +mixdown(int16_t *src, int16_t *dest, unsigned n) +{ + int16_t *end; + + for (end = dest + n; dest < end; dest++) + *dest = (int16_t)(((int)*dest - (int)*src++) / 2); +} + +static void +aggch_feed_adc_mono(struct agg_rchinfo *ch) +{ + unsigned cur, last; + + agg_lock(ch->parent); + cur = wp_rdapu(ch->parent, 0, APUREG_CURPTR); + agg_unlock(ch->parent); + cur -= 0xffff & ((ch->phys - ch->base) >> 1); + last = ch->hwptr; + + if (cur < last) { + mixdown(ch->src + last, ch->sink + last, ch->buflen - last); + mixdown(ch->src, ch->sink, cur); + } else if (cur > last) + mixdown(ch->src + last, ch->sink + last, cur - last); + ch->hwptr = cur; } +#ifdef AGG_JITTER_CORRECTION /* * Stereo jitter suppressor. * Sometimes playback pointers differ in stereo-paired channels. * Calling this routine within intr fixes the problem. */ -static inline void +static void suppress_jitter(struct agg_chinfo *ch) { - if (ch->wcreg_tpl & WAVCACHE_CHCTL_STEREO) { - int cp, diff, halfsize = ch->parent->bufsz >> 2; + if (ch->stereo) { + int cp1, cp2, diff /*, halfsize*/ ; + + /*halfsize = (ch->qs16? ch->buflen >> 2 : ch->buflen >> 1);*/ + cp1 = wp_rdapu(ch->parent, (ch->num << 1) | 32, APUREG_CURPTR); + cp2 = wp_rdapu(ch->parent, (ch->num << 1) | 33, APUREG_CURPTR); + if (cp1 != cp2) { + diff = (cp1 > cp2 ? cp1 - cp2 : cp2 - cp1); + if (diff > 1 /* && diff < halfsize*/ ) + AGG_WR(ch->parent, PORT_DSP_DATA, cp1, 2); + } + } +} + +static void +suppress_rec_jitter(struct agg_rchinfo *ch) +{ + int cp1, cp2, diff /*, halfsize*/ ; - if (ch->aputype == APUTYPE_16BITSTEREO) - halfsize >>= 1; - cp = wp_rdapu(ch->parent, (ch->num << 1), APUREG_CURPTR); - diff = wp_rdapu(ch->parent, (ch->num << 1) + 1, APUREG_CURPTR); - diff -= cp; - if (diff >> 1 && diff > -halfsize && diff < halfsize) - bus_space_write_2(ch->parent->st, ch->parent->sh, - PORT_DSP_DATA, cp); + /*halfsize = (ch->stereo? ch->buflen >> 2 : ch->buflen >> 1);*/ + cp1 = (ch->stereo? ch->parent->bufsz >> 2 : ch->parent->bufsz >> 1) + + wp_rdapu(ch->parent, 0, APUREG_CURPTR); + cp2 = wp_rdapu(ch->parent, 1, APUREG_CURPTR); + if (cp1 != cp2) { + diff = (cp1 > cp2 ? cp1 - cp2 : cp2 - cp1); + if (diff > 1 /* && diff < halfsize*/ ) + AGG_WR(ch->parent, PORT_DSP_DATA, cp1, 2); } } +#endif -static inline u_int -calc_timer_freq(struct agg_chinfo *ch) +static u_int +calc_timer_div(struct agg_chinfo *ch) { - u_int ss = 2; + u_int speed; - if (ch->aputype == APUTYPE_16BITSTEREO) - ss <<= 1; - if (ch->aputype == APUTYPE_8BITLINEAR) - ss >>= 1; + speed = ch->speed; +#ifdef INVARIANTS + if (speed == 0) { + printf("snd_maestro: pch[%d].speed == 0, which shouldn't\n", + ch->num); + speed = 1; + } +#endif + return (48000 * (ch->blklen << (!ch->qs16 + !ch->stereo)) + + speed - 1) / speed; +} + +static u_int +calc_timer_div_rch(struct agg_rchinfo *ch) +{ + u_int speed; - return (ch->speed * ss) / ch->blocksize; + speed = ch->speed; +#ifdef INVARIANTS + if (speed == 0) { + printf("snd_maestro: rch.speed == 0, which shouldn't\n"); + speed = 1; + } +#endif + return (48000 * (ch->blklen << (!ch->stereo)) + + speed - 1) / speed; } static void set_timer(struct agg_info *ess) { int i; - u_int freq = 0; + u_int dv = 32 << 7, newdv; for (i = 0; i < ess->playchns; i++) if ((ess->active & (1 << i)) && - (freq < calc_timer_freq(ess->pch + i))) - freq = calc_timer_freq(ess->pch + i); + (dv > (newdv = calc_timer_div(ess->pch + i)))) + dv = newdv; + if ((ess->active & (1 << i)) && + (dv > (newdv = calc_timer_div_rch(&ess->rch)))) + dv = newdv; - wp_settimer(ess, freq); + wp_settimer(ess, dv); } @@ -639,142 +1227,226 @@ * Newpcm glue. */ +/* AC97 mixer interface. */ + +static u_int32_t +agg_ac97_init(kobj_t obj, void *sc) +{ + struct agg_info *ess = sc; + + return (AGG_RD(ess, PORT_CODEC_STAT, 1) & CODEC_STAT_MASK)? 0 : 1; +} + +static int +agg_ac97_read(kobj_t obj, void *sc, int regno) +{ + struct agg_info *ess = sc; + int ret; + + /* XXX sound locking violation: agg_lock(ess); */ + ret = agg_rdcodec(ess, regno); + /* agg_unlock(ess); */ + return ret; +} + +static int +agg_ac97_write(kobj_t obj, void *sc, int regno, u_int32_t data) +{ + struct agg_info *ess = sc; + int ret; + + /* XXX sound locking violation: agg_lock(ess); */ + ret = agg_wrcodec(ess, regno, data); + /* agg_unlock(ess); */ + return ret; +} + + +static kobj_method_t agg_ac97_methods[] = { + KOBJMETHOD(ac97_init, agg_ac97_init), + KOBJMETHOD(ac97_read, agg_ac97_read), + KOBJMETHOD(ac97_write, agg_ac97_write), + { 0, 0 } +}; +AC97_DECLARE(agg_ac97); + + +/* -------------------------------------------------------------------- */ + +/* Playback channel. */ + static void * -aggch_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) +aggpch_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, + struct pcm_channel *c, int dir) { struct agg_info *ess = devinfo; struct agg_chinfo *ch; bus_addr_t physaddr; void *p; - ch = (dir == PCMDIR_PLAY)? ess->pch + ess->playchns : &ess->rch; + KASSERT((dir == PCMDIR_PLAY), + ("aggpch_init() called for RECORDING channel!")); + ch = ess->pch + ess->playchns; ch->parent = ess; ch->channel = c; ch->buffer = b; ch->num = ess->playchns; - ch->dir = dir; - physaddr = ess->baseaddr + ch->offset; - p = ess->stat + ch->offset; + p = dma_malloc(ess->buf_dmat, ess->bufsz, &physaddr); + if (p == NULL) + return NULL; + ch->phys = physaddr; + ch->base = physaddr & ((~(bus_addr_t)0) << WAVCACHE_BASEADDR_SHIFT); + sndbuf_setup(b, p, ess->bufsz); + ch->blklen = sndbuf_getblksz(b) / 2; + ch->buflen = sndbuf_getsize(b) / 2; + ess->playchns++; - ch->wcreg_tpl = (physaddr - 16) & WAVCACHE_CHCTL_ADDRTAG_MASK; + return ch; +} - if (dir == PCMDIR_PLAY) { - ess->playchns++; - if (bootverbose) - device_printf(ess->dev, "pch[%d].offset = %#llx\n", ch->num, (long long)ch->offset); - } else if (bootverbose) - device_printf(ess->dev, "rch.offset = %#llx\n", (long long)ch->offset); +static void +adjust_pchbase(struct agg_chinfo *chans, u_int n, u_int size) +{ + struct agg_chinfo *pchs[AGG_MAXPLAYCH]; + u_int i, j, k; + bus_addr_t base; + + /* sort pchs by phys address */ + for (i = 0; i < n; i++) { + for (j = 0; j < i; j++) + if (chans[i].phys < pchs[j]->phys) { + for (k = i; k > j; k--) + pchs[k] = pchs[k - 1]; + break; + } + pchs[j] = chans + i; + } + + /* use new base register if next buffer can not be addressed + via current base. */ +#define BASE_SHIFT (WPWA_WTBAR_SHIFT(2) + 2 + 1) + base = pchs[0]->base; + for (k = 1, i = 1; i < n; i++) { + if (pchs[i]->phys + size - base >= 1 << BASE_SHIFT) + /* not addressable: assign new base */ + base = (pchs[i]->base -= k++ << BASE_SHIFT); + else + pchs[i]->base = base; + } +#undef BASE_SHIFT - return ch; + if (bootverbose) { + printf("Total of %d bases are assigned.\n", k); + for (i = 0; i < n; i++) { + printf("ch.%d: phys 0x%llx, wpwa 0x%llx\n", + i, (long long)chans[i].phys, + (long long)(chans[i].phys - + chans[i].base) >> 1); + } + } } static int -aggch_free(kobj_t obj, void *data) +aggpch_free(kobj_t obj, void *data) { + struct agg_chinfo *ch = data; + struct agg_info *ess = ch->parent; + + /* free up buffer - called after channel stopped */ + dma_free(ess->buf_dmat, sndbuf_getbuf(ch->buffer)); + /* return 0 if ok */ return 0; } static int -aggch_setplayformat(kobj_t obj, void *data, u_int32_t format) +aggpch_setformat(kobj_t obj, void *data, u_int32_t format) { struct agg_chinfo *ch = data; - u_int16_t wcreg_tpl; - u_int16_t aputype = APUTYPE_16BITLINEAR; - wcreg_tpl = ch->wcreg_tpl & WAVCACHE_CHCTL_ADDRTAG_MASK; + if (format & AFMT_BIGENDIAN || format & AFMT_U16_LE) + return EINVAL; + ch->stereo = ch->qs16 = ch->us = 0; + if (format & AFMT_STEREO) + ch->stereo = 1; - if (format & AFMT_STEREO) { - wcreg_tpl |= WAVCACHE_CHCTL_STEREO; - aputype += 1; - } if (format & AFMT_U8 || format & AFMT_S8) { - aputype += 2; if (format & AFMT_U8) - wcreg_tpl |= WAVCACHE_CHCTL_U8; - } - if (format & AFMT_BIGENDIAN || format & AFMT_U16_LE) { - format &= ~AFMT_BIGENDIAN & ~AFMT_U16_LE; - format |= AFMT_S16_LE; - } - ch->wcreg_tpl = wcreg_tpl; - ch->aputype = aputype; + ch->us = 1; + } else + ch->qs16 = 1; return 0; } static int -aggch_setspeed(kobj_t obj, void *data, u_int32_t speed) +aggpch_setspeed(kobj_t obj, void *data, u_int32_t speed) { - struct agg_chinfo *ch = data; - - ch->speed = speed; - return ch->speed; + return ((struct agg_chinfo*)data)->speed = speed; } static int -aggch_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) +aggpch_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) { - return ((struct agg_chinfo*)data)->blocksize = blocksize; + struct agg_chinfo *ch = data; + int blkcnt; + + /* try to keep at least 20msec DMA space */ + blkcnt = (ch->speed << (ch->stereo + ch->qs16)) / (50 * blocksize); + RANGE(blkcnt, 2, ch->parent->bufsz / blocksize); + + if (sndbuf_getsize(ch->buffer) != blkcnt * blocksize) { + sndbuf_resize(ch->buffer, blkcnt, blocksize); + blkcnt = sndbuf_getblkcnt(ch->buffer); + blocksize = sndbuf_getblksz(ch->buffer); + } else { + sndbuf_setblkcnt(ch->buffer, blkcnt); + sndbuf_setblksz(ch->buffer, blocksize); + } + + ch->blklen = blocksize / 2; + ch->buflen = blkcnt * blocksize / 2; + return blocksize; } static int -aggch_trigger(kobj_t obj, void *data, int go) +aggpch_trigger(kobj_t obj, void *data, int go) { struct agg_chinfo *ch = data; switch (go) { case PCMTRIG_EMLDMAWR: - return 0; + break; case PCMTRIG_START: - ch->parent->active |= (1 << ch->num); - if (ch->dir == PCMDIR_PLAY) - aggch_start_dac(ch); -#if 0 /* XXX - RECORDING */ - else - aggch_start_adc(ch); -#endif + aggch_start_dac(ch); break; case PCMTRIG_ABORT: case PCMTRIG_STOP: - ch->parent->active &= ~(1 << ch->num); - if (ch->dir == PCMDIR_PLAY) - aggch_stop_dac(ch); -#if 0 /* XXX - RECORDING */ - else - aggch_stop_adc(ch); -#endif + aggch_stop_dac(ch); break; } - - if (ch->parent->active) { - set_timer(ch->parent); - wp_starttimer(ch->parent); - } else - wp_stoptimer(ch->parent); - return 0; } static int -aggch_getplayptr(kobj_t obj, void *data) +aggpch_getptr(kobj_t obj, void *data) { struct agg_chinfo *ch = data; u_int cp; - cp = wp_rdapu(ch->parent, (ch->num << 1), APUREG_CURPTR); - if (ch->aputype == APUTYPE_16BITSTEREO) - cp = (0xffff << 2) & ((cp << 2) - ch->offset); - else - cp = (0xffff << 1) & ((cp << 1) - ch->offset); - - return cp; + agg_lock(ch->parent); + cp = wp_rdapu(ch->parent, (ch->num << 1) | 32, APUREG_CURPTR); + agg_unlock(ch->parent); + + return ch->qs16 && ch->stereo + ? (cp << 2) - ((0xffff << 2) & (ch->phys - ch->base)) + : (cp << 1) - ((0xffff << 1) & (ch->phys - ch->base)); } static struct pcmchan_caps * -aggch_getcaps(kobj_t obj, void *data) +aggpch_getcaps(kobj_t obj, void *data) { static u_int32_t playfmt[] = { AFMT_U8, @@ -785,33 +1457,162 @@ AFMT_STEREO | AFMT_S16_LE, 0 }; - static struct pcmchan_caps playcaps = {2000, 96000, playfmt, 0}; + static struct pcmchan_caps playcaps = {8000, 48000, playfmt, 0}; + + return &playcaps; +} + +static kobj_method_t aggpch_methods[] = { + KOBJMETHOD(channel_init, aggpch_init), + KOBJMETHOD(channel_free, aggpch_free), + KOBJMETHOD(channel_setformat, aggpch_setformat), + KOBJMETHOD(channel_setspeed, aggpch_setspeed), + KOBJMETHOD(channel_setblocksize, aggpch_setblocksize), + KOBJMETHOD(channel_trigger, aggpch_trigger), + KOBJMETHOD(channel_getptr, aggpch_getptr), + KOBJMETHOD(channel_getcaps, aggpch_getcaps), + { 0, 0 } +}; +CHANNEL_DECLARE(aggpch); + + +/* -------------------------------------------------------------------- */ + +/* Recording channel. */ + +static void * +aggrch_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, + struct pcm_channel *c, int dir) +{ + struct agg_info *ess = devinfo; + struct agg_rchinfo *ch; + u_int8_t *p; + + KASSERT((dir == PCMDIR_REC), + ("aggrch_init() called for PLAYBACK channel!")); + ch = &ess->rch; + + ch->parent = ess; + ch->channel = c; + ch->buffer = b; + + /* Uses the bottom-half of the status buffer. */ + p = ess->stat + ess->bufsz; + ch->phys = ess->phys + ess->bufsz; + ch->base = ess->phys; + ch->src = (int16_t *)(p + ess->bufsz); + ch->srcphys = ch->phys + ess->bufsz; + ch->sink = (int16_t *)p; + + sndbuf_setup(b, p, ess->bufsz); + ch->blklen = sndbuf_getblksz(b) / 2; + ch->buflen = sndbuf_getsize(b) / 2; + + return ch; +} + +static int +aggrch_setformat(kobj_t obj, void *data, u_int32_t format) +{ + struct agg_rchinfo *ch = data; + + if (!(format & AFMT_S16_LE)) + return EINVAL; + if (format & AFMT_STEREO) + ch->stereo = 1; + else + ch->stereo = 0; + return 0; +} + +static int +aggrch_setspeed(kobj_t obj, void *data, u_int32_t speed) +{ + return ((struct agg_rchinfo*)data)->speed = speed; +} + +static int +aggrch_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) +{ + struct agg_rchinfo *ch = data; + int blkcnt; + + /* try to keep at least 20msec DMA space */ + blkcnt = (ch->speed << ch->stereo) / (25 * blocksize); + RANGE(blkcnt, 2, ch->parent->bufsz / blocksize); + + if (sndbuf_getsize(ch->buffer) != blkcnt * blocksize) { + sndbuf_resize(ch->buffer, blkcnt, blocksize); + blkcnt = sndbuf_getblkcnt(ch->buffer); + blocksize = sndbuf_getblksz(ch->buffer); + } else { + sndbuf_setblkcnt(ch->buffer, blkcnt); + sndbuf_setblksz(ch->buffer, blocksize); + } + + ch->blklen = blocksize / 2; + ch->buflen = blkcnt * blocksize / 2; + return blocksize; +} + +static int +aggrch_trigger(kobj_t obj, void *sc, int go) +{ + struct agg_rchinfo *ch = sc; + + switch (go) { + case PCMTRIG_EMLDMARD: + if (ch->stereo) + aggch_feed_adc_stereo(ch); + else + aggch_feed_adc_mono(ch); + break; + case PCMTRIG_START: + aggch_start_adc(ch); + break; + case PCMTRIG_ABORT: + case PCMTRIG_STOP: + aggch_stop_adc(ch); + break; + } + return 0; +} + +static int +aggrch_getptr(kobj_t obj, void *sc) +{ + struct agg_rchinfo *ch = sc; + + return ch->stereo? ch->hwptr << 2 : ch->hwptr << 1; +} + +static struct pcmchan_caps * +aggrch_getcaps(kobj_t obj, void *sc) +{ static u_int32_t recfmt[] = { - AFMT_S8, - AFMT_STEREO | AFMT_S8, AFMT_S16_LE, AFMT_STEREO | AFMT_S16_LE, 0 }; - static struct pcmchan_caps reccaps = {4000, 48000, recfmt, 0}; + static struct pcmchan_caps reccaps = {8000, 48000, recfmt, 0}; - return (((struct agg_chinfo*)data)->dir == PCMDIR_PLAY)? - &playcaps : &reccaps; + return &reccaps; } -static kobj_method_t aggch_methods[] = { - KOBJMETHOD(channel_init, aggch_init), - KOBJMETHOD(channel_free, aggch_free), - KOBJMETHOD(channel_setformat, aggch_setplayformat), - KOBJMETHOD(channel_setspeed, aggch_setspeed), - KOBJMETHOD(channel_setblocksize, aggch_setblocksize), - KOBJMETHOD(channel_trigger, aggch_trigger), - KOBJMETHOD(channel_getptr, aggch_getplayptr), - KOBJMETHOD(channel_getcaps, aggch_getcaps), +static kobj_method_t aggrch_methods[] = { + KOBJMETHOD(channel_init, aggrch_init), + /* channel_free: no-op */ + KOBJMETHOD(channel_setformat, aggrch_setformat), + KOBJMETHOD(channel_setspeed, aggrch_setspeed), + KOBJMETHOD(channel_setblocksize, aggrch_setblocksize), + KOBJMETHOD(channel_trigger, aggrch_trigger), + KOBJMETHOD(channel_getptr, aggrch_getptr), + KOBJMETHOD(channel_getcaps, aggrch_getcaps), { 0, 0 } }; -CHANNEL_DECLARE(aggch); +CHANNEL_DECLARE(aggrch); + /* ----------------------------- * Bus space. @@ -821,25 +1622,61 @@ agg_intr(void *sc) { struct agg_info* ess = sc; - u_int16_t status; + register u_int8_t status; int i; + u_int m; - status = bus_space_read_1(ess->st, ess->sh, PORT_HOSTINT_STAT); + status = AGG_RD(ess, PORT_HOSTINT_STAT, 1); if (!status) return; - /* Acknowledge all. */ - bus_space_write_2(ess->st, ess->sh, PORT_INT_STAT, 1); - bus_space_write_1(ess->st, ess->sh, PORT_HOSTINT_STAT, 0xff); + /* Acknowledge intr. */ + AGG_WR(ess, PORT_HOSTINT_STAT, status, 1); + + if (status & HOSTINT_STAT_DSOUND) { +#ifdef AGG_JITTER_CORRECTION + agg_lock(ess); +#endif + if (ess->curpwr <= PCI_POWERSTATE_D1) { + AGG_WR(ess, PORT_INT_STAT, 1, 2); +#ifdef AGG_JITTER_CORRECTION + for (i = 0, m = 1; i < ess->playchns; i++, m <<= 1) { + if (ess->active & m) + suppress_jitter(ess->pch + i); + } + if (ess->active & m) + suppress_rec_jitter(&ess->rch); + agg_unlock(ess); +#endif + for (i = 0, m = 1; i < ess->playchns; i++, m <<= 1) { + if (ess->active & m) { + if (ess->curpwr <= PCI_POWERSTATE_D1) + chn_intr(ess->pch[i].channel); + else { + m = 0; + break; + } + } + } + if ((ess->active & m) + && ess->curpwr <= PCI_POWERSTATE_D1) + chn_intr(ess->rch.channel); + } +#ifdef AGG_JITTER_CORRECTION + else + agg_unlock(ess); +#endif + } if (status & HOSTINT_STAT_HWVOL) { - u_int event; + register u_int8_t event; + + agg_lock(ess); + event = AGG_RD(ess, PORT_HWVOL_MASTER, 1); + AGG_WR(ess, PORT_HWVOL_MASTER, HWVOL_NOP, 1); + agg_unlock(ess); - event = bus_space_read_1(ess->st, ess->sh, PORT_HWVOL_MASTER); switch (event) { - case HWVOL_MUTE: - mixer_hwvol_mute(ess->dev); - break; case HWVOL_UP: mixer_hwvol_step(ess->dev, 1, 1); break; @@ -849,22 +1686,15 @@ case HWVOL_NOP: break; default: - device_printf(ess->dev, "%s: unknown HWVOL event 0x%x\n", - device_get_nameunit(ess->dev), event); + if (event & HWVOL_MUTE) { + mixer_hwvol_mute(ess->dev); + break; + } + device_printf(ess->dev, + "%s: unknown HWVOL event 0x%x\n", + device_get_nameunit(ess->dev), event); } - bus_space_write_1(ess->st, ess->sh, PORT_HWVOL_MASTER, - HWVOL_NOP); } - - for (i = 0; i < ess->playchns; i++) - if (ess->active & (1 << i)) { - suppress_jitter(ess->pch + i); - chn_intr(ess->pch[i].channel); - } -#if 0 /* XXX - RECORDING */ - if (ess->active & (1 << i)) - chn_intr(ess->rch.channel); -#endif } static void @@ -882,25 +1712,25 @@ } static void * -dma_malloc(struct agg_info *sc, u_int32_t sz, bus_addr_t *phys) +dma_malloc(bus_dma_tag_t dmat, u_int32_t sz, bus_addr_t *phys) { void *buf; bus_dmamap_t map; - if (bus_dmamem_alloc(sc->parent_dmat, &buf, BUS_DMA_NOWAIT, &map)) + if (bus_dmamem_alloc(dmat, &buf, BUS_DMA_NOWAIT, &map)) return NULL; - if (bus_dmamap_load(sc->parent_dmat, map, buf, sz, setmap, phys, 0) - || !*phys) { - bus_dmamem_free(sc->parent_dmat, buf, map); + if (bus_dmamap_load(dmat, map, buf, sz, setmap, phys, 0) + || !*phys || map) { + bus_dmamem_free(dmat, buf, map); return NULL; } return buf; } static void -dma_free(struct agg_info *sc, void *buf) +dma_free(bus_dma_tag_t dmat, void *buf) { - bus_dmamem_free(sc->parent_dmat, buf, NULL); + bus_dmamem_free(dmat, buf, NULL); } static int @@ -924,7 +1754,7 @@ if (s != NULL && pci_get_class(dev) == PCIC_MULTIMEDIA) { device_set_desc(dev, s); - return 0; + return BUS_PROBE_DEFAULT; } return ENXIO; } @@ -934,7 +1764,6 @@ { struct agg_info *ess = NULL; u_int32_t data; - int mapped = 0; int regid = PCIR_BAR(0); struct resource *reg = NULL; struct ac97_info *codec = NULL; @@ -942,103 +1771,147 @@ struct resource *irq = NULL; void *ih = NULL; char status[SND_STATUSLEN]; - bus_addr_t offset; - int i; + int ret = 0; - if ((ess = malloc(sizeof *ess, M_DEVBUF, M_NOWAIT | M_ZERO)) == NULL) { - device_printf(dev, "cannot allocate softc\n"); - return ENXIO; - } + ess = malloc(sizeof(*ess), M_DEVBUF, M_WAITOK | M_ZERO); ess->dev = dev; +#ifdef USING_MUTEX + mtx_init(&ess->lock, device_get_desc(dev), "snd_maestro softc", + MTX_DEF | MTX_RECURSE); + if (!mtx_initialized(&ess->lock)) { + device_printf(dev, "failed to create a mutex.\n"); + ret = ENOMEM; + goto bad; + } +#endif + ess->bufsz = pcm_getbuffersize(dev, 4096, AGG_DEFAULT_BUFSZ, 65536); + if (bus_dma_tag_create(/*parent*/ bus_get_dma_tag(dev), + /*align */ 4, 1 << (16+1), + /*limit */ MAESTRO_MAXADDR, BUS_SPACE_MAXADDR, + /*filter*/ NULL, NULL, + /*size */ ess->bufsz, 1, 0x3ffff, + /*flags */ 0, +#if __FreeBSD_version >= 501102 + /*lock */ busdma_lock_mutex, &Giant, +#endif + &ess->buf_dmat) != 0) { + device_printf(dev, "unable to create dma tag\n"); + ret = ENOMEM; + goto bad; + } - if (bus_dma_tag_create(/*parent*/NULL, - /*alignment*/1 << WAVCACHE_BASEADDR_SHIFT, - /*boundary*/WPWA_MAXADDR + 1, - /*lowaddr*/MAESTRO_MAXADDR, /*highaddr*/BUS_SPACE_MAXADDR, - /*filter*/NULL, /*filterarg*/NULL, - /*maxsize*/ess->bufsz * (1 + AGG_MAXPLAYCH + 1), /*nsegments*/1, - /*maxsegz*/0x3ffff, /*flags*/0, /*lockfunc*/busdma_lock_mutex, - /*lockarg*/&Giant, &ess->parent_dmat) != 0) { + if (bus_dma_tag_create(/*parent*/ bus_get_dma_tag(dev), + /*align */ 1 << WAVCACHE_BASEADDR_SHIFT, + 1 << (16+1), + /*limit */ MAESTRO_MAXADDR, BUS_SPACE_MAXADDR, + /*filter*/ NULL, NULL, + /*size */ 3*ess->bufsz, 1, 0x3ffff, + /*flags */ 0, +#if __FreeBSD_version >= 501102 + /*lock */ busdma_lock_mutex, &Giant, +#endif + &ess->stat_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); + ret = ENOMEM; goto bad; } - ess->stat = dma_malloc(ess, ess->bufsz * (1 + AGG_MAXPLAYCH + 1), - &ess->baseaddr); + /* Allocate the room for brain-damaging status buffer. */ + ess->stat = dma_malloc(ess->stat_dmat, 3*ess->bufsz, &ess->phys); if (ess->stat == NULL) { - device_printf(dev, "cannot allocate DMA memory\n"); + device_printf(dev, "cannot allocate status buffer\n"); + ret = ENOMEM; goto bad; } if (bootverbose) - device_printf(dev, "Maestro DMA base: %#llx\n", - (long long)ess->baseaddr); - offset = ess->bufsz; - for (i = 0; i < AGG_MAXPLAYCH; i++) { - ess->pch[i].offset = offset; - offset += ess->bufsz; - } - ess->rch.offset = offset; + device_printf(dev, "Maestro status/record buffer: %#llx\n", + (long long)ess->phys); - agg_power(ess, PPMI_D0); - DELAY(100000); + /* State D0-uninitialized. */ + ess->curpwr = PCI_POWERSTATE_D3; + pci_set_powerstate(dev, PCI_POWERSTATE_D0); 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); - if (data & PCIM_CMD_PORTEN) { - reg = bus_alloc_resource(dev, SYS_RES_IOPORT, ®id, - 0, BUS_SPACE_UNRESTRICTED, 256, RF_ACTIVE); - if (reg != NULL) { - ess->reg = reg; - ess->regid = regid; - ess->st = rman_get_bustag(reg); - ess->sh = rman_get_bushandle(reg); - mapped++; - } - } - if (mapped == 0) { + /* Allocate resources. */ + if (data & PCIM_CMD_PORTEN) + reg = bus_alloc_resource_any(dev, SYS_RES_IOPORT, ®id, + RF_ACTIVE); + if (reg != NULL) { + ess->reg = reg; + ess->regid = regid; + ess->st = rman_get_bustag(reg); + ess->sh = rman_get_bushandle(reg); + } else { device_printf(dev, "unable to map register space\n"); + ret = ENXIO; + goto bad; + } + irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &irqid, + RF_ACTIVE | RF_SHAREABLE); + if (irq != NULL) { + ess->irq = irq; + ess->irqid = irqid; + } else { + device_printf(dev, "unable to map interrupt\n"); + ret = ENXIO; goto bad; } - agg_init(ess); - if (agg_rdcodec(NULL, ess, 0) == 0x80) { + /* Setup resources. */ + if (snd_setup_intr(dev, irq, INTR_MPSAFE, agg_intr, ess, &ih)) { + device_printf(dev, "unable to setup interrupt\n"); + ret = ENXIO; + goto bad; + } else + ess->ih = ih; + + /* Transition from D0-uninitialized to D0. */ + agg_lock(ess); + agg_power(ess, PCI_POWERSTATE_D0); + if (agg_rdcodec(ess, 0) == 0x80) { + /* XXX - TODO: PT101 */ + agg_unlock(ess); device_printf(dev, "PT101 codec detected!\n"); + ret = ENXIO; goto bad; } + agg_unlock(ess); codec = AC97_CREATE(dev, ess, agg_ac97); - if (codec == NULL) - goto bad; - if (mixer_init(dev, ac97_getmixerclass(), codec) == -1) + if (codec == NULL) { + device_printf(dev, "failed to create AC97 codec softc!\n"); + ret = ENOMEM; goto bad; - ess->codec = codec; - - irq = bus_alloc_resource(dev, SYS_RES_IRQ, &irqid, - 0, BUS_SPACE_UNRESTRICTED, 1, RF_ACTIVE | RF_SHAREABLE); - if (irq == NULL || snd_setup_intr(dev, irq, 0, agg_intr, ess, &ih)) { - device_printf(dev, "unable to map interrupt\n"); + } + if (mixer_init(dev, ac97_getmixerclass(), codec) == -1) { + device_printf(dev, "mixer initialization failed!\n"); + ret = ENXIO; goto bad; } - ess->irq = irq; - ess->irqid = irqid; - ess->ih = ih; - - snprintf(status, SND_STATUSLEN, "at I/O port 0x%lx irq %ld %s", - rman_get_start(reg), rman_get_start(irq),PCM_KLDSTRING(snd_maestro)); + ess->codec = codec; - if (pcm_register(dev, ess, AGG_MAXPLAYCH, 1)) + ret = pcm_register(dev, ess, AGG_MAXPLAYCH, 1); + if (ret) goto bad; mixer_hwvol_init(dev); + agg_lock(ess); + agg_power(ess, powerstate_init); + agg_unlock(ess); for (data = 0; data < AGG_MAXPLAYCH; data++) - pcm_addchan(dev, PCMDIR_PLAY, &aggch_class, ess); -#if 0 /* XXX - RECORDING */ + pcm_addchan(dev, PCMDIR_PLAY, &aggpch_class, ess); pcm_addchan(dev, PCMDIR_REC, &aggrch_class, ess); -#endif + adjust_pchbase(ess->pch, ess->playchns, ess->bufsz); + + snprintf(status, SND_STATUSLEN, + "port 0x%lx-0x%lx irq %ld at device %d.%d on pci%d", + rman_get_start(reg), rman_get_end(reg), rman_get_start(irq), + pci_get_slot(dev), pci_get_function(dev), pci_get_bus(dev)); pcm_setstatus(dev, status); return 0; @@ -1053,15 +1926,20 @@ if (reg != NULL) bus_release_resource(dev, SYS_RES_IOPORT, regid, reg); if (ess != NULL) { - agg_power(ess, PPMI_D3); if (ess->stat != NULL) - dma_free(ess, ess->stat); - if (ess->parent_dmat != NULL) - bus_dma_tag_destroy(ess->parent_dmat); + dma_free(ess->stat_dmat, ess->stat); + if (ess->stat_dmat != NULL) + bus_dma_tag_destroy(ess->stat_dmat); + if (ess->buf_dmat != NULL) + bus_dma_tag_destroy(ess->buf_dmat); +#ifdef USING_MUTEX + if (mtx_initialized(&ess->lock)) + mtx_destroy(&ess->lock); +#endif free(ess, M_DEVBUF); } - return ENXIO; + return ret; } static int @@ -1069,24 +1947,38 @@ { struct agg_info *ess = pcm_getdevinfo(dev); int r; + u_int16_t icr; + + icr = AGG_RD(ess, PORT_HOSTINT_CTRL, 2); + AGG_WR(ess, PORT_HOSTINT_CTRL, 0, 2); + + agg_lock(ess); + if (ess->active) { + AGG_WR(ess, PORT_HOSTINT_CTRL, icr, 2); + agg_unlock(ess); + return EBUSY; + } + agg_unlock(ess); r = pcm_unregister(dev); - if (r) + if (r) { + AGG_WR(ess, PORT_HOSTINT_CTRL, icr, 2); return r; + } - ess = pcm_getdevinfo(dev); - dma_free(ess, ess->stat); - - /* Power down everything except clock and vref. */ - agg_wrcodec(NULL, ess, AC97_REG_POWER, 0xd700); - DELAY(20); - bus_space_write_4(ess->st, ess->sh, PORT_RINGBUS_CTRL, 0); - agg_power(ess, PPMI_D3); + agg_lock(ess); + agg_power(ess, PCI_POWERSTATE_D3); + agg_unlock(ess); bus_teardown_intr(dev, ess->irq, ess->ih); bus_release_resource(dev, SYS_RES_IRQ, ess->irqid, ess->irq); bus_release_resource(dev, SYS_RES_IOPORT, ess->regid, ess->reg); - bus_dma_tag_destroy(ess->parent_dmat); + dma_free(ess->stat_dmat, ess->stat); + bus_dma_tag_destroy(ess->stat_dmat); + bus_dma_tag_destroy(ess->buf_dmat); +#ifdef USING_MUTEX + mtx_destroy(&ess->lock); +#endif free(ess, M_DEVBUF); return 0; } @@ -1095,25 +1987,18 @@ agg_suspend(device_t dev) { struct agg_info *ess = pcm_getdevinfo(dev); - int i, x; +#ifndef USING_MUTEX + int x; x = spltty(); - wp_stoptimer(ess); - bus_space_write_2(ess->st, ess->sh, PORT_HOSTINT_CTRL, 0); - - for (i = 0; i < ess->playchns; i++) - aggch_stop_dac(ess->pch + i); - -#if 0 /* XXX - RECORDING */ - aggch_stop_adc(&ess->rch); #endif + AGG_WR(ess, PORT_HOSTINT_CTRL, 0, 2); + agg_lock(ess); + agg_power(ess, PCI_POWERSTATE_D3); + agg_unlock(ess); +#ifndef USING_MUTEX splx(x); - /* Power down everything except clock. */ - agg_wrcodec(NULL, ess, AC97_REG_POWER, 0xdf00); - DELAY(20); - bus_space_write_4(ess->st, ess->sh, PORT_RINGBUS_CTRL, 0); - DELAY(1); - agg_power(ess, PPMI_D3); +#endif return 0; } @@ -1121,30 +2006,32 @@ static int agg_resume(device_t dev) { - int i, x; + int i; struct agg_info *ess = pcm_getdevinfo(dev); - - agg_power(ess, PPMI_D0); - DELAY(100000); - agg_init(ess); - if (mixer_reinit(dev)) { - device_printf(dev, "unable to reinitialize the mixer\n"); - return ENXIO; - } +#ifndef USING_MUTEX + int x; x = spltty(); +#endif for (i = 0; i < ess->playchns; i++) if (ess->active & (1 << i)) aggch_start_dac(ess->pch + i); -#if 0 /* XXX - RECORDING */ if (ess->active & (1 << i)) aggch_start_adc(&ess->rch); + + agg_lock(ess); + if (!ess->active) + agg_power(ess, powerstate_init); + agg_unlock(ess); +#ifndef USING_MUTEX + splx(x); #endif - if (ess->active) { - set_timer(ess); - wp_starttimer(ess); + + if (mixer_reinit(dev)) { + device_printf(dev, "unable to reinitialize the mixer\n"); + return ENXIO; } - splx(x); + return 0; } @@ -1152,17 +2039,11 @@ agg_shutdown(device_t dev) { struct agg_info *ess = pcm_getdevinfo(dev); - int i; - wp_stoptimer(ess); - bus_space_write_2(ess->st, ess->sh, PORT_HOSTINT_CTRL, 0); + agg_lock(ess); + agg_power(ess, PCI_POWERSTATE_D3); + agg_unlock(ess); - for (i = 0; i < ess->playchns; i++) - aggch_stop_dac(ess->pch + i); - -#if 0 /* XXX - RECORDING */ - aggch_stop_adc(&ess->rch); -#endif return 0; } @@ -1183,6 +2064,8 @@ agg_methods, PCM_SOFTC_SIZE, }; + +/*static devclass_t pcm_devclass;*/ DRIVER_MODULE(snd_maestro, pci, agg_driver, pcm_devclass, 0, 0); MODULE_DEPEND(snd_maestro, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); --- sys/dev/sound/pci/maestro3.c.orig Wed Sep 21 11:20:36 2005 +++ sys/dev/sound/pci/maestro3.c Thu Jul 12 12:04:19 2007 @@ -61,7 +61,7 @@ #include #include -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/maestro3.c,v 1.25.2.3 2005/09/21 03:20:36 yongari Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/maestro3.c,v 1.35 2007/06/17 06:10:42 ariff Exp $"); /* -------------------------------------------------------------------- */ @@ -86,7 +86,7 @@ { 0, 0, 0, 0, NULL } }; -#define M3_BUFSIZE_MIN 1024 +#define M3_BUFSIZE_MIN 4096 #define M3_BUFSIZE_MAX 65536 #define M3_BUFSIZE_DEFAULT 4096 #define M3_PCHANS 4 /* create /dev/dsp0.[0-N] to use more than one */ @@ -105,6 +105,8 @@ u_int32_t dac_data; u_int32_t dac_idx; u_int32_t active; + u_int32_t ptr; + u_int32_t prevptr; }; struct sc_rchinfo { @@ -117,6 +119,8 @@ u_int32_t adc_data; u_int32_t adc_idx; u_int32_t active; + u_int32_t ptr; + u_int32_t prevptr; }; struct sc_info { @@ -162,7 +166,8 @@ static int m3_pchan_setblocksize(kobj_t, void *, u_int32_t); static int m3_pchan_trigger(kobj_t, void *, int); static int m3_pchan_trigger_locked(kobj_t, void *, int); -static int m3_pchan_getptr(kobj_t, void *); +static u_int32_t m3_pchan_getptr_internal(struct sc_pchinfo *); +static u_int32_t m3_pchan_getptr(kobj_t, void *); static struct pcmchan_caps *m3_pchan_getcaps(kobj_t, void *); /* record channel interface */ @@ -173,9 +178,12 @@ static int m3_rchan_setblocksize(kobj_t, void *, u_int32_t); static int m3_rchan_trigger(kobj_t, void *, int); static int m3_rchan_trigger_locked(kobj_t, void *, int); -static int m3_rchan_getptr(kobj_t, void *); +static u_int32_t m3_rchan_getptr_internal(struct sc_rchinfo *); +static u_int32_t m3_rchan_getptr(kobj_t, void *); static struct pcmchan_caps *m3_rchan_getcaps(kobj_t, void *); +static int m3_chan_active(struct sc_info *); + /* talk to the codec - called from ac97.c */ static int m3_initcd(kobj_t, void *); static int m3_rdcd(kobj_t, void *, int); @@ -399,7 +407,7 @@ ch->fmt = AFMT_U8; ch->spd = DSP_DEFAULT_SPEED; M3_UNLOCK(sc); /* XXX */ - if (sndbuf_alloc(ch->buffer, sc->parent_dmat, sc->bufsz) != 0) { + if (sndbuf_alloc(ch->buffer, sc->parent_dmat, 0, sc->bufsz) != 0) { device_printf(sc->dev, "m3_pchan_init chn_allocbuf failed\n"); return (NULL); } @@ -555,7 +563,7 @@ M3_DEBUG(CHANGE, ("m3_pchan_setblocksize(dac=%d, blocksize=%d)\n", ch->dac_idx, blocksize)); - return blocksize; + return (sndbuf_getblksz(ch->buffer)); } static int @@ -565,6 +573,9 @@ struct sc_info *sc = ch->parent; int ret; + if (!PCMTRIG_COMMON(go)) + return (0); + M3_LOCK(sc); ret = m3_pchan_trigger_locked(kobj, chdata, go); M3_UNLOCK(sc); @@ -573,6 +584,22 @@ } static int +m3_chan_active(struct sc_info *sc) +{ + int i, ret; + + ret = 0; + + for (i = 0; i < sc->pch_cnt; i++) + ret += sc->pch[i].active; + + for (i = 0; i < sc->rch_cnt; i++) + ret += sc->rch[i].active; + + return (ret); +} + +static int m3_pchan_trigger_locked(kobj_t kobj, void *chdata, int go) { struct sc_pchinfo *ch = chdata; @@ -595,13 +622,17 @@ return 0; } ch->active = 1; + ch->ptr = 0; + ch->prevptr = 0; sc->pch_active_cnt++; /*[[inc_timer_users]]*/ - m3_wr_assp_data(sc, KDATA_TIMER_COUNT_RELOAD, 240); - m3_wr_assp_data(sc, KDATA_TIMER_COUNT_CURRENT, 240); - data = m3_rd_2(sc, HOST_INT_CTRL); - m3_wr_2(sc, HOST_INT_CTRL, data | CLKRUN_GEN_ENABLE); + if (m3_chan_active(sc) == 1) { + m3_wr_assp_data(sc, KDATA_TIMER_COUNT_RELOAD, 240); + m3_wr_assp_data(sc, KDATA_TIMER_COUNT_CURRENT, 240); + data = m3_rd_2(sc, HOST_INT_CTRL); + m3_wr_2(sc, HOST_INT_CTRL, data | CLKRUN_GEN_ENABLE); + } m3_wr_assp_data(sc, ch->dac_data + CDATA_INSTANCE_READY, 1); m3_wr_assp_data(sc, KDATA_MIXER_TASK_NUMBER, @@ -618,10 +649,12 @@ /* XXX should the channel be drained? */ /*[[dec_timer_users]]*/ - m3_wr_assp_data(sc, KDATA_TIMER_COUNT_RELOAD, 0); - m3_wr_assp_data(sc, KDATA_TIMER_COUNT_CURRENT, 0); - data = m3_rd_2(sc, HOST_INT_CTRL); - m3_wr_2(sc, HOST_INT_CTRL, data & ~CLKRUN_GEN_ENABLE); + if (m3_chan_active(sc) == 0) { + m3_wr_assp_data(sc, KDATA_TIMER_COUNT_RELOAD, 0); + m3_wr_assp_data(sc, KDATA_TIMER_COUNT_CURRENT, 0); + data = m3_rd_2(sc, HOST_INT_CTRL); + m3_wr_2(sc, HOST_INT_CTRL, data & ~CLKRUN_GEN_ENABLE); + } m3_wr_assp_data(sc, ch->dac_data + CDATA_INSTANCE_READY, 0); m3_wr_assp_data(sc, KDATA_MIXER_TASK_NUMBER, @@ -638,14 +671,12 @@ return 0; } -static int -m3_pchan_getptr(kobj_t kobj, void *chdata) +static u_int32_t +m3_pchan_getptr_internal(struct sc_pchinfo *ch) { - struct sc_pchinfo *ch = chdata; struct sc_info *sc = ch->parent; u_int32_t hi, lo, bus_base, bus_crnt; - M3_LOCK(sc); bus_base = sndbuf_getbufaddr(ch->buffer); hi = m3_rd_assp_data(sc, ch->dac_data + CDATA_HOST_SRC_CURRENTH); lo = m3_rd_assp_data(sc, ch->dac_data + CDATA_HOST_SRC_CURRENTL); @@ -653,11 +684,24 @@ M3_DEBUG(CALL, ("m3_pchan_getptr(dac=%d) result=%d\n", ch->dac_idx, bus_crnt - bus_base)); - M3_UNLOCK(sc); return (bus_crnt - bus_base); /* current byte offset of channel */ } +static u_int32_t +m3_pchan_getptr(kobj_t kobj, void *chdata) +{ + struct sc_pchinfo *ch = chdata; + struct sc_info *sc = ch->parent; + u_int32_t ptr; + + M3_LOCK(sc); + ptr = ch->ptr; + M3_UNLOCK(sc); + + return (ptr); +} + static struct pcmchan_caps * m3_pchan_getcaps(kobj_t kobj, void *chdata) { @@ -715,7 +759,7 @@ ch->fmt = AFMT_U8; ch->spd = DSP_DEFAULT_SPEED; M3_UNLOCK(sc); /* XXX */ - if (sndbuf_alloc(ch->buffer, sc->parent_dmat, sc->bufsz) != 0) { + if (sndbuf_alloc(ch->buffer, sc->parent_dmat, 0, sc->bufsz) != 0) { device_printf(sc->dev, "m3_rchan_init chn_allocbuf failed\n"); return (NULL); } @@ -865,7 +909,7 @@ M3_DEBUG(CHANGE, ("m3_rchan_setblocksize(adc=%d, blocksize=%d)\n", ch->adc_idx, blocksize)); - return blocksize; + return (sndbuf_getblksz(ch->buffer)); } static int @@ -875,6 +919,9 @@ struct sc_info *sc = ch->parent; int ret; + if (!PCMTRIG_COMMON(go)) + return (0); + M3_LOCK(sc); ret = m3_rchan_trigger_locked(kobj, chdata, go); M3_UNLOCK(sc); @@ -905,12 +952,16 @@ return 0; } ch->active = 1; + ch->ptr = 0; + ch->prevptr = 0; /*[[inc_timer_users]]*/ - m3_wr_assp_data(sc, KDATA_TIMER_COUNT_RELOAD, 240); - m3_wr_assp_data(sc, KDATA_TIMER_COUNT_CURRENT, 240); - data = m3_rd_2(sc, HOST_INT_CTRL); - m3_wr_2(sc, HOST_INT_CTRL, data | CLKRUN_GEN_ENABLE); + if (m3_chan_active(sc) == 1) { + m3_wr_assp_data(sc, KDATA_TIMER_COUNT_RELOAD, 240); + m3_wr_assp_data(sc, KDATA_TIMER_COUNT_CURRENT, 240); + data = m3_rd_2(sc, HOST_INT_CTRL); + m3_wr_2(sc, HOST_INT_CTRL, data | CLKRUN_GEN_ENABLE); + } m3_wr_assp_data(sc, KDATA_ADC1_REQUEST, 1); m3_wr_assp_data(sc, ch->adc_data + CDATA_INSTANCE_READY, 1); @@ -924,10 +975,12 @@ ch->active = 0; /*[[dec_timer_users]]*/ - m3_wr_assp_data(sc, KDATA_TIMER_COUNT_RELOAD, 0); - m3_wr_assp_data(sc, KDATA_TIMER_COUNT_CURRENT, 0); - data = m3_rd_2(sc, HOST_INT_CTRL); - m3_wr_2(sc, HOST_INT_CTRL, data & ~CLKRUN_GEN_ENABLE); + if (m3_chan_active(sc) == 0) { + m3_wr_assp_data(sc, KDATA_TIMER_COUNT_RELOAD, 0); + m3_wr_assp_data(sc, KDATA_TIMER_COUNT_CURRENT, 0); + data = m3_rd_2(sc, HOST_INT_CTRL); + m3_wr_2(sc, HOST_INT_CTRL, data & ~CLKRUN_GEN_ENABLE); + } m3_wr_assp_data(sc, ch->adc_data + CDATA_INSTANCE_READY, 0); m3_wr_assp_data(sc, KDATA_ADC1_REQUEST, 0); @@ -943,14 +996,12 @@ return 0; } -static int -m3_rchan_getptr(kobj_t kobj, void *chdata) +static u_int32_t +m3_rchan_getptr_internal(struct sc_rchinfo *ch) { - struct sc_rchinfo *ch = chdata; struct sc_info *sc = ch->parent; u_int32_t hi, lo, bus_base, bus_crnt; - M3_LOCK(sc); bus_base = sndbuf_getbufaddr(ch->buffer); hi = m3_rd_assp_data(sc, ch->adc_data + CDATA_HOST_SRC_CURRENTH); lo = m3_rd_assp_data(sc, ch->adc_data + CDATA_HOST_SRC_CURRENTL); @@ -958,11 +1009,24 @@ M3_DEBUG(CALL, ("m3_rchan_getptr(adc=%d) result=%d\n", ch->adc_idx, bus_crnt - bus_base)); - M3_UNLOCK(sc); return (bus_crnt - bus_base); /* current byte offset of channel */ } +static u_int32_t +m3_rchan_getptr(kobj_t kobj, void *chdata) +{ + struct sc_rchinfo *ch = chdata; + struct sc_info *sc = ch->parent; + u_int32_t ptr; + + M3_LOCK(sc); + ptr = ch->ptr; + M3_UNLOCK(sc); + + return (ptr); +} + static struct pcmchan_caps * m3_rchan_getcaps(kobj_t kobj, void *chdata) { @@ -980,7 +1044,9 @@ m3_intr(void *p) { struct sc_info *sc = (struct sc_info *)p; - u_int32_t status, ctl, i; + struct sc_pchinfo *pch; + struct sc_rchinfo *rch; + u_int32_t status, ctl, i, delta; M3_DEBUG(INTR, ("m3_intr\n")); @@ -1024,25 +1090,44 @@ m3_wr_1(sc, ASSP_HOST_INT_STATUS, DSP2HOST_REQ_TIMER); /*[[ess_update_ptr]]*/ + goto m3_handle_channel_intr; } } } + goto m3_handle_channel_intr_out; + +m3_handle_channel_intr: for (i=0 ; ipch_cnt ; i++) { - if (sc->pch[i].active) { + pch = &sc->pch[i]; + if (pch->active) { + pch->ptr = m3_pchan_getptr_internal(pch); + delta = pch->bufsize + pch->ptr - pch->prevptr; + delta %= pch->bufsize; + if (delta < sndbuf_getblksz(pch->buffer)) + continue; + pch->prevptr = pch->ptr; M3_UNLOCK(sc); - chn_intr(sc->pch[i].channel); + chn_intr(pch->channel); M3_LOCK(sc); } } for (i=0 ; irch_cnt ; i++) { - if (sc->rch[i].active) { + rch = &sc->rch[i]; + if (rch->active) { + rch->ptr = m3_rchan_getptr_internal(rch); + delta = rch->bufsize + rch->ptr - rch->prevptr; + delta %= rch->bufsize; + if (delta < sndbuf_getblksz(rch->buffer)) + continue; + rch->prevptr = rch->ptr; M3_UNLOCK(sc); - chn_intr(sc->rch[i].channel); + chn_intr(rch->channel); M3_LOCK(sc); } } +m3_handle_channel_intr_out: M3_UNLOCK(sc); } @@ -1162,7 +1247,7 @@ for (card = m3_card_types ; card->pci_id ; card++) { if (pci_get_devid(dev) == card->pci_id) { device_set_desc(dev, card->name); - return 0; + return BUS_PROBE_DEFAULT; } } return ENXIO; @@ -1173,27 +1258,18 @@ { struct sc_info *sc; struct ac97_info *codec = NULL; - u_int32_t data, i; + u_int32_t data; char status[SND_STATUSLEN]; struct m3_card_type *card; - int len; + int i, len, dacn, adcn; M3_DEBUG(CALL, ("m3_pci_attach\n")); - 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); sc->sc_lock = snd_mtxcreate(device_get_nameunit(dev), - "sound softc"); - if (sc->sc_lock == NULL) { - device_printf(dev, "cannot create mutex\n"); - free(sc, M_DEVBUF); - return (ENXIO); - } + "snd_maestro3 softc"); for (card = m3_card_types ; card->pci_id ; card++) { if (sc->type == card->pci_id) { sc->which = card->which; @@ -1203,6 +1279,19 @@ } } + if (resource_int_value(device_get_name(dev), device_get_unit(dev), + "dac", &i) == 0) { + if (i < 1) + dacn = 1; + else if (i > M3_PCHANS) + dacn = M3_PCHANS; + else + dacn = i; + } else + dacn = M3_PCHANS; + + adcn = M3_RCHANS; + data = pci_read_config(dev, PCIR_COMMAND, 2); data |= (PCIM_CMD_PORTEN | PCIM_CMD_MEMEN | PCIM_CMD_BUSMASTEREN); pci_write_config(dev, PCIR_COMMAND, data, 2); @@ -1236,11 +1325,11 @@ goto bad; } - sc->bufsz = pcm_getbuffersize(dev, M3_BUFSIZE_MAX, M3_BUFSIZE_DEFAULT, + sc->bufsz = pcm_getbuffersize(dev, M3_BUFSIZE_MIN, M3_BUFSIZE_DEFAULT, M3_BUFSIZE_MAX); if (bus_dma_tag_create( - NULL, /* parent */ + bus_get_dma_tag(dev), /* parent */ 2, 0, /* alignment, boundary */ M3_MAXADDR, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ @@ -1279,17 +1368,17 @@ m3_enable_ints(sc); - if (pcm_register(dev, sc, M3_PCHANS, M3_RCHANS)) { + if (pcm_register(dev, sc, dacn, adcn)) { device_printf(dev, "pcm_register error\n"); goto bad; } - for (i=0 ; isavemem = (u_int16_t*)malloc(len, M_DEVBUF, M_NOWAIT | M_ZERO); - if (sc->savemem == NULL) { - device_printf(dev, "Failed to create suspend buffer\n"); - goto bad; - } + sc->savemem = (u_int16_t*)malloc(len, M_DEVBUF, M_WAITOK | M_ZERO); return 0; --- sys/dev/sound/pci/maestro_reg.h.orig Wed Jan 3 09:32:57 2001 +++ sys/dev/sound/pci/maestro_reg.h Wed Nov 10 12:29:09 2004 @@ -23,8 +23,8 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $Id: maestro_reg.h,v 1.10 2000/08/29 17:27:29 taku Exp $ - * $FreeBSD: src/sys/dev/sound/pci/maestro_reg.h,v 1.2 2001/01/03 01:32:57 jhb Exp $ + * maestro_reg.h,v 1.13 2001/11/11 18:29:46 taku Exp + * $FreeBSD: src/sys/dev/sound/pci/maestro_reg.h,v 1.3 2004/11/10 04:29:09 julian Exp $ */ #ifndef MAESTRO_REG_H_INCLUDED @@ -41,10 +41,13 @@ /* Chip configurations */ #define CONF_MAESTRO 0x50 +#define MAESTRO_PMC 0x08000000 +#define MAESTRO_SPDIF 0x01000000 +#define MAESTRO_HWVOL 0x00800000 #define MAESTRO_CHIBUS 0x00100000 #define MAESTRO_POSTEDWRITE 0x00000080 #define MAESTRO_DMA_PCITIMING 0x00000040 -#define MAESTRO_SWAP_LR 0x00000010 +#define MAESTRO_SWAP_LR 0x00000020 /* ACPI configurations */ #define CONF_ACPI_STOPCLOCK 0x54 @@ -135,12 +138,15 @@ #define HOSTINT_STAT_SB 0x01 /* Hardware volume */ +#define PORT_HWVOL_CTRL 0x1b /* BYTE RW */ +#define HWVOL_CTRL_SPLIT_SHADOW 0x01 + #define PORT_HWVOL_VOICE_SHADOW 0x1c /* BYTE RW */ #define PORT_HWVOL_VOICE 0x1d /* BYTE RW */ #define PORT_HWVOL_MASTER_SHADOW 0x1e /* BYTE RW */ #define PORT_HWVOL_MASTER 0x1f /* BYTE RW */ #define HWVOL_NOP 0x88 -#define HWVOL_MUTE 0x99 +#define HWVOL_MUTE 0x11 #define HWVOL_UP 0xaa #define HWVOL_DOWN 0x66 @@ -163,8 +169,6 @@ #define RINGBUS_CTRL_RINGBUS_ENABLED 0x20000000 #define RINGBUS_CTRL_ACLINK_ENABLED 0x10000000 #define RINGBUS_CTRL_AC97_SWRESET 0x08000000 -#define RINGBUS_CTRL_IODMA_PLAYBACK_ENABLED 0x04000000 -#define RINGBUS_CTRL_IODMA_RECORD_ENABLED 0x02000000 #define RINGBUS_SRC_MIC 20 #define RINGBUS_SRC_I2S 16 @@ -182,6 +186,15 @@ #define RINGBUS_DEST_DSOUND_IN 4 #define RINGBUS_DEST_ASSP_IN 5 +/* Ring bus control B */ +#define PORT_RINGBUS_CTRL_B 0x38 /* BYTE RW */ +#define RINGBUS_CTRL_SSPE 0x40 +#define RINGBUS_CTRL_2ndCODEC 0x20 +#define RINGBUS_CTRL_SPDIF 0x10 +#define RINGBUS_CTRL_ITB_DISABLE 0x08 +#define RINGBUS_CTRL_CODEC_ID_MASK 0x03 +#define RINGBUS_CTRL_CODEC_ID_AC98 2 + /* General Purpose I/O */ #define PORT_GPIO_DATA 0x60 /* WORD RW */ #define PORT_GPIO_MASK 0x64 /* WORD RW */ @@ -297,22 +310,35 @@ /* APU register 4 */ #define APUREG_WAVESPACE 4 -#define APU_STEREO 0x8000 -#define APU_USE_SYSMEM 0x4000 -#define APU_PCMBAR_MASK 0x6000 #define APU_64KPAGE_MASK 0xff00 -/* PCM Base Address Register selection */ -#define APU_PCMBAR_SHIFT 13 - /* 64KW (==128KB) Page */ #define APU_64KPAGE_SHIFT 8 +/* Wave Processor Wavespace Address */ +#define WPWA_MAX ((1 << 22) - 1) +#define WPWA_STEREO (1 << 23) +#define WPWA_USE_SYSMEM (1 << 22) + +#define WPWA_WTBAR_SHIFT(wtsz) WPWA_WTBAR_SHIFT_##wtsz +#define WPWA_WTBAR_SHIFT_1 15 +#define WPWA_WTBAR_SHIFT_2 16 +#define WPWA_WTBAR_SHIFT_4 17 +#define WPWA_WTBAR_SHIFT_8 18 + +#define WPWA_PCMBAR_SHIFT 20 + /* APU register 5 - 7 */ #define APUREG_CURPTR 5 #define APUREG_ENDPTR 6 #define APUREG_LOOPLEN 7 +/* APU register 8 */ +#define APUREG_EFFECT_GAIN 8 + +/* Effect gain? */ +#define APUREG_EFFECT_GAIN_MASK 0x00ff + /* APU register 9 */ #define APUREG_AMPLITUDE 9 #define APU_AMPLITUDE_NOW_MASK 0xff00 @@ -338,11 +364,20 @@ #define PAN_FRONT 0x08 #define PAN_LEFT 0x10 +/* Source routing. */ +#define APUREG_ROUTING 11 +#define APU_INVERT_POLARITY_B 0x8000 +#define APU_DATASRC_B_MASK 0x7f00 +#define APU_INVERT_POLARITY_A 0x0080 +#define APU_DATASRC_A_MASK 0x007f + +#define APU_DATASRC_A_SHIFT 0 +#define APU_DATASRC_B_SHIFT 8 + /* ----------------------------- * Limits. */ -#define WPWA_MAX ((1 << 22) - 1) #define WPWA_MAXADDR ((1 << 23) - 1) #define MAESTRO_MAXADDR ((1 << 28) - 1) --- sys/dev/sound/pci/neomagic-coeff.h.orig Sun Jan 30 09:00:04 2005 +++ sys/dev/sound/pci/neomagic-coeff.h Thu Jan 6 09:43:19 2005 @@ -25,7 +25,7 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THEPOSSIBILITY OF * SUCH DAMAGE. * - * $FreeBSD: src/sys/dev/sound/pci/neomagic-coeff.h,v 1.3.2.1 2005/01/30 01:00:04 imp Exp $ + * $FreeBSD: src/sys/dev/sound/pci/neomagic-coeff.h,v 1.4 2005/01/06 01:43:19 imp Exp $ */ #define NM_TOTAL_COEFF_COUNT 0x3158 --- sys/dev/sound/pci/neomagic.c.orig Sun Jan 30 09:00:04 2005 +++ sys/dev/sound/pci/neomagic.c Thu Jul 12 12:04:19 2007 @@ -34,7 +34,7 @@ #include #include -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/neomagic.c,v 1.33.2.1 2005/01/30 01:00:04 imp Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/neomagic.c,v 1.37 2007/06/17 06:10:42 ariff Exp $"); /* -------------------------------------------------------------------- */ @@ -223,7 +223,16 @@ struct sc_info *sc = (struct sc_info *)devinfo; nm_wr(sc, 0x6c0, 0x01, 1); +#if 0 + /* + * The following code-line may cause a hang for some chipsets, see + * PR 56617. + * In case of a bugreport without this line have a look at the PR and + * conditionize the code-line based upon the specific version of + * the chip. + */ nm_wr(sc, 0x6cc, 0x87, 1); +#endif nm_wr(sc, 0x6cc, 0x80, 1); nm_wr(sc, 0x6cc, 0x00, 1); return 1; @@ -397,7 +406,7 @@ struct sc_info *sc = ch->parent; int ssz; - if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) + if (!PCMTRIG_COMMON(go)) return 0; ssz = (ch->fmt & AFMT_16BIT)? 2 : 1; @@ -662,11 +671,7 @@ struct ac97_info *codec = 0; 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); --- sys/dev/sound/pci/neomagic.h.orig Sun Jan 30 09:00:04 2005 +++ sys/dev/sound/pci/neomagic.h Thu Jan 6 09:43:19 2005 @@ -25,7 +25,7 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THEPOSSIBILITY OF * SUCH DAMAGE. * - * $FreeBSD: src/sys/dev/sound/pci/neomagic.h,v 1.3.4.1 2005/01/30 01:00:04 imp Exp $ + * $FreeBSD: src/sys/dev/sound/pci/neomagic.h,v 1.4 2005/01/06 01:43:19 imp Exp $ */ #ifndef _NM256_H_ --- sys/dev/sound/pci/solo.c.orig Sun Jan 30 09:00:04 2005 +++ sys/dev/sound/pci/solo.c Thu Jul 12 12:04:19 2007 @@ -33,17 +33,20 @@ #include "mixer_if.h" -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/solo.c,v 1.32.2.2 2005/01/30 01:00:04 imp Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/solo.c,v 1.45 2007/06/17 06:10:42 ariff Exp $"); #define SOLO_DEFAULT_BUFSZ 16384 #define ABS(x) (((x) < 0)? -(x) : (x)) /* if defined, playback always uses the 2nd channel and full duplex works */ -#undef ESS18XX_DUPLEX +#define ESS18XX_DUPLEX 1 /* more accurate clocks and split audio1/audio2 rates */ #define ESS18XX_NEWSPEED +/* 1 = INTR_MPSAFE, 0 = GIANT */ +#define ESS18XX_MPSAFE 1 + static u_int32_t ess_playfmt[] = { AFMT_U8, AFMT_STEREO | AFMT_U8, @@ -55,7 +58,7 @@ AFMT_STEREO | AFMT_U16_LE, 0 }; -static struct pcmchan_caps ess_playcaps = {5000, 49000, ess_playfmt, 0}; +static struct pcmchan_caps ess_playcaps = {6000, 48000, ess_playfmt, 0}; /* * Recording output is byte-swapped @@ -71,7 +74,7 @@ AFMT_STEREO | AFMT_U16_BE, 0 }; -static struct pcmchan_caps ess_reccaps = {5000, 49000, ess_recfmt, 0}; +static struct pcmchan_caps ess_reccaps = {6000, 48000, ess_recfmt, 0}; struct ess_info; @@ -89,12 +92,26 @@ void *ih; bus_dma_tag_t parent_dmat; - int simplex_dir, type, duplex:1, newspeed:1, dmasz[2]; + int simplex_dir, type, dmasz[2]; + unsigned int duplex:1, newspeed:1; unsigned int bufsz; struct ess_chinfo pch, rch; +#if ESS18XX_MPSAFE == 1 + struct mtx *lock; +#endif }; +#if ESS18XX_MPSAFE == 1 +#define ess_lock(_ess) snd_mtxlock((_ess)->lock) +#define ess_unlock(_ess) snd_mtxunlock((_ess)->lock) +#define ess_lock_assert(_ess) snd_mtxassert((_ess)->lock) +#else +#define ess_lock(_ess) +#define ess_unlock(_ess) +#define ess_lock_assert(_ess) +#endif + static int ess_rd(struct ess_info *sc, int reg); static void ess_wr(struct ess_info *sc, int reg, u_int8_t val); static int ess_dspready(struct ess_info *sc); @@ -219,29 +236,22 @@ static void ess_setmixer(struct ess_info *sc, u_int port, u_int value) { - u_long flags; - DEB(printf("ess_setmixer: reg=%x, val=%x\n", port, value);) - flags = spltty(); ess_wr(sc, SB_MIX_ADDR, (u_char) (port & 0xff)); /* Select register */ DELAY(10); ess_wr(sc, SB_MIX_DATA, (u_char) (value & 0xff)); DELAY(10); - splx(flags); } static int ess_getmixer(struct ess_info *sc, u_int port) { int val; - u_long flags; - flags = spltty(); ess_wr(sc, SB_MIX_ADDR, (u_char) (port & 0xff)); /* Select register */ DELAY(10); val = ess_rd(sc, SB_MIX_DATA); DELAY(10); - splx(flags); return val; } @@ -296,14 +306,17 @@ struct ess_info *sc = (struct ess_info *)arg; int src, pirq = 0, rirq = 0; + ess_lock(sc); src = 0; if (ess_getmixer(sc, 0x7a) & 0x80) src |= 2; if (ess_rd(sc, 0x0c) & 0x01) src |= 1; - if (src == 0) + if (src == 0) { + ess_unlock(sc); return; + } if (sc->duplex) { pirq = (src & sc->pch.hwch)? 1 : 0; @@ -328,7 +341,9 @@ else ess_setmixer(sc, 0x78, ess_getmixer(sc, 0x78) & ~0x03); } + ess_unlock(sc); chn_intr(sc->pch.channel); + ess_lock(sc); } if (rirq) { @@ -338,13 +353,17 @@ /* XXX: will this stop audio2? */ ess_write(sc, 0xb8, ess_read(sc, 0xb8) & ~0x01); } + ess_unlock(sc); chn_intr(sc->rch.channel); + ess_lock(sc); } if (src & 2) ess_setmixer(sc, 0x7a, ess_getmixer(sc, 0x7a) & ~0x80); if (src & 1) ess_rd(sc, DSP_DATA_AVAIL); + + ess_unlock(sc); } /* utility functions for ESS */ @@ -520,7 +539,7 @@ ch->channel = c; ch->buffer = b; ch->dir = dir; - 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->hwch = 1; if ((dir == PCMDIR_PLAY) && (sc->duplex)) @@ -566,10 +585,12 @@ struct ess_chinfo *ch = data; struct ess_info *sc = ch->parent; - DEB(printf("esschan_trigger: %d\n",go)); - if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) + if (!PCMTRIG_COMMON(go)) return 0; + DEB(printf("esschan_trigger: %d\n",go)); + + ess_lock(sc); switch (go) { case PCMTRIG_START: ess_dmasetup(sc, ch->hwch, sndbuf_getbufaddr(ch->buffer), sndbuf_getsize(ch->buffer), ch->dir); @@ -583,6 +604,7 @@ ess_stop(ch); break; } + ess_unlock(sc); return 0; } @@ -591,8 +613,12 @@ { struct ess_chinfo *ch = data; struct ess_info *sc = ch->parent; + int ret; - return ess_dmapos(sc, ch->hwch); + ess_lock(sc); + ret = ess_dmapos(sc, ch->hwch); + ess_unlock(sc); + return ret; } static struct pcmchan_caps * @@ -760,10 +786,8 @@ ess_dmapos(struct ess_info *sc, int ch) { int p = 0, i = 0, j = 0; - u_long flags; KASSERT(ch == 1 || ch == 2, ("bad ch")); - flags = spltty(); if (ch == 1) { /* @@ -787,7 +811,6 @@ } else if (ch == 2) p = port_rd(sc->io, 0x4, 2); - splx(flags); return sc->dmasz[ch - 1] - p; } @@ -841,6 +864,13 @@ sc->parent_dmat = 0; } +#if ESS18XX_MPSAFE == 1 + if (sc->lock) { + snd_mtxfree(sc->lock); + sc->lock = NULL; + } +#endif + free(sc, M_DEVBUF); } @@ -868,7 +898,14 @@ sc->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_ACTIVE | RF_SHAREABLE); +#if ESS18XX_MPSAFE == 1 + sc->lock = snd_mtxcreate(device_get_nameunit(dev), "snd_solo softc"); + + return (sc->irq && sc->io && sc->sb && sc->vc && + sc->mpu && sc->gp && sc->lock)? 0 : ENXIO; +#else return (sc->irq && sc->io && sc->sb && sc->vc && sc->mpu && sc->gp)? 0 : ENXIO; +#endif } static int @@ -891,7 +928,7 @@ if (s) device_set_desc(dev, s); - return s? 0 : ENXIO; + return s ? BUS_PROBE_DEFAULT : ENXIO; } #define ESS_PCI_LEGACYCONTROL 0x40 @@ -911,6 +948,7 @@ uint32_t data; struct ess_info *sc = pcm_getdevinfo(dev); + ess_lock(sc); data = pci_read_config(dev, PCIR_COMMAND, 2); data |= PCIM_CMD_PORTEN | PCIM_CMD_BUSMASTEREN; pci_write_config(dev, PCIR_COMMAND, data, 2); @@ -921,14 +959,19 @@ pci_write_config(dev, ESS_PCI_DDMACONTROL, ddma, 2); pci_write_config(dev, ESS_PCI_CONFIG, 0, 2); - if (ess_reset_dsp(sc)) + if (ess_reset_dsp(sc)) { + ess_unlock(sc); goto no; + } + ess_unlock(sc); if (mixer_reinit(dev)) goto no; + ess_lock(sc); if (sc->newspeed) ess_setmixer(sc, 0x71, 0x2a); port_wr(sc->io, 0x7, 0xb0, 1); /* enable irqs */ + ess_unlock(sc); return 0; no: @@ -943,10 +986,7 @@ u_int16_t ddma; u_int32_t data; - 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); data = pci_read_config(dev, PCIR_COMMAND, 2); data |= PCIM_CMD_PORTEN | PCIM_CMD_BUSMASTEREN; pci_write_config(dev, PCIR_COMMAND, data, 2); @@ -962,11 +1002,6 @@ pci_write_config(dev, ESS_PCI_DDMACONTROL, ddma, 2); pci_write_config(dev, ESS_PCI_CONFIG, 0, 2); - if (ess_reset_dsp(sc)) - goto no; - if (mixer_init(dev, &solomixer_class, sc)) - goto no; - port_wr(sc->io, 0x7, 0xb0, 1); /* enable irqs */ #ifdef ESS18XX_DUPLEX sc->duplex = 1; @@ -979,24 +1014,48 @@ #else sc->newspeed = 0; #endif - if (sc->newspeed) - ess_setmixer(sc, 0x71, 0x2a); + if (snd_setup_intr(dev, sc->irq, +#if ESS18XX_MPSAFE == 1 + INTR_MPSAFE +#else + 0 +#endif + , ess_intr, sc, &sc->ih)) { + device_printf(dev, "unable to map interrupt\n"); + goto no; + } - snd_setup_intr(dev, sc->irq, 0, ess_intr, sc, &sc->ih); if (!sc->duplex) pcm_setflags(dev, pcm_getflags(dev) | SD_F_SIMPLEX); - if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/65536, /*boundary*/0, +#if 0 + if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(dev), /*alignment*/65536, /*boundary*/0, +#endif + 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, +#if ESS18XX_MPSAFE == 1 + /*lockfunc*/NULL, /*lockarg*/NULL, +#else + /*lockfunc*/busdma_lock_mutex, /*lockarg*/&Giant, +#endif + &sc->parent_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto no; } + + if (ess_reset_dsp(sc)) + goto no; + + if (sc->newspeed) + ess_setmixer(sc, 0x71, 0x2a); + + if (mixer_init(dev, &solomixer_class, sc)) + goto no; snprintf(status, SND_STATUSLEN, "at io 0x%lx,0x%lx,0x%lx irq %ld %s", rman_get_start(sc->io), rman_get_start(sc->sb), rman_get_start(sc->vc), --- sys/dev/sound/pci/spicds.c.orig Thu Jan 1 07:30:00 1970 +++ sys/dev/sound/pci/spicds.c Thu Jul 12 12:04:19 2007 @@ -0,0 +1,360 @@ +/* + * Copyright (c) 2006 Konstantin Dimitrov + * 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/spicds.c,v 1.6 2007/05/27 19:58:39 joel Exp $ + */ + +#include + +#include + +MALLOC_DEFINE(M_SPICDS, "spicds", "SPI codec"); + +#define SPICDS_NAMELEN 16 +struct spicds_info { + device_t dev; + spicds_ctrl ctrl; + void *devinfo; + int num; /* number of this device */ + unsigned int type; /* codec type */ + unsigned int cif; /* Controll data Interface Format (0/1) */ + unsigned int format; /* data format and master clock frequency */ + unsigned int dvc; /* De-emphasis and Volume Control */ + unsigned int left, right; + char name[SPICDS_NAMELEN]; + struct mtx *lock; +}; + +static void +spicds_wrbit(struct spicds_info *codec, int bit) +{ + unsigned int cs, cdti; + if (codec->cif) + cs = 1; + else + cs = 0; + if (bit) + cdti = 1; + else + cdti = 0; + codec->ctrl(codec->devinfo, cs, 0, cdti); + DELAY(1); + codec->ctrl(codec->devinfo, cs, 1, cdti); + DELAY(1); + + return; +} + +static void +spicds_wrcd(struct spicds_info *codec, int reg, u_int16_t val) +{ + int mask; + +#if(0) + device_printf(codec->dev, "spicds_wrcd(codec, 0x%02x, 0x%02x)\n", reg, val); +#endif + /* start */ + if (codec->cif) + codec->ctrl(codec->devinfo, 1, 1, 0); + else + codec->ctrl(codec->devinfo, 0, 1, 0); + DELAY(1); + if (codec->type != SPICDS_TYPE_WM8770) { + if (codec->type == SPICDS_TYPE_AK4381) { + /* AK4381 chip address */ + spicds_wrbit(codec, 0); + spicds_wrbit(codec, 1); + } + else if (codec->type == SPICDS_TYPE_AK4396) + { + /* AK4396 chip address */ + spicds_wrbit(codec, 0); + spicds_wrbit(codec, 0); + } + else { + /* chip address */ + spicds_wrbit(codec, 1); + spicds_wrbit(codec, 0); + } + /* write */ + spicds_wrbit(codec, 1); + /* register address */ + for (mask = 0x10; mask != 0; mask >>= 1) + spicds_wrbit(codec, reg & mask); + /* data */ + for (mask = 0x80; mask != 0; mask >>= 1) + spicds_wrbit(codec, val & mask); + /* stop */ + DELAY(1); + } + else { + /* register address */ + for (mask = 0x40; mask != 0; mask >>= 1) + spicds_wrbit(codec, reg & mask); + /* data */ + for (mask = 0x100; mask != 0; mask >>= 1) + spicds_wrbit(codec, val & mask); + /* stop */ + DELAY(1); + } + if (codec->cif) { + codec->ctrl(codec->devinfo, 0, 1, 0); + DELAY(1); + codec->ctrl(codec->devinfo, 1, 1, 0); + } + else { + codec->ctrl(codec->devinfo, 1, 1, 0); + } + + return; +} + +struct spicds_info * +spicds_create(device_t dev, void *devinfo, int num, spicds_ctrl ctrl) +{ + struct spicds_info *codec; + +#if(0) + device_printf(dev, "spicds_create(dev, devinfo, %d, ctrl)\n", num); +#endif + codec = (struct spicds_info *)malloc(sizeof *codec, M_SPICDS, M_NOWAIT); + if (codec == NULL) + return NULL; + + snprintf(codec->name, SPICDS_NAMELEN, "%s:spicds%d", device_get_nameunit(dev), num); + codec->lock = snd_mtxcreate(codec->name, codec->name); + codec->dev = dev; + codec->ctrl = ctrl; + codec->devinfo = devinfo; + codec->num = num; + codec->type = SPICDS_TYPE_AK4524; + codec->cif = 0; + codec->format = AK452X_FORMAT_I2S | AK452X_FORMAT_256FSN | AK452X_FORMAT_1X; + codec->dvc = AK452X_DVC_DEMOFF | AK452X_DVC_ZTM1024 | AK452X_DVC_ZCE; + + return codec; +} + +void +spicds_destroy(struct spicds_info *codec) +{ + snd_mtxfree(codec->lock); + free(codec, M_SPICDS); +} + +void +spicds_settype(struct spicds_info *codec, unsigned int type) +{ + snd_mtxlock(codec->lock); + codec->type = type; + snd_mtxunlock(codec->lock); +} + +void +spicds_setcif(struct spicds_info *codec, unsigned int cif) +{ + snd_mtxlock(codec->lock); + codec->cif = cif; + snd_mtxunlock(codec->lock); +} + +void +spicds_setformat(struct spicds_info *codec, unsigned int format) +{ + snd_mtxlock(codec->lock); + codec->format = format; + snd_mtxunlock(codec->lock); +} + +void +spicds_setdvc(struct spicds_info *codec, unsigned int dvc) +{ + snd_mtxlock(codec->lock); + codec->dvc = dvc; + snd_mtxunlock(codec->lock); +} + +void +spicds_init(struct spicds_info *codec) +{ +#if(0) + device_printf(codec->dev, "spicds_init(codec)\n"); +#endif + snd_mtxlock(codec->lock); + if (codec->type == SPICDS_TYPE_AK4524 ||\ + codec->type == SPICDS_TYPE_AK4528) { + /* power off */ + spicds_wrcd(codec, AK4524_POWER, 0); + /* set parameter */ + spicds_wrcd(codec, AK4524_FORMAT, codec->format); + spicds_wrcd(codec, AK4524_DVC, codec->dvc); + /* power on */ + spicds_wrcd(codec, AK4524_POWER, AK452X_POWER_PWDA | AK452X_POWER_PWAD | AK452X_POWER_PWVR); + /* free reset register */ + spicds_wrcd(codec, AK4524_RESET, AK452X_RESET_RSDA | AK452X_RESET_RSAD); + } + if (codec->type == SPICDS_TYPE_WM8770) { + /* WM8770 init values are taken from ALSA */ + /* These come first to reduce init pop noise */ + spicds_wrcd(codec, 0x1b, 0x044); /* ADC Mux (AC'97 source) */ + spicds_wrcd(codec, 0x1c, 0x00B); /* Out Mux1 (VOUT1 = DAC+AUX, VOUT2 = DAC) */ + spicds_wrcd(codec, 0x1d, 0x009); /* Out Mux2 (VOUT2 = DAC, VOUT3 = DAC) */ + + spicds_wrcd(codec, 0x18, 0x000); /* All power-up */ + + spicds_wrcd(codec, 0x16, 0x122); /* I2S, normal polarity, 24bit */ + spicds_wrcd(codec, 0x17, 0x022); /* 256fs, slave mode */ + + spicds_wrcd(codec, 0x19, 0x000); /* -12dB ADC/L */ + spicds_wrcd(codec, 0x1a, 0x000); /* -12dB ADC/R */ + } + if (codec->type == SPICDS_TYPE_AK4358) + spicds_wrcd(codec, 0x00, 0x07); /* I2S, 24bit, power-up */ + if (codec->type == SPICDS_TYPE_AK4381) + spicds_wrcd(codec, 0x00, 0x0f); /* I2S, 24bit, power-up */ + if (codec->type == SPICDS_TYPE_AK4396) + spicds_wrcd(codec, 0x00, 0x07); /* I2S, 24bit, power-up */ + snd_mtxunlock(codec->lock); +} + +void +spicds_reinit(struct spicds_info *codec) +{ + snd_mtxlock(codec->lock); + if (codec->type != SPICDS_TYPE_WM8770) { + /* reset */ + spicds_wrcd(codec, AK4524_RESET, 0); + /* set parameter */ + spicds_wrcd(codec, AK4524_FORMAT, codec->format); + spicds_wrcd(codec, AK4524_DVC, codec->dvc); + /* free reset register */ + spicds_wrcd(codec, AK4524_RESET, AK452X_RESET_RSDA | AK452X_RESET_RSAD); + } + else { + /* WM8770 reinit */ + /* AK4358 reinit */ + /* AK4381 reinit */ + } + snd_mtxunlock(codec->lock); +} + +void +spicds_set(struct spicds_info *codec, int dir, unsigned int left, unsigned int right) +{ +#if(0) + device_printf(codec->dev, "spicds_set(codec, %d, %d, %d)\n", dir, left, right); +#endif + snd_mtxlock(codec->lock); + if (left >= 100) + if ((codec->type == SPICDS_TYPE_AK4381) || \ + (codec->type == SPICDS_TYPE_AK4396)) + left = 255; + else + left = 127; + else + switch (codec->type) { + case SPICDS_TYPE_WM8770: + left = left + 27; + break; + case SPICDS_TYPE_AK4381 || SPICDS_TYPE_AK4396: + left = left * 255 / 100; + break; + default: + left = left * 127 / 100; + } + if (right >= 100) + if ((codec->type == SPICDS_TYPE_AK4381) || \ + (codec->type == SPICDS_TYPE_AK4396)) + right = 255; + else + right = 127; + else + switch (codec->type) { + case SPICDS_TYPE_WM8770: + right = right + 27; + break; + case SPICDS_TYPE_AK4381 || SPICDS_TYPE_AK4396: + right = right * 255 / 100; + break; + default: + right = right * 127 / 100; + } + if (dir == PCMDIR_REC && codec->type == SPICDS_TYPE_AK4524) { +#if(0) + device_printf(codec->dev, "spicds_set(): AK4524(REC) %d/%d\n", left, right); +#endif + spicds_wrcd(codec, AK4524_LIPGA, left); + spicds_wrcd(codec, AK4524_RIPGA, right); + } + if (dir == PCMDIR_PLAY && codec->type == SPICDS_TYPE_AK4524) { +#if(0) + device_printf(codec->dev, "spicds_set(): AK4524(PLAY) %d/%d\n", left, right); +#endif + spicds_wrcd(codec, AK4524_LOATT, left); + spicds_wrcd(codec, AK4524_ROATT, right); + } + if (dir == PCMDIR_PLAY && codec->type == SPICDS_TYPE_AK4528) { +#if(0) + device_printf(codec->dev, "spicds_set(): AK4528(PLAY) %d/%d\n", left, right); +#endif + spicds_wrcd(codec, AK4528_LOATT, left); + spicds_wrcd(codec, AK4528_ROATT, right); + } + if (dir == PCMDIR_PLAY && codec->type == SPICDS_TYPE_WM8770) { +#if(0) + device_printf(codec->dev, "spicds_set(): WM8770(PLAY) %d/%d\n", left, right); +#endif + spicds_wrcd(codec, WM8770_AOATT_L1, left | WM8770_AOATT_UPDATE); + spicds_wrcd(codec, WM8770_AOATT_R1, right | WM8770_AOATT_UPDATE); + } + if (dir == PCMDIR_PLAY && codec->type == SPICDS_TYPE_AK4358) { +#if(0) + device_printf(codec->dev, "spicds_set(): AK4358(PLAY) %d/%d\n", left, right); +#endif + spicds_wrcd(codec, AK4358_LO1ATT, left | AK4358_OATT_ENABLE); + spicds_wrcd(codec, AK4358_RO1ATT, right | AK4358_OATT_ENABLE); + } + if (dir == PCMDIR_PLAY && codec->type == SPICDS_TYPE_AK4381) { +#if(0) + device_printf(codec->dev, "spicds_set(): AK4381(PLAY) %d/%d\n", left, right); +#endif + spicds_wrcd(codec, AK4381_LOATT, left); + spicds_wrcd(codec, AK4381_ROATT, right); + } + + if (dir == PCMDIR_PLAY && codec->type == SPICDS_TYPE_AK4396) { +#if(0) + device_printf(codec->dev, "spicds_set(): AK4396(PLAY) %d/%d\n", left, right); +#endif + spicds_wrcd(codec, AK4396_LOATT, left); + spicds_wrcd(codec, AK4396_ROATT, right); + } + + snd_mtxunlock(codec->lock); +} + +MODULE_DEPEND(snd_spicds, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); +MODULE_VERSION(snd_spicds, 1); --- sys/dev/sound/pci/spicds.h.orig Thu Jan 1 07:30:00 1970 +++ sys/dev/sound/pci/spicds.h Thu Jul 12 12:04:19 2007 @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2006 Konstantin Dimitrov + * 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/spicds.h,v 1.4 2007/05/27 19:58:39 joel Exp $ + */ + +/* supported CODECs */ +#define SPICDS_TYPE_AK4524 0 +#define SPICDS_TYPE_AK4528 1 +#define SPICDS_TYPE_WM8770 2 +#define SPICDS_TYPE_AK4358 3 +#define SPICDS_TYPE_AK4381 4 +#define SPICDS_TYPE_AK4396 5 + +/* AK4524/AK4528 control registers */ +#define AK4524_POWER 0x00 +#define AK4528_POWER 0x00 +#define AK452X_POWER_PWDA 0x01 +#define AK452X_POWER_PWAD 0x02 +#define AK452X_POWER_PWVR 0x04 +#define AK4524_RESET 0x01 +#define AK4528_RESET 0x01 +#define AK452X_RESET_RSDA 0x01 +#define AK452X_RESET_RSAD 0x02 +#define AK4524_FORMAT 0x02 +#define AK4528_FORMAT 0x02 +#define AK452X_FORMAT_1X 0x00 +#define AK452X_FORMAT_2X 0x01 +#define AK452X_FORMAT_4X1 0x02 +#define AK452X_FORMAT_4X2 0x03 +#define AK452X_FORMAT_256FSN 0x00 +#define AK452X_FORMAT_512FSN 0x04 +#define AK452X_FORMAT_1024FSN 0x08 +#define AK452X_FORMAT_384FSN 0x10 +#define AK452X_FORMAT_768FSN 0x14 +#define AK452X_FORMAT_OM24IL16 0x00 +#define AK452X_FORMAT_OM24IL20 0x20 +#define AK452X_FORMAT_OM24IM24 0x40 +#define AK452X_FORMAT_I2S 0x60 +#define AK452X_FORMAT_OM24IL24 0x80 +#define AK4524_DVC 0x03 +#define AK452X_DVC_DEM441 0x00 +#define AK452X_DVC_DEMOFF 0x01 +#define AK452X_DVC_DEM48 0x02 +#define AK452X_DVC_DEM32 0x03 +#define AK452X_DVC_ZTM256 0x00 +#define AK452X_DVC_ZTM512 0x04 +#define AK452X_DVC_ZTM1024 0x08 +#define AK452X_DVC_ZTM2048 0x0c +#define AK452X_DVC_ZCE 0x10 +#define AK452X_DVC_HPFL 0x04 +#define AK452X_DVC_HPFR 0x08 +#define AK452X_DVC_SMUTE 0x80 +#define AK4524_LIPGA 0x04 +#define AK4524_RIPGA 0x05 +#define AK4524_LOATT 0x06 +#define AK4524_ROATT 0x07 +#define AK4528_LOATT 0x04 +#define AK4528_ROATT 0x05 + +/* WM8770 control registers */ +#define WM8770_AOATT_L1 0x00 +#define WM8770_AOATT_R1 0x01 +#define WM8770_AOATT_L2 0x02 +#define WM8770_AOATT_R2 0x03 +#define WM8770_AOATT_L3 0x04 +#define WM8770_AOATT_R3 0x05 +#define WM8770_AOATT_L4 0x06 +#define WM8770_AOATT_R4 0x07 +#define WM8770_AOATT_MAST 0x08 +#define WM8770_AOATT_UPDATE 0x100 + +/* AK4358 control registers */ +#define AK4358_LO1ATT 0x04 +#define AK4358_RO1ATT 0x05 +#define AK4358_OATT_ENABLE 0x80 + +/* AK4381 control registers */ +#define AK4381_LOATT 0x03 +#define AK4381_ROATT 0x04 + +/* AK4396 control registers */ +#define AK4396_LOATT 0x03 +#define AK4396_ROATT 0x04 + +struct spicds_info; + +typedef void (*spicds_ctrl)(void *, unsigned int, unsigned int, unsigned int); + +struct spicds_info *spicds_create(device_t dev, void *devinfo, int num, spicds_ctrl); +void spicds_destroy(struct spicds_info *codec); +void spicds_settype(struct spicds_info *codec, unsigned int type); +void spicds_setcif(struct spicds_info *codec, unsigned int cif); +void spicds_setformat(struct spicds_info *codec, unsigned int format); +void spicds_setdvc(struct spicds_info *codec, unsigned int dvc); +void spicds_init(struct spicds_info *codec); +void spicds_reinit(struct spicds_info *codec); +void spicds_set(struct spicds_info *codec, int dir, unsigned int left, unsigned int right); --- sys/dev/sound/pci/t4dwave.c.orig Sun Jan 30 09:00:04 2005 +++ sys/dev/sound/pci/t4dwave.c Thu Jul 12 12:04:19 2007 @@ -31,7 +31,7 @@ #include #include -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/t4dwave.c,v 1.44.2.3 2005/01/30 01:00:04 imp Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/t4dwave.c,v 1.53 2007/06/17 06:10:42 ariff Exp $"); /* -------------------------------------------------------------------- */ @@ -494,7 +494,7 @@ ch->buffer = b; ch->parent = tr; ch->channel = c; - if (sndbuf_alloc(ch->buffer, tr->parent_dmat, tr->bufsz) != 0) + if (sndbuf_alloc(ch->buffer, tr->parent_dmat, 0, tr->bufsz) != 0) return NULL; return ch; @@ -533,7 +533,7 @@ { struct tr_chinfo *ch = data; - if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) + if (!PCMTRIG_COMMON(go)) return 0; if (go == PCMTRIG_START) { @@ -602,7 +602,7 @@ ch->buffer = b; ch->parent = tr; ch->channel = c; - if (sndbuf_alloc(ch->buffer, tr->parent_dmat, tr->bufsz) != 0) + if (sndbuf_alloc(ch->buffer, tr->parent_dmat, 0, tr->bufsz) != 0) return NULL; return ch; @@ -658,7 +658,7 @@ struct tr_info *tr = ch->parent; u_int32_t i; - if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) + if (!PCMTRIG_COMMON(go)) return 0; if (go == PCMTRIG_START) { @@ -790,16 +790,16 @@ switch (pci_get_devid(dev)) { case SPA_PCI_ID: device_set_desc(dev, "SiS 7018"); - return 0; + return BUS_PROBE_DEFAULT; case ALI_PCI_ID: device_set_desc(dev, "Acer Labs M5451"); - return 0; + return BUS_PROBE_DEFAULT; case TDX_PCI_ID: device_set_desc(dev, "Trident 4DWave DX"); - return 0; + return BUS_PROBE_DEFAULT; case TNX_PCI_ID: device_set_desc(dev, "Trident 4DWave NX"); - return 0; + return BUS_PROBE_DEFAULT; } return ENXIO; @@ -814,14 +814,10 @@ int i; char status[SND_STATUSLEN]; - if ((tr = malloc(sizeof(*tr), M_DEVBUF, M_NOWAIT | M_ZERO)) == NULL) { - device_printf(dev, "cannot allocate softc\n"); - return ENXIO; - } - + tr = malloc(sizeof(*tr), M_DEVBUF, M_WAITOK | M_ZERO); tr->type = pci_get_devid(dev); tr->rev = pci_get_revid(dev); - tr->lock = snd_mtxcreate(device_get_nameunit(dev), "sound softc"); + tr->lock = snd_mtxcreate(device_get_nameunit(dev), "snd_t4dwave softc"); data = pci_read_config(dev, PCIR_COMMAND, 2); data |= (PCIM_CMD_PORTEN|PCIM_CMD_MEMEN|PCIM_CMD_BUSMASTEREN); @@ -860,7 +856,8 @@ 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*/TR_MAXADDR, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, --- sys/dev/sound/pci/t4dwave.h.orig Sun Jan 30 09:00:04 2005 +++ sys/dev/sound/pci/t4dwave.h Thu Jan 6 09:43:19 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/t4dwave.h,v 1.6.4.1 2005/01/30 01:00:04 imp Exp $ + * $FreeBSD: src/sys/dev/sound/pci/t4dwave.h,v 1.7 2005/01/06 01:43:19 imp Exp $ */ #ifndef _T4DWAVE_REG_H --- sys/dev/sound/pci/via8233.c.orig Sun Jan 30 09:00:04 2005 +++ sys/dev/sound/pci/via8233.c Thu Jul 12 12:04:19 2007 @@ -32,7 +32,7 @@ * Grzybowski Rafal, Russell Davies, Mark Handley, Daniel O'Connor for * comments, machine time, testing patches, and patience. VIA for * providing specs. ALSA for helpful comments and some register poke - * ordering. + * ordering. */ #include @@ -44,7 +44,7 @@ #include -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/via8233.c,v 1.17.2.2 2005/01/30 01:00:04 imp Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/via8233.c,v 1.37 2007/06/14 11:13:38 ariff Exp $"); #define VIA8233_PCI_ID 0x30591106 @@ -54,6 +54,7 @@ #define VIA8233_REV_ID_8233A 0x40 #define VIA8233_REV_ID_8235 0x50 #define VIA8233_REV_ID_8237 0x60 +#define VIA8233_REV_ID_8251 0x70 #define SEGS_PER_CHAN 2 /* Segments per channel */ #define NDXSCHANS 4 /* No of DXS channels */ @@ -61,13 +62,18 @@ #define NWRCHANS 1 /* No of write channels */ #define NCHANS (NWRCHANS + NDXSCHANS + NMSGDCHANS) #define NSEGS NCHANS * SEGS_PER_CHAN /* Segments in SGD table */ +#define VIA_SEGS_MIN 2 +#define VIA_SEGS_MAX 64 +#define VIA_SEGS_DEFAULT 2 +#define VIA_BLK_MIN 32 +#define VIA_BLK_ALIGN (~(VIA_BLK_MIN - 1)) #define VIA_DEFAULT_BUFSZ 0x1000 /* we rely on this struct being packed to 64 bits */ struct via_dma_op { - volatile u_int32_t ptr; - volatile u_int32_t flags; + volatile uint32_t ptr; + volatile uint32_t flags; #define VIA_DMAOP_EOL 0x80000000 #define VIA_DMAOP_FLAG 0x40000000 #define VIA_DMAOP_STOP 0x20000000 @@ -82,11 +88,14 @@ struct snd_dbuf *buffer; struct via_dma_op *sgd_table; bus_addr_t sgd_addr; - int dir, blksz; - int rbase; + int dir, rbase, active; + unsigned int blksz, blkcnt; + unsigned int ptr, prevptr; }; struct via_info { + device_t dev; + bus_space_tag_t st; bus_space_handle_t sh; bus_dma_tag_t parent_dmat; @@ -99,16 +108,21 @@ void *ih; struct ac97_info *codec; - unsigned int bufsz; + unsigned int bufsz, blkcnt; + int dxs_src, dma_eol_wake; struct via_chinfo pch[NDXSCHANS + NMSGDCHANS]; struct via_chinfo rch[NWRCHANS]; struct via_dma_op *sgd_table; - u_int16_t codec_caps; - u_int16_t n_dxs_registered; + uint16_t codec_caps; + uint16_t n_dxs_registered; + int play_num, rec_num; + struct mtx *lock; + struct callout poll_timer; + int poll_ticks, polling; }; -static u_int32_t via_fmt[] = { +static uint32_t via_fmt[] = { AFMT_U8, AFMT_STEREO | AFMT_U8, AFMT_S16_LE, @@ -119,23 +133,161 @@ static struct pcmchan_caps via_vracaps = { 4000, 48000, via_fmt, 0 }; static struct pcmchan_caps via_caps = { 48000, 48000, via_fmt, 0 }; -static u_int32_t +static __inline int +via_chan_active(struct via_info *via) +{ + int i, ret = 0; + + if (via == NULL) + return (0); + + for (i = 0; i < NDXSCHANS + NMSGDCHANS; i++) + ret += via->pch[i].active; + + for (i = 0; i < NWRCHANS; i++) + ret += via->rch[i].active; + + return (ret); +} + +#ifdef SND_DYNSYSCTL +static int +sysctl_via8233_spdif_enable(SYSCTL_HANDLER_ARGS) +{ + struct via_info *via; + device_t dev; + uint32_t r; + int err, new_en; + + dev = oidp->oid_arg1; + via = pcm_getdevinfo(dev); + snd_mtxlock(via->lock); + r = pci_read_config(dev, VIA_PCI_SPDIF, 1); + snd_mtxunlock(via->lock); + new_en = (r & VIA_SPDIF_EN) ? 1 : 0; + err = sysctl_handle_int(oidp, &new_en, 0, req); + + if (err || req->newptr == NULL) + return (err); + if (new_en < 0 || new_en > 1) + return (EINVAL); + + if (new_en) + r |= VIA_SPDIF_EN; + else + r &= ~VIA_SPDIF_EN; + snd_mtxlock(via->lock); + pci_write_config(dev, VIA_PCI_SPDIF, r, 1); + snd_mtxunlock(via->lock); + + return (0); +} + +static int +sysctl_via8233_dxs_src(SYSCTL_HANDLER_ARGS) +{ + struct via_info *via; + device_t dev; + int err, val; + + dev = oidp->oid_arg1; + via = pcm_getdevinfo(dev); + snd_mtxlock(via->lock); + val = via->dxs_src; + snd_mtxunlock(via->lock); + err = sysctl_handle_int(oidp, &val, 0, req); + + if (err || req->newptr == NULL) + return (err); + if (val < 0 || val > 1) + return (EINVAL); + + snd_mtxlock(via->lock); + via->dxs_src = val; + snd_mtxunlock(via->lock); + + return (0); +} + +static int +sysctl_via_polling(SYSCTL_HANDLER_ARGS) +{ + struct via_info *via; + device_t dev; + int err, val; + + dev = oidp->oid_arg1; + via = pcm_getdevinfo(dev); + if (via == NULL) + return (EINVAL); + snd_mtxlock(via->lock); + val = via->polling; + snd_mtxunlock(via->lock); + err = sysctl_handle_int(oidp, &val, 0, req); + + if (err || req->newptr == NULL) + return (err); + if (val < 0 || val > 1) + return (EINVAL); + + snd_mtxlock(via->lock); + if (val != via->polling) { + if (via_chan_active(via) != 0) + err = EBUSY; + else if (val == 0) + via->polling = 0; + else + via->polling = 1; + } + snd_mtxunlock(via->lock); + + return (err); +} +#endif /* SND_DYNSYSCTL */ + +static void +via_init_sysctls(device_t dev) +{ +#ifdef SND_DYNSYSCTL + /* 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_PROC(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, + "spdif_enabled", CTLTYPE_INT | CTLFLAG_RW, dev, sizeof(dev), + sysctl_via8233_spdif_enable, "I", + "Enable S/PDIF output on primary playback channel"); + SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, + "dxs_src", CTLTYPE_INT | CTLFLAG_RW, dev, sizeof(dev), + sysctl_via8233_dxs_src, "I", + "Enable VIA DXS Sample Rate Converter"); + SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, + "polling", CTLTYPE_INT | CTLFLAG_RW, dev, sizeof(dev), + sysctl_via_polling, "I", + "Enable polling mode"); +#endif +} + +static __inline uint32_t via_rd(struct via_info *via, int regno, int size) { switch (size) { case 1: - return bus_space_read_1(via->st, via->sh, regno); + return (bus_space_read_1(via->st, via->sh, regno)); case 2: - return bus_space_read_2(via->st, via->sh, regno); + return (bus_space_read_2(via->st, via->sh, regno)); case 4: - return bus_space_read_4(via->st, via->sh, regno); + return (bus_space_read_4(via->st, via->sh, regno)); default: - return 0xFFFFFFFF; + return (0xFFFFFFFF); } } -static void -via_wr(struct via_info *via, int regno, u_int32_t data, int size) +static __inline void +via_wr(struct via_info *via, int regno, uint32_t data, int size) { switch (size) { @@ -162,11 +314,11 @@ /* poll until codec not busy */ for (i = 0; i < 1000; i++) { if ((via_rd(via, VIA_AC97_CONTROL, 4) & VIA_AC97_BUSY) == 0) - return 0; + return (0); DELAY(1); } - printf("via: codec busy\n"); - return 1; + device_printf(via->dev, "%s: codec busy\n", __func__); + return (1); } static int @@ -177,25 +329,26 @@ /* poll until codec valid */ for (i = 0; i < 1000; i++) { if (via_rd(via, VIA_AC97_CONTROL, 4) & VIA_AC97_CODEC00_VALID) - return 0; + return (0); DELAY(1); } - printf("via: codec invalid\n"); - return 1; + device_printf(via->dev, "%s: codec invalid\n", __func__); + return (1); } static int -via_write_codec(kobj_t obj, void *addr, int reg, u_int32_t val) +via_write_codec(kobj_t obj, void *addr, int reg, uint32_t val) { struct via_info *via = addr; - if (via_waitready_codec(via)) return -1; + if (via_waitready_codec(via)) + return (-1); - via_wr(via, VIA_AC97_CONTROL, + via_wr(via, VIA_AC97_CONTROL, VIA_AC97_CODEC00_VALID | VIA_AC97_INDEX(reg) | VIA_AC97_DATA(val), 4); - return 0; + return (0); } static int @@ -204,23 +357,23 @@ struct via_info *via = addr; if (via_waitready_codec(via)) - return -1; + return (-1); - via_wr(via, VIA_AC97_CONTROL, VIA_AC97_CODEC00_VALID | - VIA_AC97_READ | VIA_AC97_INDEX(reg), 4); + via_wr(via, VIA_AC97_CONTROL, VIA_AC97_CODEC00_VALID | + VIA_AC97_READ | VIA_AC97_INDEX(reg), 4); if (via_waitready_codec(via)) - return -1; + return (-1); if (via_waitvalid_codec(via)) - return -1; + return (-1); - return via_rd(via, VIA_AC97_CONTROL, 2); + return (via_rd(via, VIA_AC97_CONTROL, 2)); } static kobj_method_t via_ac97_methods[] = { - KOBJMETHOD(ac97_read, via_read_codec), - KOBJMETHOD(ac97_write, via_write_codec), + KOBJMETHOD(ac97_read, via_read_codec), + KOBJMETHOD(ac97_write, via_write_codec), { 0, 0 } }; AC97_DECLARE(via_ac97); @@ -230,68 +383,72 @@ static int via_buildsgdt(struct via_chinfo *ch) { - u_int32_t phys_addr, flag; - int i, seg_size; + uint32_t phys_addr, flag; + int i; - seg_size = sndbuf_getsize(ch->buffer) / SEGS_PER_CHAN; phys_addr = sndbuf_getbufaddr(ch->buffer); - for (i = 0; i < SEGS_PER_CHAN; i++) { - flag = (i == SEGS_PER_CHAN - 1) ? VIA_DMAOP_EOL : VIA_DMAOP_FLAG; - ch->sgd_table[i].ptr = phys_addr + (i * seg_size); - ch->sgd_table[i].flags = flag | seg_size; + for (i = 0; i < ch->blkcnt; i++) { + flag = (i == ch->blkcnt - 1) ? VIA_DMAOP_EOL : VIA_DMAOP_FLAG; + ch->sgd_table[i].ptr = phys_addr + (i * ch->blksz); + ch->sgd_table[i].flags = flag | ch->blksz; } - return 0; + return (0); } /* -------------------------------------------------------------------- */ /* Format setting functions */ static int -via8233wr_setformat(kobj_t obj, void *data, u_int32_t format) +via8233wr_setformat(kobj_t obj, void *data, uint32_t format) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; - - u_int32_t f = WR_FORMAT_STOP_INDEX; + + uint32_t f = WR_FORMAT_STOP_INDEX; if (format & AFMT_STEREO) f |= WR_FORMAT_STEREO; if (format & AFMT_S16_LE) f |= WR_FORMAT_16BIT; + snd_mtxlock(via->lock); via_wr(via, VIA_WR0_FORMAT, f, 4); + snd_mtxunlock(via->lock); - return 0; + return (0); } static int -via8233dxs_setformat(kobj_t obj, void *data, u_int32_t format) +via8233dxs_setformat(kobj_t obj, void *data, uint32_t format) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; + uint32_t r, v; - u_int32_t r = ch->rbase + VIA8233_RP_DXS_RATEFMT; - u_int32_t v = via_rd(via, r, 4); + r = ch->rbase + VIA8233_RP_DXS_RATEFMT; + snd_mtxlock(via->lock); + v = via_rd(via, r, 4); v &= ~(VIA8233_DXS_RATEFMT_STEREO | VIA8233_DXS_RATEFMT_16BIT); if (format & AFMT_STEREO) v |= VIA8233_DXS_RATEFMT_STEREO; - if (format & AFMT_16BIT) + if (format & AFMT_16BIT) v |= VIA8233_DXS_RATEFMT_16BIT; via_wr(via, r, v, 4); + snd_mtxunlock(via->lock); - return 0; + return (0); } static int -via8233msgd_setformat(kobj_t obj, void *data, u_int32_t format) +via8233msgd_setformat(kobj_t obj, void *data, uint32_t format) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; - u_int32_t s = 0xff000000; - u_int8_t v = (format & AFMT_S16_LE) ? MC_SGD_16BIT : MC_SGD_8BIT; + uint32_t s = 0xff000000; + uint8_t v = (format & AFMT_S16_LE) ? MC_SGD_16BIT : MC_SGD_8BIT; if (format & AFMT_STEREO) { v |= MC_SGD_CHANNELS(2); @@ -301,55 +458,59 @@ s |= SLOT3(1) | SLOT4(1); } + snd_mtxlock(via->lock); via_wr(via, VIA_MC_SLOT_SELECT, s, 4); via_wr(via, VIA_MC_SGD_FORMAT, v, 1); + snd_mtxunlock(via->lock); - return 0; + return (0); } /* -------------------------------------------------------------------- */ /* Speed setting functions */ static int -via8233wr_setspeed(kobj_t obj, void *data, u_int32_t speed) +via8233wr_setspeed(kobj_t obj, void *data, uint32_t speed) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; - u_int32_t spd = 48000; - if (via->codec_caps & AC97_EXTCAP_VRA) { - spd = ac97_setrate(via->codec, AC97_REGEXT_LADCRATE, speed); - } - return spd; + if (via->codec_caps & AC97_EXTCAP_VRA) + return (ac97_setrate(via->codec, AC97_REGEXT_LADCRATE, speed)); + + return (48000); } static int -via8233dxs_setspeed(kobj_t obj, void *data, u_int32_t speed) +via8233dxs_setspeed(kobj_t obj, void *data, uint32_t speed) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; + uint32_t r, v; - u_int32_t r = ch->rbase + VIA8233_RP_DXS_RATEFMT; - u_int32_t v = via_rd(via, r, 4) & ~VIA8233_DXS_RATEFMT_48K; + r = ch->rbase + VIA8233_RP_DXS_RATEFMT; + snd_mtxlock(via->lock); + v = via_rd(via, r, 4) & ~VIA8233_DXS_RATEFMT_48K; /* Careful to avoid overflow (divide by 48 per vt8233c docs) */ v |= VIA8233_DXS_RATEFMT_48K * (speed / 48) / (48000 / 48); via_wr(via, r, v, 4); + snd_mtxunlock(via->lock); - return speed; + return (speed); } static int -via8233msgd_setspeed(kobj_t obj, void *data, u_int32_t speed) +via8233msgd_setspeed(kobj_t obj, void *data, uint32_t speed) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; if (via->codec_caps & AC97_EXTCAP_VRA) - return ac97_setrate(via->codec, AC97_REGEXT_FDACRATE, speed); + return (ac97_setrate(via->codec, AC97_REGEXT_FDACRATE, speed)); - return 48000; + return (48000); } /* -------------------------------------------------------------------- */ @@ -362,16 +523,26 @@ struct via_info *via = ch->parent; /* Controlled by ac97 registers */ - if (via->codec_caps & AC97_EXTCAP_VRA) - return &via_vracaps; - return &via_caps; + if (via->codec_caps & AC97_EXTCAP_VRA) + return (&via_vracaps); + return (&via_caps); } static struct pcmchan_caps * via8233dxs_getcaps(kobj_t obj, void *data) { - /* Controlled by onboard registers */ - return &via_caps; + struct via_chinfo *ch = data; + struct via_info *via = ch->parent; + + /* + * Controlled by onboard registers + * + * Apparently, few boards can do DXS sample rate + * conversion. + */ + if (via->dxs_src) + return (&via_vracaps); + return (&via_caps); } static struct pcmchan_caps * @@ -381,21 +552,62 @@ struct via_info *via = ch->parent; /* Controlled by ac97 registers */ - if (via->codec_caps & AC97_EXTCAP_VRA) - return &via_vracaps; - return &via_caps; + if (via->codec_caps & AC97_EXTCAP_VRA) + return (&via_vracaps); + return (&via_caps); } /* -------------------------------------------------------------------- */ /* Common functions */ static int -via8233chan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) +via8233chan_setfragments(kobj_t obj, void *data, + uint32_t blksz, uint32_t blkcnt) { struct via_chinfo *ch = data; - sndbuf_resize(ch->buffer, SEGS_PER_CHAN, blocksize); + struct via_info *via = ch->parent; + + blksz &= VIA_BLK_ALIGN; + + if (blksz > (sndbuf_getmaxsize(ch->buffer) / VIA_SEGS_MIN)) + blksz = sndbuf_getmaxsize(ch->buffer) / VIA_SEGS_MIN; + if (blksz < VIA_BLK_MIN) + blksz = VIA_BLK_MIN; + if (blkcnt > VIA_SEGS_MAX) + blkcnt = VIA_SEGS_MAX; + if (blkcnt < VIA_SEGS_MIN) + blkcnt = VIA_SEGS_MIN; + + while ((blksz * blkcnt) > sndbuf_getmaxsize(ch->buffer)) { + if ((blkcnt >> 1) >= VIA_SEGS_MIN) + blkcnt >>= 1; + else if ((blksz >> 1) >= VIA_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(via->dev, "%s: failed blksz=%u blkcnt=%u\n", + __func__, blksz, blkcnt); + ch->blksz = sndbuf_getblksz(ch->buffer); - return ch->blksz; + ch->blkcnt = sndbuf_getblkcnt(ch->buffer); + + return (1); +} + +static int +via8233chan_setblocksize(kobj_t obj, void *data, uint32_t blksz) +{ + struct via_chinfo *ch = data; + struct via_info *via = ch->parent; + + via8233chan_setfragments(obj, data, blksz, via->blkcnt); + + return (ch->blksz); } static int @@ -403,14 +615,23 @@ { struct via_chinfo *ch = data; struct via_info *via = ch->parent; + uint32_t v, index, count; + int ptr; - u_int32_t v = via_rd(via, ch->rbase + VIA_RP_CURRENT_COUNT, 4); - u_int32_t index = v >> 24; /* Last completed buffer */ - u_int32_t count = v & 0x00ffffff; /* Bytes remaining */ - int ptr = (index + 1) * ch->blksz - count; - ptr %= SEGS_PER_CHAN * ch->blksz; /* Wrap to available space */ + snd_mtxlock(via->lock); + if (via->polling != 0) { + ptr = ch->ptr; + snd_mtxunlock(via->lock); + } else { + v = via_rd(via, ch->rbase + VIA_RP_CURRENT_COUNT, 4); + snd_mtxunlock(via->lock); + index = v >> 24; /* Last completed buffer */ + count = v & 0x00ffffff; /* Bytes remaining */ + ptr = (index + 1) * ch->blksz - count; + ptr %= ch->blkcnt * ch->blksz; /* Wrap to available space */ + } - return ptr; + return (ptr); } static void @@ -418,8 +639,8 @@ { via_wr(via, ch->rbase + VIA_RP_CONTROL, SGD_CONTROL_STOP, 1); via_wr(via, ch->rbase + VIA_RP_CONTROL, 0x00, 1); - via_wr(via, ch->rbase + VIA_RP_STATUS, - SGD_STATUS_EOL | SGD_STATUS_FLAG, 1); + via_wr(via, ch->rbase + VIA_RP_STATUS, + SGD_STATUS_EOL | SGD_STATUS_FLAG, 1); } /* -------------------------------------------------------------------- */ @@ -428,44 +649,58 @@ static void via8233chan_sgdinit(struct via_info *via, struct via_chinfo *ch, int chnum) { - ch->sgd_table = &via->sgd_table[chnum * SEGS_PER_CHAN]; - ch->sgd_addr = via->sgd_addr + chnum * SEGS_PER_CHAN * sizeof(struct via_dma_op); + ch->sgd_table = &via->sgd_table[chnum * VIA_SEGS_MAX]; + ch->sgd_addr = via->sgd_addr + chnum * VIA_SEGS_MAX * + sizeof(struct via_dma_op); } static void* via8233wr_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, - struct pcm_channel *c, int dir) + struct pcm_channel *c, int dir) { struct via_info *via = devinfo; - struct via_chinfo *ch = &via->rch[c->num]; + struct via_chinfo *ch; + int num; + snd_mtxlock(via->lock); + num = via->rec_num++; + ch = &via->rch[num]; ch->parent = via; ch->channel = c; ch->buffer = b; ch->dir = dir; - - ch->rbase = VIA_WR_BASE(c->num); + ch->blkcnt = via->blkcnt; + ch->rbase = VIA_WR_BASE(num); via_wr(via, ch->rbase + VIA_WR_RP_SGD_FORMAT, WR_FIFO_ENABLE, 1); + snd_mtxunlock(via->lock); + + if (sndbuf_alloc(ch->buffer, via->parent_dmat, 0, via->bufsz) != 0) + return (NULL); - if (sndbuf_alloc(ch->buffer, via->parent_dmat, via->bufsz) != 0) - return NULL; - via8233chan_sgdinit(via, ch, c->num); + snd_mtxlock(via->lock); + via8233chan_sgdinit(via, ch, num); via8233chan_reset(via, ch); + snd_mtxunlock(via->lock); - return ch; + return (ch); } static void* via8233dxs_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, - struct pcm_channel *c, int dir) + struct pcm_channel *c, int dir) { struct via_info *via = devinfo; - struct via_chinfo *ch = &via->pch[c->num]; + struct via_chinfo *ch; + int num; + snd_mtxlock(via->lock); + num = via->play_num++; + ch = &via->pch[num]; ch->parent = via; ch->channel = c; ch->buffer = b; ch->dir = dir; + ch->blkcnt = via->blkcnt; /* * All cards apparently support DXS3, but not other DXS @@ -474,34 +709,47 @@ */ ch->rbase = VIA_DXS_BASE(NDXSCHANS - 1 - via->n_dxs_registered); via->n_dxs_registered++; + snd_mtxunlock(via->lock); + + if (sndbuf_alloc(ch->buffer, via->parent_dmat, 0, via->bufsz) != 0) + return (NULL); - if (sndbuf_alloc(ch->buffer, via->parent_dmat, via->bufsz) != 0) - return NULL; - via8233chan_sgdinit(via, ch, NWRCHANS + c->num); + snd_mtxlock(via->lock); + via8233chan_sgdinit(via, ch, NWRCHANS + num); via8233chan_reset(via, ch); + snd_mtxunlock(via->lock); - return ch; + return (ch); } static void* via8233msgd_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, - struct pcm_channel *c, int dir) + struct pcm_channel *c, int dir) { struct via_info *via = devinfo; - struct via_chinfo *ch = &via->pch[c->num]; + struct via_chinfo *ch; + int num; + snd_mtxlock(via->lock); + num = via->play_num++; + ch = &via->pch[num]; ch->parent = via; ch->channel = c; ch->buffer = b; ch->dir = dir; ch->rbase = VIA_MC_SGD_STATUS; + ch->blkcnt = via->blkcnt; + snd_mtxunlock(via->lock); - if (sndbuf_alloc(ch->buffer, via->parent_dmat, via->bufsz) != 0) - return NULL; - via8233chan_sgdinit(via, ch, NWRCHANS + c->num); + if (sndbuf_alloc(ch->buffer, via->parent_dmat, 0, via->bufsz) != 0) + return (NULL); + + snd_mtxlock(via->lock); + via8233chan_sgdinit(via, ch, NWRCHANS + num); via8233chan_reset(via, ch); + snd_mtxunlock(via->lock); - return ch; + return (ch); } static void @@ -512,71 +760,246 @@ muted = (muted) ? VIA8233_DXS_MUTE : 0; via_wr(via, ch->rbase + VIA8233_RP_DXS_LVOL, muted, 1); via_wr(via, ch->rbase + VIA8233_RP_DXS_RVOL, muted, 1); - r = via_rd(via, ch->rbase + VIA8233_RP_DXS_LVOL, 1) & VIA8233_DXS_MUTE; - if (r != muted) { - printf("via: failed to set dxs volume " - "(dxs base 0x%02x).\n", ch->rbase); - } + r = via_rd(via, ch->rbase + VIA8233_RP_DXS_LVOL, 1) & + VIA8233_DXS_MUTE; + if (r != muted) + device_printf(via->dev, + "%s: failed to set dxs volume " + "(dxs base 0x%02x).\n", __func__, ch->rbase); + } +} + +static __inline int +via_poll_channel(struct via_chinfo *ch) +{ + struct via_info *via; + uint32_t sz, delta; + uint32_t v, index, count; + int ptr; + + if (ch == NULL || ch->channel == NULL || ch->active == 0) + return (0); + + via = ch->parent; + sz = ch->blksz * ch->blkcnt; + v = via_rd(via, ch->rbase + VIA_RP_CURRENT_COUNT, 4); + index = v >> 24; + count = v & 0x00ffffff; + ptr = ((index + 1) * ch->blksz) - count; + ptr %= sz; + ptr &= ~(ch->blksz - 1); + ch->ptr = ptr; + delta = (sz + ptr - ch->prevptr) % sz; + + if (delta < ch->blksz) + return (0); + + ch->prevptr = ptr; + + return (1); +} + +static void +via_poll_callback(void *arg) +{ + struct via_info *via = arg; + uint32_t ptrigger = 0, rtrigger = 0; + int i; + + if (via == NULL) + return; + + snd_mtxlock(via->lock); + if (via->polling == 0 || via_chan_active(via) == 0) { + snd_mtxunlock(via->lock); + return; + } + + for (i = 0; i < NDXSCHANS + NMSGDCHANS; i++) + ptrigger |= (via_poll_channel(&via->pch[i]) != 0) ? + (1 << i) : 0; + + for (i = 0; i < NWRCHANS; i++) + rtrigger |= (via_poll_channel(&via->rch[i]) != 0) ? + (1 << i) : 0; + + /* XXX */ + callout_reset(&via->poll_timer, 1/*via->poll_ticks*/, + via_poll_callback, via); + + snd_mtxunlock(via->lock); + + for (i = 0; i < NDXSCHANS + NMSGDCHANS; i++) { + if (ptrigger & (1 << i)) + chn_intr(via->pch[i].channel); + } + for (i = 0; i < NWRCHANS; i++) { + if (rtrigger & (1 << i)) + chn_intr(via->rch[i].channel); } } static int +via_poll_ticks(struct via_info *via) +{ + struct via_chinfo *ch; + int i; + int ret = hz; + int pollticks; + + for (i = 0; i < NDXSCHANS + NMSGDCHANS; i++) { + ch = &via->pch[i]; + if (ch->channel == NULL || ch->active == 0) + continue; + 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 < ret) + ret = pollticks; + } + + for (i = 0; i < NWRCHANS; i++) { + ch = &via->rch[i]; + if (ch->channel == NULL || ch->active == 0) + continue; + 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 < ret) + ret = pollticks; + } + + return (ret); +} + +static int via8233chan_trigger(kobj_t obj, void* data, int go) { struct via_chinfo *ch = data; struct via_info *via = ch->parent; + int pollticks; + if (!PCMTRIG_COMMON(go)) + return (0); + + snd_mtxlock(via->lock); switch(go) { case PCMTRIG_START: via_buildsgdt(ch); via8233chan_mute(via, ch, 0); via_wr(via, ch->rbase + VIA_RP_TABLE_PTR, ch->sgd_addr, 4); + if (via->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 (via_chan_active(via) == 0 || + pollticks < via->poll_ticks) { + if (bootverbose) { + if (via_chan_active(via) == 0) + printf("%s: pollticks=%d\n", + __func__, pollticks); + else + printf("%s: " + "pollticks %d -> %d\n", + __func__, via->poll_ticks, + pollticks); + } + via->poll_ticks = pollticks; + callout_reset(&via->poll_timer, 1, + via_poll_callback, via); + } + } via_wr(via, ch->rbase + VIA_RP_CONTROL, - SGD_CONTROL_START | SGD_CONTROL_AUTOSTART | - SGD_CONTROL_I_EOL | SGD_CONTROL_I_FLAG, 1); + SGD_CONTROL_START | SGD_CONTROL_AUTOSTART | + ((via->polling == 0) ? + (SGD_CONTROL_I_EOL | SGD_CONTROL_I_FLAG) : 0), 1); + ch->active = 1; break; case PCMTRIG_STOP: case PCMTRIG_ABORT: via_wr(via, ch->rbase + VIA_RP_CONTROL, SGD_CONTROL_STOP, 1); via8233chan_mute(via, ch, 1); via8233chan_reset(via, ch); + ch->active = 0; + if (via->polling != 0) { + if (via_chan_active(via) == 0) { + callout_stop(&via->poll_timer); + via->poll_ticks = 1; + } else { + pollticks = via_poll_ticks(via); + if (pollticks > via->poll_ticks) { + if (bootverbose) + printf("%s: pollticks " + "%d -> %d\n", + __func__, via->poll_ticks, + pollticks); + via->poll_ticks = pollticks; + callout_reset(&via->poll_timer, + 1, via_poll_callback, + via); + } + } + } + break; + default: break; } - return 0; + snd_mtxunlock(via->lock); + return (0); } static kobj_method_t via8233wr_methods[] = { - KOBJMETHOD(channel_init, via8233wr_init), - KOBJMETHOD(channel_setformat, via8233wr_setformat), - KOBJMETHOD(channel_setspeed, via8233wr_setspeed), - KOBJMETHOD(channel_getcaps, via8233wr_getcaps), - KOBJMETHOD(channel_setblocksize, via8233chan_setblocksize), - KOBJMETHOD(channel_trigger, via8233chan_trigger), - KOBJMETHOD(channel_getptr, via8233chan_getptr), + KOBJMETHOD(channel_init, via8233wr_init), + KOBJMETHOD(channel_setformat, via8233wr_setformat), + KOBJMETHOD(channel_setspeed, via8233wr_setspeed), + KOBJMETHOD(channel_getcaps, via8233wr_getcaps), + KOBJMETHOD(channel_setblocksize, via8233chan_setblocksize), + KOBJMETHOD(channel_setfragments, via8233chan_setfragments), + KOBJMETHOD(channel_trigger, via8233chan_trigger), + KOBJMETHOD(channel_getptr, via8233chan_getptr), { 0, 0 } }; CHANNEL_DECLARE(via8233wr); static kobj_method_t via8233dxs_methods[] = { - KOBJMETHOD(channel_init, via8233dxs_init), - KOBJMETHOD(channel_setformat, via8233dxs_setformat), - KOBJMETHOD(channel_setspeed, via8233dxs_setspeed), - KOBJMETHOD(channel_getcaps, via8233dxs_getcaps), - KOBJMETHOD(channel_setblocksize, via8233chan_setblocksize), - KOBJMETHOD(channel_trigger, via8233chan_trigger), - KOBJMETHOD(channel_getptr, via8233chan_getptr), + KOBJMETHOD(channel_init, via8233dxs_init), + KOBJMETHOD(channel_setformat, via8233dxs_setformat), + KOBJMETHOD(channel_setspeed, via8233dxs_setspeed), + KOBJMETHOD(channel_getcaps, via8233dxs_getcaps), + KOBJMETHOD(channel_setblocksize, via8233chan_setblocksize), + KOBJMETHOD(channel_setfragments, via8233chan_setfragments), + KOBJMETHOD(channel_trigger, via8233chan_trigger), + KOBJMETHOD(channel_getptr, via8233chan_getptr), { 0, 0 } }; CHANNEL_DECLARE(via8233dxs); static kobj_method_t via8233msgd_methods[] = { - KOBJMETHOD(channel_init, via8233msgd_init), - KOBJMETHOD(channel_setformat, via8233msgd_setformat), - KOBJMETHOD(channel_setspeed, via8233msgd_setspeed), - KOBJMETHOD(channel_getcaps, via8233msgd_getcaps), - KOBJMETHOD(channel_setblocksize, via8233chan_setblocksize), - KOBJMETHOD(channel_trigger, via8233chan_trigger), - KOBJMETHOD(channel_getptr, via8233chan_getptr), + KOBJMETHOD(channel_init, via8233msgd_init), + KOBJMETHOD(channel_setformat, via8233msgd_setformat), + KOBJMETHOD(channel_setspeed, via8233msgd_setspeed), + KOBJMETHOD(channel_getcaps, via8233msgd_getcaps), + KOBJMETHOD(channel_setblocksize, via8233chan_setblocksize), + KOBJMETHOD(channel_setfragments, via8233chan_setfragments), + KOBJMETHOD(channel_trigger, via8233chan_trigger), + KOBJMETHOD(channel_getptr, via8233chan_getptr), { 0, 0 } }; CHANNEL_DECLARE(via8233msgd); @@ -587,29 +1010,56 @@ via_intr(void *p) { struct via_info *via = p; - int i, stat; + uint32_t ptrigger = 0, rtrigger = 0; + int i, reg, stat; + snd_mtxlock(via->lock); + if (via->polling != 0) { + snd_mtxunlock(via->lock); + return; + } /* Poll playback channels */ for (i = 0; i < NDXSCHANS + NMSGDCHANS; i++) { - if (via->pch[i].rbase == 0) + if (via->pch[i].channel == NULL || via->pch[i].active == 0) continue; - stat = via->pch[i].rbase + VIA_RP_STATUS; - if (via_rd(via, stat, 1) & SGD_STATUS_INTR) { - via_wr(via, stat, SGD_STATUS_INTR, 1); - chn_intr(via->pch[i].channel); + reg = via->pch[i].rbase + VIA_RP_STATUS; + stat = via_rd(via, reg, 1); + if (stat & SGD_STATUS_INTR) { + if (via->dma_eol_wake && ((stat & SGD_STATUS_EOL) || + !(stat & SGD_STATUS_ACTIVE))) + via_wr(via, via->pch[i].rbase + VIA_RP_CONTROL, + SGD_CONTROL_START | SGD_CONTROL_AUTOSTART | + SGD_CONTROL_I_EOL | SGD_CONTROL_I_FLAG, 1); + via_wr(via, reg, stat, 1); + ptrigger |= 1 << i; } } - /* Poll record channels */ for (i = 0; i < NWRCHANS; i++) { - if (via->rch[i].rbase == 0) + if (via->rch[i].channel == NULL || via->rch[i].active == 0) continue; - stat = via->rch[i].rbase + VIA_RP_STATUS; - if (via_rd(via, stat, 1) & SGD_STATUS_INTR) { - via_wr(via, stat, SGD_STATUS_INTR, 1); - chn_intr(via->rch[i].channel); + reg = via->rch[i].rbase + VIA_RP_STATUS; + stat = via_rd(via, reg, 1); + if (stat & SGD_STATUS_INTR) { + if (via->dma_eol_wake && ((stat & SGD_STATUS_EOL) || + !(stat & SGD_STATUS_ACTIVE))) + via_wr(via, via->rch[i].rbase + VIA_RP_CONTROL, + SGD_CONTROL_START | SGD_CONTROL_AUTOSTART | + SGD_CONTROL_I_EOL | SGD_CONTROL_I_FLAG, 1); + via_wr(via, reg, stat, 1); + rtrigger |= 1 << i; } } + snd_mtxunlock(via->lock); + + for (i = 0; i < NDXSCHANS + NMSGDCHANS; i++) { + if (ptrigger & (1 << i)) + chn_intr(via->pch[i].channel); + } + for (i = 0; i < NWRCHANS; i++) { + if (rtrigger & (1 << i)) + chn_intr(via->rch[i].channel); + } } /* @@ -621,30 +1071,33 @@ switch(pci_get_devid(dev)) { case VIA8233_PCI_ID: switch(pci_get_revid(dev)) { - case VIA8233_REV_ID_8233PRE: + case VIA8233_REV_ID_8233PRE: device_set_desc(dev, "VIA VT8233 (pre)"); - return 0; + return (BUS_PROBE_DEFAULT); case VIA8233_REV_ID_8233C: device_set_desc(dev, "VIA VT8233C"); - return 0; + return (BUS_PROBE_DEFAULT); case VIA8233_REV_ID_8233: device_set_desc(dev, "VIA VT8233"); - return 0; + return (BUS_PROBE_DEFAULT); case VIA8233_REV_ID_8233A: device_set_desc(dev, "VIA VT8233A"); - return 0; + return (BUS_PROBE_DEFAULT); case VIA8233_REV_ID_8235: device_set_desc(dev, "VIA VT8235"); - return 0; + return (BUS_PROBE_DEFAULT); case VIA8233_REV_ID_8237: device_set_desc(dev, "VIA VT8237"); - return 0; + return (BUS_PROBE_DEFAULT); + case VIA8233_REV_ID_8251: + device_set_desc(dev, "VIA VT8251"); + return (BUS_PROBE_DEFAULT); default: device_set_desc(dev, "VIA VT8233X"); /* Unknown */ - return 0; - } + return (BUS_PROBE_DEFAULT); + } } - return ENXIO; + return (ENXIO); } static void @@ -657,7 +1110,7 @@ static int via_chip_init(device_t dev) { - u_int32_t data, cnt; + uint32_t data, cnt; /* Wake up and reset AC97 if necessary */ data = pci_read_config(dev, VIA_PCI_ACLINK_STAT, 1); @@ -665,32 +1118,32 @@ if ((data & VIA_PCI_ACLINK_C00_READY) == 0) { /* Cold reset per ac97r2.3 spec (page 95) */ /* Assert low */ - pci_write_config(dev, VIA_PCI_ACLINK_CTRL, - VIA_PCI_ACLINK_EN, 1); + pci_write_config(dev, VIA_PCI_ACLINK_CTRL, + VIA_PCI_ACLINK_EN, 1); /* Wait T_rst_low */ - DELAY(100); + DELAY(100); /* Assert high */ - pci_write_config(dev, VIA_PCI_ACLINK_CTRL, - VIA_PCI_ACLINK_EN | VIA_PCI_ACLINK_NRST, 1); + pci_write_config(dev, VIA_PCI_ACLINK_CTRL, + VIA_PCI_ACLINK_EN | VIA_PCI_ACLINK_NRST, 1); /* Wait T_rst2clk */ DELAY(5); /* Assert low */ - pci_write_config(dev, VIA_PCI_ACLINK_CTRL, - VIA_PCI_ACLINK_EN, 1); + pci_write_config(dev, VIA_PCI_ACLINK_CTRL, + VIA_PCI_ACLINK_EN, 1); } else { /* Warm reset */ /* Force no sync */ - pci_write_config(dev, VIA_PCI_ACLINK_CTRL, - VIA_PCI_ACLINK_EN, 1); + pci_write_config(dev, VIA_PCI_ACLINK_CTRL, + VIA_PCI_ACLINK_EN, 1); DELAY(100); /* Sync */ - pci_write_config(dev, VIA_PCI_ACLINK_CTRL, - VIA_PCI_ACLINK_EN | VIA_PCI_ACLINK_SYNC, 1); + pci_write_config(dev, VIA_PCI_ACLINK_CTRL, + VIA_PCI_ACLINK_EN | VIA_PCI_ACLINK_SYNC, 1); /* Wait T_sync_high */ DELAY(5); /* Force no sync */ - pci_write_config(dev, VIA_PCI_ACLINK_CTRL, - VIA_PCI_ACLINK_EN, 1); + pci_write_config(dev, VIA_PCI_ACLINK_CTRL, + VIA_PCI_ACLINK_EN, 1); /* Wait T_sync2clk */ DELAY(5); } @@ -701,58 +1154,12 @@ /* Wait for codec to become ready (largest reported delay 310ms) */ for (cnt = 0; cnt < 2000; cnt++) { data = pci_read_config(dev, VIA_PCI_ACLINK_STAT, 1); - if (data & VIA_PCI_ACLINK_C00_READY) { - return 0; - } + if (data & VIA_PCI_ACLINK_C00_READY) + return (0); DELAY(5000); } device_printf(dev, "primary codec not ready (cnt = 0x%02x)\n", cnt); - return ENXIO; -} - -#ifdef SND_DYNSYSCTL -static int via8233_spdif_en; - -static int -sysctl_via8233_spdif_enable(SYSCTL_HANDLER_ARGS) -{ - device_t dev; - int err, new_en, r; - - new_en = via8233_spdif_en; - err = sysctl_handle_int(oidp, &new_en, sizeof(new_en), req); - if (err || req->newptr == NULL) - return err; - - if (new_en < 0 || new_en > 1) - return EINVAL; - via8233_spdif_en = new_en; - - dev = oidp->oid_arg1; - r = pci_read_config(dev, VIA_PCI_SPDIF, 1) & ~VIA_SPDIF_EN; - if (new_en) - r |= VIA_SPDIF_EN; - pci_write_config(dev, VIA_PCI_SPDIF, r, 1); - return 0; -} -#endif /* SND_DYNSYSCTL */ - -static void -via_init_sysctls(device_t dev) -{ -#ifdef SND_DYNSYSCTL - int r; - - r = pci_read_config(dev, VIA_PCI_SPDIF, 1); - via8233_spdif_en = (r & VIA_SPDIF_EN) ? 1 : 0; - - SYSCTL_ADD_PROC(snd_sysctl_tree(dev), - SYSCTL_CHILDREN(snd_sysctl_tree_top(dev)), - OID_AUTO, "spdif_enabled", - CTLTYPE_INT | CTLFLAG_RW, dev, sizeof(dev), - sysctl_via8233_spdif_enable, "I", - "Enable S/PDIF output on primary playback channel"); -#endif + return (ENXIO); } static int @@ -760,15 +1167,27 @@ { struct via_info *via = 0; char status[SND_STATUSLEN]; - - if ((via = malloc(sizeof *via, M_DEVBUF, M_NOWAIT | M_ZERO)) == NULL) { - device_printf(dev, "cannot allocate softc\n"); - return ENXIO; - } + int i, via_dxs_disabled, via_dxs_src, via_dxs_chnum, via_sgd_chnum; + int nsegs; + uint32_t revid; + + via = malloc(sizeof *via, M_DEVBUF, M_WAITOK | M_ZERO); + via->lock = snd_mtxcreate(device_get_nameunit(dev), + "snd_via8233 softc"); + via->dev = dev; + + callout_init(&via->poll_timer, CALLOUT_MPSAFE); + via->poll_ticks = 1; + + if (resource_int_value(device_get_name(dev), + device_get_unit(dev), "polling", &i) == 0 && i != 0) + via->polling = 1; + else + via->polling = 0; pci_set_powerstate(dev, PCI_POWERSTATE_D0); pci_enable_busmaster(dev); - + via->regid = PCIR_BAR(0); via->reg = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &via->regid, RF_ACTIVE); @@ -779,25 +1198,107 @@ via->st = rman_get_bustag(via->reg); via->sh = rman_get_bushandle(via->reg); - via->bufsz = pcm_getbuffersize(dev, 4096, VIA_DEFAULT_BUFSZ, 65536); - via->irqid = 0; via->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &via->irqid, - RF_ACTIVE | RF_SHAREABLE); - if (!via->irq || - snd_setup_intr(dev, via->irq, 0, via_intr, via, &via->ih)) { + RF_ACTIVE | RF_SHAREABLE); + if (!via->irq || + snd_setup_intr(dev, via->irq, INTR_MPSAFE, + via_intr, via, &via->ih)) { device_printf(dev, "unable to map interrupt\n"); goto bad; } + via->bufsz = pcm_getbuffersize(dev, 4096, VIA_DEFAULT_BUFSZ, 65536); + if (resource_int_value(device_get_name(dev), + device_get_unit(dev), "blocksize", &i) == 0 && i > 0) { + i &= VIA_BLK_ALIGN; + if (i < VIA_BLK_MIN) + i = VIA_BLK_MIN; + via->blkcnt = via->bufsz / i; + i = 0; + while (via->blkcnt >> i) + i++; + via->blkcnt = 1 << (i - 1); + if (via->blkcnt < VIA_SEGS_MIN) + via->blkcnt = VIA_SEGS_MIN; + else if (via->blkcnt > VIA_SEGS_MAX) + via->blkcnt = VIA_SEGS_MAX; + + } else + via->blkcnt = VIA_SEGS_DEFAULT; + + revid = pci_get_revid(dev); + + /* + * VIA8251 lost its interrupt after DMA EOL, and need + * a gentle spank on its face within interrupt handler. + */ + if (revid == VIA8233_REV_ID_8251) + via->dma_eol_wake = 1; + else + via->dma_eol_wake = 0; + + /* + * Decide whether DXS had to be disabled or not + */ + if (revid == VIA8233_REV_ID_8233A) { + /* + * DXS channel is disabled. Reports from multiple users + * that it plays at half-speed. Do not see this behaviour + * on available 8233C or when emulating 8233A register set + * on 8233C (either with or without ac97 VRA). + */ + via_dxs_disabled = 1; + } else if (resource_int_value(device_get_name(dev), + device_get_unit(dev), "via_dxs_disabled", + &via_dxs_disabled) == 0) + via_dxs_disabled = (via_dxs_disabled > 0) ? 1 : 0; + else + via_dxs_disabled = 0; + + if (via_dxs_disabled) { + via_dxs_chnum = 0; + via_sgd_chnum = 1; + } else { + if (resource_int_value(device_get_name(dev), + device_get_unit(dev), "via_dxs_channels", + &via_dxs_chnum) != 0) + via_dxs_chnum = NDXSCHANS; + if (resource_int_value(device_get_name(dev), + device_get_unit(dev), "via_sgd_channels", + &via_sgd_chnum) != 0) + via_sgd_chnum = NMSGDCHANS; + } + if (via_dxs_chnum > NDXSCHANS) + via_dxs_chnum = NDXSCHANS; + else if (via_dxs_chnum < 0) + via_dxs_chnum = 0; + if (via_sgd_chnum > NMSGDCHANS) + via_sgd_chnum = NMSGDCHANS; + else if (via_sgd_chnum < 0) + via_sgd_chnum = 0; + if (via_dxs_chnum + via_sgd_chnum < 1) { + /* Minimalist ? */ + via_dxs_chnum = 1; + via_sgd_chnum = 0; + } + if (via_dxs_chnum > 0 && resource_int_value(device_get_name(dev), + device_get_unit(dev), "via_dxs_src", &via_dxs_src) == 0) + via->dxs_src = (via_dxs_src > 0) ? 1 : 0; + else + via->dxs_src = 0; + + nsegs = (via_dxs_chnum + via_sgd_chnum + NWRCHANS) * VIA_SEGS_MAX; + /* DMA tag for buffers */ - 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*/via->bufsz, /*nsegments*/1, /*maxsegz*/0x3ffff, - /*flags*/0, /*lockfunc*/busdma_lock_mutex, - /*lockarg*/&Giant, &via->parent_dmat) != 0) { + /*flags*/0, /*lockfunc*/NULL, + /*lockarg*/NULL, &via->parent_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto bad; } @@ -807,23 +1308,24 @@ * requires a list in memory of work to do. We need only 16 bytes * for this list, and it is wasteful to allocate 16K. */ - 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*/NSEGS * sizeof(struct via_dma_op), + /*maxsize*/nsegs * sizeof(struct via_dma_op), /*nsegments*/1, /*maxsegz*/0x3ffff, - /*flags*/0, /*lockfunc*/busdma_lock_mutex, - /*lockarg*/&Giant, &via->sgd_dmat) != 0) { + /*flags*/0, /*lockfunc*/NULL, + /*lockarg*/NULL, &via->sgd_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto bad; } - if (bus_dmamem_alloc(via->sgd_dmat, (void **)&via->sgd_table, - BUS_DMA_NOWAIT, &via->sgd_dmamap) == -1) + if (bus_dmamem_alloc(via->sgd_dmat, (void **)&via->sgd_table, + BUS_DMA_NOWAIT, &via->sgd_dmamap) == -1) goto bad; - if (bus_dmamap_load(via->sgd_dmat, via->sgd_dmamap, via->sgd_table, - NSEGS * sizeof(struct via_dma_op), dma_cb, via, 0)) + if (bus_dmamap_load(via->sgd_dmat, via->sgd_dmamap, via->sgd_table, + nsegs * sizeof(struct via_dma_op), dma_cb, via, 0)) goto bad; if (via_chip_init(dev)) @@ -838,74 +1340,91 @@ via->codec_caps = ac97_getextcaps(via->codec); /* Try to set VRA without generating an error, VRM not reqrd yet */ - if (via->codec_caps & + if (via->codec_caps & (AC97_EXTCAP_VRA | AC97_EXTCAP_VRM | AC97_EXTCAP_DRA)) { - u_int16_t ext = ac97_getextmode(via->codec); - ext |= (via->codec_caps & - (AC97_EXTCAP_VRA | AC97_EXTCAP_VRM)); + uint16_t ext = ac97_getextmode(via->codec); + ext |= (via->codec_caps & + (AC97_EXTCAP_VRA | AC97_EXTCAP_VRM)); ext &= ~AC97_EXTCAP_DRA; ac97_setextmode(via->codec, ext); } - snprintf(status, SND_STATUSLEN, "at io 0x%lx irq %ld %s", - rman_get_start(via->reg), rman_get_start(via->irq),PCM_KLDSTRING(snd_via8233)); + snprintf(status, SND_STATUSLEN, "at io 0x%lx irq %ld %s", + rman_get_start(via->reg), rman_get_start(via->irq), + PCM_KLDSTRING(snd_via8233)); /* Register */ - if (pci_get_revid(dev) == VIA8233_REV_ID_8233A) { - if (pcm_register(dev, via, NMSGDCHANS, 1)) goto bad; - /* - * DXS channel is disabled. Reports from multiple users - * that it plays at half-speed. Do not see this behaviour - * on available 8233C or when emulating 8233A register set - * on 8233C (either with or without ac97 VRA). - pcm_addchan(dev, PCMDIR_PLAY, &via8233dxs_class, via); - */ - pcm_addchan(dev, PCMDIR_PLAY, &via8233msgd_class, via); - pcm_addchan(dev, PCMDIR_REC, &via8233wr_class, via); - } else { - int i; - if (pcm_register(dev, via, NMSGDCHANS + NDXSCHANS, NWRCHANS)) goto bad; - for (i = 0; i < NDXSCHANS; i++) - pcm_addchan(dev, PCMDIR_PLAY, &via8233dxs_class, via); - pcm_addchan(dev, PCMDIR_PLAY, &via8233msgd_class, via); - for (i = 0; i < NWRCHANS; i++) - pcm_addchan(dev, PCMDIR_REC, &via8233wr_class, via); + if (pcm_register(dev, via, via_dxs_chnum + via_sgd_chnum, NWRCHANS)) + goto bad; + for (i = 0; i < via_dxs_chnum; i++) + pcm_addchan(dev, PCMDIR_PLAY, &via8233dxs_class, via); + for (i = 0; i < via_sgd_chnum; i++) + pcm_addchan(dev, PCMDIR_PLAY, &via8233msgd_class, via); + for (i = 0; i < NWRCHANS; i++) + pcm_addchan(dev, PCMDIR_REC, &via8233wr_class, via); + if (via_dxs_chnum > 0) via_init_sysctls(dev); - } + device_printf(dev, "\n", + (via_dxs_chnum > 0) ? "En" : "Dis", (via->dxs_src) ? "(SRC)" : "", + via_dxs_chnum, via_sgd_chnum, NWRCHANS); pcm_setstatus(dev, status); - return 0; + return (0); bad: - if (via->codec) ac97_destroy(via->codec); - if (via->reg) bus_release_resource(dev, SYS_RES_IOPORT, via->regid, via->reg); - if (via->ih) bus_teardown_intr(dev, via->irq, via->ih); - if (via->irq) bus_release_resource(dev, SYS_RES_IRQ, via->irqid, via->irq); - if (via->parent_dmat) bus_dma_tag_destroy(via->parent_dmat); - if (via->sgd_dmamap) bus_dmamap_unload(via->sgd_dmat, via->sgd_dmamap); - if (via->sgd_dmat) bus_dma_tag_destroy(via->sgd_dmat); - if (via) free(via, M_DEVBUF); - return ENXIO; + if (via->codec) + ac97_destroy(via->codec); + if (via->reg) + bus_release_resource(dev, SYS_RES_IOPORT, via->regid, via->reg); + if (via->ih) + bus_teardown_intr(dev, via->irq, via->ih); + if (via->irq) + bus_release_resource(dev, SYS_RES_IRQ, via->irqid, via->irq); + if (via->parent_dmat) + bus_dma_tag_destroy(via->parent_dmat); + if (via->sgd_dmamap) + bus_dmamap_unload(via->sgd_dmat, via->sgd_dmamap); + if (via->sgd_table) + bus_dmamem_free(via->sgd_dmat, via->sgd_table, via->sgd_dmamap); + if (via->sgd_dmat) + bus_dma_tag_destroy(via->sgd_dmat); + if (via->lock) + snd_mtxfree(via->lock); + if (via) + free(via, M_DEVBUF); + return (ENXIO); } static int via_detach(device_t dev) { int r; - struct via_info *via = 0; + struct via_info *via; r = pcm_unregister(dev); - if (r) return r; + if (r) + return (r); via = pcm_getdevinfo(dev); + + if (via != NULL && (via->play_num != 0 || via->rec_num != 0)) { + snd_mtxlock(via->lock); + via->polling = 0; + callout_stop(&via->poll_timer); + snd_mtxunlock(via->lock); + callout_drain(&via->poll_timer); + } + bus_release_resource(dev, SYS_RES_IOPORT, via->regid, via->reg); bus_teardown_intr(dev, via->irq, via->ih); bus_release_resource(dev, SYS_RES_IRQ, via->irqid, via->irq); bus_dma_tag_destroy(via->parent_dmat); bus_dmamap_unload(via->sgd_dmat, via->sgd_dmamap); + bus_dmamem_free(via->sgd_dmat, via->sgd_table, via->sgd_dmamap); bus_dma_tag_destroy(via->sgd_dmat); + snd_mtxfree(via->lock); free(via, M_DEVBUF); - return 0; + return (0); } --- sys/dev/sound/pci/via8233.h.orig Sun Jan 30 09:00:04 2005 +++ sys/dev/sound/pci/via8233.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/via8233.h,v 1.3.6.1 2005/01/30 01:00:04 imp Exp $ + * $FreeBSD: src/sys/dev/sound/pci/via8233.h,v 1.4 2005/01/06 01:43:19 imp Exp $ */ #ifndef _SYS_SOUND_PCI_VIA8233_H_ --- sys/dev/sound/pci/via82c686.c.orig Sun Jan 30 09:00:04 2005 +++ sys/dev/sound/pci/via82c686.c Thu Jul 12 12:04:19 2007 @@ -33,7 +33,7 @@ #include -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/via82c686.c,v 1.31.2.2 2005/01/30 01:00:04 imp Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/via82c686.c,v 1.43 2007/06/17 06:10:42 ariff Exp $"); #define VIA_PCI_ID 0x30581106 #define NSEGS 4 /* Number of segments in SGD table */ @@ -86,6 +86,7 @@ struct via_chinfo pch, rch; struct via_dma_op *sgd_table; u_int16_t codec_caps; + struct mtx *lock; }; static u_int32_t via_fmt[] = { @@ -98,7 +99,7 @@ static struct pcmchan_caps via_vracaps = {4000, 48000, via_fmt, 0}; static struct pcmchan_caps via_caps = {48000, 48000, via_fmt, 0}; -static u_int32_t +static __inline u_int32_t via_rd(struct via_info *via, int regno, int size) { @@ -115,7 +116,7 @@ } -static void +static __inline void via_wr(struct via_info *via, int regno, u_int32_t data, int size) { @@ -244,6 +245,7 @@ struct via_info *via = devinfo; struct via_chinfo *ch; + snd_mtxlock(via->lock); if (dir == PCMDIR_PLAY) { ch = &via->pch; ch->base = VIA_PLAY_DMAOPS_BASE; @@ -266,9 +268,11 @@ ch->channel = c; ch->buffer = b; ch->dir = dir; + snd_mtxunlock(via->lock); - if (sndbuf_alloc(ch->buffer, via->parent_dmat, via->bufsz) != 0) + if (sndbuf_alloc(ch->buffer, via->parent_dmat, 0, via->bufsz) != 0) return NULL; + return ch; } @@ -286,10 +290,12 @@ mode_set |= VIA_RPMODE_16BIT; DEB(printf("set format: dir = %d, format=%x\n", ch->dir, format)); + snd_mtxlock(via->lock); mode = via_rd(via, ch->mode, 1); mode &= ~(VIA_RPMODE_16BIT | VIA_RPMODE_STEREO); mode |= mode_set; via_wr(via, ch->mode, mode, 1); + snd_mtxunlock(via->lock); return 0; } @@ -336,18 +342,20 @@ struct via_dma_op *ado; bus_addr_t sgd_addr = ch->sgd_addr; - if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) + if (!PCMTRIG_COMMON(go)) return 0; ado = ch->sgd_table; DEB(printf("ado located at va=%p pa=%x\n", ado, sgd_addr)); + snd_mtxlock(via->lock); if (go == PCMTRIG_START) { via_buildsgdt(ch); via_wr(via, ch->base, sgd_addr, 4); via_wr(via, ch->ctrl, VIA_RPCTRL_START, 1); } else via_wr(via, ch->ctrl, VIA_RPCTRL_TERMINATE, 1); + snd_mtxunlock(via->lock); DEB(printf("viachan_trigger: go=%d\n", go)); return 0; @@ -363,11 +371,13 @@ int ptr, base, base1, len, seg; ado = ch->sgd_table; + snd_mtxlock(via->lock); base1 = via_rd(via, ch->base, 4); len = via_rd(via, ch->count, 4); base = via_rd(via, ch->base, 4); if (base != base1) /* Avoid race hazard */ len = via_rd(via, ch->count, 4); + snd_mtxunlock(via->lock); DEB(printf("viachan_getptr: len / base = %x / %x\n", len, base)); @@ -417,22 +427,25 @@ via_intr(void *p) { struct via_info *via = p; - int st; /* DEB(printf("viachan_intr\n")); */ /* Read channel */ - st = via_rd(via, VIA_PLAY_STAT, 1); - if (st & VIA_RPSTAT_INTR) { + snd_mtxlock(via->lock); + if (via_rd(via, VIA_PLAY_STAT, 1) & VIA_RPSTAT_INTR) { via_wr(via, VIA_PLAY_STAT, VIA_RPSTAT_INTR, 1); + snd_mtxunlock(via->lock); chn_intr(via->pch.channel); + snd_mtxlock(via->lock); } /* Write channel */ - st = via_rd(via, VIA_RECORD_STAT, 1); - if (st & VIA_RPSTAT_INTR) { + if (via_rd(via, VIA_RECORD_STAT, 1) & VIA_RPSTAT_INTR) { via_wr(via, VIA_RECORD_STAT, VIA_RPSTAT_INTR, 1); + snd_mtxunlock(via->lock); chn_intr(via->rch.channel); + return; } + snd_mtxunlock(via->lock); } /* @@ -442,8 +455,8 @@ via_probe(device_t dev) { if (pci_get_devid(dev) == VIA_PCI_ID) { - device_set_desc(dev, "VIA VT82C686A"); - return 0; + device_set_desc(dev, "VIA VT82C686A"); + return BUS_PROBE_DEFAULT; } return ENXIO; } @@ -464,10 +477,9 @@ char status[SND_STATUSLEN]; u_int32_t data, cnt; - if ((via = malloc(sizeof *via, M_DEVBUF, M_NOWAIT | M_ZERO)) == NULL) { - device_printf(dev, "cannot allocate softc\n"); - return ENXIO; - } + via = malloc(sizeof(*via), M_DEVBUF, M_WAITOK | M_ZERO); + via->lock = snd_mtxcreate(device_get_nameunit(dev), + "snd_via82c686 softc"); /* Get resources */ data = pci_read_config(dev, PCIR_COMMAND, 2); @@ -521,7 +533,7 @@ via->irqid = 0; via->irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &via->irqid, RF_ACTIVE | RF_SHAREABLE); - if (!via->irq || snd_setup_intr(dev, via->irq, 0, via_intr, via, &via->ih)) { + if (!via->irq || snd_setup_intr(dev, via->irq, INTR_MPSAFE, via_intr, via, &via->ih)) { device_printf(dev, "unable to map interrupt\n"); goto bad; } @@ -541,13 +553,14 @@ via->codec_caps & (AC97_EXTCAP_VRA | AC97_EXTCAP_VRM)); /* DMA tag for buffers */ - 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*/via->bufsz, /*nsegments*/1, /*maxsegz*/0x3ffff, - /*flags*/0, /*lockfunc*/busdma_lock_mutex, - /*lockarg*/&Giant, &via->parent_dmat) != 0) { + /*flags*/0, /*lockfunc*/NULL, + /*lockarg*/NULL, &via->parent_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto bad; } @@ -557,14 +570,15 @@ * requires a list in memory of work to do. We need only 16 bytes * for this list, and it is wasteful to allocate 16K. */ - 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*/NSEGS * sizeof(struct via_dma_op), /*nsegments*/1, /*maxsegz*/0x3ffff, - /*flags*/0, /*lockfunc*/busdma_lock_mutex, - /*lockarg*/&Giant, &via->sgd_dmat) != 0) { + /*flags*/0, /*lockfunc*/NULL, + /*lockarg*/NULL, &via->sgd_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); goto bad; } @@ -593,7 +607,9 @@ if (via->irq) bus_release_resource(dev, SYS_RES_IRQ, via->irqid, via->irq); if (via->parent_dmat) bus_dma_tag_destroy(via->parent_dmat); if (via->sgd_dmamap) bus_dmamap_unload(via->sgd_dmat, via->sgd_dmamap); + if (via->sgd_table) bus_dmamem_free(via->sgd_dmat, via->sgd_table, via->sgd_dmamap); if (via->sgd_dmat) bus_dma_tag_destroy(via->sgd_dmat); + if (via->lock) snd_mtxfree(via->lock); if (via) free(via, M_DEVBUF); return ENXIO; } @@ -614,7 +630,9 @@ bus_release_resource(dev, SYS_RES_IRQ, via->irqid, via->irq); bus_dma_tag_destroy(via->parent_dmat); bus_dmamap_unload(via->sgd_dmat, via->sgd_dmamap); + bus_dmamem_free(via->sgd_dmat, via->sgd_table, via->sgd_dmamap); bus_dma_tag_destroy(via->sgd_dmat); + snd_mtxfree(via->lock); free(via, M_DEVBUF); return 0; } --- sys/dev/sound/pci/vibes.c.orig Sun Jan 30 09:00:04 2005 +++ sys/dev/sound/pci/vibes.c Thu Jul 12 12:04:19 2007 @@ -36,7 +36,7 @@ #include "mixer_if.h" -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/vibes.c,v 1.17.2.1 2005/01/30 01:00:04 imp Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pci/vibes.c,v 1.26 2007/06/17 06:10:42 ariff Exp $"); /* ------------------------------------------------------------------------- */ /* Constants */ @@ -192,7 +192,7 @@ ch->channel = c; ch->dir = dir; - if (sndbuf_alloc(b, sc->parent_dmat, sc->bufsz) != 0) { + if (sndbuf_alloc(b, sc->parent_dmat, 0, sc->bufsz) != 0) { DEB(printf("svchan_init failed\n")); return NULL; } @@ -336,6 +336,7 @@ sv_indirect_set(sc, SV_REG_ENABLE, enable); ch->dma_active = 1; break; + case PCMTRIG_STOP: case PCMTRIG_ABORT: enable = sv_indirect_get(sc, SV_REG_ENABLE) & ~SV_RECORD_ENABLE; sv_indirect_set(sc, SV_REG_ENABLE, enable); @@ -412,6 +413,7 @@ sv_indirect_set(sc, SV_REG_ENABLE, enable); ch->dma_active = 1; break; + case PCMTRIG_STOP: case PCMTRIG_ABORT: enable = sv_indirect_get(sc, SV_REG_ENABLE) & ~SV_PLAY_ENABLE; sv_indirect_set(sc, SV_REG_ENABLE, enable); @@ -704,7 +706,7 @@ switch(pci_get_devid(dev)) { case SV_PCI_ID: device_set_desc(dev, "S3 Sonicvibes"); - return 0; + return BUS_PROBE_DEFAULT; default: return ENXIO; } @@ -712,19 +714,12 @@ static int sv_attach(device_t dev) { - struct snddev_info *d; struct sc_info *sc; u_int32_t data; char status[SND_STATUSLEN]; u_long midi_start, games_start, count, sdmaa, sdmac, ml, mu; - 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"); - return ENXIO; - } + sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO); sc->dev = dev; data = pci_read_config(dev, PCIR_COMMAND, 2); @@ -765,13 +760,14 @@ sc->irq = bus_alloc_resource(dev, SYS_RES_IRQ, &sc->irqid, 0, ~0, 1, RF_ACTIVE | RF_SHAREABLE); if (!sc->irq || - bus_setup_intr(dev, sc->irq, INTR_TYPE_AV, sv_intr, sc, &sc->ih)) { + snd_setup_intr(dev, sc->irq, 0, sv_intr, sc, &sc->ih)) { device_printf(dev, "sv_attach: Unable to map interrupt\n"); goto fail; } sc->bufsz = pcm_getbuffersize(dev, 4096, SV_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_24BIT, /*highaddr*/BUS_SPACE_MAXADDR, /*filter*/NULL, /*filterarg*/NULL, --- sys/dev/sound/pci/vibes.h.orig Sun Jan 30 09:00:04 2005 +++ sys/dev/sound/pci/vibes.h Thu Jan 6 09:43:19 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/vibes.h,v 1.1.10.1 2005/01/30 01:00:04 imp Exp $ + * $FreeBSD: src/sys/dev/sound/pci/vibes.h,v 1.2 2005/01/06 01:43:19 imp Exp $ */ /* ------------------------------------------------------------------------- */ --- sys/dev/sound/pcm/ac97.c.orig Sun May 1 22:31:06 2005 +++ sys/dev/sound/pcm/ac97.c Thu Jul 12 12:04:19 2007 @@ -28,14 +28,16 @@ #include #include +#include + #include "mixer_if.h" -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pcm/ac97.c,v 1.51.2.2 2005/05/01 14:31:06 scottl Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pcm/ac97.c,v 1.73 2007/06/17 06:10:43 ariff Exp $"); MALLOC_DEFINE(M_AC97, "ac97", "ac97 codec"); struct ac97mixtable_entry { - int reg:8; /* register index */ + int reg; /* register index */ /* reg < 0 if inverted polarity */ unsigned bits:4; /* width of control field */ unsigned ofs:4; /* offset (only if stereo=0) */ @@ -46,16 +48,18 @@ unsigned enable:1; /* entry is enabled */ }; -#define AC97_NAMELEN 16 +#define AC97_MIXER_SIZE SOUND_MIXER_NRDEVICES + struct ac97_info { kobj_t methods; device_t dev; void *devinfo; u_int32_t id; + u_int32_t subvendor; unsigned count, caps, se, extcaps, extid, extstat, noext:1; u_int32_t flags; - struct ac97mixtable_entry mix[32]; - char name[AC97_NAMELEN]; + struct ac97mixtable_entry mix[AC97_MIXER_SIZE]; + char name[16]; struct mtx *lock; }; @@ -72,7 +76,7 @@ ac97_patch patch; }; -static const struct ac97mixtable_entry ac97mixtable_default[32] = { +static const struct ac97mixtable_entry ac97mixtable_default[AC97_MIXER_SIZE] = { /* [offset] reg bits of st mu re mk en */ [SOUND_MIXER_VOLUME] = { AC97_MIX_MASTER, 5, 0, 1, 1, 6, 0, 1 }, [SOUND_MIXER_OGAIN] = { AC97_MIX_AUXOUT, 5, 0, 1, 1, 0, 0, 0 }, @@ -84,10 +88,8 @@ [SOUND_MIXER_LINE] = { AC97_MIX_LINE, 5, 0, 1, 1, 5, 0, 1 }, [SOUND_MIXER_PHONEIN] = { AC97_MIX_PHONE, 5, 0, 0, 1, 8, 0, 0 }, [SOUND_MIXER_MIC] = { AC97_MIX_MIC, 5, 0, 0, 1, 1, 1, 1 }, -#if 0 /* use igain for the mic 20dB boost */ [SOUND_MIXER_IGAIN] = { -AC97_MIX_MIC, 1, 6, 0, 0, 0, 1, 1 }, -#endif [SOUND_MIXER_CD] = { AC97_MIX_CD, 5, 0, 1, 1, 2, 0, 1 }, [SOUND_MIXER_LINE1] = { AC97_MIX_AUX, 5, 0, 1, 1, 4, 0, 0 }, [SOUND_MIXER_VIDEO] = { AC97_MIX_VIDEO, 5, 0, 1, 1, 3, 0, 0 }, @@ -118,6 +120,11 @@ { 0x57454300, "Winbond" }, { 0x574d4c00, "Wolfson" }, { 0x594d4800, "Yamaha" }, + /* + * XXX This is a fluke, really! The real vendor + * should be SigmaTel, not this! This should be + * removed someday! + */ { 0x01408300, "Creative" }, { 0x00000000, NULL } }; @@ -133,8 +140,9 @@ { 0x41445368, 0x00, 0, "AD1888", ad198x_patch }, { 0x41445370, 0x00, 0, "AD1980", ad198x_patch }, { 0x41445372, 0x00, 0, "AD1981A", 0 }, - { 0x41445374, 0x00, 0, "AD1981B", 0 }, + { 0x41445374, 0x00, 0, "AD1981B", ad1981b_patch }, { 0x41445375, 0x00, 0, "AD1985", ad198x_patch }, + { 0x41445378, 0x00, 0, "AD1986", ad198x_patch }, { 0x414b4d00, 0x00, 1, "AK4540", 0 }, { 0x414b4d01, 0x00, 1, "AK4542", 0 }, { 0x414b4d02, 0x00, 1, "AK4543", 0 }, @@ -145,7 +153,9 @@ { 0x414c4710, 0x0f, 0, "ALC200", 0 }, { 0x414c4740, 0x0f, 0, "ALC202", 0 }, { 0x414c4720, 0x0f, 0, "ALC650", 0 }, - { 0x414c4760, 0x0f, 0, "ALC655", 0 }, + { 0x414c4752, 0x0f, 0, "ALC250", 0 }, + { 0x414c4760, 0x0f, 0, "ALC655", alc655_patch }, + { 0x414c4770, 0x0f, 0, "ALC203", 0 }, { 0x414c4780, 0x0f, 0, "ALC658", 0 }, { 0x414c4790, 0x0f, 0, "ALC850", 0 }, { 0x43525900, 0x07, 0, "CS4297", 0 }, @@ -156,10 +166,14 @@ { 0x43525940, 0x07, 0, "CS4201", 0 }, { 0x43525958, 0x07, 0, "CS4205", 0 }, { 0x43525960, 0x07, 0, "CS4291A", 0 }, - { 0x434d4961, 0x00, 0, "CMI9739", 0 }, + { 0x434d4961, 0x00, 0, "CMI9739", cmi9739_patch }, { 0x434d4941, 0x00, 0, "CMI9738", 0 }, + { 0x434d4978, 0x00, 0, "CMI9761", 0 }, + { 0x434d4982, 0x00, 0, "CMI9761", 0 }, + { 0x434d4983, 0x00, 0, "CMI9761", 0 }, { 0x43585421, 0x00, 0, "HSD11246", 0 }, { 0x43585428, 0x07, 0, "CX20468", 0 }, + { 0x43585430, 0x00, 0, "CX20468-21", 0 }, { 0x44543000, 0x00, 0, "DT0398", 0 }, { 0x454d4323, 0x00, 0, "EM28023", 0 }, { 0x454d4328, 0x00, 0, "EM28028", 0 }, @@ -192,6 +206,7 @@ { 0x83847658, 0x00, 0, "STAC9758/59", 0 }, { 0x83847660, 0x00, 0, "STAC9760/61", 0 }, /* Extrapolated */ { 0x83847662, 0x00, 0, "STAC9762/63", 0 }, /* Extrapolated */ + { 0x83847666, 0x00, 0, "STAC9766/67", 0 }, { 0x53494c22, 0x00, 0, "Si3036", 0 }, { 0x53494c23, 0x00, 0, "Si3038", 0 }, { 0x54524103, 0x00, 0, "TR28023", 0 }, /* Extrapolated */ @@ -201,6 +216,7 @@ { 0x54524e03, 0x07, 0, "TLV320AIC27", 0 }, { 0x54584e20, 0x00, 0, "TLC320AD90", 0 }, { 0x56494161, 0x00, 0, "VIA1612A", 0 }, + { 0x56494170, 0x00, 0, "VIA1617A", 0 }, { 0x574d4c00, 0x00, 0, "WM9701A", 0 }, { 0x574d4c03, 0x00, 0, "WM9703/4/7/8", 0 }, { 0x574d4c04, 0x00, 0, "WM9704Q", 0 }, @@ -211,6 +227,11 @@ { 0x594d4800, 0x00, 0, "YMF743", 0 }, { 0x594d4802, 0x00, 0, "YMF752", 0 }, { 0x594d4803, 0x00, 0, "YMF753", 0 }, + /* + * XXX This is a fluke, really! The real codec + * should be STAC9704, not this! This should be + * removed someday! + */ { 0x01408384, 0x00, 0, "EV1938", 0 }, { 0, 0, 0, NULL, 0 } }; @@ -283,6 +304,21 @@ u_int16_t ac97_rdcd(struct ac97_info *codec, int reg) { + if (codec->flags & AC97_F_RDCD_BUG) { + u_int16_t i[2], j = 100; + + i[0] = AC97_READ(codec->methods, codec->devinfo, reg); + i[1] = AC97_READ(codec->methods, codec->devinfo, reg); + while (i[0] != i[1] && j) + i[j-- & 1] = AC97_READ(codec->methods, codec->devinfo, reg); +#if 0 + if (j < 100) { + device_printf(codec->dev, "%s(): Inconsistent register value at" + " 0x%08x (retry: %d)\n", __func__, reg, 100 - j); + } +#endif + return i[!(j & 1)]; + } return AC97_READ(codec->methods, codec->devinfo, reg); } @@ -371,6 +407,12 @@ return codec->caps; } +u_int32_t +ac97_getsubvendor(struct ac97_info *codec) +{ + return codec->subvendor; +} + static int ac97_setrecsrc(struct ac97_info *codec, int channel) { @@ -452,14 +494,16 @@ */ snd_mtxlock(codec->lock); if (e->mask) { - int cur = ac97_rdcd(codec, e->reg); + int cur = ac97_rdcd(codec, reg); val |= cur & ~(mask); } ac97_wrcd(codec, reg, val); snd_mtxunlock(codec->lock); return left | (right << 8); } else { - /* printf("ac97_setmixer: reg=%d, bits=%d, enable=%d\n", e->reg, e->bits, e->enable); */ +#if 0 + printf("ac97_setmixer: reg=%d, bits=%d, enable=%d\n", e->reg, e->bits, e->enable); +#endif return -1; } } @@ -502,6 +546,23 @@ static void ac97_fix_tone(struct ac97_info *codec) { + /* + * YMF chips does not indicate tone and 3D enhancement capability + * in the AC97_REG_RESET register. + */ + switch (codec->id) { + case 0x594d4800: /* YMF743 */ + case 0x594d4803: /* YMF753 */ + codec->caps |= AC97_CAP_TONE; + codec->se |= 0x04; + break; + case 0x594d4802: /* YMF752 */ + codec->se |= 0x04; + break; + default: + break; + } + /* Hide treble and bass if they don't exist */ if ((codec->caps & AC97_CAP_TONE) == 0) { bzero(&codec->mix[SOUND_MIXER_BASS], @@ -536,8 +597,9 @@ const char *cname, *vname; char desc[80]; u_int8_t model, step; - unsigned i, j, k, old; + unsigned i, j, k, bit, old; u_int32_t id; + int reg; snd_mtxlock(codec->lock); codec->count = AC97_INIT(codec->methods, codec->devinfo); @@ -552,6 +614,17 @@ ac97_wrcd(codec, AC97_REG_POWER, (codec->flags & AC97_F_EAPD_INV)? 0x8000 : 0x0000); i = ac97_rdcd(codec, AC97_REG_RESET); + j = ac97_rdcd(codec, AC97_REG_RESET); + k = ac97_rdcd(codec, AC97_REG_RESET); + /* + * Let see if this codec can return consistent value. + * If not, turn on aggressive read workaround + * (STAC9704 comes in mind). + */ + if (i != j || j != k) { + codec->flags |= AC97_F_RDCD_BUG; + i = ac97_rdcd(codec, AC97_REG_RESET); + } codec->caps = i & 0x03ff; codec->se = (i & 0x7c00) >> 10; @@ -563,6 +636,9 @@ } codec->id = id; + codec->subvendor = (u_int32_t)pci_get_subdevice(codec->dev) << 16; + codec->subvendor |= (u_int32_t)pci_get_subvendor(codec->dev) & + 0x0000ffff; codec->noext = 0; codec_patch = NULL; @@ -600,7 +676,7 @@ } } - for (i = 0; i < 32; i++) { + for (i = 0; i < AC97_MIXER_SIZE; i++) { codec->mix[i] = ac97mixtable_default[i]; } ac97_fix_auxout(codec); @@ -608,24 +684,76 @@ if (codec_patch) codec_patch(codec); - for (i = 0; i < 32; i++) { + for (i = 0; i < AC97_MIXER_SIZE; i++) { k = codec->noext? codec->mix[i].enable : 1; - if (k && (codec->mix[i].reg > 0)) { - old = ac97_rdcd(codec, codec->mix[i].reg); - ac97_wrcd(codec, codec->mix[i].reg, 0x3f); - j = ac97_rdcd(codec, codec->mix[i].reg); - ac97_wrcd(codec, codec->mix[i].reg, old); - codec->mix[i].enable = (j != 0 && j != old)? 1 : 0; - for (k = 1; j & (1 << k); k++); - codec->mix[i].bits = j? k - codec->mix[i].ofs : 0; + reg = codec->mix[i].reg; + if (reg < 0) + reg = -reg; + if (k && reg) { + j = old = ac97_rdcd(codec, reg); + /* + * Test for mute bit (except for AC97_MIX_TONE, + * where we simply assume it as available). + */ + if (codec->mix[i].mute) { + ac97_wrcd(codec, reg, j | 0x8000); + j = ac97_rdcd(codec, reg); + } else + j |= 0x8000; + if ((j & 0x8000)) { + /* + * Test whether the control width should be + * 4, 5 or 6 bit. For 5bit register, we should + * test it whether it's really 5 or 6bit. Leave + * 4bit register alone, because sometimes an + * attempt to write past 4th bit may cause + * incorrect result especially for AC97_MIX_BEEP + * (ac97 2.3). + */ + bit = codec->mix[i].bits; + if (bit == 5) + bit++; + j = ((1 << bit) - 1) << codec->mix[i].ofs; + ac97_wrcd(codec, reg, + j | (codec->mix[i].mute ? 0x8000 : 0)); + k = ac97_rdcd(codec, reg) & j; + k >>= codec->mix[i].ofs; + if (reg == AC97_MIX_TONE && + ((k & 0x0001) == 0x0000)) + k >>= 1; + for (j = 0; k >> j; j++) + ; + if (j != 0) { +#if 0 + device_printf(codec->dev, "%2d: [ac97_rdcd() = %d] [Testbit = %d] %d -> %d\n", + i, k, bit, codec->mix[i].bits, j); +#endif + codec->mix[i].enable = 1; + codec->mix[i].bits = j; + } else if (reg == AC97_MIX_BEEP) { + /* + * Few codec such as CX20468-21 does + * have this control register, although + * the only usable part is the mute bit. + */ + codec->mix[i].enable = 1; + } else + codec->mix[i].enable = 0; + } else + codec->mix[i].enable = 0; + ac97_wrcd(codec, reg, old); } - /* printf("mixch %d, en=%d, b=%d\n", i, codec->mix[i].enable, codec->mix[i].bits); */ +#if 0 + printf("mixch %d, en=%d, b=%d\n", i, codec->mix[i].enable, codec->mix[i].bits); +#endif } device_printf(codec->dev, "<%s>\n", ac97_hw_desc(codec->id, vname, cname, desc)); if (bootverbose) { + if (codec->flags & AC97_F_RDCD_BUG) + device_printf(codec->dev, "Buggy AC97 Codec: aggressive ac97_rdcd() workaround enabled\n"); device_printf(codec->dev, "Codec features "); for (i = j = 0; i < 10; i++) if (codec->caps & (1 << i)) @@ -645,8 +773,16 @@ } } - if ((ac97_rdcd(codec, AC97_REG_POWER) & 2) == 0) - device_printf(codec->dev, "ac97 codec reports dac not ready\n"); + i = 0; + while ((ac97_rdcd(codec, AC97_REG_POWER) & 2) == 0) { + if (++i == 100) { + device_printf(codec->dev, "ac97 codec reports dac not ready\n"); + break; + } + DELAY(1000); + } + if (bootverbose) + device_printf(codec->dev, "ac97 codec dac ready count: %d\n", i); snd_mtxunlock(codec->lock); return 0; } @@ -686,24 +822,21 @@ ac97_create(device_t dev, void *devinfo, kobj_class_t cls) { struct ac97_info *codec; + int eapdinv; - codec = (struct ac97_info *)malloc(sizeof *codec, M_AC97, M_NOWAIT); - if (codec == NULL) - return NULL; - - snprintf(codec->name, AC97_NAMELEN, "%s:ac97", device_get_nameunit(dev)); + codec = malloc(sizeof(*codec), M_AC97, M_WAITOK | M_ZERO); + snprintf(codec->name, sizeof(codec->name), "%s:ac97", + device_get_nameunit(dev)); codec->lock = snd_mtxcreate(codec->name, "ac97 codec"); - codec->methods = kobj_create(cls, M_AC97, M_WAITOK); - if (codec->methods == NULL) { - snd_mtxlock(codec->lock); - snd_mtxfree(codec->lock); - free(codec, M_AC97); - return NULL; - } - + codec->methods = kobj_create(cls, M_AC97, M_WAITOK | M_ZERO); codec->dev = dev; codec->devinfo = devinfo; codec->flags = 0; + if (resource_int_value(device_get_name(dev), device_get_unit(dev), + "eapdinv", &eapdinv) == 0) { + if (eapdinv != 0) + codec->flags |= AC97_F_EAPD_INV; + } return codec; } @@ -731,6 +864,60 @@ /* -------------------------------------------------------------------- */ +#ifdef SND_DYNSYSCTL +static int +sysctl_hw_snd_ac97_eapd(SYSCTL_HANDLER_ARGS) +{ + struct ac97_info *codec; + int ea, inv, err = 0; + u_int16_t val; + + codec = oidp->oid_arg1; + if (codec == NULL || codec->id == 0 || codec->lock == NULL) + return EINVAL; + snd_mtxlock(codec->lock); + val = ac97_rdcd(codec, AC97_REG_POWER); + inv = (codec->flags & AC97_F_EAPD_INV) ? 0 : 1; + ea = (val >> 15) ^ inv; + snd_mtxunlock(codec->lock); + err = sysctl_handle_int(oidp, &ea, 0, req); + if (err == 0 && req->newptr != NULL) { + if (ea != 0 && ea != 1) + return EINVAL; + if (ea != ((val >> 15) ^ inv)) { + snd_mtxlock(codec->lock); + ac97_wrcd(codec, AC97_REG_POWER, val ^ 0x8000); + snd_mtxunlock(codec->lock); + } + } + return err; +} +#endif + +static void +ac97_init_sysctl(struct ac97_info *codec) +{ +#ifdef SND_DYNSYSCTL + u_int16_t orig, val; + + if (codec == NULL || codec->dev == NULL) + return; + snd_mtxlock(codec->lock); + orig = ac97_rdcd(codec, AC97_REG_POWER); + ac97_wrcd(codec, AC97_REG_POWER, orig ^ 0x8000); + val = ac97_rdcd(codec, AC97_REG_POWER); + ac97_wrcd(codec, AC97_REG_POWER, orig); + snd_mtxunlock(codec->lock); + if ((val & 0x8000) == (orig & 0x8000)) + return; + SYSCTL_ADD_PROC(device_get_sysctl_ctx(codec->dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(codec->dev)), + OID_AUTO, "eapd", CTLTYPE_INT | CTLFLAG_RW, + codec, sizeof(codec), sysctl_hw_snd_ac97_eapd, + "I", "AC97 External Amplifier"); +#endif +} + static int ac97mix_init(struct snd_mixer *m) { @@ -743,15 +930,67 @@ if (ac97_initmixer(codec)) return -1; + switch (codec->id) { + case 0x41445374: /* AD1981B */ + if (codec->subvendor == 0x02d91014) { + /* + * IBM Thinkcentre: + * Tie "ogain" and "phone" to "vol" since its + * master volume is basically useless and can't + * control anything. + */ + mask = 0; + if (codec->mix[SOUND_MIXER_OGAIN].enable) + mask |= SOUND_MASK_OGAIN; + if (codec->mix[SOUND_MIXER_PHONEOUT].enable) + mask |= SOUND_MASK_PHONEOUT; + if (codec->mix[SOUND_MIXER_VOLUME].enable) + mix_setparentchild(m, SOUND_MIXER_VOLUME, + mask); + else { + mix_setparentchild(m, SOUND_MIXER_VOLUME, + mask); + mix_setrealdev(m, SOUND_MIXER_VOLUME, + SOUND_MIXER_NONE); + } + } + break; + case 0x434d4941: /* CMI9738 */ + case 0x434d4961: /* CMI9739 */ + case 0x434d4978: /* CMI9761 */ + case 0x434d4982: /* CMI9761 */ + case 0x434d4983: /* CMI9761 */ + ac97_wrcd(codec, AC97_MIX_PCM, 0); + bzero(&codec->mix[SOUND_MIXER_PCM], + sizeof(codec->mix[SOUND_MIXER_PCM])); + pcm_setflags(codec->dev, pcm_getflags(codec->dev) | + SD_F_SOFTPCMVOL); + /* XXX How about master volume ? */ + break; + default: + break; + } + +#if 0 + /* XXX For the sake of debugging purposes */ + mix_setparentchild(m, SOUND_MIXER_VOLUME, + SOUND_MASK_PCM | SOUND_MASK_CD); + mix_setrealdev(m, SOUND_MIXER_VOLUME, SOUND_MIXER_NONE); + ac97_wrcd(codec, AC97_MIX_MASTER, 0); +#endif + mask = 0; - for (i = 0; i < 32; i++) + for (i = 0; i < AC97_MIXER_SIZE; i++) mask |= codec->mix[i].enable? 1 << i : 0; mix_setdevs(m, mask); mask = 0; - for (i = 0; i < 32; i++) + for (i = 0; i < AC97_MIXER_SIZE; i++) mask |= codec->mix[i].recidx? 1 << i : 0; mix_setrecdevs(m, mask); + + ac97_init_sysctl(codec); + return 0; } @@ -785,7 +1024,7 @@ { struct ac97_info *codec = mix_getdevinfo(m); - if (codec == NULL) + if (codec == NULL || dev >= AC97_MIXER_SIZE) return -1; return ac97_setmixer(codec, dev, left, right); } @@ -798,7 +1037,7 @@ if (codec == NULL) return -1; - for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) + for (i = 0; i < AC97_MIXER_SIZE; i++) if ((src & (1 << i)) != 0) break; return (ac97_setrecsrc(codec, i) == 0)? 1 << i : -1; @@ -821,5 +1060,3 @@ { return &ac97mixer_class; } - - --- sys/dev/sound/pcm/ac97.h.orig Sun Jan 30 09:00:04 2005 +++ sys/dev/sound/pcm/ac97.h Thu Jul 12 12:04:19 2007 @@ -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/pcm/ac97.h,v 1.15.4.1 2005/01/30 01:00:04 imp Exp $ + * $FreeBSD: src/sys/dev/sound/pcm/ac97.h,v 1.20 2007/04/19 13:54:22 ariff Exp $ */ #define AC97_MUTE 0x8080 @@ -81,6 +81,7 @@ #define AC97_REG_ID2 0x7e #define AC97_F_EAPD_INV 0x00000001 +#define AC97_F_RDCD_BUG 0x00000002 #define AC97_DECLARE(name) static DEFINE_CLASS(name, name ## _methods, sizeof(struct kobj)) #define AC97_CREATE(dev, devinfo, cls) ac97_create(dev, devinfo, &cls ## _class) @@ -100,7 +101,7 @@ u_int16_t ac97_getextmode(struct ac97_info *codec); u_int16_t ac97_getextcaps(struct ac97_info *codec); u_int16_t ac97_getcaps(struct ac97_info *codec); +u_int32_t ac97_getsubvendor(struct ac97_info *codec); u_int16_t ac97_rdcd(struct ac97_info *codec, int reg); void ac97_wrcd(struct ac97_info *codec, int reg, u_int16_t val); - --- sys/dev/sound/pcm/ac97_if.m.orig Sun Jan 30 09:00:04 2005 +++ sys/dev/sound/pcm/ac97_if.m Thu Jan 6 09:43:20 2005 @@ -25,7 +25,7 @@ # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF # SUCH DAMAGE. # -# $FreeBSD: src/sys/dev/sound/pcm/ac97_if.m,v 1.3.8.1 2005/01/30 01:00:04 imp Exp $ +# $FreeBSD: src/sys/dev/sound/pcm/ac97_if.m,v 1.4 2005/01/06 01:43:20 imp Exp $ # #include --- sys/dev/sound/pcm/ac97_patch.c.orig Sun Jan 30 09:00:04 2005 +++ sys/dev/sound/pcm/ac97_patch.c Thu Jul 12 12:04:19 2007 @@ -1,5 +1,6 @@ /*- - * Copyright 2002 FreeBSD, Inc. All rights reserved. + * Copyright (c) 2002 Orion Hodson + * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -27,7 +28,7 @@ #include #include -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pcm/ac97_patch.c,v 1.2.4.1 2005/01/30 01:00:04 imp Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pcm/ac97_patch.c,v 1.10 2007/07/01 17:28:58 ariff Exp $"); void ad1886_patch(struct ac97_info* codec) { @@ -43,6 +44,69 @@ void ad198x_patch(struct ac97_info* codec) { - ac97_wrcd(codec, 0x76, ac97_rdcd(codec, 0x76) | 0x0420); + switch (ac97_getsubvendor(codec)) { + case 0x11931043: /* Not for ASUS A9T (probably else too). */ + break; + default: + ac97_wrcd(codec, 0x76, ac97_rdcd(codec, 0x76) | 0x0420); + break; + } } +void ad1981b_patch(struct ac97_info* codec) +{ + /* + * Enable headphone jack sensing. + */ + switch (ac97_getsubvendor(codec)) { + case 0x02d91014: /* IBM Thinkcentre */ + ac97_wrcd(codec, AC97_AD_JACK_SPDIF, + ac97_rdcd(codec, AC97_AD_JACK_SPDIF) | 0x0800); + break; + default: + break; + } +} + +void cmi9739_patch(struct ac97_info* codec) +{ + /* + * Few laptops need extra register initialization + * to power up the internal speakers. + */ + switch (ac97_getsubvendor(codec)) { + case 0x18431043: /* ASUS W1000N */ + ac97_wrcd(codec, AC97_REG_POWER, 0x000f); + ac97_wrcd(codec, AC97_MIXEXT_CLFE, 0x0000); + ac97_wrcd(codec, 0x64, 0x7110); + break; + default: + break; + } +} + +void alc655_patch(struct ac97_info* codec) +{ + /* + * MSI (Micro-Star International) specific EAPD quirk. + */ + switch (ac97_getsubvendor(codec)) { + case 0x00611462: /* MSI S250 */ + case 0x01311462: /* MSI S270 */ + case 0x01611462: /* LG K1 Express */ + case 0x03511462: /* MSI L725 */ + ac97_wrcd(codec, 0x7a, ac97_rdcd(codec, 0x7a) & 0xfffd); + break; + case 0x10ca1734: + /* + * Amilo Pro V2055 with ALC655 has phone out by default + * disabled (surround on), leaving us only with internal + * speakers. This should really go to mixer. We write the + * Data Flow Control reg. + */ + ac97_wrcd(codec, 0x6a, ac97_rdcd(codec, 0x6a) | 0x0001); + break; + default: + break; + } +} --- sys/dev/sound/pcm/ac97_patch.h.orig Sun Jan 30 09:00:04 2005 +++ sys/dev/sound/pcm/ac97_patch.h Thu Jul 12 12:04:19 2007 @@ -1,5 +1,6 @@ /*- - * Copyright 2003 FreeBSD, Inc. All rights reserved. + * Copyright (c) 2003 Orion Hodson + * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -22,10 +23,13 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $FreeBSD: src/sys/dev/sound/pcm/ac97_patch.h,v 1.2.4.1 2005/01/30 01:00:04 imp Exp $ + * $FreeBSD: src/sys/dev/sound/pcm/ac97_patch.h,v 1.7 2007/04/19 13:54:22 ariff Exp $ */ typedef void (*ac97_patch)(struct ac97_info*); void ad1886_patch(struct ac97_info*); void ad198x_patch(struct ac97_info*); +void ad1981b_patch(struct ac97_info*); +void cmi9739_patch(struct ac97_info*); +void alc655_patch(struct ac97_info*); --- sys/dev/sound/pcm/buffer.c.orig Sun Jan 30 09:00:04 2005 +++ sys/dev/sound/pcm/buffer.c Thu Jul 12 12:04:19 2007 @@ -28,7 +28,7 @@ #include "feeder_if.h" -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pcm/buffer.c,v 1.23.2.2 2005/01/30 01:00:04 imp Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pcm/buffer.c,v 1.37 2007/06/16 03:37:27 ariff Exp $"); struct snd_dbuf * sndbuf_create(device_t dev, char *drv, char *desc, struct pcm_channel *channel) @@ -46,6 +46,7 @@ void sndbuf_destroy(struct snd_dbuf *b) { + sndbuf_free(b); free(b, M_DEVBUF); } @@ -77,33 +78,41 @@ */ int -sndbuf_alloc(struct snd_dbuf *b, bus_dma_tag_t dmatag, unsigned int size) +sndbuf_alloc(struct snd_dbuf *b, bus_dma_tag_t dmatag, int dmaflags, + unsigned int size) { int ret; b->dmatag = dmatag; + b->dmaflags = dmaflags | BUS_DMA_NOWAIT; b->maxsize = size; b->bufsize = b->maxsize; b->buf_addr = 0; - if (bus_dmamem_alloc(b->dmatag, (void **)&b->buf, BUS_DMA_NOWAIT, - &b->dmamap)) + b->flags |= SNDBUF_F_MANAGED; + if (bus_dmamem_alloc(b->dmatag, (void **)&b->buf, b->dmaflags, + &b->dmamap)) { + sndbuf_free(b); return (ENOMEM); + } if (bus_dmamap_load(b->dmatag, b->dmamap, b->buf, b->maxsize, sndbuf_setmap, b, 0) != 0 || b->buf_addr == 0) { - bus_dmamem_free(b->dmatag, b->buf, b->dmamap); - b->dmamap = NULL; + sndbuf_free(b); return (ENOMEM); } ret = sndbuf_resize(b, 2, b->maxsize / 2); if (ret != 0) sndbuf_free(b); + return (ret); } int sndbuf_setup(struct snd_dbuf *b, void *buf, unsigned int size) { + b->flags &= ~SNDBUF_F_MANAGED; + if (buf) + b->flags |= SNDBUF_F_MANAGED; b->buf = buf; b->maxsize = size; b->bufsize = b->maxsize; @@ -115,21 +124,35 @@ { if (b->tmpbuf) free(b->tmpbuf, M_DEVBUF); - b->tmpbuf = NULL; - if (b->dmamap) - bus_dmamap_unload(b->dmatag, b->dmamap); + if (b->shadbuf) + free(b->shadbuf, M_DEVBUF); - if (b->dmamap && b->buf) - bus_dmamem_free(b->dmatag, b->buf, b->dmamap); - b->dmamap = NULL; + if (b->buf) { + if (b->flags & SNDBUF_F_MANAGED) { + if (b->dmamap) + bus_dmamap_unload(b->dmatag, b->dmamap); + if (b->dmatag) + bus_dmamem_free(b->dmatag, b->buf, b->dmamap); + } else + free(b->buf, M_DEVBUF); + } + + b->tmpbuf = NULL; + b->shadbuf = NULL; b->buf = NULL; + b->sl = 0; + b->dmatag = NULL; + b->dmamap = NULL; } +#define SNDBUF_CACHE_SHIFT 5 + int sndbuf_resize(struct snd_dbuf *b, unsigned int blkcnt, unsigned int blksz) { - u_int8_t *tmpbuf, *f2; + unsigned int bufsize, allocsize; + u_int8_t *tmpbuf; chn_lock(b->channel); if (b->maxsize == 0) @@ -138,27 +161,38 @@ blkcnt = b->blkcnt; if (blksz == 0) blksz = b->blksz; - if (blkcnt < 2 || blksz < 16 || (blkcnt * blksz > b->maxsize)) { + if (blkcnt < 2 || blksz < 16 || (blkcnt * blksz) > b->maxsize) { chn_unlock(b->channel); return EINVAL; } if (blkcnt == b->blkcnt && blksz == b->blksz) goto out; - chn_unlock(b->channel); - tmpbuf = malloc(blkcnt * blksz, M_DEVBUF, M_NOWAIT); - if (tmpbuf == NULL) - return ENOMEM; - chn_lock(b->channel); + bufsize = blkcnt * blksz; + + if (bufsize > b->allocsize || + bufsize < (b->allocsize >> SNDBUF_CACHE_SHIFT)) { + allocsize = round_page(bufsize); + chn_unlock(b->channel); + tmpbuf = malloc(allocsize, M_DEVBUF, M_WAITOK); + chn_lock(b->channel); + if (snd_verbose > 3) + printf("%s(): b=%p %p -> %p [%d -> %d : %d]\n", + __func__, b, b->tmpbuf, tmpbuf, + b->allocsize, allocsize, bufsize); + if (b->tmpbuf != NULL) + free(b->tmpbuf, M_DEVBUF); + b->tmpbuf = tmpbuf; + b->allocsize = allocsize; + } else if (snd_verbose > 3) + printf("%s(): b=%p %d [%d] NOCHANGE\n", + __func__, b, b->allocsize, b->bufsize); + b->blkcnt = blkcnt; b->blksz = blksz; - b->bufsize = blkcnt * blksz; - f2 = b->tmpbuf; - b->tmpbuf = tmpbuf; + b->bufsize = bufsize; + sndbuf_reset(b); - chn_unlock(b->channel); - free(f2, M_DEVBUF); - return 0; out: chn_unlock(b->channel); return 0; @@ -167,53 +201,59 @@ int sndbuf_remalloc(struct snd_dbuf *b, unsigned int blkcnt, unsigned int blksz) { - u_int8_t *buf, *tmpbuf, *f1, *f2; - unsigned int bufsize; - int ret; + unsigned int bufsize, allocsize; + u_int8_t *buf, *tmpbuf, *shadbuf; if (blkcnt < 2 || blksz < 16) return EINVAL; bufsize = blksz * blkcnt; - chn_unlock(b->channel); - buf = malloc(bufsize, M_DEVBUF, M_WAITOK); - if (buf == NULL) { - ret = ENOMEM; - goto out; - } - - tmpbuf = malloc(bufsize, M_DEVBUF, M_WAITOK); - if (tmpbuf == NULL) { - free(buf, M_DEVBUF); - ret = ENOMEM; - goto out; - } - chn_lock(b->channel); + if (bufsize > b->allocsize || + bufsize < (b->allocsize >> SNDBUF_CACHE_SHIFT)) { + allocsize = round_page(bufsize); + chn_unlock(b->channel); + buf = malloc(allocsize, M_DEVBUF, M_WAITOK); + tmpbuf = malloc(allocsize, M_DEVBUF, M_WAITOK); + shadbuf = malloc(allocsize, M_DEVBUF, M_WAITOK); + chn_lock(b->channel); + if (b->buf != NULL) + free(b->buf, M_DEVBUF); + b->buf = buf; + if (b->tmpbuf != NULL) + free(b->tmpbuf, M_DEVBUF); + b->tmpbuf = tmpbuf; + if (b->shadbuf != NULL) + free(b->shadbuf, M_DEVBUF); + b->shadbuf = shadbuf; + if (snd_verbose > 3) + printf("%s(): b=%p %d -> %d [%d]\n", + __func__, b, b->allocsize, allocsize, bufsize); + b->allocsize = allocsize; + } else if (snd_verbose > 3) + printf("%s(): b=%p %d [%d] NOCHANGE\n", + __func__, b, b->allocsize, b->bufsize); b->blkcnt = blkcnt; b->blksz = blksz; b->bufsize = bufsize; b->maxsize = bufsize; - f1 = b->buf; - f2 = b->tmpbuf; - b->buf = buf; - b->tmpbuf = tmpbuf; + b->sl = bufsize; sndbuf_reset(b); - chn_unlock(b->channel); - if (f1) - free(f1, M_DEVBUF); - if (f2) - free(f2, M_DEVBUF); - - ret = 0; -out: - chn_lock(b->channel); - return ret; + return 0; } +/** + * @brief Zero out space in buffer free area + * + * This function clears a chunk of @c length bytes in the buffer free area + * (i.e., where the next write will be placed). + * + * @param b buffer context + * @param length number of bytes to blank + */ void sndbuf_clear(struct snd_dbuf *b, unsigned int length) { @@ -225,10 +265,7 @@ if (length > b->bufsize) length = b->bufsize; - if (b->fmt & AFMT_SIGNED) - data = 0x00; - else - data = 0x80; + data = sndbuf_zerodata(b->fmt); i = sndbuf_getfreeptr(b); p = sndbuf_getbuf(b); @@ -241,25 +278,37 @@ } } +/** + * @brief Zap buffer contents, resetting "ready area" fields + * + * @param b buffer context + */ void sndbuf_fillsilence(struct snd_dbuf *b) { - int i; - u_char data, *p; - - if (b->fmt & AFMT_SIGNED) - data = 0x00; - else - data = 0x80; - - i = 0; - p = sndbuf_getbuf(b); - while (i < b->bufsize) - p[i++] = data; + if (b->bufsize > 0) + memset(sndbuf_getbuf(b), sndbuf_zerodata(b->fmt), b->bufsize); b->rp = 0; b->rl = b->bufsize; } +/** + * @brief Reset buffer w/o flushing statistics + * + * This function just zeroes out buffer contents and sets the "ready length" + * to zero. This was originally to facilitate minimal playback interruption + * (i.e., dropped samples) in SNDCTL_DSP_SILENCE/SKIP ioctls. + * + * @param b buffer context + */ +void +sndbuf_softreset(struct snd_dbuf *b) +{ + b->rl = 0; + if (b->buf && b->bufsize > 0) + sndbuf_clear(b, b->bufsize); +} + void sndbuf_reset(struct snd_dbuf *b) { @@ -272,6 +321,7 @@ b->xrun = 0; if (b->buf && b->bufsize > 0) sndbuf_clear(b, b->bufsize); + sndbuf_clearshadow(b); } u_int32_t @@ -286,8 +336,12 @@ b->fmt = fmt; b->bps = 1; b->bps <<= (b->fmt & AFMT_STEREO)? 1 : 0; - b->bps <<= (b->fmt & AFMT_16BIT)? 1 : 0; - b->bps <<= (b->fmt & AFMT_32BIT)? 2 : 0; + if (b->fmt & AFMT_16BIT) + b->bps <<= 1; + else if (b->fmt & AFMT_24BIT) + b->bps *= 3; + else if (b->fmt & AFMT_32BIT) + b->bps <<= 2; return 0; } @@ -368,6 +422,12 @@ } unsigned int +sndbuf_getallocsize(struct snd_dbuf *b) +{ + return b->allocsize; +} + +unsigned int sndbuf_runsz(struct snd_dbuf *b) { return b->dl; @@ -395,11 +455,11 @@ } void -sndbuf_setxrun(struct snd_dbuf *b, unsigned int cnt) +sndbuf_setxrun(struct snd_dbuf *b, unsigned int xrun) { SNDBUF_LOCKASSERT(b); - b->xrun = cnt; + b->xrun = xrun; } unsigned int @@ -487,8 +547,67 @@ b->prev_total = b->total; } +unsigned int +snd_xbytes(unsigned int v, unsigned int from, unsigned int to) +{ + unsigned int w, x, y; + + if (from == to) + return v; + + if (from == 0 || to == 0 || v == 0) + return 0; + + x = from; + y = to; + while (y != 0) { + w = x % y; + x = y; + y = w; + } + from /= x; + to /= x; + + return (unsigned int)(((u_int64_t)v * to) / from); +} + +unsigned int +sndbuf_xbytes(unsigned int v, struct snd_dbuf *from, struct snd_dbuf *to) +{ + if (from == NULL || to == NULL || v == 0) + return 0; + + return snd_xbytes(v, sndbuf_getbps(from) * sndbuf_getspd(from), + sndbuf_getbps(to) * sndbuf_getspd(to)); +} + +u_int8_t +sndbuf_zerodata(u_int32_t fmt) +{ + if (fmt & AFMT_SIGNED) + return (0x00); + else if (fmt & AFMT_MU_LAW) + return (0x7f); + else if (fmt & AFMT_A_LAW) + return (0x55); + return (0x80); +} + /************************************************************/ +/** + * @brief Acquire buffer space to extend ready area + * + * This function extends the ready area length by @c count bytes, and may + * optionally copy samples from another location stored in @c from. The + * counter @c snd_dbuf::total is also incremented by @c count bytes. + * + * @param b audio buffer + * @param from sample source (optional) + * @param count number of bytes to acquire + * + * @retval 0 Unconditional + */ int sndbuf_acquire(struct snd_dbuf *b, u_int8_t *from, unsigned int count) { @@ -499,7 +618,7 @@ b->total += count; if (from != NULL) { while (count > 0) { - l = MIN(count, sndbuf_getsize(b) - sndbuf_getfreeptr(b)); + l = min(count, sndbuf_getsize(b) - sndbuf_getfreeptr(b)); bcopy(from, sndbuf_getbufofs(b, sndbuf_getfreeptr(b)), l); from += l; b->rl += l; @@ -512,6 +631,20 @@ return 0; } +/** + * @brief Dispose samples from channel buffer, increasing size of ready area + * + * This function discards samples from the supplied buffer by advancing the + * ready area start pointer and decrementing the ready area length. If + * @c to is not NULL, then the discard samples will be copied to the location + * it points to. + * + * @param b PCM channel sound buffer + * @param to destination buffer (optional) + * @param count number of bytes to discard + * + * @returns 0 unconditionally + */ int sndbuf_dispose(struct snd_dbuf *b, u_int8_t *to, unsigned int count) { @@ -521,7 +654,7 @@ KASSERT((b->rl >= 0) && (b->rl <= b->bufsize), ("%s: b->rl invalid %d", __func__, b->rl)); if (to != NULL) { while (count > 0) { - l = MIN(count, sndbuf_getsize(b) - sndbuf_getreadyptr(b)); + l = min(count, sndbuf_getsize(b) - sndbuf_getreadyptr(b)); bcopy(sndbuf_getbufofs(b, sndbuf_getreadyptr(b)), to, l); to += l; b->rl -= l; @@ -541,15 +674,20 @@ int sndbuf_feed(struct snd_dbuf *from, struct snd_dbuf *to, struct pcm_channel *channel, struct pcm_feeder *feeder, unsigned int count) { + unsigned int cnt; + KASSERT(count > 0, ("can't feed 0 bytes")); if (sndbuf_getfree(to) < count) return EINVAL; - count = FEEDER_FEED(feeder, channel, to->tmpbuf, count, from); - if (count) - sndbuf_acquire(to, to->tmpbuf, count); - /* the root feeder has called sndbuf_dispose(from, , bytes fetched) */ + do { + cnt = FEEDER_FEED(feeder, channel, to->tmpbuf, count, from); + if (cnt) { + sndbuf_acquire(to, to->tmpbuf, cnt); + count -= cnt; + } + } while (count && cnt); return 0; } @@ -588,3 +726,45 @@ b->flags |= flags; } +/** + * @brief Clear the shadow buffer by filling with samples equal to zero. + * + * @param b buffer to clear + */ +void +sndbuf_clearshadow(struct snd_dbuf *b) +{ + KASSERT(b != NULL, ("b is a null pointer")); + KASSERT(b->sl >= 0, ("illegal shadow length")); + + if ((b->shadbuf != NULL) && (b->sl > 0)) + memset(b->shadbuf, sndbuf_zerodata(b->fmt), b->sl); +} + +#ifdef OSSV4_EXPERIMENT +/** + * @brief Return peak value from samples in buffer ready area. + * + * Peak ranges from 0-32767. If channel is monaural, most significant 16 + * bits will be zero. For now, only expects to work with 1-2 channel + * buffers. + * + * @note Currently only operates with linear PCM formats. + * + * @param b buffer to analyze + * @param lpeak pointer to store left peak value + * @param rpeak pointer to store right peak value + */ +void +sndbuf_getpeaks(struct snd_dbuf *b, int *lp, int *rp) +{ + u_int32_t lpeak, rpeak; + + lpeak = 0; + rpeak = 0; + + /** + * @todo fill this in later + */ +} +#endif --- sys/dev/sound/pcm/buffer.h.orig Sun Jan 30 09:00:04 2005 +++ sys/dev/sound/pcm/buffer.h Thu Jul 12 12:04:19 2007 @@ -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/pcm/buffer.h,v 1.9.2.1 2005/01/30 01:00:04 imp Exp $ + * $FreeBSD: src/sys/dev/sound/pcm/buffer.h,v 1.17 2007/06/14 11:15:51 ariff Exp $ */ #define SND_DMA(b) (sndbuf_getflags((b)) & SNDBUF_F_DMA) @@ -32,13 +32,16 @@ #define SNDBUF_F_DMA 0x00000001 #define SNDBUF_F_XRUN 0x00000002 #define SNDBUF_F_RUNNING 0x00000004 +#define SNDBUF_F_MANAGED 0x00000008 #define SNDBUF_NAMELEN 48 struct snd_dbuf { device_t dev; u_int8_t *buf, *tmpbuf; - unsigned int bufsize, maxsize; + u_int8_t *shadbuf; /**< shadow buffer used w/ S_D_SILENCE/SKIP */ + volatile int sl; /**< shadbuf ready length in # of bytes */ + unsigned int bufsize, maxsize, allocsize; volatile int dl; /* transfer size */ volatile int rp; /* pointers to the ready area */ volatile int rl; /* length of ready area */ @@ -51,7 +54,8 @@ u_int32_t flags; bus_dmamap_t dmamap; bus_dma_tag_t dmatag; - u_int32_t buf_addr; + bus_addr_t buf_addr; + int dmaflags; struct selinfo sel; struct pcm_channel *channel; char name[SNDBUF_NAMELEN]; @@ -62,7 +66,7 @@ void sndbuf_dump(struct snd_dbuf *b, char *s, u_int32_t what); -int sndbuf_alloc(struct snd_dbuf *b, bus_dma_tag_t dmatag, unsigned int size); +int sndbuf_alloc(struct snd_dbuf *b, bus_dma_tag_t dmatag, int dmaflags, unsigned int size); int sndbuf_setup(struct snd_dbuf *b, void *buf, unsigned int size); void sndbuf_free(struct snd_dbuf *b); int sndbuf_resize(struct snd_dbuf *b, unsigned int blkcnt, unsigned int blksz); @@ -70,6 +74,8 @@ void sndbuf_reset(struct snd_dbuf *b); void sndbuf_clear(struct snd_dbuf *b, unsigned int length); void sndbuf_fillsilence(struct snd_dbuf *b); +void sndbuf_softreset(struct snd_dbuf *b); +void sndbuf_clearshadow(struct snd_dbuf *b); u_int32_t sndbuf_getfmt(struct snd_dbuf *b); int sndbuf_setfmt(struct snd_dbuf *b, u_int32_t fmt); @@ -83,6 +89,7 @@ void *sndbuf_getbufofs(struct snd_dbuf *b, unsigned int ofs); unsigned int sndbuf_getsize(struct snd_dbuf *b); unsigned int sndbuf_getmaxsize(struct snd_dbuf *b); +unsigned int sndbuf_getallocsize(struct snd_dbuf *b); unsigned int sndbuf_getalign(struct snd_dbuf *b); unsigned int sndbuf_getblkcnt(struct snd_dbuf *b); void sndbuf_setblkcnt(struct snd_dbuf *b, unsigned int blkcnt); @@ -93,7 +100,7 @@ struct selinfo *sndbuf_getsel(struct snd_dbuf *b); unsigned int sndbuf_getxrun(struct snd_dbuf *b); -void sndbuf_setxrun(struct snd_dbuf *b, unsigned int cnt); +void sndbuf_setxrun(struct snd_dbuf *b, unsigned int xrun); unsigned int sndbuf_gethwptr(struct snd_dbuf *b); void sndbuf_sethwptr(struct snd_dbuf *b, unsigned int ptr); unsigned int sndbuf_getfree(struct snd_dbuf *b); @@ -103,6 +110,9 @@ unsigned int sndbuf_getblocks(struct snd_dbuf *b); unsigned int sndbuf_getprevblocks(struct snd_dbuf *b); unsigned int sndbuf_gettotal(struct snd_dbuf *b); +unsigned int snd_xbytes(unsigned int v, unsigned int from, unsigned int to); +unsigned int sndbuf_xbytes(unsigned int v, struct snd_dbuf *from, struct snd_dbuf *to); +u_int8_t sndbuf_zerodata(u_int32_t fmt); void sndbuf_updateprevtotal(struct snd_dbuf *b); int sndbuf_acquire(struct snd_dbuf *b, u_int8_t *from, unsigned int count); @@ -117,3 +127,7 @@ void sndbuf_dma(struct snd_dbuf *b, int go); int sndbuf_dmaptr(struct snd_dbuf *b); void sndbuf_dmabounce(struct snd_dbuf *b); + +#ifdef OSSV4_EXPERIMENT +void sndbuf_getpeaks(struct snd_dbuf *b, int *lp, int *rp); +#endif --- sys/dev/sound/pcm/channel.c.orig Sun Jan 30 09:00:04 2005 +++ sys/dev/sound/pcm/channel.c Thu Jul 12 12:04:19 2007 @@ -25,72 +25,243 @@ * SUCH DAMAGE. */ +#include "opt_isa.h" + #include #include "feeder_if.h" -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pcm/channel.c,v 1.97.2.1 2005/01/30 01:00:04 imp Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pcm/channel.c,v 1.121 2007/06/16 03:37:28 ariff Exp $"); -#define MIN_CHUNK_SIZE 256 /* for uiomove etc. */ -#define DMA_ALIGN_THRESHOLD 4 -#define DMA_ALIGN_MASK (~(DMA_ALIGN_THRESHOLD - 1)) +int report_soft_formats = 1; +SYSCTL_INT(_hw_snd, OID_AUTO, report_soft_formats, CTLFLAG_RW, + &report_soft_formats, 1, "report software-emulated formats"); -#define CANCHANGE(c) (!(c->flags & CHN_F_TRIGGERED)) +int chn_latency = CHN_LATENCY_DEFAULT; +TUNABLE_INT("hw.snd.latency", &chn_latency); -/* -#define DEB(x) x -*/ +static int +sysctl_hw_snd_latency(SYSCTL_HANDLER_ARGS) +{ + int err, val; + + val = chn_latency; + err = sysctl_handle_int(oidp, &val, 0, req); + if (err != 0 || req->newptr == NULL) + return err; + if (val < CHN_LATENCY_MIN || val > CHN_LATENCY_MAX) + err = EINVAL; + else + chn_latency = val; + + return err; +} +SYSCTL_PROC(_hw_snd, OID_AUTO, latency, CTLTYPE_INT | CTLFLAG_RW, + 0, sizeof(int), sysctl_hw_snd_latency, "I", + "buffering latency (0=low ... 10=high)"); -static int chn_targetirqrate = 32; -TUNABLE_INT("hw.snd.targetirqrate", &chn_targetirqrate); +int chn_latency_profile = CHN_LATENCY_PROFILE_DEFAULT; +TUNABLE_INT("hw.snd.latency_profile", &chn_latency_profile); static int -sysctl_hw_snd_targetirqrate(SYSCTL_HANDLER_ARGS) +sysctl_hw_snd_latency_profile(SYSCTL_HANDLER_ARGS) { int err, val; - val = chn_targetirqrate; - err = sysctl_handle_int(oidp, &val, sizeof(val), req); - if (val < 16 || val > 512) + val = chn_latency_profile; + err = sysctl_handle_int(oidp, &val, 0, req); + if (err != 0 || req->newptr == NULL) + return err; + if (val < CHN_LATENCY_PROFILE_MIN || val > CHN_LATENCY_PROFILE_MAX) err = EINVAL; else - chn_targetirqrate = val; + chn_latency_profile = val; return err; } -SYSCTL_PROC(_hw_snd, OID_AUTO, targetirqrate, CTLTYPE_INT | CTLFLAG_RW, - 0, sizeof(int), sysctl_hw_snd_targetirqrate, "I", ""); -static int report_soft_formats = 1; -SYSCTL_INT(_hw_snd, OID_AUTO, report_soft_formats, CTLFLAG_RW, - &report_soft_formats, 1, "report software-emulated formats"); +SYSCTL_PROC(_hw_snd, OID_AUTO, latency_profile, CTLTYPE_INT | CTLFLAG_RW, + 0, sizeof(int), sysctl_hw_snd_latency_profile, "I", + "buffering latency profile (0=aggresive 1=safe)"); + +static int chn_timeout = CHN_TIMEOUT; +TUNABLE_INT("hw.snd.timeout", &chn_timeout); +#ifdef SND_DEBUG +static int +sysctl_hw_snd_timeout(SYSCTL_HANDLER_ARGS) +{ + int err, val; + + val = chn_timeout; + err = sysctl_handle_int(oidp, &val, 0, req); + if (err != 0 || req->newptr == NULL) + return err; + if (val < CHN_TIMEOUT_MIN || val > CHN_TIMEOUT_MAX) + err = EINVAL; + else + chn_timeout = val; + + return err; +} +SYSCTL_PROC(_hw_snd, OID_AUTO, timeout, CTLTYPE_INT | CTLFLAG_RW, + 0, sizeof(int), sysctl_hw_snd_timeout, "I", + "interrupt timeout (1 - 10) seconds"); +#endif + +static int chn_vpc_autoreset = 0; +TUNABLE_INT("hw.snd.vpc_autoreset", &chn_vpc_autoreset); +SYSCTL_INT(_hw_snd, OID_AUTO, vpc_autoreset, CTLFLAG_RW, + &chn_vpc_autoreset, 0, "automatically reset channels volume to 0db"); + +static int chn_vol_0db_pcm = SND_VOL_0DB_PCM; + +static void +chn_vpc_proc(int reset, int db) +{ + struct snddev_info *d; + struct pcm_channel *c; + int i; + + for (i = 0; pcm_devclass != NULL && + i < devclass_get_maxunit(pcm_devclass); i++) { + d = devclass_get_softc(pcm_devclass, i); + if (!PCM_REGISTERED(d)) + continue; + pcm_lock(d); + PCM_WAIT(d); + PCM_ACQUIRE(d); + CHN_FOREACH(c, d, channels.pcm) { + CHN_LOCK(c); + CHN_SETVOLUME(c, SND_VOL_C_PCM, SND_CHN_T_VOL_0DB, db); + if (reset != 0) + chn_vpc_reset(c, SND_VOL_C_PCM, 1); + CHN_UNLOCK(c); + } + PCM_RELEASE(d); + pcm_unlock(d); + } +} + +static int +sysctl_hw_snd_vpc_0db(SYSCTL_HANDLER_ARGS) +{ + int err, val; + + val = chn_vol_0db_pcm; + err = sysctl_handle_int(oidp, &val, 0, req); + if (err != 0 || req->newptr == NULL) + return (err); + if (val < SND_VOL_0DB_MIN || val > SND_VOL_0DB_MAX) + return (EINVAL); + + chn_vol_0db_pcm = val; + chn_vpc_proc(0, val); + + return (0); +} +SYSCTL_PROC(_hw_snd, OID_AUTO, vpc_0db, CTLTYPE_INT | CTLFLAG_RW, + 0, sizeof(int), sysctl_hw_snd_vpc_0db, "I", + "0db relative level"); + +static int +sysctl_hw_snd_vpc_reset(SYSCTL_HANDLER_ARGS) +{ + int err, val; + + val = 0; + err = sysctl_handle_int(oidp, &val, 0, req); + if (err != 0 || req->newptr == NULL || val == 0) + return (err); + + chn_vol_0db_pcm = SND_VOL_0DB_PCM; + chn_vpc_proc(1, SND_VOL_0DB_PCM); + + return (0); +} +SYSCTL_PROC(_hw_snd, OID_AUTO, vpc_reset, CTLTYPE_INT | CTLFLAG_RW, + 0, sizeof(int), sysctl_hw_snd_vpc_reset, "I", + "reset volume on all channels"); + +static int chn_usefrags = 0; +TUNABLE_INT("hw.snd.usefrags", &chn_usefrags); +static int chn_syncdelay = -1; +TUNABLE_INT("hw.snd.syncdelay", &chn_syncdelay); +#ifdef SND_DEBUG +SYSCTL_INT(_hw_snd, OID_AUTO, usefrags, CTLFLAG_RW, + &chn_usefrags, 1, "prefer setfragments() over setblocksize()"); +SYSCTL_INT(_hw_snd, OID_AUTO, syncdelay, CTLFLAG_RW, + &chn_syncdelay, 1, + "append (0-1000) millisecond trailing buffer delay on each sync"); +#endif + +/** + * @brief Channel sync group lock + * + * Clients should acquire this lock @b without holding any channel locks + * before touching syncgroups or the main syncgroup list. + */ +struct mtx snd_pcm_syncgroups_mtx; +MTX_SYSINIT(pcm_syncgroup, &snd_pcm_syncgroups_mtx, "PCM channel sync group lock", MTX_DEF); +/** + * @brief syncgroups' master list + * + * Each time a channel syncgroup is created, it's added to this list. This + * list should only be accessed with @sa snd_pcm_syncgroups_mtx held. + * + * See SNDCTL_DSP_SYNCGROUP for more information. + */ +struct pcm_synclist snd_pcm_syncgroups = SLIST_HEAD_INITIALIZER(head); static int chn_buildfeeder(struct pcm_channel *c); static void chn_lockinit(struct pcm_channel *c, int dir) { - switch(dir) { + switch (dir) { case PCMDIR_PLAY: c->lock = snd_mtxcreate(c->name, "pcm play channel"); + cv_init(&c->intr_cv, "pcmwr"); + break; + case PCMDIR_PLAY_VIRTUAL: + c->lock = snd_mtxcreate(c->name, "pcm virtual play channel"); + cv_init(&c->intr_cv, "pcmwrv"); break; case PCMDIR_REC: c->lock = snd_mtxcreate(c->name, "pcm record channel"); + cv_init(&c->intr_cv, "pcmrd"); break; - case PCMDIR_VIRTUAL: - c->lock = snd_mtxcreate(c->name, "pcm virtual play channel"); + case PCMDIR_REC_VIRTUAL: + c->lock = snd_mtxcreate(c->name, "pcm virtual record channel"); + cv_init(&c->intr_cv, "pcmrdv"); break; case 0: c->lock = snd_mtxcreate(c->name, "pcm fake channel"); + cv_init(&c->intr_cv, "pcmfk"); break; } + + cv_init(&c->cv, "pcmchn"); } static void chn_lockdestroy(struct pcm_channel *c) { + CHN_LOCKASSERT(c); + + CHN_BROADCAST(&c->cv); + CHN_BROADCAST(&c->intr_cv); + + cv_destroy(&c->cv); + cv_destroy(&c->intr_cv); + snd_mtxfree(c->lock); } +/** + * @brief Determine channel is ready for I/O + * + * @retval 1 = ready for I/O + * @retval 0 = not ready for I/O + */ static int chn_polltrigger(struct pcm_channel *c) { @@ -105,9 +276,11 @@ return (sndbuf_getblocks(bs) > sndbuf_getprevblocks(bs))? 1 : 0; } else { amt = (c->direction == PCMDIR_PLAY)? sndbuf_getfree(bs) : sndbuf_getready(bs); +#if 0 lim = (c->flags & CHN_F_HAS_SIZE)? sndbuf_getblksz(bs) : 1; - lim = 1; - return (amt >= lim)? 1 : 0; +#endif + lim = c->lw; + return (amt >= lim) ? 1 : 0; } return 0; } @@ -125,43 +298,53 @@ static void chn_wakeup(struct pcm_channel *c) { - struct snd_dbuf *bs = c->bufsoft; - struct pcmchan_children *pce; + struct snd_dbuf *bs; + struct pcm_channel *ch; CHN_LOCKASSERT(c); - if (SLIST_EMPTY(&c->children)) { + + bs = c->bufsoft; + + if (CHN_EMPTY(c, children.busy)) { if (SEL_WAITING(sndbuf_getsel(bs)) && chn_polltrigger(c)) selwakeuppri(sndbuf_getsel(bs), PRIBIO); + if (c->flags & CHN_F_SLEEPING) { + /* + * Ok, I can just panic it right here since it is + * quite obvious that we never allow multiple waiters + * from userland. I'm too generous... + */ + CHN_BROADCAST(&c->intr_cv); + } } else { - SLIST_FOREACH(pce, &c->children, link) { - CHN_LOCK(pce->channel); - chn_wakeup(pce->channel); - CHN_UNLOCK(pce->channel); + CHN_FOREACH(ch, c, children.busy) { + CHN_LOCK(ch); + chn_wakeup(ch); + CHN_UNLOCK(ch); } } - - wakeup(bs); } static int -chn_sleep(struct pcm_channel *c, char *str, int timeout) +chn_sleep(struct pcm_channel *c, int timeout) { - struct snd_dbuf *bs = c->bufsoft; int ret; CHN_LOCKASSERT(c); -#ifdef USING_MUTEX - ret = msleep(bs, c->lock, PRIBIO | PCATCH, str, timeout); -#else - ret = tsleep(bs, PRIBIO | PCATCH, str, timeout); -#endif - return ret; + if (c->flags & CHN_F_DEAD) + return (EINVAL); + + c->flags |= CHN_F_SLEEPING; + ret = cv_timedwait_sig(&c->intr_cv, c->lock, timeout); + c->flags &= ~CHN_F_SLEEPING; + + return ((c->flags & CHN_F_DEAD) ? EINVAL : ret); } /* * chn_dmaupdate() tracks the status of a dma transfer, - * updating pointers. It must be called at spltty(). + * updating pointers. */ static unsigned int @@ -178,22 +361,24 @@ delta = (sndbuf_getsize(b) + hwptr - old) % sndbuf_getsize(b); sndbuf_sethwptr(b, hwptr); - DEB( - if (delta >= ((sndbuf_getsize(b) * 15) / 16)) { - if (!(c->flags & (CHN_F_CLOSING | CHN_F_ABORTING))) - device_printf(c->dev, "hwptr went backwards %d -> %d\n", old, hwptr); - } - ); - if (c->direction == PCMDIR_PLAY) { - amt = MIN(delta, sndbuf_getready(b)); + amt = min(delta, sndbuf_getready(b)); + amt -= amt % sndbuf_getbps(b); if (amt > 0) sndbuf_dispose(b, NULL, amt); } else { - amt = MIN(delta, sndbuf_getfree(b)); + amt = min(delta, sndbuf_getfree(b)); + amt -= amt % sndbuf_getbps(b); if (amt > 0) sndbuf_acquire(b, NULL, amt); } + if (snd_verbose > 3 && CHN_STARTED(c) && delta == 0) { + device_printf(c->dev, "WARNING: %s DMA completion " + "too fast/slow ! hwptr=%u, old=%u " + "delta=%u amt=%u ready=%u free=%u\n", + CHN_DIRSTR(c), hwptr, old, delta, amt, + sndbuf_getready(b), sndbuf_getfree(b)); + } return delta; } @@ -206,7 +391,7 @@ CHN_LOCKASSERT(c); KASSERT(c->direction == PCMDIR_PLAY, ("chn_wrupdate on bad channel")); - if ((c->flags & (CHN_F_MAPPED | CHN_F_VIRTUAL)) || !(c->flags & CHN_F_TRIGGERED)) + if ((c->flags & (CHN_F_MAPPED | CHN_F_VIRTUAL)) || CHN_STOPPED(c)) return; chn_dmaupdate(c); ret = chn_wrfeed(c); @@ -225,24 +410,20 @@ unsigned int ret, amt; CHN_LOCKASSERT(c); -/* DEB( - if (c->flags & CHN_F_CLOSING) { - sndbuf_dump(b, "b", 0x02); - sndbuf_dump(bs, "bs", 0x02); - }) */ - if (c->flags & CHN_F_MAPPED) + if ((c->flags & CHN_F_MAPPED) && !(c->flags & CHN_F_CLOSING)) sndbuf_acquire(bs, NULL, sndbuf_getfree(bs)); amt = sndbuf_getfree(b); - KASSERT(amt <= sndbuf_getsize(bs), - ("%s(%s): amt %d > source size %d, flags 0x%x", __func__, c->name, - amt, sndbuf_getsize(bs), c->flags)); - if (sndbuf_getready(bs) < amt) + + ret = (amt > 0) ? sndbuf_feed(bs, b, c, c->feeder, amt) : ENOSPC; + /* + * Possible xruns. There should be no empty space left in buffer. + */ + if (sndbuf_getfree(b) > 0) c->xruns++; - ret = (amt > 0)? sndbuf_feed(bs, b, c, c->feeder, amt) : ENOSPC; - if (ret == 0 && sndbuf_getfree(b) < amt) + if (sndbuf_getfree(b) < amt) chn_wakeup(c); return ret; @@ -274,96 +455,68 @@ int chn_write(struct pcm_channel *c, struct uio *buf) { - int ret, timeout, newsize, count, sz; struct snd_dbuf *bs = c->bufsoft; void *off; - int t, x,togo,p; + int ret, timeout, sz, t, p; CHN_LOCKASSERT(c); - /* - * XXX Certain applications attempt to write larger size - * of pcm data than c->blocksize2nd without blocking, - * resulting partial write. Expand the block size so that - * the write operation avoids blocking. - */ - if ((c->flags & CHN_F_NBIO) && buf->uio_resid > sndbuf_getblksz(bs)) { - DEB(device_printf(c->dev, "broken app, nbio and tried to write %d bytes with fragsz %d\n", - buf->uio_resid, sndbuf_getblksz(bs))); - newsize = 16; - while (newsize < min(buf->uio_resid, CHN_2NDBUFMAXSIZE / 2)) - newsize <<= 1; - chn_setblocksize(c, sndbuf_getblkcnt(bs), newsize); - DEB(device_printf(c->dev, "frags reset to %d x %d\n", sndbuf_getblkcnt(bs), sndbuf_getblksz(bs))); - } ret = 0; - count = hz; - while (!ret && (buf->uio_resid > 0) && (count > 0)) { - sz = sndbuf_getfree(bs); - if (sz == 0) { - if (c->flags & CHN_F_NBIO) - ret = EWOULDBLOCK; - else { - timeout = (hz * sndbuf_getblksz(bs)) / (sndbuf_getspd(bs) * sndbuf_getbps(bs)); - if (timeout < 1) - timeout = 1; - timeout = 1; - ret = chn_sleep(c, "pcmwr", timeout); - if (ret == EWOULDBLOCK) { - count -= timeout; - ret = 0; - } else if (ret == 0) - count = hz; - } - } else { - sz = MIN(sz, buf->uio_resid); - KASSERT(sz > 0, ("confusion in chn_write")); - /* printf("sz: %d\n", sz); */ + timeout = chn_timeout * hz; + while (ret == 0 && buf->uio_resid > 0) { + sz = min(buf->uio_resid, sndbuf_getfree(bs)); + if (sz > 0) { /* * The following assumes that the free space in * the buffer can never be less around the * unlock-uiomove-lock sequence. */ - togo = sz; - while (ret == 0 && togo> 0) { + while (ret == 0 && sz > 0) { p = sndbuf_getfreeptr(bs); - t = MIN(togo, sndbuf_getsize(bs) - p); + t = min(sz, sndbuf_getsize(bs) - p); off = sndbuf_getbufofs(bs, p); CHN_UNLOCK(c); ret = uiomove(off, t, buf); CHN_LOCK(c); - togo -= t; - x = sndbuf_acquire(bs, NULL, t); + sz -= t; + sndbuf_acquire(bs, NULL, t); } ret = 0; - if (ret == 0 && !(c->flags & CHN_F_TRIGGERED)) - chn_start(c, 0); + if (CHN_STOPPED(c)) { + ret = chn_start(c, 0); + if (ret != 0) + c->flags |= CHN_F_DEAD; + } + } else if (c->flags & (CHN_F_NBIO | CHN_F_NOTRIGGER)) { + /** + * @todo Evaluate whether EAGAIN is truly desirable. + * 4Front drivers behave like this, but I'm + * not sure if it at all violates the "write + * should be allowed to block" model. + * + * The idea is that, while set with CHN_F_NOTRIGGER, + * a channel isn't playing, *but* without this we + * end up with "interrupt timeout / channel dead". + */ + ret = EAGAIN; + } else { + ret = chn_sleep(c, timeout); + if (ret == EAGAIN) { + ret = EINVAL; + c->flags |= CHN_F_DEAD; + printf("%s: play interrupt timeout, " + "channel dead\n", c->name); + } else if (ret == ERESTART || ret == EINTR) + c->flags |= CHN_F_ABORTING; } } - /* printf("ret: %d left: %d\n", ret, buf->uio_resid); */ - - if (count <= 0) { - c->flags |= CHN_F_DEAD; - printf("%s: play interrupt timeout, channel dead\n", c->name); - } - - return ret; -} - -static int -chn_rddump(struct pcm_channel *c, unsigned int cnt) -{ - struct snd_dbuf *b = c->bufhard; - CHN_LOCKASSERT(c); - sndbuf_setxrun(b, sndbuf_getxrun(b) + cnt); - return sndbuf_dispose(b, NULL, cnt); + return (ret); } /* * Feed new data from the read buffer. Can be called in the bottom half. - * Hence must be called at spltty. */ int chn_rdfeed(struct pcm_channel *c) @@ -373,24 +526,21 @@ unsigned int ret, amt; CHN_LOCKASSERT(c); - DEB( - if (c->flags & CHN_F_CLOSING) { - sndbuf_dump(b, "b", 0x02); - sndbuf_dump(bs, "bs", 0x02); - }) + + if (c->flags & CHN_F_MAPPED) + sndbuf_dispose(bs, NULL, sndbuf_getready(bs)); + + amt = sndbuf_getfree(bs); + ret = (amt > 0) ? sndbuf_feed(b, bs, c, c->feeder, amt) : ENOSPC; amt = sndbuf_getready(b); - if (sndbuf_getfree(bs) < amt) { + if (amt > 0) { c->xruns++; - amt = sndbuf_getfree(bs); + sndbuf_dispose(b, NULL, amt); } - ret = (amt > 0)? sndbuf_feed(b, bs, c, c->feeder, amt) : 0; - - amt = sndbuf_getready(b); - if (amt > 0) - chn_rddump(c, amt); - chn_wakeup(c); + if (sndbuf_getready(bs) > 0) + chn_wakeup(c); return ret; } @@ -403,13 +553,13 @@ CHN_LOCKASSERT(c); KASSERT(c->direction == PCMDIR_REC, ("chn_rdupdate on bad channel")); - if ((c->flags & CHN_F_MAPPED) || !(c->flags & CHN_F_TRIGGERED)) + if ((c->flags & (CHN_F_MAPPED | CHN_F_VIRTUAL)) || CHN_STOPPED(c)) return; chn_trigger(c, PCMTRIG_EMLDMARD); chn_dmaupdate(c); ret = chn_rdfeed(c); - if (ret) - printf("chn_rdfeed: %d\n", ret); + DEB(if (ret) + printf("chn_rdfeed: %d\n", ret);) } @@ -438,63 +588,57 @@ int chn_read(struct pcm_channel *c, struct uio *buf) { - int ret, timeout, sz, count; - struct snd_dbuf *bs = c->bufsoft; + struct snd_dbuf *bs = c->bufsoft; void *off; - int t, x,togo,p; + int ret, timeout, sz, t, p; CHN_LOCKASSERT(c); - if (!(c->flags & CHN_F_TRIGGERED)) - chn_start(c, 0); + + if (CHN_STOPPED(c)) { + ret = chn_start(c, 0); + if (ret != 0) { + c->flags |= CHN_F_DEAD; + return (ret); + } + } ret = 0; - count = hz; - while (!ret && (buf->uio_resid > 0) && (count > 0)) { - sz = MIN(buf->uio_resid, sndbuf_getready(bs)); + timeout = chn_timeout * hz; + while (ret == 0 && buf->uio_resid > 0) { + sz = min(buf->uio_resid, sndbuf_getready(bs)); if (sz > 0) { /* * The following assumes that the free space in * the buffer can never be less around the * unlock-uiomove-lock sequence. */ - togo = sz; - while (ret == 0 && togo> 0) { + while (ret == 0 && sz > 0) { p = sndbuf_getreadyptr(bs); - t = MIN(togo, sndbuf_getsize(bs) - p); + t = min(sz, sndbuf_getsize(bs) - p); off = sndbuf_getbufofs(bs, p); CHN_UNLOCK(c); ret = uiomove(off, t, buf); CHN_LOCK(c); - togo -= t; - x = sndbuf_dispose(bs, NULL, t); + sz -= t; + sndbuf_dispose(bs, NULL, t); } ret = 0; - } else { - if (c->flags & CHN_F_NBIO) { - ret = EWOULDBLOCK; - } else { - timeout = (hz * sndbuf_getblksz(bs)) / (sndbuf_getspd(bs) * sndbuf_getbps(bs)); - if (timeout < 1) - timeout = 1; - ret = chn_sleep(c, "pcmrd", timeout); - if (ret == EWOULDBLOCK) { - count -= timeout; - ret = 0; - } else { - count = hz; - } - - } + } else if (c->flags & (CHN_F_NBIO | CHN_F_NOTRIGGER)) + ret = EAGAIN; + else { + ret = chn_sleep(c, timeout); + if (ret == EAGAIN) { + ret = EINVAL; + c->flags |= CHN_F_DEAD; + printf("%s: record interrupt timeout, " + "channel dead\n", c->name); + } else if (ret == ERESTART || ret == EINTR) + c->flags |= CHN_F_ABORTING; } } - if (count <= 0) { - c->flags |= CHN_F_DEAD; - printf("%s: record interrupt timeout, channel dead\n", c->name); - } - - return ret; + return (ret); } void @@ -515,36 +659,67 @@ u_int32_t i, j; struct snd_dbuf *b = c->bufhard; struct snd_dbuf *bs = c->bufsoft; + int err; CHN_LOCKASSERT(c); /* if we're running, or if we're prevented from triggering, bail */ - if ((c->flags & CHN_F_TRIGGERED) || ((c->flags & CHN_F_NOTRIGGER) && !force)) - return EINVAL; + if (CHN_STARTED(c) || ((c->flags & CHN_F_NOTRIGGER) && !force)) + return (EINVAL); - i = (c->direction == PCMDIR_PLAY)? sndbuf_getready(bs) : sndbuf_getfree(bs); - j = (c->direction == PCMDIR_PLAY)? sndbuf_getfree(b) : sndbuf_getready(b); - if (force || (i >= j)) { - c->flags |= CHN_F_TRIGGERED; - /* - * if we're starting because a vchan started, don't feed any data - * or it becomes impossible to start vchans synchronised with the - * first one. the hardbuf should be empty so we top it up with - * silence to give it something to chew. the real data will be - * fed at the first irq. - */ - if (c->direction == PCMDIR_PLAY) { - if (SLIST_EMPTY(&c->children)) - chn_wrfeed(c); - else - sndbuf_fillsilence(b); + err = 0; + + if (force) { + i = 1; + j = 0; + } else { + if (c->direction == PCMDIR_REC) { + i = sndbuf_getfree(bs); + j = (i > 0) ? 1 : sndbuf_getready(b); + } else { + if (sndbuf_getfree(bs) == 0) { + i = 1; + j = 0; + } else { + struct snd_dbuf *pb; + + pb = CHN_BUF_PARENT(c, b); + i = sndbuf_xbytes(sndbuf_getready(bs), bs, pb); + j = sndbuf_getbps(pb); + } } + if (snd_verbose > 3 && CHN_EMPTY(c, children)) + printf("%s: %s (%s) threshold i=%d j=%d\n", + __func__, CHN_DIRSTR(c), + (c->flags & CHN_F_VIRTUAL) ? "virtual" : "hardware", + i, j); + } + + if (i >= j) { + c->flags |= CHN_F_TRIGGERED; sndbuf_setrun(b, 1); + c->feedcount = (c->flags & CHN_F_CLOSING) ? 2 : 0; + c->interrupts = 0; c->xruns = 0; - chn_trigger(c, PCMTRIG_START); - return 0; + if (c->direction == PCMDIR_PLAY && c->parentchannel == NULL) { + sndbuf_fillsilence(b); + if (snd_verbose > 3) + printf("%s: %s starting! (%s) (ready=%d " + "force=%d i=%d j=%d intrtimeout=%u " + "latency=%dms)\n", + __func__, + (c->flags & CHN_F_HAS_VCHAN) ? + "VCHAN" : "HW", + (c->flags & CHN_F_CLOSING) ? "closing" : + "running", + sndbuf_getready(b), + force, i, j, c->timeout, + (sndbuf_getsize(b) * 1000) / + (sndbuf_getbps(b) * sndbuf_getspd(b))); + } + err = chn_trigger(c, PCMTRIG_START); } - return 0; + return (err); } void @@ -566,35 +741,127 @@ int chn_sync(struct pcm_channel *c, int threshold) { - u_long rdy; - int ret; - struct snd_dbuf *bs = c->bufsoft; + struct snd_dbuf *b, *bs; + int ret, count, hcount, minflush, resid, residp, syncdelay, blksz; + u_int32_t cflag; CHN_LOCKASSERT(c); + if (c->direction != PCMDIR_PLAY) + return (EINVAL); + + bs = c->bufsoft; + + if ((c->flags & (CHN_F_DEAD | CHN_F_ABORTING)) || + (threshold < 1 && sndbuf_getready(bs) < 1)) + return (0); + /* if we haven't yet started and nothing is buffered, else start*/ - if (!(c->flags & CHN_F_TRIGGERED)) { - if (sndbuf_getready(bs) > 0) { + if (CHN_STOPPED(c)) { + if (threshold > 0 || sndbuf_getready(bs) > 0) { ret = chn_start(c, 1); - if (ret) - return ret; - } else { - return 0; - } + if (ret != 0) + return (ret); + } else + return (0); + } + + b = CHN_BUF_PARENT(c, c->bufhard); + + minflush = threshold + sndbuf_xbytes(sndbuf_getready(b), b, bs); + + syncdelay = chn_syncdelay; + + if (syncdelay < 0 && (threshold > 0 || sndbuf_getready(bs) > 0)) + minflush += sndbuf_xbytes(sndbuf_getsize(b), b, bs); + + /* + * Append (0-1000) millisecond trailing buffer (if needed) + * for slower / high latency hardwares (notably USB audio) + * to avoid audible truncation. + */ + if (syncdelay > 0) + minflush += (sndbuf_getbps(bs) * sndbuf_getspd(bs) * + ((syncdelay > 1000) ? 1000 : syncdelay)) / 1000; + + minflush -= minflush % sndbuf_getbps(bs); + + if (minflush > 0) { + threshold = min(minflush, sndbuf_getfree(bs)); + sndbuf_clear(bs, threshold); + sndbuf_acquire(bs, NULL, threshold); + minflush -= threshold; + } + + resid = sndbuf_getready(bs); + residp = resid; + blksz = sndbuf_getblksz(b); + if (blksz < 1) { + printf("%s: WARNING: blksz < 1 ! maxsize=%d [%d/%d/%d]\n", + __func__, sndbuf_getmaxsize(b), sndbuf_getsize(b), + sndbuf_getblksz(b), sndbuf_getblkcnt(b)); + if (sndbuf_getblkcnt(b) > 0) + blksz = sndbuf_getsize(b) / sndbuf_getblkcnt(b); + if (blksz < 1) + blksz = 1; } + count = sndbuf_xbytes(minflush + resid, bs, b) / blksz; + hcount = count; + ret = 0; + + if (snd_verbose > 3) + printf("%s: [begin] timeout=%d count=%d " + "minflush=%d resid=%d\n", __func__, c->timeout, count, + minflush, resid); - for (;;) { - rdy = (c->direction == PCMDIR_PLAY)? sndbuf_getfree(bs) : sndbuf_getready(bs); - if (rdy <= threshold) { - ret = chn_sleep(c, "pcmsyn", 1); - if (ret == ERESTART || ret == EINTR) { - DEB(printf("chn_sync: tsleep returns %d\n", ret)); - return -1; - } + cflag = c->flags & CHN_F_CLOSING; + c->flags |= CHN_F_CLOSING; + while (count > 0 && (resid > 0 || minflush > 0)) { + ret = chn_sleep(c, c->timeout); + if (ret == ERESTART || ret == EINTR) { + c->flags |= CHN_F_ABORTING; + break; + } else if (ret == 0 || ret == EAGAIN) { + resid = sndbuf_getready(bs); + if (resid == residp) { + --count; + if (snd_verbose > 3) + printf("%s: [stalled] timeout=%d " + "count=%d hcount=%d " + "resid=%d minflush=%d\n", + __func__, c->timeout, count, + hcount, resid, minflush); + } else if (resid < residp && count < hcount) { + ++count; + if (snd_verbose > 3) + printf("%s: [resume] timeout=%d " + "count=%d hcount=%d " + "resid=%d minflush=%d\n", + __func__, c->timeout, count, + hcount, resid, minflush); + } + if (minflush > 0 && sndbuf_getfree(bs) > 0) { + threshold = min(minflush, + sndbuf_getfree(bs)); + sndbuf_clear(bs, threshold); + sndbuf_acquire(bs, NULL, threshold); + resid = sndbuf_getready(bs); + minflush -= threshold; + } + residp = resid; } else break; - } - return 0; + } + c->flags &= ~CHN_F_CLOSING; + c->flags |= cflag; + + if (snd_verbose > 3) + printf("%s: timeout=%d count=%d hcount=%d resid=%d residp=%d " + "minflush=%d ret=%d\n", + __func__, c->timeout, count, hcount, resid, residp, + minflush, ret); + + return (0); } /* called externally, handle locking */ @@ -605,14 +872,17 @@ int ret; CHN_LOCKASSERT(c); - if (!(c->flags & CHN_F_MAPPED) && !(c->flags & CHN_F_TRIGGERED)) - chn_start(c, 1); + if (!(c->flags & (CHN_F_MAPPED | CHN_F_TRIGGERED))) { + ret = chn_start(c, 1); + if (ret != 0) + return (0); + } ret = 0; if (chn_polltrigger(c) && chn_pollreset(c)) ret = ev; else selrecord(td, sndbuf_getsel(bs)); - return ret; + return (ret); } /* @@ -629,7 +899,7 @@ struct snd_dbuf *bs = c->bufsoft; CHN_LOCKASSERT(c); - if (!(c->flags & CHN_F_TRIGGERED)) + if (CHN_STOPPED(c)) return 0; c->flags |= CHN_F_ABORTING; @@ -639,7 +909,7 @@ sndbuf_setrun(b, 0); if (!(c->flags & CHN_F_VIRTUAL)) chn_dmaupdate(c); - missing = sndbuf_getready(bs) + sndbuf_getready(b); + missing = sndbuf_getready(bs); c->flags &= ~CHN_F_ABORTING; return missing; @@ -659,48 +929,14 @@ int chn_flush(struct pcm_channel *c) { - int ret, count, resid, resid_p; struct snd_dbuf *b = c->bufhard; - struct snd_dbuf *bs = c->bufsoft; CHN_LOCKASSERT(c); KASSERT(c->direction == PCMDIR_PLAY, ("chn_flush on bad channel")); DEB(printf("chn_flush: c->flags 0x%08x\n", c->flags)); - /* if we haven't yet started and nothing is buffered, else start*/ - if (!(c->flags & CHN_F_TRIGGERED)) { - if (sndbuf_getready(bs) > 0) { - ret = chn_start(c, 1); - if (ret) - return ret; - } else { - return 0; - } - } - c->flags |= CHN_F_CLOSING; - resid = sndbuf_getready(bs) + sndbuf_getready(b); - resid_p = resid; - count = 10; - ret = 0; - while ((count > 0) && (resid > sndbuf_getsize(b)) && (ret == 0)) { - /* still pending output data. */ - ret = chn_sleep(c, "pcmflu", hz / 10); - if (ret == EWOULDBLOCK) - ret = 0; - if (ret == 0) { - resid = sndbuf_getready(bs) + sndbuf_getready(b); - if (resid == resid_p) - count--; - if (resid > resid_p) - DEB(printf("chn_flush: buffer length increasind %d -> %d\n", resid_p, resid)); - resid_p = resid; - } - } - if (count == 0) - DEB(printf("chn_flush: timeout, hw %d, sw %d\n", - sndbuf_getready(b), sndbuf_getready(bs))); - + chn_sync(c, 0); c->flags &= ~CHN_F_TRIGGERED; /* kill the channel */ chn_trigger(c, PCMTRIG_ABORT); @@ -721,33 +957,166 @@ return 0; } +static struct afmtstr_table default_afmtstr_table[] = { + { "alaw", AFMT_A_LAW }, { "mulaw", AFMT_MU_LAW }, + { "u8", AFMT_U8 }, { "s8", AFMT_S8 }, + { "s16le", AFMT_S16_LE }, { "s16be", AFMT_S16_BE }, + { "u16le", AFMT_U16_LE }, { "u16be", AFMT_U16_BE }, + { "s24le", AFMT_S24_LE }, { "s24be", AFMT_S24_BE }, + { "u24le", AFMT_U24_LE }, { "u24be", AFMT_U24_BE }, + { "s32le", AFMT_S32_LE }, { "s32be", AFMT_S32_BE }, + { "u32le", AFMT_U32_LE }, { "u32be", AFMT_U32_BE }, + { NULL, 0 }, +}; + +int +afmtstr_swap_sign(char *s) +{ + if (s == NULL || strlen(s) < 2) /* full length of "s8" */ + return 0; + if (*s == 's') + *s = 'u'; + else if (*s == 'u') + *s = 's'; + else + return 0; + return 1; +} + +int +afmtstr_swap_endian(char *s) +{ + if (s == NULL || strlen(s) < 5) /* full length of "s16le" */ + return 0; + if (s[3] == 'l') + s[3] = 'b'; + else if (s[3] == 'b') + s[3] = 'l'; + else + return 0; + return 1; +} + +u_int32_t +afmtstr2afmt(struct afmtstr_table *tbl, const char *s, int stereo) +{ + size_t fsz, sz; + + sz = (s == NULL) ? 0 : strlen(s); + + if (sz > 1) { + + if (tbl == NULL) + tbl = default_afmtstr_table; + + for (; tbl->fmtstr != NULL; tbl++) { + fsz = strlen(tbl->fmtstr); + if (sz < fsz) + continue; + if (strncmp(s, tbl->fmtstr, fsz) != 0) + continue; + if (fsz == sz) + return tbl->format | + ((stereo) ? AFMT_STEREO : 0); + if ((sz - fsz) < 2 || s[fsz] != ':') + break; + /* + * For now, just handle mono/stereo. + */ + if ((s[fsz + 2] == '\0' && (s[fsz + 1] == 'm' || + s[fsz + 1] == '1')) || + strcmp(s + fsz + 1, "mono") == 0) + return tbl->format; + if ((s[fsz + 2] == '\0' && (s[fsz + 1] == 's' || + s[fsz + 1] == '2')) || + strcmp(s + fsz + 1, "stereo") == 0) + return tbl->format | AFMT_STEREO; + break; + } + } + + return 0; +} + +u_int32_t +afmt2afmtstr(struct afmtstr_table *tbl, u_int32_t afmt, char *dst, + size_t len, int type, int stereo) +{ + u_int32_t fmt = 0; + char *fmtstr = NULL, *tag = ""; + + if (tbl == NULL) + tbl = default_afmtstr_table; + + for (; tbl->format != 0; tbl++) { + if (tbl->format == 0) + break; + if ((afmt & ~AFMT_STEREO) != tbl->format) + continue; + fmt = afmt; + fmtstr = tbl->fmtstr; + break; + } + + if (fmt != 0 && fmtstr != NULL && dst != NULL && len > 0) { + strlcpy(dst, fmtstr, len); + switch (type) { + case AFMTSTR_SIMPLE: + tag = (fmt & AFMT_STEREO) ? ":s" : ":m"; + break; + case AFMTSTR_NUM: + tag = (fmt & AFMT_STEREO) ? ":2" : ":1"; + break; + case AFMTSTR_FULL: + tag = (fmt & AFMT_STEREO) ? ":stereo" : ":mono"; + break; + case AFMTSTR_NONE: + default: + break; + } + if (strlen(tag) > 0 && ((stereo && !(fmt & AFMT_STEREO)) || \ + (!stereo && (fmt & AFMT_STEREO)))) + strlcat(dst, tag, len); + } + + return fmt; +} + int chn_reset(struct pcm_channel *c, u_int32_t fmt) { int hwspd, r; CHN_LOCKASSERT(c); + c->feedcount = 0; c->flags &= CHN_F_RESET; c->interrupts = 0; + c->timeout = 1; c->xruns = 0; r = CHANNEL_RESET(c->methods, c->devinfo); if (fmt != 0) { +#if 0 hwspd = DSP_DEFAULT_SPEED; /* only do this on a record channel until feederbuilder works */ if (c->direction == PCMDIR_REC) RANGE(hwspd, chn_getcaps(c)->minspeed, chn_getcaps(c)->maxspeed); c->speed = hwspd; +#endif + hwspd = chn_getcaps(c)->minspeed; + c->speed = hwspd; if (r == 0) r = chn_setformat(c, fmt); if (r == 0) r = chn_setspeed(c, hwspd); +#if 0 if (r == 0) r = chn_setvolume(c, 100, 100); +#endif } if (r == 0) - r = chn_setblocksize(c, 0, 0); + r = chn_setlatency(c, chn_latency); if (r == 0) { chn_resetbuf(c); r = CHANNEL_RESETDONE(c->methods, c->devinfo); @@ -762,12 +1131,19 @@ struct snd_dbuf *b, *bs; int ret; + if (chn_timeout < CHN_TIMEOUT_MIN || chn_timeout > CHN_TIMEOUT_MAX) + chn_timeout = CHN_TIMEOUT; + chn_lockinit(c, dir); b = NULL; bs = NULL; + CHN_INIT(c, children); + CHN_INIT(c, children.busy); c->devinfo = NULL; c->feeder = NULL; + c->latency = -1; + c->timeout = 1; ret = ENOMEM; b = sndbuf_create(c->dev, c->name, "primary", c); @@ -798,6 +1174,20 @@ c->bufsoft = bs; c->flags = 0; c->feederflags = 0; + c->sm = NULL; + + /* Only Front Left/Right, for now. */ + c->matrix[0] = SND_CHN_T_FL; + c->matrix[1] = SND_CHN_T_FR; + c->matrix[2] = SND_CHN_T_MAX; + + c->volume[SND_VOL_C_MASTER][SND_CHN_T_VOL_0DB] = SND_VOL_0DB_MASTER; + c->volume[SND_VOL_C_PCM][SND_CHN_T_VOL_0DB] = chn_vol_0db_pcm; + + c->volume[SND_VOL_C_MASTER][SND_CHN_T_FL] = SND_VOL_0DB_MASTER; + c->volume[SND_VOL_C_MASTER][SND_CHN_T_FR] = SND_VOL_0DB_MASTER; + + chn_vpc_reset(c, SND_VOL_C_PCM, 1); ret = ENODEV; CHN_UNLOCK(c); /* XXX - Unlock for CHANNEL_INIT() malloc() call */ @@ -822,6 +1212,19 @@ if (ret) goto out; + /** + * @todo Should this be moved somewhere else? The primary buffer + * is allocated by the driver or via DMA map setup, and tmpbuf + * seems to only come into existence in sndbuf_resize(). + */ + if (c->direction == PCMDIR_PLAY) { + bs->sl = sndbuf_getmaxsize(bs); + bs->shadbuf = malloc(bs->sl, M_DEVBUF, M_NOWAIT); + if (bs->shadbuf == NULL) { + ret = ENOMEM; + goto out; + } + } out: CHN_UNLOCK(c); @@ -834,6 +1237,7 @@ sndbuf_destroy(bs); if (b) sndbuf_destroy(b); + CHN_LOCK(c); c->flags |= CHN_F_DEAD; chn_lockdestroy(c); @@ -849,40 +1253,509 @@ struct snd_dbuf *b = c->bufhard; struct snd_dbuf *bs = c->bufsoft; - if (c->flags & CHN_F_TRIGGERED) + if (CHN_STARTED(c)) { + CHN_LOCK(c); chn_trigger(c, PCMTRIG_ABORT); - while (chn_removefeeder(c) == 0); + CHN_UNLOCK(c); + } + while (chn_removefeeder(c) == 0) + ; if (CHANNEL_FREE(c->methods, c->devinfo)) sndbuf_free(b); - c->flags |= CHN_F_DEAD; sndbuf_destroy(bs); sndbuf_destroy(b); + CHN_LOCK(c); + c->flags |= CHN_F_DEAD; chn_lockdestroy(c); - return 0; + + return (0); } int chn_setdir(struct pcm_channel *c, int dir) { +#ifdef DEV_ISA struct snd_dbuf *b = c->bufhard; +#endif int r; CHN_LOCKASSERT(c); c->direction = dir; r = CHANNEL_SETDIR(c->methods, c->devinfo, c->direction); +#ifdef DEV_ISA if (!r && SND_DMA(b)) sndbuf_dmasetdir(b, c->direction); +#endif return r; } +/* XXX Obsolete. Use *_matrix() variant instead. */ int chn_setvolume(struct pcm_channel *c, int left, int right) { - CHN_LOCKASSERT(c); - /* should add a feeder for volume changing if channel returns -1 */ - c->volume = (left << 8) | right; - return 0; -} + int ret; + + ret = chn_setvolume_matrix(c, SND_VOL_C_MASTER, SND_CHN_T_FL, left); + ret |= chn_setvolume_matrix(c, SND_VOL_C_MASTER, SND_CHN_T_FR, + right) << 8; + + return (ret); +} + +int +chn_setvolume_matrix(struct pcm_channel *c, snd_volume_class_t vc, + snd_channel_t vt, int val) +{ + int i; + + KASSERT(c != NULL && vc >= SND_VOL_C_MASTER && vc < SND_VOL_C_MAX && + (vc == SND_VOL_C_MASTER || (vc & 1)) && + (vt == SND_CHN_T_VOL_0DB || (vt >= SND_CHN_T_BEGIN && + vt <= SND_CHN_T_END)) && (vt != SND_CHN_T_VOL_0DB || + (val >= SND_VOL_0DB_MIN && val <= SND_VOL_0DB_MAX)), + ("%s(): invalid volume matrix c=%p vc=%d vt=%d val=%d", + __func__, c, vc, vt, val)); + CHN_LOCKASSERT(c); + + if (val < 0) + val = 0; + if (val > 100) + val = 100; + + c->volume[vc][vt] = val; + + /* + * Do relative calculation here and store it into class + 1 + * to ease the job of feeder_volume. + */ + if (vc == SND_VOL_C_MASTER) { + for (vc = SND_VOL_C_BEGIN; vc <= SND_VOL_C_END; + vc += SND_VOL_C_STEP) + c->volume[SND_VOL_C_VAL(vc)][vt] = + SND_VOL_CALC_VAL(c->volume, vc, vt); + } else if (vc & 1) { + if (vt == SND_CHN_T_VOL_0DB) { + for (i = 0; c->matrix[i] != SND_CHN_T_MAX; i++) { + vt = c->matrix[i]; + c->volume[SND_VOL_C_VAL(vc)][vt] = + SND_VOL_CALC_VAL(c->volume, vc, vt); + } + } else + c->volume[SND_VOL_C_VAL(vc)][vt] = + SND_VOL_CALC_VAL(c->volume, vc, vt); + } + + return (val); +} + +int +chn_getvolume_matrix(struct pcm_channel *c, snd_volume_class_t vc, + snd_channel_t vt) +{ + KASSERT(c != NULL && vc >= SND_VOL_C_MASTER && vc < SND_VOL_C_MAX && + (vt == SND_CHN_T_VOL_0DB || + (vt >= SND_CHN_T_BEGIN && vt <= SND_CHN_T_END)), + ("%s(): invalid volume matrix c=%p vc=%d vt=%d", + __func__, c, vc, vt)); + CHN_LOCKASSERT(c); + + return (c->volume[vc][vt]); +} + +void +chn_vpc_reset(struct pcm_channel *c, snd_volume_class_t vc, int force) +{ + int i; + + KASSERT(c != NULL && vc >= SND_VOL_C_BEGIN && vc <= SND_VOL_C_END, + ("%s(): invalid reset c=%p vc=%d", __func__, c, vc)); + CHN_LOCKASSERT(c); + + if (force == 0 && chn_vpc_autoreset == 0) + return; + + for (i = 0; c->matrix[i] != SND_CHN_T_MAX; i++) + CHN_SETVOLUME(c, vc, c->matrix[i], + c->volume[vc][SND_CHN_T_VOL_0DB]); +} + +static u_int32_t +round_pow2(u_int32_t v) +{ + u_int32_t ret; + + if (v < 2) + v = 2; + ret = 0; + while (v >> ret) + ret++; + ret = 1 << (ret - 1); + while (ret < v) + ret <<= 1; + return ret; +} + +static u_int32_t +round_blksz(u_int32_t v, int round) +{ + u_int32_t ret, tmp; + + if (round < 1) + round = 1; + + ret = min(round_pow2(v), CHN_2NDBUFMAXSIZE >> 1); + + if (ret > v && (ret >> 1) > 0 && (ret >> 1) >= ((v * 3) >> 2)) + ret >>= 1; + + tmp = ret - (ret % round); + while (tmp < 16 || tmp < round) { + ret <<= 1; + tmp = ret - (ret % round); + } + + return ret; +} + +/* + * 4Front call it DSP Policy, while we call it "Latency Profile". The idea + * is to keep 2nd buffer short so that it doesn't cause long queue during + * buffer transfer. + * + * Latency reference table for 48khz stereo 16bit: (PLAY) + * + * +---------+------------+-----------+------------+ + * | Latency | Blockcount | Blocksize | Buffersize | + * +---------+------------+-----------+------------+ + * | 0 | 2 | 64 | 128 | + * +---------+------------+-----------+------------+ + * | 1 | 4 | 128 | 512 | + * +---------+------------+-----------+------------+ + * | 2 | 8 | 512 | 4096 | + * +---------+------------+-----------+------------+ + * | 3 | 16 | 512 | 8192 | + * +---------+------------+-----------+------------+ + * | 4 | 32 | 512 | 16384 | + * +---------+------------+-----------+------------+ + * | 5 | 32 | 1024 | 32768 | + * +---------+------------+-----------+------------+ + * | 6 | 16 | 2048 | 32768 | + * +---------+------------+-----------+------------+ + * | 7 | 8 | 4096 | 32768 | + * +---------+------------+-----------+------------+ + * | 8 | 4 | 8192 | 32768 | + * +---------+------------+-----------+------------+ + * | 9 | 2 | 16384 | 32768 | + * +---------+------------+-----------+------------+ + * | 10 | 2 | 32768 | 65536 | + * +---------+------------+-----------+------------+ + * + * Recording need a different reference table. All we care is + * gobbling up everything within reasonable buffering threshold. + * + * Latency reference table for 48khz stereo 16bit: (REC) + * + * +---------+------------+-----------+------------+ + * | Latency | Blockcount | Blocksize | Buffersize | + * +---------+------------+-----------+------------+ + * | 0 | 512 | 32 | 16384 | + * +---------+------------+-----------+------------+ + * | 1 | 256 | 64 | 16384 | + * +---------+------------+-----------+------------+ + * | 2 | 128 | 128 | 16384 | + * +---------+------------+-----------+------------+ + * | 3 | 64 | 256 | 16384 | + * +---------+------------+-----------+------------+ + * | 4 | 32 | 512 | 16384 | + * +---------+------------+-----------+------------+ + * | 5 | 32 | 1024 | 32768 | + * +---------+------------+-----------+------------+ + * | 6 | 16 | 2048 | 32768 | + * +---------+------------+-----------+------------+ + * | 7 | 8 | 4096 | 32768 | + * +---------+------------+-----------+------------+ + * | 8 | 4 | 8192 | 32768 | + * +---------+------------+-----------+------------+ + * | 9 | 2 | 16384 | 32768 | + * +---------+------------+-----------+------------+ + * | 10 | 2 | 32768 | 65536 | + * +---------+------------+-----------+------------+ + * + * Calculations for other data rate are entirely based on these reference + * tables. For normal operation, Latency 5 seems give the best, well + * balanced performance for typical workload. Anything below 5 will + * eat up CPU to keep up with increasing context switches because of + * shorter buffer space and usually require the application to handle it + * aggresively through possibly real time programming technique. + * + */ +#define CHN_LATENCY_PBLKCNT_REF \ + {{1, 2, 3, 4, 5, 5, 4, 3, 2, 1, 1}, \ + {1, 2, 3, 4, 5, 5, 4, 3, 2, 1, 1}} +#define CHN_LATENCY_PBUFSZ_REF \ + {{7, 9, 12, 13, 14, 15, 15, 15, 15, 15, 16}, \ + {11, 12, 13, 14, 15, 16, 16, 16, 16, 16, 17}} + +#define CHN_LATENCY_RBLKCNT_REF \ + {{9, 8, 7, 6, 5, 5, 4, 3, 2, 1, 1}, \ + {9, 8, 7, 6, 5, 5, 4, 3, 2, 1, 1}} +#define CHN_LATENCY_RBUFSZ_REF \ + {{14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 16}, \ + {15, 15, 15, 15, 15, 16, 16, 16, 16, 16, 17}} + +#define CHN_LATENCY_DATA_REF 192000 /* 48khz stereo 16bit ~ 48000 x 2 x 2 */ + +static int +chn_calclatency(int dir, int latency, int bps, u_int32_t datarate, + u_int32_t max, int *rblksz, int *rblkcnt) +{ + static int pblkcnts[CHN_LATENCY_PROFILE_MAX + 1][CHN_LATENCY_MAX + 1] = + CHN_LATENCY_PBLKCNT_REF; + static int pbufszs[CHN_LATENCY_PROFILE_MAX + 1][CHN_LATENCY_MAX + 1] = + CHN_LATENCY_PBUFSZ_REF; + static int rblkcnts[CHN_LATENCY_PROFILE_MAX + 1][CHN_LATENCY_MAX + 1] = + CHN_LATENCY_RBLKCNT_REF; + static int rbufszs[CHN_LATENCY_PROFILE_MAX + 1][CHN_LATENCY_MAX + 1] = + CHN_LATENCY_RBUFSZ_REF; + u_int32_t bufsz; + int lprofile, blksz, blkcnt; + + if (latency < CHN_LATENCY_MIN || latency > CHN_LATENCY_MAX || + bps < 1 || datarate < 1 || + !(dir == PCMDIR_PLAY || dir == PCMDIR_REC)) { + if (rblksz != NULL) + *rblksz = CHN_2NDBUFMAXSIZE >> 1; + if (rblkcnt != NULL) + *rblkcnt = 2; + printf("%s: FAILED dir=%d latency=%d bps=%d " + "datarate=%u max=%u\n", + __func__, dir, latency, bps, datarate, max); + return CHN_2NDBUFMAXSIZE; + } + + lprofile = chn_latency_profile; + + if (dir == PCMDIR_PLAY) { + blkcnt = pblkcnts[lprofile][latency]; + bufsz = pbufszs[lprofile][latency]; + } else { + blkcnt = rblkcnts[lprofile][latency]; + bufsz = rbufszs[lprofile][latency]; + } + + bufsz = round_pow2(snd_xbytes(1 << bufsz, CHN_LATENCY_DATA_REF, + datarate)); + if (bufsz > max) + bufsz = max; + blksz = round_blksz(bufsz >> blkcnt, bps); + + if (rblksz != NULL) + *rblksz = blksz; + if (rblkcnt != NULL) + *rblkcnt = 1 << blkcnt; + + return blksz << blkcnt; +} + +static int +chn_resizebuf(struct pcm_channel *c, int latency, + int blkcnt, int blksz) +{ + struct snd_dbuf *b, *bs, *pb; + int sblksz, sblkcnt, hblksz, hblkcnt, limit = 1; + int ret; + + CHN_LOCKASSERT(c); + + if ((c->flags & (CHN_F_MAPPED | CHN_F_TRIGGERED)) || + !(c->direction == PCMDIR_PLAY || c->direction == PCMDIR_REC)) + return EINVAL; + + if (latency == -1) { + c->latency = -1; + latency = chn_latency; + } else if (latency == -2) { + latency = c->latency; + if (latency < CHN_LATENCY_MIN || latency > CHN_LATENCY_MAX) + latency = chn_latency; + } else if (latency < CHN_LATENCY_MIN || latency > CHN_LATENCY_MAX) + return EINVAL; + else { + c->latency = latency; + limit = 0; + } + + bs = c->bufsoft; + b = c->bufhard; + + if (!(blksz == 0 || blkcnt == -1) && + (blksz < 16 || blksz < sndbuf_getbps(bs) || blkcnt < 2 || + (blksz * blkcnt) > CHN_2NDBUFMAXSIZE)) + return EINVAL; + + chn_calclatency(c->direction, latency, sndbuf_getbps(bs), + sndbuf_getbps(bs) * sndbuf_getspd(bs), CHN_2NDBUFMAXSIZE, + &sblksz, &sblkcnt); + + if (blksz == 0 || blkcnt == -1) { + if (blkcnt == -1) + c->flags &= ~CHN_F_HAS_SIZE; + if (c->flags & CHN_F_HAS_SIZE) { + blksz = sndbuf_getblksz(bs); + blkcnt = sndbuf_getblkcnt(bs); + } + } else + c->flags |= CHN_F_HAS_SIZE; + + if (c->flags & CHN_F_HAS_SIZE) { + /* + * The application has requested their own blksz/blkcnt. + * Just obey with it, and let them toast alone. We can + * clamp it to the nearest latency profile, but that would + * defeat the purpose of having custom control. The least + * we can do is round it to the nearest ^2 and align it. + */ + sblksz = round_blksz(blksz, sndbuf_getbps(bs)); + sblkcnt = round_pow2(blkcnt); + limit = 0; + } + + if (c->parentchannel != NULL) { + pb = CHN_BUF_PARENT(c, NULL); + CHN_UNLOCK(c); + CHN_LOCK(c->parentchannel); + chn_notify(c->parentchannel, CHN_N_BLOCKSIZE); + CHN_UNLOCK(c->parentchannel); + CHN_LOCK(c); + limit = (limit != 0 && pb != NULL) ? + sndbuf_xbytes(sndbuf_getsize(pb), pb, bs) : 0; + c->timeout = c->parentchannel->timeout; + } else { + hblkcnt = 2; + if (c->flags & CHN_F_HAS_SIZE) { + hblksz = round_blksz(sndbuf_xbytes(sblksz, bs, b), + sndbuf_getbps(b)); + hblkcnt = round_pow2(sndbuf_getblkcnt(bs)); + } else + chn_calclatency(c->direction, latency, + sndbuf_getbps(b), + sndbuf_getbps(b) * sndbuf_getspd(b), + CHN_2NDBUFMAXSIZE, &hblksz, &hblkcnt); + + if ((hblksz << 1) > sndbuf_getmaxsize(b)) + hblksz = round_blksz(sndbuf_getmaxsize(b) >> 1, + sndbuf_getbps(b)); + + while ((hblksz * hblkcnt) > sndbuf_getmaxsize(b)) { + if (hblkcnt < 4) + hblksz >>= 1; + else + hblkcnt >>= 1; + } + + hblksz -= hblksz % sndbuf_getbps(b); + +#if 0 + hblksz = sndbuf_getmaxsize(b) >> 1; + hblksz -= hblksz % sndbuf_getbps(b); + hblkcnt = 2; +#endif + + CHN_UNLOCK(c); + if (chn_usefrags == 0 || + CHANNEL_SETFRAGMENTS(c->methods, c->devinfo, + hblksz, hblkcnt) < 1) + sndbuf_setblksz(b, CHANNEL_SETBLOCKSIZE(c->methods, + c->devinfo, hblksz)); + CHN_LOCK(c); + + if (!CHN_EMPTY(c, children)) { + sblksz = round_blksz( + sndbuf_xbytes(sndbuf_getsize(b) >> 1, b, bs), + sndbuf_getbps(bs)); + sblkcnt = 2; + limit = 0; + } else if (limit != 0) + limit = sndbuf_xbytes(sndbuf_getsize(b), b, bs); + + /* + * Interrupt timeout + */ + c->timeout = ((u_int64_t)hz * sndbuf_getsize(b)) / + ((u_int64_t)sndbuf_getspd(b) * sndbuf_getbps(b)); + if (c->timeout < 1) + c->timeout = 1; + } + + if (limit > CHN_2NDBUFMAXSIZE) + limit = CHN_2NDBUFMAXSIZE; + +#if 0 + while (limit > 0 && (sblksz * sblkcnt) > limit) { + if (sblkcnt < 4) + break; + sblkcnt >>= 1; + } +#endif + + while ((sblksz * sblkcnt) < limit) + sblkcnt <<= 1; + + while ((sblksz * sblkcnt) > CHN_2NDBUFMAXSIZE) { + if (sblkcnt < 4) + sblksz >>= 1; + else + sblkcnt >>= 1; + } + + sblksz -= sblksz % sndbuf_getbps(bs); + + if (sndbuf_getblkcnt(bs) != sblkcnt || sndbuf_getblksz(bs) != sblksz || + sndbuf_getsize(bs) != (sblkcnt * sblksz)) { + ret = sndbuf_remalloc(bs, sblkcnt, sblksz); + if (ret != 0) { + printf("%s: Failed: %d %d\n", __func__, + sblkcnt, sblksz); + return ret; + } + } + + /* + * OSSv4 docs: "By default OSS will set the low water level equal + * to the fragment size which is optimal in most cases." + */ + c->lw = sndbuf_getblksz(bs); + chn_resetbuf(c); + + if (snd_verbose > 3) + printf("%s: %s (%s) timeout=%u " + "b[%d/%d/%d] bs[%d/%d/%d] limit=%d\n", + __func__, CHN_DIRSTR(c), + (c->flags & CHN_F_VIRTUAL) ? "virtual" : "hardware", + c->timeout, + sndbuf_getsize(b), sndbuf_getblksz(b), + sndbuf_getblkcnt(b), + sndbuf_getsize(bs), sndbuf_getblksz(bs), + sndbuf_getblkcnt(bs), limit); + + return 0; +} + +int +chn_setlatency(struct pcm_channel *c, int latency) +{ + CHN_LOCKASSERT(c); + /* Destroy blksz/blkcnt, enforce latency profile. */ + return chn_resizebuf(c, latency, -1, 0); +} + +int +chn_setblocksize(struct pcm_channel *c, int blkcnt, int blksz) +{ + CHN_LOCKASSERT(c); + /* Destroy latency profile, enforce blksz/blkcnt */ + return chn_resizebuf(c, -1, blkcnt, blksz); +} static int chn_tryspeed(struct pcm_channel *c, int speed) @@ -898,7 +1771,7 @@ DEB(printf("want speed %d, ", speed)); if (speed <= 0) return EINVAL; - if (CANCHANGE(c)) { + if (CHN_STOPPED(c)) { r = 0; c->speed = speed; sndbuf_setspd(bs, speed); @@ -912,7 +1785,10 @@ delta = -delta; c->feederflags &= ~(1 << FEEDER_RATE); - if (delta > 500) + /* + * Used to be 500. It was too big! + */ + if (delta > feeder_rate_round) c->feederflags |= 1 << FEEDER_RATE; else sndbuf_setspd(bs, sndbuf_getspd(b)); @@ -922,10 +1798,6 @@ if (r) goto out; - r = chn_setblocksize(c, 0, 0); - if (r) - goto out; - if (!(c->feederflags & (1 << FEEDER_RATE))) goto out; @@ -945,6 +1817,13 @@ r = FEEDER_SET(f, FEEDRATE_DST, sndbuf_getspd(x)); DEB(printf("feeder_set(FEEDRATE_DST, %d) = %d\n", sndbuf_getspd(x), r)); out: + if (!r) + r = CHANNEL_SETFORMAT(c->methods, c->devinfo, + sndbuf_getfmt(b)); + if (!r) + sndbuf_setfmt(bs, c->format); + if (!r) + r = chn_resizebuf(c, -2, 0, 0); DEB(printf("setspeed done, r = %d\n", r)); return r; } else @@ -958,7 +1837,9 @@ r = chn_tryspeed(c, speed); if (r) { - DEB(printf("Failed to set speed %d falling back to %d\n", speed, oldspeed)); + if (snd_verbose > 3) + printf("Failed to set speed %d falling back to %d\n", + speed, oldspeed); r = chn_tryspeed(c, oldspeed); } return r; @@ -972,7 +1853,7 @@ int r; CHN_LOCKASSERT(c); - if (CANCHANGE(c)) { + if (CHN_STOPPED(c)) { DEB(printf("want format %d\n", fmt)); c->format = fmt; r = chn_buildfeeder(c); @@ -996,154 +1877,96 @@ r = chn_tryformat(c, fmt); if (r) { - DEB(printf("Format change %d failed, reverting to %d\n", fmt, oldfmt)); + if (snd_verbose > 3) + printf("Format change 0x%08x failed, reverting to 0x%08x\n", + fmt, oldfmt); chn_tryformat(c, oldfmt); } return r; } int -chn_setblocksize(struct pcm_channel *c, int blkcnt, int blksz) -{ - struct snd_dbuf *b = c->bufhard; - struct snd_dbuf *bs = c->bufsoft; - int irqhz, tmp, ret, maxsize, reqblksz, tmpblksz; - - CHN_LOCKASSERT(c); - if (!CANCHANGE(c) || (c->flags & CHN_F_MAPPED)) { - KASSERT(sndbuf_getsize(bs) == 0 || - sndbuf_getsize(bs) >= sndbuf_getsize(b), - ("%s(%s): bufsoft size %d < bufhard size %d", __func__, - c->name, sndbuf_getsize(bs), sndbuf_getsize(b))); - return EINVAL; - } - c->flags |= CHN_F_SETBLOCKSIZE; - - ret = 0; - DEB(printf("%s(%d, %d)\n", __func__, blkcnt, blksz)); - if (blksz == 0 || blksz == -1) { - if (blksz == -1) - c->flags &= ~CHN_F_HAS_SIZE; - if (!(c->flags & CHN_F_HAS_SIZE)) { - blksz = (sndbuf_getbps(bs) * sndbuf_getspd(bs)) / chn_targetirqrate; - tmp = 32; - while (tmp <= blksz) - tmp <<= 1; - tmp >>= 1; - blksz = tmp; - blkcnt = CHN_2NDBUFMAXSIZE / blksz; - - RANGE(blksz, 16, CHN_2NDBUFMAXSIZE / 2); - RANGE(blkcnt, 2, CHN_2NDBUFMAXSIZE / blksz); - DEB(printf("%s: defaulting to (%d, %d)\n", __func__, blkcnt, blksz)); - } else { - blkcnt = sndbuf_getblkcnt(bs); - blksz = sndbuf_getblksz(bs); - DEB(printf("%s: updating (%d, %d)\n", __func__, blkcnt, blksz)); - } - } else { - ret = EINVAL; - if ((blksz < 16) || (blkcnt < 2) || (blkcnt * blksz > CHN_2NDBUFMAXSIZE)) - goto out; - ret = 0; - c->flags |= CHN_F_HAS_SIZE; - } - - reqblksz = blksz; - - /* adjust for different hw format/speed */ - irqhz = (sndbuf_getbps(bs) * sndbuf_getspd(bs)) / blksz; - DEB(printf("%s: soft bps %d, spd %d, irqhz == %d\n", __func__, sndbuf_getbps(bs), sndbuf_getspd(bs), irqhz)); - RANGE(irqhz, 16, 512); - - tmpblksz = (sndbuf_getbps(b) * sndbuf_getspd(b)) / irqhz; - - /* round down to 2^x */ - blksz = 32; - while (blksz <= tmpblksz) - blksz <<= 1; - blksz >>= 1; - - /* round down to fit hw buffer size */ - if (sndbuf_getmaxsize(b) > 0) - RANGE(blksz, 16, sndbuf_getmaxsize(b) / 2); - else - /* virtual channels don't appear to allocate bufhard */ - RANGE(blksz, 16, CHN_2NDBUFMAXSIZE / 2); - DEB(printf("%s: hard blksz requested %d (maxsize %d), ", __func__, blksz, sndbuf_getmaxsize(b))); - - /* Increase the size of bufsoft if before increasing bufhard. */ - maxsize = sndbuf_getsize(b); - if (sndbuf_getsize(bs) > maxsize) - maxsize = sndbuf_getsize(bs); - if (reqblksz * blkcnt > maxsize) - maxsize = reqblksz * blkcnt; - if (sndbuf_getsize(bs) != maxsize || sndbuf_getblksz(bs) != reqblksz) { - ret = sndbuf_remalloc(bs, maxsize/reqblksz, reqblksz); - if (ret) - goto out1; - } - - CHN_UNLOCK(c); - sndbuf_setblksz(b, CHANNEL_SETBLOCKSIZE(c->methods, c->devinfo, blksz)); - CHN_LOCK(c); - - /* Decrease the size of bufsoft after decreasing bufhard. */ - maxsize = sndbuf_getsize(b); - if (reqblksz * blkcnt > maxsize) - maxsize = reqblksz * blkcnt; - if (maxsize > sndbuf_getsize(bs)) - printf("Danger! %s bufsoft size increasing from %d to %d after CHANNEL_SETBLOCKSIZE()\n", - c->name, sndbuf_getsize(bs), maxsize); - if (sndbuf_getsize(bs) != maxsize || sndbuf_getblksz(bs) != reqblksz) { - ret = sndbuf_remalloc(bs, maxsize/reqblksz, reqblksz); - if (ret) - goto out1; - } - - irqhz = (sndbuf_getbps(b) * sndbuf_getspd(b)) / sndbuf_getblksz(b); - DEB(printf("got %d, irqhz == %d\n", sndbuf_getblksz(b), irqhz)); - - chn_resetbuf(c); -out1: - KASSERT(sndbuf_getsize(bs) == 0 || - sndbuf_getsize(bs) >= sndbuf_getsize(b), - ("%s(%s): bufsoft size %d < bufhard size %d, reqblksz=%d blksz=%d maxsize=%d blkcnt=%d", - __func__, c->name, sndbuf_getsize(bs), sndbuf_getsize(b), reqblksz, - blksz, maxsize, blkcnt)); -out: - c->flags &= ~CHN_F_SETBLOCKSIZE; - return ret; -} - -int chn_trigger(struct pcm_channel *c, int go) { +#ifdef DEV_ISA struct snd_dbuf *b = c->bufhard; +#endif + struct snddev_info *d = c->parentsnddev; int ret; CHN_LOCKASSERT(c); +#ifdef DEV_ISA if (SND_DMA(b) && (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD)) sndbuf_dmabounce(b); +#endif + if (!PCMTRIG_COMMON(go)) + return (CHANNEL_TRIGGER(c->methods, c->devinfo, go)); + + if (go == c->trigger) + return (0); + ret = CHANNEL_TRIGGER(c->methods, c->devinfo, go); + if (ret != 0) + return (ret); - return ret; + switch (go) { + case PCMTRIG_START: + if (snd_verbose > 3) + device_printf(c->dev, + "%s() %s: calling go=0x%08x , " + "prev=0x%08x\n", __func__, c->name, go, + c->trigger); + if (c->trigger != PCMTRIG_START) { + c->trigger = go; + CHN_UNLOCK(c); + pcm_lock(d); + CHN_INSERT_HEAD(d, c, channels.pcm.busy); + pcm_unlock(d); + CHN_LOCK(c); + } + break; + case PCMTRIG_STOP: + case PCMTRIG_ABORT: + if (snd_verbose > 3) + device_printf(c->dev, + "%s() %s: calling go=0x%08x , " + "prev=0x%08x\n", __func__, c->name, go, + c->trigger); + if (c->trigger == PCMTRIG_START) { + c->trigger = go; + CHN_UNLOCK(c); + pcm_lock(d); + CHN_REMOVE(d, c, channels.pcm.busy); + pcm_unlock(d); + CHN_LOCK(c); + } + break; + default: + break; + } + + return (0); } +/** + * @brief Queries sound driver for sample-aligned hardware buffer pointer index + * + * This function obtains the hardware pointer location, then aligns it to + * the current bytes-per-sample value before returning. (E.g., a channel + * running in 16 bit stereo mode would require 4 bytes per sample, so a + * hwptr value ranging from 32-35 would be returned as 32.) + * + * @param c PCM channel context + * @returns sample-aligned hardware buffer pointer index + */ int chn_getptr(struct pcm_channel *c) { int hwptr; - int a = (1 << c->align) - 1; CHN_LOCKASSERT(c); - hwptr = (c->flags & CHN_F_TRIGGERED)? CHANNEL_GETPTR(c->methods, c->devinfo) : 0; - /* don't allow unaligned values in the hwa ptr */ -#if 1 - hwptr &= ~a ; /* Apply channel align mask */ -#endif - hwptr &= DMA_ALIGN_MASK; /* Apply DMA align mask */ - return hwptr; + hwptr = (CHN_STARTED(c)) ? CHANNEL_GETPTR(c->methods, c->devinfo) : 0; + return (hwptr - (hwptr % sndbuf_getbps(c->bufhard))); } struct pcmchan_caps * @@ -1166,7 +1989,9 @@ /* report software-supported formats */ if (report_soft_formats) - fmts |= AFMT_MU_LAW|AFMT_A_LAW|AFMT_U16_LE|AFMT_U16_BE| + fmts |= AFMT_MU_LAW|AFMT_A_LAW|AFMT_U32_LE|AFMT_U32_BE| + AFMT_S32_LE|AFMT_S32_BE|AFMT_U24_LE|AFMT_U24_BE| + AFMT_S24_LE|AFMT_S24_BE|AFMT_U16_LE|AFMT_U16_BE| AFMT_S16_LE|AFMT_S16_BE|AFMT_U8|AFMT_S8; return fmts; @@ -1175,18 +2000,26 @@ static int chn_buildfeeder(struct pcm_channel *c) { + struct snddev_info *d; struct feeder_class *fc; struct pcm_feederdesc desc; - u_int32_t tmp[2], type, flags, hwfmt; - int err; + struct pcm_feeder *f; + struct snd_mixer *m; + u_int32_t tmp[2], type, flags, hwfmt, parent, *fmtlist; + int err, vol, left, right; + char fmtstr[AFMTSTR_MAXSZ]; CHN_LOCKASSERT(c); - while (chn_removefeeder(c) == 0); + while (chn_removefeeder(c) == 0) + ; KASSERT((c->feeder == NULL), ("feeder chain not empty")); c->align = sndbuf_getalign(c->bufsoft); - if (SLIST_EMPTY(&c->children)) { + if (CHN_EMPTY(c, children) || c->direction == PCMDIR_REC) { + /* + * Virtual rec need this. + */ fc = feeder_getclass(NULL); KASSERT(fc != NULL, ("can't find root feeder")); @@ -1197,9 +2030,14 @@ return err; } c->feeder->desc->out = c->format; - } else { - desc.type = FEEDER_MIXER; - desc.in = 0; + } else if (c->direction == PCMDIR_PLAY) { + if (c->flags & CHN_F_HAS_VCHAN) { + desc.type = FEEDER_MIXER; + desc.in = c->format; + } else { + DEB(printf("can't decide which feeder type to use!\n")); + return EOPNOTSUPP; + } desc.out = c->format; desc.flags = 0; fc = feeder_getclass(&desc); @@ -1215,18 +2053,81 @@ return err; } - } + } else + return EOPNOTSUPP; + + d = c->parentsnddev; + + if (c->direction == PCMDIR_PLAY && (d->flags & SD_F_SOFTPCMVOL) && + d->mixer_dev != NULL) + m = d->mixer_dev->si_drv1; + else + m = NULL; + + c->feederflags &= ~(1 << FEEDER_VOLUME); + if (((d->flags & SD_F_VPC) && CHN_EMPTY(c, children)) || + (!(d->flags & SD_F_VPC) && (d->flags & SD_F_SOFTPCMVOL) && + !(c->flags & CHN_F_VIRTUAL))) + c->feederflags |= 1 << FEEDER_VOLUME; + + c->feederflags &= ~(1 << FEEDER_SWAPLR); + if (!(c->flags & CHN_F_VIRTUAL) && ((c->direction == PCMDIR_PLAY && + (d->flags & SD_F_PSWAPLR)) || (c->direction == PCMDIR_REC && + (d->flags & SD_F_RSWAPLR)))) + c->feederflags |= 1 << FEEDER_SWAPLR; + flags = c->feederflags; + fmtlist = chn_getcaps(c)->fmtlist; + DEB(printf("feederflags %x\n", flags)); - for (type = FEEDER_RATE; type <= FEEDER_LAST; type++) { + for (type = FEEDER_RATE; type < FEEDER_LAST; type++) { if (flags & (1 << type)) { desc.type = type; desc.in = 0; desc.out = 0; desc.flags = 0; DEB(printf("find feeder type %d, ", type)); + if (type == FEEDER_VOLUME || type == FEEDER_RATE) { + if (c->feeder->desc->out & AFMT_32BIT) + strlcpy(fmtstr,"s32le", + sizeof(fmtstr)); + else if (c->feeder->desc->out & AFMT_24BIT) + strlcpy(fmtstr, "s24le", + sizeof(fmtstr)); + else { + /* + * 8bit doesn't provide enough headroom + * for proper processing without + * creating too much noises. Force to + * 16bit instead. + */ + strlcpy(fmtstr, "s16le", + sizeof(fmtstr)); + } + if (!(c->feeder->desc->out & AFMT_8BIT) && + c->feeder->desc->out & AFMT_BIGENDIAN) + afmtstr_swap_endian(fmtstr); + if (!(c->feeder->desc->out & AFMT_SIGNED)) + afmtstr_swap_sign(fmtstr); + desc.in = afmtstr2afmt(NULL, fmtstr, + AFMTSTR_MONO_RETURN); + if (desc.in == 0) + desc.in = AFMT_S16_LE; + /* feeder_volume need stereo processing */ + if (type == FEEDER_VOLUME || + (c->feeder->desc->out & AFMT_STEREO) || + (type != FEEDER_VOLUME && + (flags & (1 << FEEDER_VOLUME)))) + desc.in |= AFMT_STEREO; + desc.out = desc.in; + } else if (type == FEEDER_SWAPLR) { + desc.in = c->feeder->desc->out; + desc.in |= AFMT_STEREO; + desc.out = desc.in; + } + fc = feeder_getclass(&desc); DEB(printf("got %p\n", fc)); if (fc == NULL) { @@ -1235,19 +2136,20 @@ return EOPNOTSUPP; } - if (c->feeder->desc->out != fc->desc->in) { - DEB(printf("build fmtchain from 0x%x to 0x%x: ", c->feeder->desc->out, fc->desc->in)); - tmp[0] = fc->desc->in; - tmp[1] = 0; - if (chn_fmtchain(c, tmp) == 0) { - DEB(printf("failed\n")); + if (desc.in == 0 || desc.out == 0) + desc = *fc->desc; - return ENODEV; - } - DEB(printf("ok\n")); + DEB(printf("build fmtchain from 0x%08x to 0x%08x: ", c->feeder->desc->out, fc->desc->in)); + tmp[0] = desc.in; + tmp[1] = 0; + if (chn_fmtchain(c, tmp) == 0) { + DEB(printf("failed\n")); + + return ENODEV; } + DEB(printf("ok\n")); - err = chn_addfeeder(c, fc, fc->desc); + err = chn_addfeeder(c, fc, &desc); if (err) { DEB(printf("can't add feeder %p, output 0x%x, err %d\n", fc, fc->desc->out, err)); @@ -1257,111 +2159,191 @@ } } - if (fmtvalid(c->feeder->desc->out, chn_getcaps(c)->fmtlist)) { - hwfmt = c->feeder->desc->out; - } else { - if (c->direction == PCMDIR_REC) { - tmp[0] = c->format; - tmp[1] = 0; - hwfmt = chn_fmtchain(c, tmp); - } else { -#if 0 - u_int32_t *x = chn_getcaps(c)->fmtlist; - printf("acceptable formats for %s:\n", c->name); - while (*x) { - printf("[0x%8x] ", *x); - x++; - } -#endif - hwfmt = chn_fmtchain(c, chn_getcaps(c)->fmtlist); - } - } + if (c->direction == PCMDIR_REC) { + tmp[0] = c->format; + tmp[1] = 0; + hwfmt = chn_fmtchain(c, tmp); + } else + hwfmt = chn_fmtchain(c, fmtlist); - if (hwfmt == 0) + if (hwfmt == 0 || !fmtvalid(hwfmt, fmtlist)) { + DEB(printf("Invalid hardware format: 0x%08x\n", hwfmt)); return ENODEV; + } else if (c->direction == PCMDIR_REC && !CHN_EMPTY(c, children)) { + /* + * Kind of awkward. This whole "MIXER" concept need a + * rethinking, I guess :) . Recording is the inverse + * of Playback, which is why we push mixer vchan down here. + */ + if (c->flags & CHN_F_HAS_VCHAN) { + desc.type = FEEDER_MIXER; + desc.in = c->format; + } else + return EOPNOTSUPP; + desc.out = c->format; + desc.flags = 0; + fc = feeder_getclass(&desc); + if (fc == NULL) + return EOPNOTSUPP; + + err = chn_addfeeder(c, fc, &desc); + if (err != 0) + return err; + } sndbuf_setfmt(c->bufhard, hwfmt); - return 0; + if (flags & (1 << FEEDER_VOLUME)) { + if (m != NULL) { + CHN_UNLOCK(c); + vol = mix_get(m, SOUND_MIXER_PCM); + if (vol == -1) { + device_printf(c->dev, + "Soft PCM Volume: Failed to read " + "default value\n"); + vol = 100 | (100 << 8); + } + left = vol & 0x7f; + right = (vol >> 8) & 0x7f; + parent = mix_getparent(m, SOUND_MIXER_PCM); + if (parent != SOUND_MIXER_NONE) { + vol = mix_get(m, parent); + if (vol == -1) { + device_printf(c->dev, + "Soft Volume: Failed to read " + "parent default value\n"); + vol = 100 | (100 << 8); + } + left = (left * (vol & 0x7f)) / 100; + right = (right * ((vol >> 8) & 0x7f)) / 100; + } + CHN_LOCK(c); + CHN_SETVOLUME(c, SND_VOL_C_MASTER, SND_CHN_T_FL, left); + CHN_SETVOLUME(c, SND_VOL_C_MASTER, SND_CHN_T_FR, right); + } + f = chn_findfeeder(c, FEEDER_VOLUME); + if (f != NULL) + FEEDER_SET(f, FEEDVOL_CLASS, SND_VOL_C_PCM); + } + + return (0); } int chn_notify(struct pcm_channel *c, u_int32_t flags) { - struct pcmchan_children *pce; - struct pcm_channel *child; - int run; + int err, run, nrun; - CHN_LOCK(c); + CHN_LOCKASSERT(c); - if (SLIST_EMPTY(&c->children)) { - CHN_UNLOCK(c); - return ENODEV; - } + if (CHN_EMPTY(c, children)) + return (ENODEV); + + err = 0; - run = (c->flags & CHN_F_TRIGGERED)? 1 : 0; /* - * if the hwchan is running, we can't change its rate, format or + * If the hwchan is running, we can't change its rate, format or * blocksize */ + run = (CHN_STARTED(c)) ? 1 : 0; if (run) flags &= CHN_N_VOLUME | CHN_N_TRIGGER; if (flags & CHN_N_RATE) { - /* - * we could do something here, like scan children and decide on - * the most appropriate rate to mix at, but we don't for now - */ + /* XXX I'll make good use of this someday. */ } if (flags & CHN_N_FORMAT) { - /* - * we could do something here, like scan children and decide on - * the most appropriate mixer feeder to use, but we don't for now - */ + /* XXX I'll make good use of this someday. */ } if (flags & CHN_N_VOLUME) { - /* - * we could do something here but we don't for now - */ + /* XXX I'll make good use of this someday. */ } if (flags & CHN_N_BLOCKSIZE) { - int blksz; /* - * scan the children, find the lowest blocksize and use that - * for the hard blocksize + * Set to default latency profile */ - blksz = sndbuf_getmaxsize(c->bufhard) / 2; - SLIST_FOREACH(pce, &c->children, link) { - child = pce->channel; - CHN_LOCK(child); - if (sndbuf_getblksz(child->bufhard) < blksz) - blksz = sndbuf_getblksz(child->bufhard); - CHN_UNLOCK(child); - } - chn_setblocksize(c, 2, blksz); + chn_setlatency(c, chn_latency); } if (flags & CHN_N_TRIGGER) { - int nrun; - /* - * scan the children, and figure out if any are running - * if so, we need to be running, otherwise we need to be stopped - * if we aren't in our target sstate, move to it - */ - nrun = 0; - SLIST_FOREACH(pce, &c->children, link) { - child = pce->channel; - CHN_LOCK(child); - if (child->flags & CHN_F_TRIGGERED) - nrun = 1; - CHN_UNLOCK(child); - } + nrun = CHN_EMPTY(c, children.busy) ? 0 : 1; if (nrun && !run) - chn_start(c, 1); + err = chn_start(c, 1); if (!nrun && run) chn_abort(c); } - CHN_UNLOCK(c); - return 0; + + return (err); +} + +/** + * @brief Fetch array of supported discrete sample rates + * + * Wrapper for CHANNEL_GETRATES. Please see channel_if.m:getrates() for + * detailed information. + * + * @note If the operation isn't supported, this function will just return 0 + * (no rates in the array), and *rates will be set to NULL. Callers + * should examine rates @b only if this function returns non-zero. + * + * @param c pcm channel to examine + * @param rates pointer to array of integers; rate table will be recorded here + * + * @return number of rates in the array pointed to be @c rates + */ +int +chn_getrates(struct pcm_channel *c, int **rates) +{ + KASSERT(rates != NULL, ("rates is null")); + CHN_LOCKASSERT(c); + return CHANNEL_GETRATES(c->methods, c->devinfo, rates); +} + +/** + * @brief Remove channel from a sync group, if there is one. + * + * This function is initially intended for the following conditions: + * - Starting a syncgroup (@c SNDCTL_DSP_SYNCSTART ioctl) + * - Closing a device. (A channel can't be destroyed if it's still in use.) + * + * @note Before calling this function, the syncgroup list mutex must be + * held. (Consider pcm_channel::sm protected by the SG list mutex + * whether @c c is locked or not.) + * + * @param c channel device to be started or closed + * @returns If this channel was the only member of a group, the group ID + * is returned to the caller so that the caller can release it + * via free_unr() after giving up the syncgroup lock. Else it + * returns 0. + */ +int +chn_syncdestroy(struct pcm_channel *c) +{ + struct pcmchan_syncmember *sm; + struct pcmchan_syncgroup *sg; + int sg_id; + + sg_id = 0; + + PCM_SG_LOCKASSERT(MA_OWNED); + + if (c->sm != NULL) { + sm = c->sm; + sg = sm->parent; + c->sm = NULL; + + KASSERT(sg != NULL, ("syncmember has null parent")); + + SLIST_REMOVE(&sg->members, sm, pcmchan_syncmember, link); + free(sm, M_DEVBUF); + + if (SLIST_EMPTY(&sg->members)) { + SLIST_REMOVE(&snd_pcm_syncgroups, sg, pcmchan_syncgroup, link); + sg_id = sg->id; + free(sg, M_DEVBUF); + } + } + + return sg_id; } void @@ -1375,3 +2357,12 @@ { CHN_UNLOCK(c); } + +#ifdef OSSV4_EXPERIMENT +int +chn_getpeaks(struct pcm_channel *c, int *lpeak, int *rpeak) +{ + CHN_LOCKASSERT(c); + return CHANNEL_GETPEAKS(c->methods, c->devinfo, lpeak, rpeak); +} +#endif --- sys/dev/sound/pcm/channel.h.orig Sun Jan 30 09:00:04 2005 +++ sys/dev/sound/pcm/channel.h Thu Jul 12 12:04:19 2007 @@ -23,13 +23,10 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $FreeBSD: src/sys/dev/sound/pcm/channel.h,v 1.30.2.1 2005/01/30 01:00:04 imp Exp $ + * $FreeBSD: src/sys/dev/sound/pcm/channel.h,v 1.37 2007/06/16 03:37:28 ariff Exp $ */ -struct pcmchan_children { - SLIST_ENTRY(pcmchan_children) link; - struct pcm_channel *channel; -}; +#include struct pcmchan_caps { u_int32_t minspeed, maxspeed; @@ -37,17 +34,50 @@ u_int32_t caps; }; -#define CHN_NAMELEN 32 +/* Forward declarations */ +struct pcm_channel; +struct pcmchan_syncgroup; +struct pcmchan_syncmember; + +extern struct mtx snd_pcm_syncgroups_mtx; +extern SLIST_HEAD(pcm_synclist, pcmchan_syncgroup) snd_pcm_syncgroups; + +#define PCM_SG_LOCK() mtx_lock(&snd_pcm_syncgroups_mtx) +#define PCM_SG_TRYLOCK() mtx_trylock(&snd_pcm_syncgroups_mtx) +#define PCM_SG_UNLOCK() mtx_unlock(&snd_pcm_syncgroups_mtx) +#define PCM_SG_LOCKASSERT(arg) mtx_assert(&snd_pcm_syncgroups_mtx, arg) + +/** + * @brief Specifies an audio device sync group + */ +struct pcmchan_syncgroup { + SLIST_ENTRY(pcmchan_syncgroup) link; + SLIST_HEAD(, pcmchan_syncmember) members; + int id; /**< Group identifier; set to address of group. */ +}; + +/** + * @brief Specifies a container for members of a sync group + */ +struct pcmchan_syncmember { + SLIST_ENTRY(pcmchan_syncmember) link; + struct pcmchan_syncgroup *parent; /**< group head */ + struct pcm_channel *ch; +}; + +#define CHN_NAMELEN 32 +#define CHN_COMM_UNUSED "" +#define CHN_COMM_UNKNOWN "" + struct pcm_channel { kobj_t methods; - int num; pid_t pid; int refcount; struct pcm_feeder *feeder; u_int32_t align; - int volume; + int latency; u_int32_t speed; u_int32_t format; u_int32_t flags; @@ -55,17 +85,159 @@ u_int32_t blocks; int direction; - unsigned int interrupts, xruns; + unsigned int interrupts, xruns, feedcount; + unsigned int timeout; struct snd_dbuf *bufhard, *bufsoft; struct snddev_info *parentsnddev; struct pcm_channel *parentchannel; void *devinfo; device_t dev; + int unit; char name[CHN_NAMELEN]; + char comm[MAXCOMLEN + 1]; struct mtx *lock; - SLIST_HEAD(, pcmchan_children) children; + int trigger; + /** + * For interrupt manipulations. + */ + struct cv intr_cv; + /** + * Increment,decrement this around operations that temporarily yield + * lock. + */ + unsigned int inprog; + /** + * Special channel operations should examine @c inprog after acquiring + * lock. If zero, operations may continue. Else, thread should + * wait on this cv for previous operation to finish. + */ + struct cv cv; + /** + * Low water mark for select()/poll(). + * + * This is initialized to the channel's fragment size, and will be + * overwritten if a new fragment size is set. Users may alter this + * value directly with the @c SNDCTL_DSP_LOW_WATER ioctl. + */ + unsigned int lw; + /** + * If part of a sync group, this will point to the syncmember + * container. + */ + struct pcmchan_syncmember *sm; +#ifdef OSSV4_EXPERIMENT + u_int16_t lpeak, rpeak; /**< Peak value from 0-32767. */ +#endif + + struct { + SLIST_HEAD(, pcm_channel) head; + SLIST_ENTRY(pcm_channel) link; + struct { + SLIST_HEAD(, pcm_channel) head; + SLIST_ENTRY(pcm_channel) link; + } busy; + } children; + + struct { + struct { + SLIST_ENTRY(pcm_channel) link; + struct { + SLIST_ENTRY(pcm_channel) link; + } busy; + struct { + SLIST_ENTRY(pcm_channel) link; + } opened; + } pcm; + } channels; + + int volume[SND_VOL_C_MAX][SND_CHN_T_VOL_MAX]; + int matrix[SND_CHN_T_MAX + 1]; + + void *data1, *data2; }; +#define CHN_HEAD(x, y) &(x)->y.head +#define CHN_INIT(x, y) SLIST_INIT(CHN_HEAD(x, y)) +#define CHN_LINK(y) y.link +#define CHN_EMPTY(x, y) SLIST_EMPTY(CHN_HEAD(x, y)) +#define CHN_FIRST(x, y) SLIST_FIRST(CHN_HEAD(x, y)) + +#define CHN_FOREACH(x, y, z) \ + SLIST_FOREACH(x, CHN_HEAD(y, z), CHN_LINK(z)) + +#define CHN_FOREACH_SAFE(w, x, y, z) \ + SLIST_FOREACH_SAFE(w, CHN_HEAD(x, z), CHN_LINK(z), y) + +#define CHN_INSERT_HEAD(x, y, z) \ + SLIST_INSERT_HEAD(CHN_HEAD(x, z), y, CHN_LINK(z)) + +#define CHN_INSERT_AFTER(x, y, z) \ + SLIST_INSERT_AFTER(x, y, CHN_LINK(z)) + +#define CHN_REMOVE(x, y, z) \ + SLIST_REMOVE(CHN_HEAD(x, z), y, pcm_channel, CHN_LINK(z)) + +#define CHN_INSERT_HEAD_SAFE(x, y, z) do { \ + struct pcm_channel *t = NULL; \ + CHN_FOREACH(t, x, z) { \ + if (t == y) \ + break; \ + } \ + if (t != y) \ + CHN_INSERT_HEAD(x, y, z); \ +} while(0) + +#define CHN_INSERT_AFTER_SAFE(w, x, y, z) do { \ + struct pcm_channel *t = NULL; \ + CHN_FOREACH(t, w, z) { \ + if (t == y) \ + break; \ + } \ + if (t != y) \ + CHN_INSERT_AFTER(x, y, z); \ +} while(0) + +#define CHN_REMOVE_SAFE(x, y, z) do { \ + struct pcm_channel *t = NULL; \ + CHN_FOREACH(t, x, z) { \ + if (t == y) \ + break; \ + } \ + if (t == y) \ + CHN_REMOVE(x, y, z); \ +} while(0) + +#define CHN_INSERT_SORT(w, x, y, z) do { \ + struct pcm_channel *t, *a = NULL; \ + CHN_FOREACH(t, x, z) { \ + if ((y)->unit w t->unit) \ + a = t; \ + else \ + break; \ + } \ + if (a != NULL) \ + CHN_INSERT_AFTER(a, y, z); \ + else \ + CHN_INSERT_HEAD(x, y, z); \ +} while(0) + +#define CHN_INSERT_SORT_ASCEND(x, y, z) CHN_INSERT_SORT(>, x, y, z) +#define CHN_INSERT_SORT_DESCEND(x, y, z) CHN_INSERT_SORT(<, x, y, z) + +#define CHN_UNIT(x) (snd_unit2u((x)->unit)) +#define CHN_DEV(x) (snd_unit2d((x)->unit)) +#define CHN_CHAN(x) (snd_unit2c((x)->unit)) + +#define CHN_BUF_PARENT(x, y) \ + (((x) != NULL && (x)->parentchannel != NULL && \ + (x)->parentchannel->bufhard != NULL) ? \ + (x)->parentchannel->bufhard : (y)) + +#define CHN_BROADCAST(x) do { \ + if ((x)->cv_waiters != 0) \ + cv_broadcastpri(x, PRIBIO); \ +} while(0) + #include "channel_if.h" int chn_reinit(struct pcm_channel *c); @@ -81,9 +253,15 @@ int chn_setdir(struct pcm_channel *c, int dir); int chn_reset(struct pcm_channel *c, u_int32_t fmt); int chn_setvolume(struct pcm_channel *c, int left, int right); +int chn_setvolume_matrix(struct pcm_channel *c, snd_volume_class_t vc, + snd_channel_t vt, int val); +int chn_getvolume_matrix(struct pcm_channel *c, snd_volume_class_t vc, + snd_channel_t vt); +void chn_vpc_reset(struct pcm_channel *c, snd_volume_class_t vc, int force); int chn_setspeed(struct pcm_channel *c, int speed); int chn_setformat(struct pcm_channel *c, u_int32_t fmt); int chn_setblocksize(struct pcm_channel *c, int blkcnt, int blksz); +int chn_setlatency(struct pcm_channel *c, int latency); int chn_trigger(struct pcm_channel *c, int go); int chn_getptr(struct pcm_channel *c); struct pcmchan_caps *chn_getcaps(struct pcm_channel *c); @@ -102,21 +280,63 @@ void chn_lock(struct pcm_channel *c); void chn_unlock(struct pcm_channel *c); +int chn_getrates(struct pcm_channel *c, int **rates); +int chn_syncdestroy(struct pcm_channel *c); + +#define CHN_SETVOLUME(x...) chn_setvolume_matrix(x) +#if defined(SND_DIAGNOSTIC) || defined(INVARIANTS) +#define CHN_GETVOLUME(x...) chn_getvolume_matrix(x) +#else +#define CHN_GETVOLUME(x, y, z) ((x)->volume[y][z]) +#endif + +#ifdef OSSV4_EXPERIMENT +int chn_getpeaks(struct pcm_channel *c, int *lpeak, int *rpeak); +#endif + #ifdef USING_MUTEX #define CHN_LOCK(c) mtx_lock((struct mtx *)((c)->lock)) #define CHN_UNLOCK(c) mtx_unlock((struct mtx *)((c)->lock)) +#define CHN_TRYLOCK(c) mtx_trylock((struct mtx *)((c)->lock)) #define CHN_LOCKASSERT(c) mtx_assert((struct mtx *)((c)->lock), MA_OWNED) #else #define CHN_LOCK(c) #define CHN_UNLOCK(c) +#define CHN_TRYLOCK(c) #define CHN_LOCKASSERT(c) #endif int fmtvalid(u_int32_t fmt, u_int32_t *fmtlist); -#define PCMDIR_VIRTUAL 2 -#define PCMDIR_PLAY 1 -#define PCMDIR_REC -1 +#define AFMTSTR_NONE 0 /* "s16le" */ +#define AFMTSTR_SIMPLE 1 /* "s16le:s" */ +#define AFMTSTR_NUM 2 /* "s16le:2" */ +#define AFMTSTR_FULL 3 /* "s16le:stereo" */ + +#define AFMTSTR_MAXSZ 13 /* include null terminator */ + +#define AFMTSTR_MONO_RETURN 0 +#define AFMTSTR_STEREO_RETURN 1 + +struct afmtstr_table { + char *fmtstr; + u_int32_t format; +}; + +int afmtstr_swap_sign(char *); +int afmtstr_swap_endian(char *); +u_int32_t afmtstr2afmt(struct afmtstr_table *, const char *, int); +u_int32_t afmt2afmtstr(struct afmtstr_table *, u_int32_t, char *, size_t, int, int); + +extern int chn_latency; +extern int chn_latency_profile; +extern int report_soft_formats; + +#define PCMDIR_FAKE 0 +#define PCMDIR_PLAY 1 +#define PCMDIR_PLAY_VIRTUAL 2 +#define PCMDIR_REC -1 +#define PCMDIR_REC_VIRTUAL -2 #define PCMTRIG_START 1 #define PCMTRIG_EMLDMAWR 2 @@ -124,11 +344,16 @@ #define PCMTRIG_STOP 0 #define PCMTRIG_ABORT -1 +#define PCMTRIG_COMMON(x) ((x) == PCMTRIG_START || \ + (x) == PCMTRIG_STOP || \ + (x) == PCMTRIG_ABORT) + #define CHN_F_CLOSING 0x00000004 /* a pending close */ #define CHN_F_ABORTING 0x00000008 /* a pending abort */ #define CHN_F_RUNNING 0x00000010 /* dma is running */ #define CHN_F_TRIGGERED 0x00000020 #define CHN_F_NOTRIGGER 0x00000040 +#define CHN_F_SLEEPING 0x00000080 #define CHN_F_BUSY 0x00001000 /* has been opened */ #define CHN_F_HAS_SIZE 0x00002000 /* user set block size */ @@ -137,16 +362,42 @@ #define CHN_F_DEAD 0x00020000 #define CHN_F_BADSETTING 0x00040000 #define CHN_F_SETBLOCKSIZE 0x00080000 +#define CHN_F_HAS_VCHAN 0x00100000 #define CHN_F_VIRTUAL 0x10000000 /* not backed by hardware */ -#define CHN_F_RESET (CHN_F_BUSY | CHN_F_DEAD | CHN_F_VIRTUAL) +#define CHN_F_RESET (CHN_F_BUSY | CHN_F_DEAD | \ + CHN_F_HAS_VCHAN | CHN_F_VIRTUAL) + +#define CHN_F_MMAP_INVALID (CHN_F_DEAD | CHN_F_RUNNING) + + #define CHN_N_RATE 0x00000001 #define CHN_N_FORMAT 0x00000002 #define CHN_N_VOLUME 0x00000004 #define CHN_N_BLOCKSIZE 0x00000008 #define CHN_N_TRIGGER 0x00000010 + +#define CHN_LATENCY_MIN 0 +#define CHN_LATENCY_MAX 10 +#define CHN_LATENCY_DEFAULT 5 +#define CHN_POLICY_MIN CHN_LATENCY_MIN +#define CHN_POLICY_MAX CHN_LATENCY_MAX +#define CHN_POLICY_DEFAULT CHN_LATENCY_DEFAULT + +#define CHN_LATENCY_PROFILE_MIN 0 +#define CHN_LATENCY_PROFILE_MAX 1 +#define CHN_LATENCY_PROFILE_DEFAULT CHN_LATENCY_PROFILE_MAX + +#define CHN_STARTED(c) ((c)->flags & CHN_F_TRIGGERED) +#define CHN_STOPPED(c) (!CHN_STARTED(c)) +#define CHN_DIRSTR(c) (((c)->direction == PCMDIR_PLAY) ? \ + "PCMDIR_PLAY" : "PCMDIR_REC") + +#define CHN_TIMEOUT 5 +#define CHN_TIMEOUT_MIN 1 +#define CHN_TIMEOUT_MAX 10 /* * This should be large enough to hold all pcm data between --- sys/dev/sound/pcm/channel_if.m.orig Sun Jan 30 09:00:04 2005 +++ sys/dev/sound/pcm/channel_if.m Thu Jul 12 12:04:19 2007 @@ -25,7 +25,7 @@ # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF # SUCH DAMAGE. # -# $FreeBSD: src/sys/dev/sound/pcm/channel_if.m,v 1.4.8.1 2005/01/30 01:00:04 imp Exp $ +# $FreeBSD: src/sys/dev/sound/pcm/channel_if.m,v 1.7 2007/03/16 17:16:24 ariff Exp $ # #include @@ -70,6 +70,25 @@ return 0; } + static int + channel_nogetpeaks(kobj_t obj, void *data, int *lpeak, int *rpeak) + { + return -1; + } + + static int + channel_nogetrates(kobj_t obj, void *data, int **rates) + { + *rates = NULL; + return 0; + } + + static int + channel_nosetfragments(kobj_t obj, void *data, u_int32_t blocksize, u_int32_t blockcount) + { + return 0; + } + }; METHOD void* init { @@ -119,6 +138,13 @@ u_int32_t blocksize; }; +METHOD int setfragments { + kobj_t obj; + void *data; + u_int32_t blocksize; + u_int32_t blockcount; +} DEFAULT channel_nosetfragments; + METHOD int trigger { kobj_t obj; void *data; @@ -140,3 +166,54 @@ void *data; u_int32_t changed; } DEFAULT channel_nonotify; + +/** + * @brief Retrieve channel peak values + * + * This function is intended to obtain peak volume values for samples + * played/recorded on a channel. Values are on a linear scale from 0 to + * 32767. If the channel is monaural, a single value should be recorded + * in @c lpeak. + * + * If hardware support isn't available, the SNDCTL_DSP_GET[IO]PEAKS + * operation should return EINVAL. However, we may opt to provide + * software support that the user may toggle via sysctl/mixext. + * + * @param obj standard kobj object (usually @c channel->methods) + * @param data driver-specific data (usually @c channel->devinfo) + * @param lpeak pointer to store left peak level + * @param rpeak pointer to store right peak level + * + * @retval -1 Error; usually operation isn't supported. + * @retval 0 success + */ +METHOD int getpeaks { + kobj_t obj; + void *data; + int *lpeak; + int *rpeak; +} DEFAULT channel_nogetpeaks; + +/** + * @brief Retrieve discrete supported sample rates + * + * Some cards operate at fixed rates, and this call is intended to retrieve + * those rates primarily for when in-kernel rate adjustment is undesirable + * (e.g., application wants direct DMA access after setting a channel to run + * "uncooked"). + * + * The parameter @c rates is a double pointer which will be reset to + * point to an array of supported sample rates. The number of elements + * in the array is returned to the caller. + * + * @param obj standard kobj object (usually @c channel->methods) + * @param data driver-specific data (usually @c channel->devinfo) + * @param rates rate array pointer + * + * @return Number of rates in the array + */ +METHOD int getrates { + kobj_t obj; + void *data; + int **rates; +} DEFAULT channel_nogetrates; --- sys/dev/sound/pcm/dsp.c.orig Sun Jan 30 09:00:04 2005 +++ sys/dev/sound/pcm/dsp.c Thu Jul 12 12:04:19 2007 @@ -24,12 +24,31 @@ * SUCH DAMAGE. */ -#include -#include - #include +#include + +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pcm/dsp.c,v 1.107 2007/07/04 12:33:11 ariff Exp $"); + +static int dsp_mmap_allow_prot_exec = 0; +SYSCTL_INT(_hw_snd, OID_AUTO, compat_linux_mmap, CTLFLAG_RW, + &dsp_mmap_allow_prot_exec, 0, "linux mmap compatibility"); + +struct dsp_cdevinfo { + struct pcm_channel *rdch, *wrch; + struct pcm_channel *volch; + int busy, simplex; + TAILQ_ENTRY(dsp_cdevinfo) link; +}; + +#define PCM_RDCH(x) (((struct dsp_cdevinfo *)(x)->si_drv1)->rdch) +#define PCM_WRCH(x) (((struct dsp_cdevinfo *)(x)->si_drv1)->wrch) +#define PCM_VOLCH(x) (((struct dsp_cdevinfo *)(x)->si_drv1)->volch) +#define PCM_SIMPLEX(x) (((struct dsp_cdevinfo *)(x)->si_drv1)->simplex) + +#define DSP_CDEVINFO_CACHESIZE 8 -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pcm/dsp.c,v 1.77.2.1 2005/01/30 01:00:04 imp Exp $"); +#define DSP_REGISTERED(x, y) (PCM_REGISTERED(x) && \ + (y) != NULL && (y)->si_drv1 != NULL) #define OLDPCM_IOCTL @@ -43,7 +62,6 @@ struct cdevsw dsp_cdevsw = { .d_version = D_VERSION, - .d_flags = D_NEEDGIANT, .d_open = dsp_open, .d_close = dsp_close, .d_read = dsp_read, @@ -52,455 +70,1063 @@ .d_poll = dsp_poll, .d_mmap = dsp_mmap, .d_name = "dsp", - .d_maj = SND_CDEV_MAJOR, }; #ifdef USING_DEVFS -static eventhandler_tag dsp_ehtag; +static eventhandler_tag dsp_ehtag = NULL; +static int dsp_umax = -1; +static int dsp_cmax = -1; +#endif + +static int dsp_oss_syncgroup(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_syncgroup *group); +static int dsp_oss_syncstart(int sg_id); +static int dsp_oss_policy(struct pcm_channel *wrch, struct pcm_channel *rdch, int policy); +#ifdef OSSV4_EXPERIMENT +static int dsp_oss_cookedmode(struct pcm_channel *wrch, struct pcm_channel *rdch, int enabled); +static int dsp_oss_getchnorder(struct pcm_channel *wrch, struct pcm_channel *rdch, unsigned long long *map); +static int dsp_oss_setchnorder(struct pcm_channel *wrch, struct pcm_channel *rdch, unsigned long long *map); +static int dsp_oss_getlabel(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_label_t *label); +static int dsp_oss_setlabel(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_label_t *label); +static int dsp_oss_getsong(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_longname_t *song); +static int dsp_oss_setsong(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_longname_t *song); +static int dsp_oss_setname(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_longname_t *name); #endif static struct snddev_info * dsp_get_info(struct cdev *dev) { - struct snddev_info *d; - int unit; - - unit = PCMUNIT(dev); - if (unit >= devclass_get_maxunit(pcm_devclass)) - return NULL; - d = devclass_get_softc(pcm_devclass, unit); - - return d; + return (devclass_get_softc(pcm_devclass, PCMUNIT(dev))); } -static u_int32_t +static uint32_t dsp_get_flags(struct cdev *dev) { device_t bdev; - int unit; - unit = PCMUNIT(dev); - if (unit >= devclass_get_maxunit(pcm_devclass)) - return 0xffffffff; - bdev = devclass_get_device(pcm_devclass, unit); + bdev = devclass_get_device(pcm_devclass, PCMUNIT(dev)); - return pcm_getflags(bdev); + return ((bdev != NULL) ? pcm_getflags(bdev) : 0xffffffff); } static void -dsp_set_flags(struct cdev *dev, u_int32_t flags) +dsp_set_flags(struct cdev *dev, uint32_t flags) { device_t bdev; - int unit; - unit = PCMUNIT(dev); - if (unit >= devclass_get_maxunit(pcm_devclass)) - return; - bdev = devclass_get_device(pcm_devclass, unit); + bdev = devclass_get_device(pcm_devclass, PCMUNIT(dev)); - pcm_setflags(bdev, flags); + if (bdev != NULL) + pcm_setflags(bdev, flags); } /* - * return the channels channels associated with an open device instance. - * set the priority if the device is simplex and one direction (only) is - * specified. + * return the channels associated with an open device instance. * lock channels specified. */ static int -getchns(struct cdev *dev, struct pcm_channel **rdch, struct pcm_channel **wrch, u_int32_t prio) +getchns(struct cdev *dev, struct pcm_channel **rdch, struct pcm_channel **wrch, + uint32_t prio) { struct snddev_info *d; - u_int32_t flags; - - flags = dsp_get_flags(dev); - d = dsp_get_info(dev); - pcm_inprog(d, 1); - pcm_lock(d); - KASSERT((flags & SD_F_PRIO_SET) != SD_F_PRIO_SET, \ - ("getchns: read and write both prioritised")); + struct pcm_channel *ch; + uint32_t flags; - if ((flags & SD_F_PRIO_SET) == 0 && (prio != (SD_F_PRIO_RD | SD_F_PRIO_WR))) { - flags |= prio & (SD_F_PRIO_RD | SD_F_PRIO_WR); + if (PCM_SIMPLEX(dev) != 0) { + d = dsp_get_info(dev); + if (!PCM_REGISTERED(d)) + return (ENXIO); + pcm_lock(d); + PCM_WAIT(d); + PCM_ACQUIRE(d); + /* + * Note: order is important - + * pcm flags -> prio query flags -> wild guess + */ + ch = NULL; + flags = dsp_get_flags(dev); + if (flags & SD_F_PRIO_WR) { + ch = PCM_RDCH(dev); + PCM_RDCH(dev) = NULL; + } else if (flags & SD_F_PRIO_RD) { + ch = PCM_WRCH(dev); + PCM_WRCH(dev) = NULL; + } else if (prio & SD_F_PRIO_WR) { + ch = PCM_RDCH(dev); + PCM_RDCH(dev) = NULL; + flags |= SD_F_PRIO_WR; + } else if (prio & SD_F_PRIO_RD) { + ch = PCM_WRCH(dev); + PCM_WRCH(dev) = NULL; + flags |= SD_F_PRIO_RD; + } else if (PCM_WRCH(dev) != NULL) { + ch = PCM_RDCH(dev); + PCM_RDCH(dev) = NULL; + flags |= SD_F_PRIO_WR; + } else if (PCM_RDCH(dev) != NULL) { + ch = PCM_WRCH(dev); + PCM_WRCH(dev) = NULL; + flags |= SD_F_PRIO_RD; + } + PCM_SIMPLEX(dev) = 0; dsp_set_flags(dev, flags); - } - - *rdch = dev->si_drv1; - *wrch = dev->si_drv2; - if ((flags & SD_F_SIMPLEX) && (flags & SD_F_PRIO_SET)) { - if (prio) { - if (*rdch && flags & SD_F_PRIO_WR) { - dev->si_drv1 = NULL; - *rdch = pcm_getfakechan(d); - } else if (*wrch && flags & SD_F_PRIO_RD) { - dev->si_drv2 = NULL; - *wrch = pcm_getfakechan(d); - } + if (ch != NULL) { + CHN_LOCK(ch); + pcm_chnref(ch, -1); + pcm_chnrelease(ch); } - - pcm_getfakechan(d)->flags |= CHN_F_BUSY; + PCM_RELEASE(d); + pcm_unlock(d); } - pcm_unlock(d); - if (*rdch && *rdch != pcm_getfakechan(d) && (prio & SD_F_PRIO_RD)) + *rdch = PCM_RDCH(dev); + *wrch = PCM_WRCH(dev); + + if (*rdch != NULL && (prio & SD_F_PRIO_RD)) CHN_LOCK(*rdch); - if (*wrch && *wrch != pcm_getfakechan(d) && (prio & SD_F_PRIO_WR)) + if (*wrch != NULL && (prio & SD_F_PRIO_WR)) CHN_LOCK(*wrch); - return 0; + return (0); } /* unlock specified channels */ static void -relchns(struct cdev *dev, struct pcm_channel *rdch, struct pcm_channel *wrch, u_int32_t prio) +relchns(struct cdev *dev, struct pcm_channel *rdch, struct pcm_channel *wrch, + uint32_t prio) { - struct snddev_info *d; - - d = dsp_get_info(dev); - if (wrch && wrch != pcm_getfakechan(d) && (prio & SD_F_PRIO_WR)) + if (wrch != NULL && (prio & SD_F_PRIO_WR)) CHN_UNLOCK(wrch); - if (rdch && rdch != pcm_getfakechan(d) && (prio & SD_F_PRIO_RD)) + if (rdch != NULL && (prio & SD_F_PRIO_RD)) CHN_UNLOCK(rdch); - pcm_inprog(d, -1); } -static int -dsp_open(struct cdev *i_dev, int flags, int mode, struct thread *td) +static void +dsp_cdevinfo_alloc(struct cdev *dev, + struct pcm_channel *rdch, struct pcm_channel *wrch, + struct pcm_channel *volch) { - struct pcm_channel *rdch, *wrch; struct snddev_info *d; - intrmask_t s; - u_int32_t fmt; - int devtype; - int rdref; - int error; + struct dsp_cdevinfo *cdi; + int simplex; - s = spltty(); - d = dsp_get_info(i_dev); - devtype = PCMDEV(i_dev); + d = dsp_get_info(dev); - /* decide default format */ - switch (devtype) { - case SND_DEV_DSP16: - fmt = AFMT_S16_LE; - break; + KASSERT(PCM_REGISTERED(d) && dev != NULL && dev->si_drv1 == NULL && + ((rdch == NULL && wrch == NULL) || rdch != wrch), + ("bogus %s(), what are you trying to accomplish here?", __func__)); + PCM_BUSYASSERT(d); + mtx_assert(d->lock, MA_OWNED); - case SND_DEV_DSP: - fmt = AFMT_U8; - break; + simplex = (dsp_get_flags(dev) & SD_F_SIMPLEX) ? 1 : 0; - case SND_DEV_AUDIO: - fmt = AFMT_MU_LAW; - break; + /* + * Scan for free instance entry and put it into the end of list. + * Create new one if necessary. + */ + TAILQ_FOREACH(cdi, &d->dsp_cdevinfo_pool, link) { + if (cdi->busy != 0) + break; + cdi->rdch = rdch; + cdi->wrch = wrch; + cdi->volch = volch; + cdi->simplex = simplex; + cdi->busy = 1; + TAILQ_REMOVE(&d->dsp_cdevinfo_pool, cdi, link); + TAILQ_INSERT_TAIL(&d->dsp_cdevinfo_pool, cdi, link); + dev->si_drv1 = cdi; + return; + } + pcm_unlock(d); + cdi = malloc(sizeof(*cdi), M_DEVBUF, M_WAITOK | M_ZERO); + pcm_lock(d); + cdi->rdch = rdch; + cdi->wrch = wrch; + cdi->volch = volch; + cdi->simplex = simplex; + cdi->busy = 1; + TAILQ_INSERT_TAIL(&d->dsp_cdevinfo_pool, cdi, link); + dev->si_drv1 = cdi; +} - case SND_DEV_NORESET: - fmt = 0; - break; +static void +dsp_cdevinfo_free(struct cdev *dev) +{ + struct snddev_info *d; + struct dsp_cdevinfo *cdi, *tmp; + uint32_t flags; + int i; + + d = dsp_get_info(dev); + + KASSERT(PCM_REGISTERED(d) && dev != NULL && dev->si_drv1 != NULL && + PCM_RDCH(dev) == NULL && PCM_WRCH(dev) == NULL && + PCM_VOLCH(dev) == NULL, + ("bogus %s(), what are you trying to accomplish here?", __func__)); + PCM_BUSYASSERT(d); + mtx_assert(d->lock, MA_OWNED); + + cdi = dev->si_drv1; + dev->si_drv1 = NULL; + cdi->rdch = NULL; + cdi->wrch = NULL; + cdi->volch = NULL; + cdi->simplex = 0; + cdi->busy = 0; + + /* + * Once it is free, move it back to the beginning of list for + * faster new entry allocation. + */ + TAILQ_REMOVE(&d->dsp_cdevinfo_pool, cdi, link); + TAILQ_INSERT_HEAD(&d->dsp_cdevinfo_pool, cdi, link); - case SND_DEV_DSPREC: - fmt = AFMT_U8; - if (mode & FWRITE) { - splx(s); - return EINVAL; + /* + * Scan the list, cache free entries up to DSP_CDEVINFO_CACHESIZE. + * Reset simplex flags. + */ + flags = dsp_get_flags(dev) & ~SD_F_PRIO_SET; + i = DSP_CDEVINFO_CACHESIZE; + TAILQ_FOREACH_SAFE(cdi, &d->dsp_cdevinfo_pool, link, tmp) { + if (cdi->busy != 0) { + if (cdi->simplex == 0) { + if (cdi->rdch != NULL) + flags |= SD_F_PRIO_RD; + if (cdi->wrch != NULL) + flags |= SD_F_PRIO_WR; + } + } else { + if (i == 0) { + TAILQ_REMOVE(&d->dsp_cdevinfo_pool, cdi, link); + free(cdi, M_DEVBUF); + } else + i--; } - break; + } + dsp_set_flags(dev, flags); +} - default: - panic("impossible devtype %d", devtype); +void +dsp_cdevinfo_init(struct snddev_info *d) +{ + struct dsp_cdevinfo *cdi; + int i; + + KASSERT(d != NULL, ("NULL snddev_info")); + PCM_BUSYASSERT(d); + mtx_assert(d->lock, MA_NOTOWNED); + + TAILQ_INIT(&d->dsp_cdevinfo_pool); + for (i = 0; i < DSP_CDEVINFO_CACHESIZE; i++) { + cdi = malloc(sizeof(*cdi), M_DEVBUF, M_WAITOK | M_ZERO); + TAILQ_INSERT_HEAD(&d->dsp_cdevinfo_pool, cdi, link); } +} + +void +dsp_cdevinfo_flush(struct snddev_info *d) +{ + struct dsp_cdevinfo *cdi, *tmp; + + KASSERT(d != NULL, ("NULL snddev_info")); + PCM_BUSYASSERT(d); + mtx_assert(d->lock, MA_NOTOWNED); + + cdi = TAILQ_FIRST(&d->dsp_cdevinfo_pool); + while (cdi != NULL) { + tmp = TAILQ_NEXT(cdi, link); + free(cdi, M_DEVBUF); + cdi = tmp; + } + TAILQ_INIT(&d->dsp_cdevinfo_pool); +} + +/* duplex / simplex cdev type */ +enum { + DSP_CDEV_TYPE_RDONLY, /* simplex read-only (record) */ + DSP_CDEV_TYPE_WRONLY, /* simplex write-only (play) */ + DSP_CDEV_TYPE_RDWR, /* duplex read, write, or both */ +}; - rdref = 0; +enum { + DSP_CDEV_VOLCTL_NONE, + DSP_CDEV_VOLCTL_READ, + DSP_CDEV_VOLCTL_WRITE, +}; + +#define DSP_F_VALID(x) ((x) & (FREAD | FWRITE)) +#define DSP_F_DUPLEX(x) (((x) & (FREAD | FWRITE)) == (FREAD | FWRITE)) +#define DSP_F_SIMPLEX(x) (!DSP_F_DUPLEX(x)) +#define DSP_F_READ(x) ((x) & FREAD) +#define DSP_F_WRITE(x) ((x) & FWRITE) + +static const struct { + int type; + char *name; + char *sep; + char *alias; + int use_sep; + int hw; + int max; + int volctl; + uint32_t fmt, spd; + int query; +} dsp_cdevs[] = { + { SND_DEV_DSP, "dsp", ".", NULL, 0, 0, 0, 0, + AFMT_U8, DSP_DEFAULT_SPEED, DSP_CDEV_TYPE_RDWR }, + { SND_DEV_AUDIO, "audio", ".", NULL, 0, 0, 0, 0, + AFMT_MU_LAW, DSP_DEFAULT_SPEED, DSP_CDEV_TYPE_RDWR }, + { SND_DEV_DSP16, "dspW", ".", NULL, 0, 0, 0, 0, + AFMT_S16_LE, DSP_DEFAULT_SPEED, DSP_CDEV_TYPE_RDWR }, + { SND_DEV_DSPHW_PLAY, "dsp", ".p", NULL, 1, 1, SND_MAXHWCHAN, 1, + AFMT_S16_LE | AFMT_STEREO, 48000, DSP_CDEV_TYPE_WRONLY }, + { SND_DEV_DSPHW_VPLAY, "dsp", ".vp", NULL, 1, 1, SND_MAXVCHANS, 1, + AFMT_S16_LE | AFMT_STEREO, 48000, DSP_CDEV_TYPE_WRONLY }, + { SND_DEV_DSPHW_REC, "dsp", ".r", NULL, 1, 1, SND_MAXHWCHAN, 1, + AFMT_S16_LE | AFMT_STEREO, 48000, DSP_CDEV_TYPE_RDONLY }, + { SND_DEV_DSPHW_VREC, "dsp", ".vr", NULL, 1, 1, SND_MAXVCHANS, 1, + AFMT_S16_LE | AFMT_STEREO, 48000, DSP_CDEV_TYPE_RDONLY }, + { SND_DEV_DSPHW_CD, "dspcd", ".", NULL, 0, 0, 0, 0, + AFMT_S16_LE | AFMT_STEREO, 44100, DSP_CDEV_TYPE_RDWR }, + /* Low priority, OSSv4 aliases. */ + { SND_DEV_DSP, "dsp_ac3", ".", "dsp", 0, 0, 0, 0, + AFMT_U8, DSP_DEFAULT_SPEED, DSP_CDEV_TYPE_RDWR }, + { SND_DEV_DSP, "dsp_mmap", ".", "dsp", 0, 0, 0, 0, + AFMT_U8, DSP_DEFAULT_SPEED, DSP_CDEV_TYPE_RDWR }, + { SND_DEV_DSP, "dsp_multich", ".", "dsp", 0, 0, 0, 0, + AFMT_U8, DSP_DEFAULT_SPEED, DSP_CDEV_TYPE_RDWR }, + { SND_DEV_DSP, "dsp_spdifout", ".", "dsp", 0, 0, 0, 0, + AFMT_U8, DSP_DEFAULT_SPEED, DSP_CDEV_TYPE_RDWR }, + { SND_DEV_DSP, "dsp_spdifin", ".", "dsp", 0, 0, 0, 0, + AFMT_U8, DSP_DEFAULT_SPEED, DSP_CDEV_TYPE_RDWR }, +}; + +#define DSP_FIXUP_ERROR() do { \ + prio = dsp_get_flags(i_dev); \ + if (!DSP_F_VALID(flags)) \ + error = EINVAL; \ + if (!DSP_F_DUPLEX(flags) && \ + ((DSP_F_READ(flags) && d->reccount == 0) || \ + (DSP_F_WRITE(flags) && d->playcount == 0))) \ + error = ENOTSUP; \ + else if (!DSP_F_DUPLEX(flags) && (prio & SD_F_SIMPLEX) && \ + ((DSP_F_READ(flags) && (prio & SD_F_PRIO_WR)) || \ + (DSP_F_WRITE(flags) && (prio & SD_F_PRIO_RD)))) \ + error = EBUSY; \ + else if (DSP_REGISTERED(d, i_dev)) \ + error = EBUSY; \ +} while(0) + +static int +dsp_open(struct cdev *i_dev, int flags, int mode, struct thread *td) +{ + struct pcm_channel *rdch, *wrch; + struct snddev_info *d; + uint32_t fmt, spd, prio, volctl; + int i, error, rderror, wrerror, devtype, wdevunit, rdevunit; + + /* Kind of impossible.. */ + if (i_dev == NULL || td == NULL) + return (ENODEV); + + d = dsp_get_info(i_dev); + if (!PCM_REGISTERED(d)) + return (EBADF); + + PCM_GIANT_ENTER(d); - /* lock snddev so nobody else can monkey with it */ + /* Lock snddev so nobody else can monkey with it. */ pcm_lock(d); + PCM_WAIT(d); + + /* + * Try to acquire cloned device before someone else pick it. + * ENODEV means this is not a cloned droids. + */ + error = snd_clone_acquire(i_dev); + if (!(error == 0 || error == ENODEV)) { + DSP_FIXUP_ERROR(); + pcm_unlock(d); + PCM_GIANT_EXIT(d); + return (error); + } - rdch = i_dev->si_drv1; - wrch = i_dev->si_drv2; + error = 0; + DSP_FIXUP_ERROR(); - if ((dsp_get_flags(i_dev) & SD_F_SIMPLEX) && (rdch || wrch)) { - /* we're a simplex device and already open, no go */ + if (error != 0) { + (void)snd_clone_release(i_dev); pcm_unlock(d); - splx(s); - return EBUSY; + PCM_GIANT_EXIT(d); + return (error); } - if (((flags & FREAD) && rdch) || ((flags & FWRITE) && wrch)) { + /* + * That is just enough. Acquire and unlock pcm lock so + * the other will just have to wait until we finish doing + * everything. + */ + PCM_ACQUIRE(d); + pcm_unlock(d); + + devtype = PCMDEV(i_dev); + wdevunit = -1; + rdevunit = -1; + fmt = 0; + spd = 0; + volctl = DSP_CDEV_VOLCTL_NONE; + + for (i = 0; i < (sizeof(dsp_cdevs) / sizeof(dsp_cdevs[0])); i++) { + if (devtype != dsp_cdevs[i].type || dsp_cdevs[i].alias != NULL) + continue; /* - * device already open in one or both directions that - * the opener wants; we can't handle this. + * Volume control only valid for DSPHW devices, + * and it must be opened in opposite direction be it + * simplex or duplex. Anything else will be handled + * as usual. */ - pcm_unlock(d); - splx(s); - return EBUSY; + if (dsp_cdevs[i].query == DSP_CDEV_TYPE_WRONLY) { + if (dsp_cdevs[i].volctl != 0 && + DSP_F_READ(flags)) { + volctl = DSP_CDEV_VOLCTL_WRITE; + flags &= ~FREAD; + flags |= FWRITE; + } + if (DSP_F_READ(flags)) { + (void)snd_clone_release(i_dev); + PCM_RELEASE_QUICK(d); + PCM_GIANT_EXIT(d); + return (ENOTSUP); + } + wdevunit = dev2unit(i_dev); + } else if (dsp_cdevs[i].query == DSP_CDEV_TYPE_RDONLY) { + if (dsp_cdevs[i].volctl != 0 && + DSP_F_WRITE(flags)) { + volctl = DSP_CDEV_VOLCTL_READ; + flags &= ~FWRITE; + flags |= FREAD; + } + if (DSP_F_WRITE(flags)) { + (void)snd_clone_release(i_dev); + PCM_RELEASE_QUICK(d); + PCM_GIANT_EXIT(d); + return (ENOTSUP); + } + rdevunit = dev2unit(i_dev); + } + fmt = dsp_cdevs[i].fmt; + spd = dsp_cdevs[i].spd; + break; } + /* No matching devtype? */ + if (fmt == 0 || spd == 0) + panic("impossible devtype %d", devtype); + + rdch = NULL; + wrch = NULL; + rderror = 0; + wrerror = 0; + /* * if we get here, the open request is valid- either: * * we were previously not open * * we were open for play xor record and the opener wants * the non-open direction */ - if (flags & FREAD) { + if (DSP_F_READ(flags)) { /* open for read */ - if (devtype == SND_DEV_DSPREC) - rdch = pcm_chnalloc(d, PCMDIR_REC, td->td_proc->p_pid, PCMCHAN(i_dev)); - else - rdch = pcm_chnalloc(d, PCMDIR_REC, td->td_proc->p_pid, -1); - if (!rdch) { - /* no channel available, exit */ - pcm_unlock(d); - splx(s); - return EBUSY; - } - /* got a channel, already locked for us */ - if (chn_reset(rdch, fmt)) { - pcm_chnrelease(rdch); - i_dev->si_drv1 = NULL; - pcm_unlock(d); - splx(s); - return ENODEV; + rderror = pcm_chnalloc(d, &rdch, PCMDIR_REC, + td->td_proc->p_pid, td->td_proc->p_comm, rdevunit); + + if (rderror == 0 && (chn_reset(rdch, fmt) != 0 || + (chn_setspeed(rdch, spd) != 0))) + rderror = ENXIO; + + if (volctl == DSP_CDEV_VOLCTL_READ) + rderror = 0; + + if (rderror != 0) { + if (rdch != NULL) + pcm_chnrelease(rdch); + if (!DSP_F_DUPLEX(flags)) { + (void)snd_clone_release(i_dev); + PCM_RELEASE_QUICK(d); + PCM_GIANT_EXIT(d); + return (rderror); + } + rdch = NULL; + } else if (volctl == DSP_CDEV_VOLCTL_READ) { + if (rdch != NULL) { + pcm_chnref(rdch, 1); + pcm_chnrelease(rdch); + } + } else { + if (flags & O_NONBLOCK) + rdch->flags |= CHN_F_NBIO; + pcm_chnref(rdch, 1); + if (volctl == DSP_CDEV_VOLCTL_NONE) + chn_vpc_reset(rdch, SND_VOL_C_PCM, 0); + CHN_UNLOCK(rdch); } + } - if (flags & O_NONBLOCK) - rdch->flags |= CHN_F_NBIO; - pcm_chnref(rdch, 1); - CHN_UNLOCK(rdch); - rdref = 1; - /* - * Record channel created, ref'ed and unlocked - */ + if (DSP_F_WRITE(flags)) { + /* open for write */ + wrerror = pcm_chnalloc(d, &wrch, PCMDIR_PLAY, + td->td_proc->p_pid, td->td_proc->p_comm, wdevunit); + + if (wrerror == 0 && (chn_reset(wrch, fmt) != 0 || + (chn_setspeed(wrch, spd) != 0))) + wrerror = ENXIO; + + if (volctl == DSP_CDEV_VOLCTL_WRITE) + wrerror = 0; + + if (wrerror != 0) { + if (wrch != NULL) + pcm_chnrelease(wrch); + if (!DSP_F_DUPLEX(flags)) { + if (rdch != NULL) { + /* + * Lock, deref and release previously + * created record channel + */ + CHN_LOCK(rdch); + pcm_chnref(rdch, -1); + pcm_chnrelease(rdch); + } + (void)snd_clone_release(i_dev); + PCM_RELEASE_QUICK(d); + PCM_GIANT_EXIT(d); + return (wrerror); + } + wrch = NULL; + } else if (volctl == DSP_CDEV_VOLCTL_WRITE) { + if (wrch != NULL) { + pcm_chnref(wrch, 1); + pcm_chnrelease(wrch); + } + } else { + if (flags & O_NONBLOCK) + wrch->flags |= CHN_F_NBIO; + pcm_chnref(wrch, 1); + if (volctl == DSP_CDEV_VOLCTL_NONE) + chn_vpc_reset(wrch, SND_VOL_C_PCM, 0); + CHN_UNLOCK(wrch); + } } - if (flags & FWRITE) { - /* open for write */ - wrch = pcm_chnalloc(d, PCMDIR_PLAY, td->td_proc->p_pid, -1); - error = 0; - - if (!wrch) - error = EBUSY; /* XXX Right return code? */ - else if (chn_reset(wrch, fmt)) - error = ENODEV; - if (error != 0) { - if (wrch) { - /* - * Free play channel - */ - pcm_chnrelease(wrch); - i_dev->si_drv2 = NULL; - } - if (rdref) { - /* - * Lock, deref and release previously created record channel - */ - CHN_LOCK(rdch); - pcm_chnref(rdch, -1); - pcm_chnrelease(rdch); - i_dev->si_drv1 = NULL; - } + pcm_lock(d); - pcm_unlock(d); - splx(s); - return error; - } - - if (flags & O_NONBLOCK) - wrch->flags |= CHN_F_NBIO; - pcm_chnref(wrch, 1); - CHN_UNLOCK(wrch); + /* + * We're done. Allocate channels information for this cdev. + */ + switch (volctl) { + case DSP_CDEV_VOLCTL_READ: + KASSERT(wrch == NULL, ("wrch=%p not null!", wrch)); + dsp_cdevinfo_alloc(i_dev, NULL, NULL, rdch); + break; + case DSP_CDEV_VOLCTL_WRITE: + KASSERT(rdch == NULL, ("rdch=%p not null!", rdch)); + dsp_cdevinfo_alloc(i_dev, NULL, NULL, wrch); + break; + case DSP_CDEV_VOLCTL_NONE: + default: + if (wrch == NULL && rdch == NULL) { + (void)snd_clone_release(i_dev); + PCM_RELEASE(d); + pcm_unlock(d); + PCM_GIANT_EXIT(d); + if (wrerror != 0) + return (wrerror); + if (rderror != 0) + return (rderror); + return (EINVAL); + } + dsp_cdevinfo_alloc(i_dev, rdch, wrch, NULL); + if (rdch != NULL) + CHN_INSERT_HEAD(d, rdch, channels.pcm.opened); + if (wrch != NULL) + CHN_INSERT_HEAD(d, wrch, channels.pcm.opened); + break; } - i_dev->si_drv1 = rdch; - i_dev->si_drv2 = wrch; + /* + * Increase clone refcount for its automatic garbage collector. + */ + (void)snd_clone_ref(i_dev); + PCM_RELEASE(d); pcm_unlock(d); - splx(s); - return 0; + + PCM_GIANT_LEAVE(d); + + return (0); } static int dsp_close(struct cdev *i_dev, int flags, int mode, struct thread *td) { - struct pcm_channel *rdch, *wrch; + struct pcm_channel *rdch, *wrch, *volch; struct snddev_info *d; - intrmask_t s; - int refs; + int sg_ids, rdref, wdref; - s = spltty(); d = dsp_get_info(i_dev); - pcm_lock(d); - rdch = i_dev->si_drv1; - wrch = i_dev->si_drv2; - - refs = 0; - - if (rdch) { - CHN_LOCK(rdch); - refs += pcm_chnref(rdch, -1); - CHN_UNLOCK(rdch); - } - if (wrch) { - CHN_LOCK(wrch); - refs += pcm_chnref(wrch, -1); - CHN_UNLOCK(wrch); - } + if (!DSP_REGISTERED(d, i_dev)) + return (EBADF); - /* - * If there are no more references, release the channels. - */ - if ((rdch || wrch) && refs == 0) { + PCM_GIANT_ENTER(d); - if (pcm_getfakechan(d)) - pcm_getfakechan(d)->flags = 0; + pcm_lock(d); + PCM_WAIT(d); + PCM_ACQUIRE(d); - i_dev->si_drv1 = NULL; - i_dev->si_drv2 = NULL; + rdch = PCM_RDCH(i_dev); + wrch = PCM_WRCH(i_dev); + volch = PCM_VOLCH(i_dev); + + PCM_RDCH(i_dev) = NULL; + PCM_WRCH(i_dev) = NULL; + PCM_VOLCH(i_dev) = NULL; + + rdref = -1; + wdref = -1; + + if (volch != NULL) { + if (volch == rdch) + rdref--; + else if (volch == wrch) + wdref--; + else { + CHN_LOCK(volch); + pcm_chnref(volch, -1); + CHN_UNLOCK(volch); + } + } - dsp_set_flags(i_dev, dsp_get_flags(i_dev) & ~SD_F_TRANSIENT); + if (rdch != NULL) + CHN_REMOVE(d, rdch, channels.pcm.opened); + if (wrch != NULL) + CHN_REMOVE(d, wrch, channels.pcm.opened); + if (rdch != NULL || wrch != NULL) { pcm_unlock(d); + if (rdch != NULL) { + /* + * The channel itself need not be locked because: + * a) Adding a channel to a syncgroup happens only + * in dsp_ioctl(), which cannot run concurrently + * to dsp_close(). + * b) The syncmember pointer (sm) is protected by + * the global syncgroup list lock. + * c) A channel can't just disappear, invalidating + * pointers, unless it's closed/dereferenced + * first. + */ + PCM_SG_LOCK(); + sg_ids = chn_syncdestroy(rdch); + PCM_SG_UNLOCK(); + if (sg_ids != 0) + free_unr(pcmsg_unrhdr, sg_ids); - if (rdch) { CHN_LOCK(rdch); + pcm_chnref(rdch, rdref); chn_abort(rdch); /* won't sleep */ - rdch->flags &= ~(CHN_F_RUNNING | CHN_F_MAPPED | CHN_F_DEAD); + rdch->flags &= ~(CHN_F_RUNNING | CHN_F_MAPPED | + CHN_F_DEAD); chn_reset(rdch, 0); pcm_chnrelease(rdch); } - if (wrch) { - CHN_LOCK(wrch); + if (wrch != NULL) { /* - * XXX: Maybe the right behaviour is to abort on non_block. - * It seems that mplayer flushes the audio queue by quickly - * closing and re-opening. In FBSD, there's a long pause - * while the audio queue flushes that I presume isn't there in - * linux. + * Please see block above. */ + PCM_SG_LOCK(); + sg_ids = chn_syncdestroy(wrch); + PCM_SG_UNLOCK(); + if (sg_ids != 0) + free_unr(pcmsg_unrhdr, sg_ids); + + CHN_LOCK(wrch); + pcm_chnref(wrch, wdref); chn_flush(wrch); /* may sleep */ - wrch->flags &= ~(CHN_F_RUNNING | CHN_F_MAPPED | CHN_F_DEAD); + wrch->flags &= ~(CHN_F_RUNNING | CHN_F_MAPPED | + CHN_F_DEAD); chn_reset(wrch, 0); pcm_chnrelease(wrch); } - } else - pcm_unlock(d); - splx(s); - return 0; + pcm_lock(d); + } + + dsp_cdevinfo_free(i_dev); + /* + * Release clone busy state and unref it so the automatic + * garbage collector will get the hint and do the remaining + * cleanup process. + */ + (void)snd_clone_release(i_dev); + (void)snd_clone_unref(i_dev); + + PCM_RELEASE(d); + pcm_unlock(d); + + PCM_GIANT_LEAVE(d); + + return (0); } -static int -dsp_read(struct cdev *i_dev, struct uio *buf, int flag) +static __inline int +dsp_io_ops(struct cdev *i_dev, struct uio *buf) { - struct pcm_channel *rdch, *wrch; - intrmask_t s; - int ret; + struct snddev_info *d; + struct pcm_channel **ch, *rdch, *wrch; + int (*chn_io)(struct pcm_channel *, struct uio *); + int prio, ret; + pid_t runpid; + + KASSERT(i_dev != NULL && buf != NULL && + (buf->uio_rw == UIO_READ || buf->uio_rw == UIO_WRITE), + ("%s(): io train wreck!", __func__)); + + d = dsp_get_info(i_dev); + if (!DSP_REGISTERED(d, i_dev)) + return (EBADF); + + PCM_GIANT_ENTER(d); + + switch (buf->uio_rw) { + case UIO_READ: + prio = SD_F_PRIO_RD; + ch = &rdch; + chn_io = chn_read; + break; + case UIO_WRITE: + prio = SD_F_PRIO_WR; + ch = &wrch; + chn_io = chn_write; + break; + default: + panic("invalid/corrupted uio direction: %d", buf->uio_rw); + break; + } + + rdch = NULL; + wrch = NULL; + runpid = buf->uio_td->td_proc->p_pid; + + getchns(i_dev, &rdch, &wrch, prio); + + if (*ch == NULL || !((*ch)->flags & CHN_F_BUSY)) { + PCM_GIANT_EXIT(d); + return (EBADF); + } + + if (((*ch)->flags & (CHN_F_MAPPED | CHN_F_DEAD)) || + (((*ch)->flags & CHN_F_RUNNING) && (*ch)->pid != runpid)) { + relchns(i_dev, rdch, wrch, prio); + PCM_GIANT_EXIT(d); + return (EINVAL); + } else if (!((*ch)->flags & CHN_F_RUNNING)) { + (*ch)->flags |= CHN_F_RUNNING; + (*ch)->pid = runpid; + } + + /* + * chn_read/write must give up channel lock in order to copy bytes + * from/to userland, so up the "in progress" counter to make sure + * someone else doesn't come along and muss up the buffer. + */ + ++(*ch)->inprog; + ret = chn_io(*ch, buf); + --(*ch)->inprog; + + CHN_BROADCAST(&(*ch)->cv); - s = spltty(); - getchns(i_dev, &rdch, &wrch, SD_F_PRIO_RD); + relchns(i_dev, rdch, wrch, prio); - KASSERT(rdch, ("dsp_read: nonexistant channel")); - KASSERT(rdch->flags & CHN_F_BUSY, ("dsp_read: nonbusy channel")); + PCM_GIANT_LEAVE(d); - if (rdch->flags & (CHN_F_MAPPED | CHN_F_DEAD)) { - relchns(i_dev, rdch, wrch, SD_F_PRIO_RD); - splx(s); - return EINVAL; - } - if (!(rdch->flags & CHN_F_RUNNING)) - rdch->flags |= CHN_F_RUNNING; - ret = chn_read(rdch, buf); - relchns(i_dev, rdch, wrch, SD_F_PRIO_RD); + return (ret); +} - splx(s); - return ret; +static int +dsp_read(struct cdev *i_dev, struct uio *buf, int flag) +{ + return (dsp_io_ops(i_dev, buf)); } static int dsp_write(struct cdev *i_dev, struct uio *buf, int flag) { - struct pcm_channel *rdch, *wrch; - intrmask_t s; - int ret; + return (dsp_io_ops(i_dev, buf)); +} + +static int +dsp_get_volume_channel(struct cdev *dev, struct pcm_channel **volch) +{ + struct snddev_info *d; + struct pcm_channel *c; + int unit; + + KASSERT(dev != NULL && volch != NULL, + ("%s(): NULL query dev=%p volch=%p", __func__, dev, volch)); + + d = dsp_get_info(dev); + if (!PCM_REGISTERED(d)) { + *volch = NULL; + return (EINVAL); + } + + mtx_assert(d->lock, MA_NOTOWNED); + + *volch = NULL; - s = spltty(); - getchns(i_dev, &rdch, &wrch, SD_F_PRIO_WR); + c = PCM_VOLCH(dev); + if (c != NULL) { + if (!(c->feederflags & (1 << FEEDER_VOLUME))) + return (-1); + *volch = c; + return (0); + } + + pcm_lock(d); + PCM_WAIT(d); + PCM_ACQUIRE(d); - KASSERT(wrch, ("dsp_write: nonexistant channel")); - KASSERT(wrch->flags & CHN_F_BUSY, ("dsp_write: nonbusy channel")); + unit = dev2unit(dev); - if (wrch->flags & (CHN_F_MAPPED | CHN_F_DEAD)) { - relchns(i_dev, rdch, wrch, SD_F_PRIO_WR); - splx(s); - return EINVAL; + CHN_FOREACH(c, d, channels.pcm) { + CHN_LOCK(c); + if (c->unit != unit) { + CHN_UNLOCK(c); + continue; + } + *volch = c; + pcm_chnref(c, 1); + PCM_VOLCH(dev) = c; + CHN_UNLOCK(c); + PCM_RELEASE(d); + pcm_unlock(d); + return ((c->feederflags & (1 << FEEDER_VOLUME)) ? 0 : -1); } - if (!(wrch->flags & CHN_F_RUNNING)) - wrch->flags |= CHN_F_RUNNING; - ret = chn_write(wrch, buf); - relchns(i_dev, rdch, wrch, SD_F_PRIO_WR); - splx(s); - return ret; + PCM_RELEASE(d); + pcm_unlock(d); + + return (EINVAL); } static int -dsp_ioctl(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode, struct thread *td) +dsp_ioctl_channel(struct cdev *dev, struct pcm_channel *volch, u_long cmd, + caddr_t arg) { - struct pcm_channel *chn, *rdch, *wrch; struct snddev_info *d; - intrmask_t s; - int kill; - int ret = 0, *arg_i = (int *)arg, tmp; + struct pcm_channel *rdch, *wrch; + int j, devtype, ret; - /* - * this is an evil hack to allow broken apps to perform mixer ioctls - * on dsp devices. - */ + d = dsp_get_info(dev); + if (!PCM_REGISTERED(d) || !(d->flags & SD_F_VPC)) + return (-1); - d = dsp_get_info(i_dev); - if (IOCGROUP(cmd) == 'M') - return mixer_ioctl(d->mixer_dev, cmd, arg, mode, td); + mtx_assert(d->lock, MA_NOTOWNED); - s = spltty(); - getchns(i_dev, &rdch, &wrch, 0); + j = cmd & 0xff; - kill = 0; - if (wrch && (wrch->flags & CHN_F_DEAD)) - kill |= 1; - if (rdch && (rdch->flags & CHN_F_DEAD)) - kill |= 2; - if (kill == 3) { - relchns(i_dev, rdch, wrch, 0); - splx(s); - return EINVAL; + rdch = PCM_RDCH(dev); + wrch = PCM_WRCH(dev); + + /* No specific channel, look into cache */ + if (volch == NULL) + volch = PCM_VOLCH(dev); + + /* Look harder */ + if (volch == NULL) { + if (j == SOUND_MIXER_RECLEV && rdch != NULL) + volch = rdch; + else if (j == SOUND_MIXER_PCM && wrch != NULL) + volch = wrch; } - if (kill & 1) - wrch = NULL; - if (kill & 2) - rdch = NULL; - - switch(cmd) { -#ifdef OLDPCM_IOCTL - /* - * we start with the new ioctl interface. - */ - case AIONWRITE: /* how many bytes can write ? */ - CHN_LOCK(wrch); -/* - if (wrch && wrch->bufhard.dl) - while (chn_wrfeed(wrch) == 0); -*/ - *arg_i = wrch? sndbuf_getfree(wrch->bufsoft) : 0; - CHN_UNLOCK(wrch); - break; - case AIOSSIZE: /* set the current blocksize */ - { - struct snd_size *p = (struct snd_size *)arg; + devtype = PCMDEV(dev); - p->play_size = 0; - p->rec_size = 0; - if (wrch) { - CHN_LOCK(wrch); - chn_setblocksize(wrch, 2, p->play_size); + /* Look super harder */ + if (volch == NULL && + (devtype == SND_DEV_DSPHW_PLAY || devtype == SND_DEV_DSPHW_VPLAY || + devtype == SND_DEV_DSPHW_REC || devtype == SND_DEV_DSPHW_VREC)) { + ret = dsp_get_volume_channel(dev, &volch); + if (ret != 0) + return (ret); + if (volch == NULL) + return (EINVAL); + } + + /* Final validation */ + if (volch != NULL) { + CHN_LOCK(volch); + if (!(volch->feederflags & (1 << FEEDER_VOLUME))) { + CHN_UNLOCK(volch); + return (-1); + } + if (volch->direction == PCMDIR_PLAY) + wrch = volch; + else + rdch = volch; + } + + ret = EINVAL; + + if (volch != NULL && + ((j == SOUND_MIXER_PCM && volch->direction == PCMDIR_PLAY) || + (j == SOUND_MIXER_RECLEV && volch->direction == PCMDIR_REC))) { + if ((cmd & MIXER_WRITE(0)) == MIXER_WRITE(0)) { + CHN_SETVOLUME(volch, SND_VOL_C_PCM, SND_CHN_T_FL, + *(int *)arg & 0x7f); + CHN_SETVOLUME(volch, SND_VOL_C_PCM, SND_CHN_T_FR, + (*(int *)arg >> 8) & 0x7f); + } else if ((cmd & MIXER_READ(0)) == MIXER_READ(0)) { + *(int *)arg = CHN_GETVOLUME(volch, + SND_VOL_C_PCM, SND_CHN_T_FL); + *(int *)arg |= CHN_GETVOLUME(volch, + SND_VOL_C_PCM, SND_CHN_T_FR) << 8; + } + ret = 0; + } else if (rdch != NULL || wrch != NULL) { + switch (j) { + case SOUND_MIXER_DEVMASK: + case SOUND_MIXER_CAPS: + case SOUND_MIXER_STEREODEVS: + if ((cmd & MIXER_READ(0)) == MIXER_READ(0)) { + *(int *)arg = 0; + if (rdch != NULL) + *(int *)arg |= SOUND_MASK_RECLEV; + if (wrch != NULL) + *(int *)arg |= SOUND_MASK_PCM; + } + ret = 0; + break; + case SOUND_MIXER_RECMASK: + case SOUND_MIXER_RECSRC: + if ((cmd & MIXER_READ(0)) == MIXER_READ(0)) + *(int *)arg = 0; + ret = 0; + break; + default: + break; + } + } + + if (volch != NULL) + CHN_UNLOCK(volch); + + return (ret); +} + +static int +dsp_ioctl(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode, + struct thread *td) +{ + struct pcm_channel *chn, *rdch, *wrch; + struct snddev_info *d; + int *arg_i, ret, tmp, xcmd; + + d = dsp_get_info(i_dev); + if (!DSP_REGISTERED(d, i_dev)) + return (EBADF); + + PCM_GIANT_ENTER(d); + + arg_i = (int *)arg; + ret = 0; + xcmd = 0; + chn = NULL; + + if (IOCGROUP(cmd) == 'M') { + ret = dsp_ioctl_channel(i_dev, PCM_VOLCH(i_dev), cmd, arg); + if (ret != -1) { + PCM_GIANT_EXIT(d); + return (ret); + } + + if (d->mixer_dev != NULL) { + PCM_ACQUIRE_QUICK(d); + ret = mixer_ioctl_cmd(d->mixer_dev, cmd, arg, -1, td, + MIXER_CMD_DIRECT); + PCM_RELEASE_QUICK(d); + } else + ret = EBADF; + + PCM_GIANT_EXIT(d); + + return (ret); + } + + /* + * Certain ioctls may be made on any type of device (audio, mixer, + * and MIDI). Handle those special cases here. + */ + if (IOCGROUP(cmd) == 'X') { + PCM_ACQUIRE_QUICK(d); + switch(cmd) { + case SNDCTL_SYSINFO: + sound_oss_sysinfo((oss_sysinfo *)arg); + break; + case SNDCTL_AUDIOINFO: + ret = dsp_oss_audioinfo(i_dev, (oss_audioinfo *)arg); + break; + case SNDCTL_MIXERINFO: + ret = mixer_oss_mixerinfo(i_dev, (oss_mixerinfo *)arg); + break; + default: + ret = EINVAL; + } + PCM_RELEASE_QUICK(d); + PCM_GIANT_EXIT(d); + return (ret); + } + + getchns(i_dev, &rdch, &wrch, 0); + + if (wrch != NULL && (wrch->flags & CHN_F_DEAD)) + wrch = NULL; + if (rdch != NULL && (rdch->flags & CHN_F_DEAD)) + rdch = NULL; + + if (wrch == NULL && rdch == NULL) { + PCM_GIANT_EXIT(d); + return (EINVAL); + } + + switch(cmd) { +#ifdef OLDPCM_IOCTL + /* + * we start with the new ioctl interface. + */ + case AIONWRITE: /* how many bytes can write ? */ + if (wrch) { + CHN_LOCK(wrch); +/* + if (wrch && wrch->bufhard.dl) + while (chn_wrfeed(wrch) == 0); +*/ + *arg_i = sndbuf_getfree(wrch->bufsoft); + CHN_UNLOCK(wrch); + } else { + *arg_i = 0; + ret = EINVAL; + } + break; + + case AIOSSIZE: /* set the current blocksize */ + { + struct snd_size *p = (struct snd_size *)arg; + + p->play_size = 0; + p->rec_size = 0; + PCM_ACQUIRE_QUICK(d); + if (wrch) { + CHN_LOCK(wrch); + chn_setblocksize(wrch, 2, p->play_size); p->play_size = sndbuf_getblksz(wrch->bufsoft); CHN_UNLOCK(wrch); } @@ -510,6 +1136,7 @@ p->rec_size = sndbuf_getblksz(rdch->bufsoft); CHN_UNLOCK(rdch); } + PCM_RELEASE_QUICK(d); } break; case AIOGSIZE: /* get the current blocksize */ @@ -534,9 +1161,16 @@ { snd_chan_param *p = (snd_chan_param *)arg; + if (cmd == AIOSFMT && + ((p->play_format != 0 && p->play_rate == 0) || + (p->rec_format != 0 && p->rec_rate == 0))) { + ret = EINVAL; + break; + } + PCM_ACQUIRE_QUICK(d); if (wrch) { CHN_LOCK(wrch); - if (cmd == AIOSFMT) { + if (cmd == AIOSFMT && p->play_format != 0) { chn_setformat(wrch, p->play_format); chn_setspeed(wrch, p->play_rate); } @@ -549,7 +1183,7 @@ } if (rdch) { CHN_LOCK(rdch); - if (cmd == AIOSFMT) { + if (cmd == AIOSFMT && p->rec_format != 0) { chn_setformat(rdch, p->rec_format); chn_setspeed(rdch, p->rec_rate); } @@ -560,6 +1194,7 @@ p->rec_rate = 0; p->rec_format = 0; } + PCM_RELEASE_QUICK(d); } break; @@ -569,6 +1204,7 @@ struct pcmchan_caps *pcaps = NULL, *rcaps = NULL; struct cdev *pdev; + pcm_lock(d); if (rdch) { CHN_LOCK(rdch); rcaps = chn_getcaps(rdch); @@ -592,10 +1228,11 @@ p->mixers = 1; /* default: one mixer */ p->inputs = pdev->si_drv1? mix_getdevs(pdev->si_drv1) : 0; p->left = p->right = 100; - if (rdch) - CHN_UNLOCK(rdch); if (wrch) CHN_UNLOCK(wrch); + if (rdch) + CHN_UNLOCK(rdch); + pcm_unlock(d); } break; @@ -630,19 +1267,21 @@ */ *arg_i = sndbuf_getready(rdch->bufsoft); CHN_UNLOCK(rdch); - } else + } else { *arg_i = 0; + ret = EINVAL; + } break; case FIOASYNC: /*set/clear async i/o */ DEB( printf("FIOASYNC\n") ; ) break; - case SNDCTL_DSP_NONBLOCK: + case SNDCTL_DSP_NONBLOCK: /* set non-blocking i/o */ case FIONBIO: /* set/clear non-blocking i/o */ if (rdch) { CHN_LOCK(rdch); - if (*arg_i) + if (cmd == SNDCTL_DSP_NONBLOCK || *arg_i) rdch->flags |= CHN_F_NBIO; else rdch->flags &= ~CHN_F_NBIO; @@ -650,7 +1289,7 @@ } if (wrch) { CHN_LOCK(wrch); - if (*arg_i) + if (cmd == SNDCTL_DSP_NONBLOCK || *arg_i) wrch->flags |= CHN_F_NBIO; else wrch->flags &= ~CHN_F_NBIO; @@ -665,13 +1304,19 @@ case THE_REAL_SNDCTL_DSP_GETBLKSIZE: case SNDCTL_DSP_GETBLKSIZE: chn = wrch ? wrch : rdch; - CHN_LOCK(chn); - *arg_i = sndbuf_getblksz(chn->bufsoft); - CHN_UNLOCK(chn); - break ; + if (chn) { + CHN_LOCK(chn); + *arg_i = sndbuf_getblksz(chn->bufsoft); + CHN_UNLOCK(chn); + } else { + *arg_i = 0; + ret = EINVAL; + } + break; case SNDCTL_DSP_SETBLKSIZE: RANGE(*arg_i, 16, 65536); + PCM_ACQUIRE_QUICK(d); if (wrch) { CHN_LOCK(wrch); chn_setblocksize(wrch, 2, *arg_i); @@ -682,6 +1327,7 @@ chn_setblocksize(rdch, 2, *arg_i); CHN_UNLOCK(rdch); } + PCM_RELEASE_QUICK(d); break; case SNDCTL_DSP_RESET: @@ -705,7 +1351,7 @@ /* chn_sync may sleep */ if (wrch) { CHN_LOCK(wrch); - chn_sync(wrch, sndbuf_getsize(wrch->bufsoft) - 4); + chn_sync(wrch, 0); CHN_UNLOCK(wrch); } break; @@ -713,6 +1359,7 @@ case SNDCTL_DSP_SPEED: /* chn_setspeed may sleep */ tmp = 0; + PCM_ACQUIRE_QUICK(d); if (wrch) { CHN_LOCK(wrch); ret = chn_setspeed(wrch, *arg_i); @@ -726,19 +1373,26 @@ tmp = rdch->speed; CHN_UNLOCK(rdch); } + PCM_RELEASE_QUICK(d); *arg_i = tmp; break; case SOUND_PCM_READ_RATE: chn = wrch ? wrch : rdch; - CHN_LOCK(chn); - *arg_i = chn->speed; - CHN_UNLOCK(chn); + if (chn) { + CHN_LOCK(chn); + *arg_i = chn->speed; + CHN_UNLOCK(chn); + } else { + *arg_i = 0; + ret = EINVAL; + } break; case SNDCTL_DSP_STEREO: tmp = -1; *arg_i = (*arg_i)? AFMT_STEREO : 0; + PCM_ACQUIRE_QUICK(d); if (wrch) { CHN_LOCK(wrch); ret = chn_setformat(wrch, (wrch->format & ~AFMT_STEREO) | *arg_i); @@ -752,6 +1406,7 @@ tmp = (rdch->format & AFMT_STEREO)? 1 : 0; CHN_UNLOCK(rdch); } + PCM_RELEASE_QUICK(d); *arg_i = tmp; break; @@ -760,6 +1415,7 @@ if (*arg_i != 0) { tmp = 0; *arg_i = (*arg_i != 1)? AFMT_STEREO : 0; + PCM_ACQUIRE_QUICK(d); if (wrch) { CHN_LOCK(wrch); ret = chn_setformat(wrch, (wrch->format & ~AFMT_STEREO) | *arg_i); @@ -773,6 +1429,7 @@ tmp = (rdch->format & AFMT_STEREO)? 2 : 1; CHN_UNLOCK(rdch); } + PCM_RELEASE_QUICK(d); *arg_i = tmp; } else { chn = wrch ? wrch : rdch; @@ -784,21 +1441,32 @@ case SOUND_PCM_READ_CHANNELS: chn = wrch ? wrch : rdch; - CHN_LOCK(chn); - *arg_i = (chn->format & AFMT_STEREO) ? 2 : 1; - CHN_UNLOCK(chn); + if (chn) { + CHN_LOCK(chn); + *arg_i = (chn->format & AFMT_STEREO) ? 2 : 1; + CHN_UNLOCK(chn); + } else { + *arg_i = 0; + ret = EINVAL; + } break; case SNDCTL_DSP_GETFMTS: /* returns a mask of supported fmts */ chn = wrch ? wrch : rdch; - CHN_LOCK(chn); - *arg_i = chn_getformats(chn); - CHN_UNLOCK(chn); - break ; + if (chn) { + CHN_LOCK(chn); + *arg_i = chn_getformats(chn); + CHN_UNLOCK(chn); + } else { + *arg_i = 0; + ret = EINVAL; + } + break; case SNDCTL_DSP_SETFMT: /* sets _one_ format */ if ((*arg_i != AFMT_QUERY)) { tmp = 0; + PCM_ACQUIRE_QUICK(d); if (wrch) { CHN_LOCK(wrch); ret = chn_setformat(wrch, (*arg_i) | (wrch->format & AFMT_STEREO)); @@ -812,6 +1480,7 @@ tmp = rdch->format & ~AFMT_STEREO; CHN_UNLOCK(rdch); } + PCM_RELEASE_QUICK(d); *arg_i = tmp; } else { chn = wrch ? wrch : rdch; @@ -824,9 +1493,10 @@ case SNDCTL_DSP_SETFRAGMENT: DEB(printf("SNDCTL_DSP_SETFRAGMENT 0x%08x\n", *(int *)arg)); { - u_int32_t fragln = (*arg_i) & 0x0000ffff; - u_int32_t maxfrags = ((*arg_i) & 0xffff0000) >> 16; - u_int32_t fragsz; + uint32_t fragln = (*arg_i) & 0x0000ffff; + uint32_t maxfrags = ((*arg_i) & 0xffff0000) >> 16; + uint32_t fragsz; + uint32_t r_maxfrags, r_fragsz; RANGE(fragln, 4, 16); fragsz = 1 << fragln; @@ -839,12 +1509,16 @@ maxfrags = CHN_2NDBUFMAXSIZE / fragsz; DEB(printf("SNDCTL_DSP_SETFRAGMENT %d frags, %d sz\n", maxfrags, fragsz)); + PCM_ACQUIRE_QUICK(d); if (rdch) { CHN_LOCK(rdch); ret = chn_setblocksize(rdch, maxfrags, fragsz); - maxfrags = sndbuf_getblkcnt(rdch->bufsoft); - fragsz = sndbuf_getblksz(rdch->bufsoft); + r_maxfrags = sndbuf_getblkcnt(rdch->bufsoft); + r_fragsz = sndbuf_getblksz(rdch->bufsoft); CHN_UNLOCK(rdch); + } else { + r_maxfrags = maxfrags; + r_fragsz = fragsz; } if (wrch && ret == 0) { CHN_LOCK(wrch); @@ -852,7 +1526,11 @@ maxfrags = sndbuf_getblkcnt(wrch->bufsoft); fragsz = sndbuf_getblksz(wrch->bufsoft); CHN_UNLOCK(wrch); + } else { /* use whatever came from the read channel */ + maxfrags = r_maxfrags; + fragsz = r_fragsz; } + PCM_RELEASE_QUICK(d); fragln = 0; while (fragsz > 1) { @@ -876,7 +1554,8 @@ a->fragstotal = sndbuf_getblkcnt(bs); a->fragsize = sndbuf_getblksz(bs); CHN_UNLOCK(rdch); - } + } else + ret = EINVAL; } break; @@ -888,13 +1567,14 @@ struct snd_dbuf *bs = wrch->bufsoft; CHN_LOCK(wrch); - chn_wrupdate(wrch); + /* XXX abusive DMA update: chn_wrupdate(wrch); */ a->bytes = sndbuf_getfree(bs); a->fragments = a->bytes / sndbuf_getblksz(bs); a->fragstotal = sndbuf_getblkcnt(bs); a->fragsize = sndbuf_getblksz(bs); CHN_UNLOCK(wrch); - } + } else + ret = EINVAL; } break; @@ -905,7 +1585,7 @@ struct snd_dbuf *bs = rdch->bufsoft; CHN_LOCK(rdch); - chn_rdupdate(rdch); + /* XXX abusive DMA update: chn_rdupdate(rdch); */ a->bytes = sndbuf_gettotal(bs); a->blocks = sndbuf_getblocks(bs) - rdch->blocks; a->ptr = sndbuf_getreadyptr(bs); @@ -923,7 +1603,7 @@ struct snd_dbuf *bs = wrch->bufsoft; CHN_LOCK(wrch); - chn_wrupdate(wrch); + /* XXX abusive DMA update: chn_wrupdate(wrch); */ a->bytes = sndbuf_gettotal(bs); a->blocks = sndbuf_getblocks(bs) - wrch->blocks; a->ptr = sndbuf_getreadyptr(bs); @@ -935,16 +1615,32 @@ break; case SNDCTL_DSP_GETCAPS: + pcm_lock(d); *arg_i = DSP_CAP_REALTIME | DSP_CAP_MMAP | DSP_CAP_TRIGGER; if (rdch && wrch && !(dsp_get_flags(i_dev) & SD_F_SIMPLEX)) *arg_i |= DSP_CAP_DUPLEX; + pcm_unlock(d); break; case SOUND_PCM_READ_BITS: chn = wrch ? wrch : rdch; - CHN_LOCK(chn); - *arg_i = (chn->format & AFMT_16BIT) ? 16 : 8; - CHN_UNLOCK(chn); + if (chn) { + CHN_LOCK(chn); + if (chn->format & AFMT_8BIT) + *arg_i = 8; + else if (chn->format & AFMT_16BIT) + *arg_i = 16; + else if (chn->format & AFMT_24BIT) + *arg_i = 24; + else if (chn->format & AFMT_32BIT) + *arg_i = 32; + else + ret = EINVAL; + CHN_UNLOCK(chn); + } else { + *arg_i = 0; + ret = EINVAL; + } break; case SNDCTL_DSP_SETTRIGGER: @@ -986,12 +1682,11 @@ case SNDCTL_DSP_GETODELAY: if (wrch) { - struct snd_dbuf *b = wrch->bufhard; struct snd_dbuf *bs = wrch->bufsoft; CHN_LOCK(wrch); - chn_wrupdate(wrch); - *arg_i = sndbuf_getready(b) + sndbuf_getready(bs); + /* XXX abusive DMA update: chn_wrupdate(wrch); */ + *arg_i = sndbuf_getready(bs); CHN_UNLOCK(wrch); } else ret = EINVAL; @@ -1011,10 +1706,348 @@ * switch to full-duplex mode if card is in half-duplex * mode and is able to work in full-duplex mode */ + pcm_lock(d); if (rdch && wrch && (dsp_get_flags(i_dev) & SD_F_SIMPLEX)) dsp_set_flags(i_dev, dsp_get_flags(i_dev)^SD_F_SIMPLEX); + pcm_unlock(d); break; + /* + * The following four ioctls are simple wrappers around mixer_ioctl + * with no further processing. xcmd is short for "translated + * command". + */ + case SNDCTL_DSP_GETRECVOL: + if (xcmd == 0) { + xcmd = SOUND_MIXER_READ_RECLEV; + chn = rdch; + } + /* FALLTHROUGH */ + case SNDCTL_DSP_SETRECVOL: + if (xcmd == 0) { + xcmd = SOUND_MIXER_WRITE_RECLEV; + chn = rdch; + } + /* FALLTHROUGH */ + case SNDCTL_DSP_GETPLAYVOL: + if (xcmd == 0) { + xcmd = SOUND_MIXER_READ_PCM; + chn = wrch; + } + /* FALLTHROUGH */ + case SNDCTL_DSP_SETPLAYVOL: + if (xcmd == 0) { + xcmd = SOUND_MIXER_WRITE_PCM; + chn = wrch; + } + + ret = dsp_ioctl_channel(i_dev, chn, xcmd, arg); + if (ret != -1) { + PCM_GIANT_EXIT(d); + return (ret); + } + + if (d->mixer_dev != NULL) { + PCM_ACQUIRE_QUICK(d); + ret = mixer_ioctl_cmd(d->mixer_dev, xcmd, arg, -1, td, + MIXER_CMD_DIRECT); + PCM_RELEASE_QUICK(d); + } else + ret = ENOTSUP; + + break; + + case SNDCTL_DSP_GET_RECSRC_NAMES: + case SNDCTL_DSP_GET_RECSRC: + case SNDCTL_DSP_SET_RECSRC: + if (d->mixer_dev != NULL) { + PCM_ACQUIRE_QUICK(d); + ret = mixer_ioctl_cmd(d->mixer_dev, cmd, arg, -1, td, + MIXER_CMD_DIRECT); + PCM_RELEASE_QUICK(d); + } else + ret = ENOTSUP; + break; + + /* + * The following 3 ioctls aren't very useful at the moment. For + * now, only a single channel is associated with a cdev (/dev/dspN + * instance), so there's only a single output routing to use (i.e., + * the wrch bound to this cdev). + */ + case SNDCTL_DSP_GET_PLAYTGT_NAMES: + { + oss_mixer_enuminfo *ei; + ei = (oss_mixer_enuminfo *)arg; + ei->dev = 0; + ei->ctrl = 0; + ei->version = 0; /* static for now */ + ei->strindex[0] = 0; + + if (wrch != NULL) { + ei->nvalues = 1; + strlcpy(ei->strings, wrch->name, + sizeof(ei->strings)); + } else { + ei->nvalues = 0; + ei->strings[0] = '\0'; + } + } + break; + case SNDCTL_DSP_GET_PLAYTGT: + case SNDCTL_DSP_SET_PLAYTGT: /* yes, they are the same for now */ + /* + * Re: SET_PLAYTGT + * OSSv4: "The value that was accepted by the device will + * be returned back in the variable pointed by the + * argument." + */ + if (wrch != NULL) + *arg_i = 0; + else + ret = EINVAL; + break; + + case SNDCTL_DSP_SILENCE: + /* + * Flush the software (pre-feed) buffer, but try to minimize playback + * interruption. (I.e., record unplayed samples with intent to + * restore by SNDCTL_DSP_SKIP.) Intended for application "pause" + * functionality. + */ + if (wrch == NULL) + ret = EINVAL; + else { + struct snd_dbuf *bs; + CHN_LOCK(wrch); + while (wrch->inprog != 0) + cv_wait(&wrch->cv, wrch->lock); + bs = wrch->bufsoft; + if ((bs->shadbuf != NULL) && (sndbuf_getready(bs) > 0)) { + bs->sl = sndbuf_getready(bs); + sndbuf_dispose(bs, bs->shadbuf, sndbuf_getready(bs)); + sndbuf_fillsilence(bs); + chn_start(wrch, 0); + } + CHN_UNLOCK(wrch); + } + break; + + case SNDCTL_DSP_SKIP: + /* + * OSSv4 docs: "This ioctl call discards all unplayed samples in the + * playback buffer by moving the current write position immediately + * before the point where the device is currently reading the samples." + */ + if (wrch == NULL) + ret = EINVAL; + else { + struct snd_dbuf *bs; + CHN_LOCK(wrch); + while (wrch->inprog != 0) + cv_wait(&wrch->cv, wrch->lock); + bs = wrch->bufsoft; + if ((bs->shadbuf != NULL) && (bs->sl > 0)) { + sndbuf_softreset(bs); + sndbuf_acquire(bs, bs->shadbuf, bs->sl); + bs->sl = 0; + chn_start(wrch, 0); + } + CHN_UNLOCK(wrch); + } + break; + + case SNDCTL_DSP_CURRENT_OPTR: + case SNDCTL_DSP_CURRENT_IPTR: + /** + * @note Changing formats resets the buffer counters, which differs + * from the 4Front drivers. However, I don't expect this to be + * much of a problem. + * + * @note In a test where @c CURRENT_OPTR is called immediately after write + * returns, this driver is about 32K samples behind whereas + * 4Front's is about 8K samples behind. Should determine source + * of discrepancy, even if only out of curiosity. + * + * @todo Actually test SNDCTL_DSP_CURRENT_IPTR. + */ + chn = (cmd == SNDCTL_DSP_CURRENT_OPTR) ? wrch : rdch; + if (chn == NULL) + ret = EINVAL; + else { + struct snd_dbuf *bs; + /* int tmp; */ + + oss_count_t *oc = (oss_count_t *)arg; + + CHN_LOCK(chn); + bs = chn->bufsoft; +#if 0 + tmp = (sndbuf_getsize(b) + chn_getptr(chn) - sndbuf_gethwptr(b)) % sndbuf_getsize(b); + oc->samples = (sndbuf_gettotal(b) + tmp) / sndbuf_getbps(b); + oc->fifo_samples = (sndbuf_getready(b) - tmp) / sndbuf_getbps(b); +#else + oc->samples = sndbuf_gettotal(bs) / sndbuf_getbps(bs); + oc->fifo_samples = sndbuf_getready(bs) / sndbuf_getbps(bs); +#endif + CHN_UNLOCK(chn); + } + break; + + case SNDCTL_DSP_HALT_OUTPUT: + case SNDCTL_DSP_HALT_INPUT: + chn = (cmd == SNDCTL_DSP_HALT_OUTPUT) ? wrch : rdch; + if (chn == NULL) + ret = EINVAL; + else { + CHN_LOCK(chn); + chn_abort(chn); + CHN_UNLOCK(chn); + } + break; + + case SNDCTL_DSP_LOW_WATER: + /* + * Set the number of bytes required to attract attention by + * select/poll. + */ + if (wrch != NULL) { + CHN_LOCK(wrch); + wrch->lw = (*arg_i > 1) ? *arg_i : 1; + CHN_UNLOCK(wrch); + } + if (rdch != NULL) { + CHN_LOCK(rdch); + rdch->lw = (*arg_i > 1) ? *arg_i : 1; + CHN_UNLOCK(rdch); + } + break; + + case SNDCTL_DSP_GETERROR: + /* + * OSSv4 docs: "All errors and counters will automatically be + * cleared to zeroes after the call so each call will return only + * the errors that occurred after the previous invocation. ... The + * play_underruns and rec_overrun fields are the only usefull fields + * returned by OSS 4.0." + */ + { + audio_errinfo *ei = (audio_errinfo *)arg; + + bzero((void *)ei, sizeof(*ei)); + + if (wrch != NULL) { + CHN_LOCK(wrch); + ei->play_underruns = wrch->xruns; + wrch->xruns = 0; + CHN_UNLOCK(wrch); + } + if (rdch != NULL) { + CHN_LOCK(rdch); + ei->rec_overruns = rdch->xruns; + rdch->xruns = 0; + CHN_UNLOCK(rdch); + } + } + break; + + case SNDCTL_DSP_SYNCGROUP: + PCM_ACQUIRE_QUICK(d); + ret = dsp_oss_syncgroup(wrch, rdch, (oss_syncgroup *)arg); + PCM_RELEASE_QUICK(d); + break; + + case SNDCTL_DSP_SYNCSTART: + PCM_ACQUIRE_QUICK(d); + ret = dsp_oss_syncstart(*arg_i); + PCM_RELEASE_QUICK(d); + break; + + case SNDCTL_DSP_POLICY: + PCM_ACQUIRE_QUICK(d); + ret = dsp_oss_policy(wrch, rdch, *arg_i); + PCM_RELEASE_QUICK(d); + break; + +#ifdef OSSV4_EXPERIMENT + /* + * XXX The following ioctls are not yet supported and just return + * EINVAL. + */ + case SNDCTL_DSP_GETOPEAKS: + case SNDCTL_DSP_GETIPEAKS: + chn = (cmd == SNDCTL_DSP_GETOPEAKS) ? wrch : rdch; + if (chn == NULL) + ret = EINVAL; + else { + oss_peaks_t *op = (oss_peaks_t *)arg; + int lpeak, rpeak; + + CHN_LOCK(chn); + ret = chn_getpeaks(chn, &lpeak, &rpeak); + if (ret == -1) + ret = EINVAL; + else { + (*op)[0] = lpeak; + (*op)[1] = rpeak; + } + CHN_UNLOCK(chn); + } + break; + + /* + * XXX Once implemented, revisit this for proper cv protection + * (if necessary). + */ + case SNDCTL_DSP_COOKEDMODE: + ret = dsp_oss_cookedmode(wrch, rdch, *arg_i); + break; + case SNDCTL_DSP_GET_CHNORDER: + ret = dsp_oss_getchnorder(wrch, rdch, (unsigned long long *)arg); + break; + case SNDCTL_DSP_SET_CHNORDER: + ret = dsp_oss_setchnorder(wrch, rdch, (unsigned long long *)arg); + break; + case SNDCTL_GETLABEL: + ret = dsp_oss_getlabel(wrch, rdch, (oss_label_t *)arg); + break; + case SNDCTL_SETLABEL: + ret = dsp_oss_setlabel(wrch, rdch, (oss_label_t *)arg); + break; + case SNDCTL_GETSONG: + ret = dsp_oss_getsong(wrch, rdch, (oss_longname_t *)arg); + break; + case SNDCTL_SETSONG: + ret = dsp_oss_setsong(wrch, rdch, (oss_longname_t *)arg); + break; + case SNDCTL_SETNAME: + ret = dsp_oss_setname(wrch, rdch, (oss_longname_t *)arg); + break; +#if 0 + /** + * @note The SNDCTL_CARDINFO ioctl was omitted per 4Front developer + * documentation. "The usability of this call is very limited. It's + * provided only for completeness of the API. OSS API doesn't have + * any concept of card. Any information returned by this ioctl calld + * is reserved exclusively for the utility programs included in the + * OSS package. Applications should not try to use for this + * information in any ways." + */ + case SNDCTL_CARDINFO: + ret = EINVAL; + break; + /** + * @note The S/PDIF interface ioctls, @c SNDCTL_DSP_READCTL and + * @c SNDCTL_DSP_WRITECTL have been omitted at the suggestion of + * 4Front Technologies. + */ + case SNDCTL_DSP_READCTL: + case SNDCTL_DSP_WRITECTL: + ret = EINVAL; + break; +#endif /* !0 (explicitly omitted ioctls) */ + +#endif /* !OSSV4_EXPERIMENT */ case SNDCTL_DSP_MAPINBUF: case SNDCTL_DSP_MAPOUTBUF: case SNDCTL_DSP_SETSYNCRO: @@ -1030,171 +2063,1121 @@ ret = EINVAL; break; } - relchns(i_dev, rdch, wrch, 0); - splx(s); - return ret; + + PCM_GIANT_LEAVE(d); + + return (ret); } static int dsp_poll(struct cdev *i_dev, int events, struct thread *td) { - struct pcm_channel *wrch = NULL, *rdch = NULL; - intrmask_t s; + struct snddev_info *d; + struct pcm_channel *wrch, *rdch; int ret, e; - s = spltty(); + d = dsp_get_info(i_dev); + if (!DSP_REGISTERED(d, i_dev)) + return (EBADF); + + PCM_GIANT_ENTER(d); + + wrch = NULL; + rdch = NULL; ret = 0; + getchns(i_dev, &rdch, &wrch, SD_F_PRIO_RD | SD_F_PRIO_WR); - if (wrch) { + if (wrch != NULL && !(wrch->flags & CHN_F_DEAD)) { e = (events & (POLLOUT | POLLWRNORM)); if (e) ret |= chn_poll(wrch, e, td); } - if (rdch) { + + if (rdch != NULL && !(rdch->flags & CHN_F_DEAD)) { e = (events & (POLLIN | POLLRDNORM)); if (e) ret |= chn_poll(rdch, e, td); } + relchns(i_dev, rdch, wrch, SD_F_PRIO_RD | SD_F_PRIO_WR); - splx(s); - return ret; + PCM_GIANT_LEAVE(d); + + return (ret); } static int dsp_mmap(struct cdev *i_dev, vm_offset_t offset, vm_paddr_t *paddr, int nprot) { - struct pcm_channel *wrch = NULL, *rdch = NULL, *c; - intrmask_t s; - - if (nprot & PROT_EXEC) - return -1; + struct snddev_info *d; + struct pcm_channel *wrch, *rdch, *c; - s = spltty(); - getchns(i_dev, &rdch, &wrch, SD_F_PRIO_RD | SD_F_PRIO_WR); -#if 0 /* - * XXX the linux api uses the nprot to select read/write buffer - * our vm system doesn't allow this, so force write buffer + * Reject PROT_EXEC by default. It just doesn't makes sense. + * Unfortunately, we have to give up this one due to linux_mmap + * changes. + * + * http://lists.freebsd.org/pipermail/freebsd-emulation/2007-June/003698.html + * */ + if ((nprot & PROT_EXEC) && dsp_mmap_allow_prot_exec == 0) + return (-1); - if (wrch && (nprot & PROT_WRITE)) { - c = wrch; - } else if (rdch && (nprot & PROT_READ)) { - c = rdch; - } else { - splx(s); - return -1; - } -#else - c = wrch; -#endif + d = dsp_get_info(i_dev); + if (!DSP_REGISTERED(d, i_dev)) + return (-1); - if (c == NULL) { - relchns(i_dev, rdch, wrch, SD_F_PRIO_RD | SD_F_PRIO_WR); - splx(s); - return -1; - } + PCM_GIANT_ENTER(d); + + getchns(i_dev, &rdch, &wrch, SD_F_PRIO_RD | SD_F_PRIO_WR); + + /* + * XXX The linux api uses the nprot to select read/write buffer + * our vm system doesn't allow this, so force write buffer. + * + * This is just a quack to fool full-duplex mmap, so that at + * least playback _or_ recording works. If you really got the + * urge to make _both_ work at the same time, avoid O_RDWR. + * Just open each direction separately and mmap() it. + * + * Failure is not an option due to INVARIANTS check within + * device_pager.c, which means, we have to give up one over + * another. + */ + c = (wrch != NULL) ? wrch : rdch; - if (offset >= sndbuf_getsize(c->bufsoft)) { + if (c == NULL || (c->flags & CHN_F_MMAP_INVALID) || + offset >= sndbuf_getsize(c->bufsoft) || + (wrch != NULL && (wrch->flags & CHN_F_MMAP_INVALID)) || + (rdch != NULL && (rdch->flags & CHN_F_MMAP_INVALID))) { relchns(i_dev, rdch, wrch, SD_F_PRIO_RD | SD_F_PRIO_WR); - splx(s); - return -1; + PCM_GIANT_EXIT(d); + return (-1); } - if (!(c->flags & CHN_F_MAPPED)) - c->flags |= CHN_F_MAPPED; + /* XXX full-duplex quack. */ + if (wrch != NULL) + wrch->flags |= CHN_F_MAPPED; + if (rdch != NULL) + rdch->flags |= CHN_F_MAPPED; *paddr = vtophys(sndbuf_getbufofs(c->bufsoft, offset)); relchns(i_dev, rdch, wrch, SD_F_PRIO_RD | SD_F_PRIO_WR); - splx(s); - return 0; + PCM_GIANT_LEAVE(d); + + return (0); } #ifdef USING_DEVFS -/* - * Clone logic is this: - * x E X = {dsp, dspW, audio} - * x -> x${sysctl("hw.snd.unit")} - * xN-> - * for i N = 1 to channels of device N - * if xN.i isn't busy, return its dev_t - */ +/* So much for dev_stdclone() */ +static int +dsp_stdclone(char *name, char *namep, char *sep, int use_sep, int *u, int *c) +{ + size_t len; + + len = strlen(namep); + + if (bcmp(name, namep, len) != 0) + return (ENODEV); + + name += len; + + if (isdigit(*name) == 0) + return (ENODEV); + + len = strlen(sep); + + if (*name == '0' && !(name[1] == '\0' || bcmp(name + 1, sep, len) == 0)) + return (ENODEV); + + for (*u = 0; isdigit(*name) != 0; name++) { + *u *= 10; + *u += *name - '0'; + if (*u > dsp_umax) + return (ENODEV); + } + + if (*name == '\0') + return ((use_sep == 0) ? 0 : ENODEV); + + if (bcmp(name, sep, len) != 0 || isdigit(name[len]) == 0) + return (ENODEV); + + name += len; + + if (*name == '0' && name[1] != '\0') + return (ENODEV); + + for (*c = 0; isdigit(*name) != 0; name++) { + *c *= 10; + *c += *name - '0'; + if (*c > dsp_cmax) + return (ENODEV); + } + + if (*name != '\0') + return (ENODEV); + + return (0); +} + static void -dsp_clone(void *arg, char *name, int namelen, struct cdev **dev) +dsp_clone(void *arg, +#if __FreeBSD_version >= 600034 + struct ucred *cred, +#endif + char *name, int namelen, struct cdev **dev) { - struct cdev *pdev; - struct snddev_info *pcm_dev; - struct snddev_channel *pcm_chan; - int i, unit, devtype; - int devtypes[3] = {SND_DEV_DSP, SND_DEV_DSP16, SND_DEV_AUDIO}; - char *devnames[3] = {"dsp", "dspW", "audio"}; + struct snddev_info *d; + struct snd_clone_entry *ce; + struct pcm_channel *c; + int i, unit, udcmask, cunit, devtype, devhw, devcmax, tumax; + char *devname, *devcmp, *devsep; + + KASSERT(dsp_umax >= 0 && dsp_cmax >= 0, ("Uninitialized unit!")); if (*dev != NULL) return; - if (pcm_devclass == NULL) - return; - devtype = 0; unit = -1; - for (i = 0; (i < 3) && (unit == -1); i++) { - devtype = devtypes[i]; - if (strcmp(name, devnames[i]) == 0) { + cunit = -1; + devtype = -1; + devhw = 0; + devcmax = -1; + tumax = -1; + devname = NULL; + devsep = NULL; + + for (i = 0; unit == -1 && + i < (sizeof(dsp_cdevs) / sizeof(dsp_cdevs[0])); i++) { + devtype = dsp_cdevs[i].type; + devcmp = dsp_cdevs[i].name; + devsep = dsp_cdevs[i].sep; + devname = dsp_cdevs[i].alias; + if (devname == NULL) + devname = devcmp; + devhw = dsp_cdevs[i].hw; + devcmax = dsp_cdevs[i].max - 1; + if (strcmp(name, devcmp) == 0) unit = snd_unit; - } else { - if (dev_stdclone(name, NULL, devnames[i], &unit) != 1) - unit = -1; + else if (dsp_stdclone(name, devcmp, devsep, + dsp_cdevs[i].use_sep, &unit, &cunit) != 0) { + unit = -1; + cunit = -1; } } - if (unit == -1 || unit >= devclass_get_maxunit(pcm_devclass)) + + d = devclass_get_softc(pcm_devclass, unit); + if (!PCM_REGISTERED(d) || d->clones == NULL) return; - pcm_dev = devclass_get_softc(pcm_devclass, unit); + /* XXX Need Giant magic entry ??? */ - if (pcm_dev == NULL) + pcm_lock(d); + if (snd_clone_disabled(d->clones)) { + pcm_unlock(d); return; + } - SLIST_FOREACH(pcm_chan, &pcm_dev->channels, link) { + PCM_WAIT(d); + PCM_ACQUIRE(d); + pcm_unlock(d); - switch(devtype) { - case SND_DEV_DSP: - pdev = pcm_chan->dsp_devt; - break; - case SND_DEV_DSP16: - pdev = pcm_chan->dspW_devt; - break; - case SND_DEV_AUDIO: - pdev = pcm_chan->audio_devt; - break; - default: - panic("Unknown devtype %d", devtype); - } + udcmask = snd_u2unit(unit) | snd_d2unit(devtype); - if ((pdev->si_drv1 == NULL) && (pdev->si_drv2 == NULL)) { - *dev = pdev; + if (devhw != 0) { + KASSERT(devcmax <= dsp_cmax, + ("overflow: devcmax=%d, dsp_cmax=%d", devcmax, dsp_cmax)); + if (cunit > devcmax) { + PCM_RELEASE_QUICK(d); return; } + udcmask |= snd_c2unit(cunit); + CHN_FOREACH(c, d, channels.pcm) { + CHN_LOCK(c); + if (c->unit != udcmask) { + CHN_UNLOCK(c); + continue; + } + CHN_UNLOCK(c); + udcmask &= ~snd_c2unit(cunit); + /* + * Temporarily increase clone maxunit to overcome + * vchan flexibility. + * + * # sysctl dev.pcm.0.play.vchans=256 + * dev.pcm.0.play.vchans: 1 -> 256 + * # cat /dev/zero > /dev/dsp0.vp255 & + * [1] 17296 + * # sysctl dev.pcm.0.play.vchans=0 + * dev.pcm.0.play.vchans: 256 -> 1 + * # fg + * [1] + running cat /dev/zero > /dev/dsp0.vp255 + * ^C + * # cat /dev/zero > /dev/dsp0.vp255 + * zsh: operation not supported: /dev/dsp0.vp255 + */ + tumax = snd_clone_getmaxunit(d->clones); + if (cunit > tumax) + snd_clone_setmaxunit(d->clones, cunit); + else + tumax = -1; + goto dsp_clone_alloc; + } + /* + * Ok, so we're requesting unallocated vchan, but still + * within maximum vchan limit. + */ + if (((devtype == SND_DEV_DSPHW_VPLAY && d->pvchancount > 0) || + (devtype == SND_DEV_DSPHW_VREC && d->rvchancount > 0)) && + cunit < snd_maxautovchans) { + udcmask &= ~snd_c2unit(cunit); + tumax = snd_clone_getmaxunit(d->clones); + if (cunit > tumax) + snd_clone_setmaxunit(d->clones, cunit); + else + tumax = -1; + goto dsp_clone_alloc; + } + PCM_RELEASE_QUICK(d); + return; + } + +dsp_clone_alloc: + ce = snd_clone_alloc(d->clones, dev, &cunit, udcmask); + if (tumax != -1) + snd_clone_setmaxunit(d->clones, tumax); + if (ce != NULL) { + udcmask |= snd_c2unit(cunit); + *dev = make_dev(&dsp_cdevsw, unit2minor(udcmask), + UID_ROOT, GID_WHEEL, 0666, "%s%d%s%d", + devname, unit, devsep, cunit); + snd_clone_register(ce, *dev); } + + PCM_RELEASE_QUICK(d); + + if (*dev != NULL) + dev_ref(*dev); } static void dsp_sysinit(void *p) { + if (dsp_ehtag != NULL) + return; + /* initialize unit numbering */ + snd_unit_init(); + dsp_umax = PCMMAXUNIT; + dsp_cmax = PCMMAXCHAN; dsp_ehtag = EVENTHANDLER_REGISTER(dev_clone, dsp_clone, 0, 1000); } static void dsp_sysuninit(void *p) { - if (dsp_ehtag != NULL) - EVENTHANDLER_DEREGISTER(dev_clone, dsp_ehtag); + if (dsp_ehtag == NULL) + return; + EVENTHANDLER_DEREGISTER(dev_clone, dsp_ehtag); + dsp_ehtag = NULL; } SYSINIT(dsp_sysinit, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, dsp_sysinit, NULL); SYSUNINIT(dsp_sysuninit, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, dsp_sysuninit, NULL); #endif +char * +dsp_unit2name(char *buf, size_t len, int unit) +{ + int i, dtype; + + KASSERT(buf != NULL && len != 0, + ("bogus buf=%p len=%ju", buf, (uintmax_t)len)); + + dtype = snd_unit2d(unit); + + for (i = 0; i < (sizeof(dsp_cdevs) / sizeof(dsp_cdevs[0])); i++) { + if (dtype != dsp_cdevs[i].type || dsp_cdevs[i].alias != NULL) + continue; + snprintf(buf, len, "%s%d%s%d", dsp_cdevs[i].name, + snd_unit2u(unit), dsp_cdevs[i].sep, snd_unit2c(unit)); + return (buf); + } + + return (NULL); +} + +/** + * @brief Handler for SNDCTL_AUDIOINFO. + * + * Gathers information about the audio device specified in ai->dev. If + * ai->dev == -1, then this function gathers information about the current + * device. If the call comes in on a non-audio device and ai->dev == -1, + * return EINVAL. + * + * This routine is supposed to go practically straight to the hardware, + * getting capabilities directly from the sound card driver, side-stepping + * the intermediate channel interface. + * + * Note, however, that the usefulness of this command is significantly + * decreased when requesting info about any device other than the one serving + * the request. While each snddev_channel refers to a specific device node, + * the converse is *not* true. Currently, when a sound device node is opened, + * the sound subsystem scans for an available audio channel (or channels, if + * opened in read+write) and then assigns them to the si_drv[12] private + * data fields. As a result, any information returned linking a channel to + * a specific character device isn't necessarily accurate. + * + * @note + * Calling threads must not hold any snddev_info or pcm_channel locks. + * + * @param dev device on which the ioctl was issued + * @param ai ioctl request data container + * + * @retval 0 success + * @retval EINVAL ai->dev specifies an invalid device + * + * @todo Verify correctness of Doxygen tags. ;) + */ +int +dsp_oss_audioinfo(struct cdev *i_dev, oss_audioinfo *ai) +{ + struct pcmchan_caps *caps; + struct pcm_channel *ch; + struct snddev_info *d; + uint32_t fmts; + int i, nchan, *rates, minch, maxch; + char *devname, buf[CHN_NAMELEN]; + + /* + * If probing the device that received the ioctl, make sure it's a + * DSP device. (Users may use this ioctl with /dev/mixer and + * /dev/midi.) + */ + if (ai->dev == -1 && i_dev->si_devsw != &dsp_cdevsw) + return (EINVAL); + + ch = NULL; + devname = NULL; + nchan = 0; + bzero(buf, sizeof(buf)); + + /* + * Search for the requested audio device (channel). Start by + * iterating over pcm devices. + */ + for (i = 0; pcm_devclass != NULL && + i < devclass_get_maxunit(pcm_devclass); i++) { + d = devclass_get_softc(pcm_devclass, i); + if (!PCM_REGISTERED(d)) + continue; + + /* XXX Need Giant magic entry ??? */ + + /* See the note in function docblock */ + mtx_assert(d->lock, MA_NOTOWNED); + pcm_lock(d); + + CHN_FOREACH(ch, d, channels.pcm) { + mtx_assert(ch->lock, MA_NOTOWNED); + CHN_LOCK(ch); + if (ai->dev == -1) { + if (DSP_REGISTERED(d, i_dev) && + (ch == PCM_RDCH(i_dev) || /* record ch */ + ch == PCM_WRCH(i_dev))) { /* playback ch */ + devname = dsp_unit2name(buf, + sizeof(buf), ch->unit); + } + } else if (ai->dev == nchan) { + devname = dsp_unit2name(buf, sizeof(buf), + ch->unit); + } + if (devname != NULL) + break; + CHN_UNLOCK(ch); + ++nchan; + } + + if (devname != NULL) { + /* + * At this point, the following synchronization stuff + * has happened: + * - a specific PCM device is locked. + * - a specific audio channel has been locked, so be + * sure to unlock when exiting; + */ + + caps = chn_getcaps(ch); + + /* + * With all handles collected, zero out the user's + * container and begin filling in its fields. + */ + bzero((void *)ai, sizeof(oss_audioinfo)); + + ai->dev = nchan; + strlcpy(ai->name, ch->name, sizeof(ai->name)); + + if ((ch->flags & CHN_F_BUSY) == 0) + ai->busy = 0; + else + ai->busy = (ch->direction == PCMDIR_PLAY) ? OPEN_WRITE : OPEN_READ; + + /** + * @note + * @c cmd - OSSv4 docs: "Only supported under Linux at + * this moment." Cop-out, I know, but I'll save + * running around in the process table for later. + * Is there a risk of leaking information? + */ + ai->pid = ch->pid; + + /* + * These flags stolen from SNDCTL_DSP_GETCAPS handler. + * Note, however, that a single channel operates in + * only one direction, so DSP_CAP_DUPLEX is out. + */ + /** + * @todo @c SNDCTL_AUDIOINFO::caps - Make drivers keep + * these in pcmchan::caps? + */ + ai->caps = DSP_CAP_REALTIME | DSP_CAP_MMAP | DSP_CAP_TRIGGER; + + /* + * Collect formats supported @b natively by the + * device. Also determine min/max channels. (I.e., + * mono, stereo, or both?) + * + * If any channel is stereo, maxch = 2; + * if all channels are stereo, minch = 2, too; + * if any channel is mono, minch = 1; + * and if all channels are mono, maxch = 1. + */ + minch = 0; + maxch = 0; + fmts = 0; + for (i = 0; caps->fmtlist[i]; i++) { + fmts |= caps->fmtlist[i]; + if (caps->fmtlist[i] & AFMT_STEREO) { + minch = (minch == 0) ? 2 : minch; + maxch = 2; + } else { + minch = 1; + maxch = (maxch == 0) ? 1 : maxch; + } + } + + if (ch->direction == PCMDIR_PLAY) + ai->oformats = fmts; + else + ai->iformats = fmts; + + /** + * @note + * @c magic - OSSv4 docs: "Reserved for internal use + * by OSS." + * + * @par + * @c card_number - OSSv4 docs: "Number of the sound + * card where this device belongs or -1 if this + * information is not available. Applications + * should normally not use this field for any + * purpose." + */ + ai->card_number = -1; + /** + * @todo @c song_name - depends first on + * SNDCTL_[GS]ETSONG @todo @c label - depends + * on SNDCTL_[GS]ETLABEL + * @todo @c port_number - routing information? + */ + ai->port_number = -1; + ai->mixer_dev = (d->mixer_dev != NULL) ? PCMUNIT(d->mixer_dev) : -1; + /** + * @note + * @c real_device - OSSv4 docs: "Obsolete." + */ + ai->real_device = -1; + strlcpy(ai->devnode, devname, sizeof(ai->devnode)); + ai->enabled = device_is_attached(d->dev) ? 1 : 0; + /** + * @note + * @c flags - OSSv4 docs: "Reserved for future use." + * + * @note + * @c binding - OSSv4 docs: "Reserved for future use." + * + * @todo @c handle - haven't decided how to generate + * this yet; bus, vendor, device IDs? + */ + ai->min_rate = caps->minspeed; + ai->max_rate = caps->maxspeed; + + ai->min_channels = minch; + ai->max_channels = maxch; + + ai->nrates = chn_getrates(ch, &rates); + if (ai->nrates > OSS_MAX_SAMPLE_RATES) + ai->nrates = OSS_MAX_SAMPLE_RATES; + + for (i = 0; i < ai->nrates; i++) + ai->rates[i] = rates[i]; + + CHN_UNLOCK(ch); + } + + pcm_unlock(d); + + if (devname != NULL) + return (0); + } + + /* Exhausted the search -- nothing is locked, so return. */ + return (EINVAL); +} + +/** + * @brief Assigns a PCM channel to a sync group. + * + * Sync groups are used to enable audio operations on multiple devices + * simultaneously. They may be used with any number of devices and may + * span across applications. Devices are added to groups with + * the SNDCTL_DSP_SYNCGROUP ioctl, and operations are triggered with the + * SNDCTL_DSP_SYNCSTART ioctl. + * + * If the @c id field of the @c group parameter is set to zero, then a new + * sync group is created. Otherwise, wrch and rdch (if set) are added to + * the group specified. + * + * @todo As far as memory allocation, should we assume that things are + * okay and allocate with M_WAITOK before acquiring channel locks, + * freeing later if not? + * + * @param wrch output channel associated w/ device (if any) + * @param rdch input channel associated w/ device (if any) + * @param group Sync group parameters + * + * @retval 0 success + * @retval non-zero error to be propagated upstream + */ +static int +dsp_oss_syncgroup(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_syncgroup *group) +{ + struct pcmchan_syncmember *smrd, *smwr; + struct pcmchan_syncgroup *sg; + int ret, sg_ids[3]; + + smrd = NULL; + smwr = NULL; + sg = NULL; + ret = 0; + + /* + * Free_unr() may sleep, so store released syncgroup IDs until after + * all locks are released. + */ + sg_ids[0] = sg_ids[1] = sg_ids[2] = 0; + PCM_SG_LOCK(); + + /* + * - Insert channel(s) into group's member list. + * - Set CHN_F_NOTRIGGER on channel(s). + * - Stop channel(s). + */ + + /* + * If device's channels are already mapped to a group, unmap them. + */ + if (wrch) { + CHN_LOCK(wrch); + sg_ids[0] = chn_syncdestroy(wrch); + } + + if (rdch) { + CHN_LOCK(rdch); + sg_ids[1] = chn_syncdestroy(rdch); + } + + /* + * Verify that mode matches character device properites. + * - Bail if PCM_ENABLE_OUTPUT && wrch == NULL. + * - Bail if PCM_ENABLE_INPUT && rdch == NULL. + */ + if (((wrch == NULL) && (group->mode & PCM_ENABLE_OUTPUT)) || + ((rdch == NULL) && (group->mode & PCM_ENABLE_INPUT))) { + ret = EINVAL; + goto out; + } + + /* + * An id of zero indicates the user wants to create a new + * syncgroup. + */ + if (group->id == 0) { + sg = (struct pcmchan_syncgroup *)malloc(sizeof(*sg), M_DEVBUF, M_NOWAIT); + if (sg != NULL) { + SLIST_INIT(&sg->members); + sg->id = alloc_unr(pcmsg_unrhdr); + + group->id = sg->id; + SLIST_INSERT_HEAD(&snd_pcm_syncgroups, sg, link); + } else + ret = ENOMEM; + } else { + SLIST_FOREACH(sg, &snd_pcm_syncgroups, link) { + if (sg->id == group->id) + break; + } + if (sg == NULL) + ret = EINVAL; + } + + /* Couldn't create or find a syncgroup. Fail. */ + if (sg == NULL) + goto out; + + /* + * Allocate a syncmember, assign it and a channel together, and + * insert into syncgroup. + */ + if (group->mode & PCM_ENABLE_INPUT) { + smrd = (struct pcmchan_syncmember *)malloc(sizeof(*smrd), M_DEVBUF, M_NOWAIT); + if (smrd == NULL) { + ret = ENOMEM; + goto out; + } + + SLIST_INSERT_HEAD(&sg->members, smrd, link); + smrd->parent = sg; + smrd->ch = rdch; + + chn_abort(rdch); + rdch->flags |= CHN_F_NOTRIGGER; + rdch->sm = smrd; + } + + if (group->mode & PCM_ENABLE_OUTPUT) { + smwr = (struct pcmchan_syncmember *)malloc(sizeof(*smwr), M_DEVBUF, M_NOWAIT); + if (smwr == NULL) { + ret = ENOMEM; + goto out; + } + + SLIST_INSERT_HEAD(&sg->members, smwr, link); + smwr->parent = sg; + smwr->ch = wrch; + + chn_abort(wrch); + wrch->flags |= CHN_F_NOTRIGGER; + wrch->sm = smwr; + } + + +out: + if (ret != 0) { + if (smrd != NULL) + free(smrd, M_DEVBUF); + if ((sg != NULL) && SLIST_EMPTY(&sg->members)) { + sg_ids[2] = sg->id; + SLIST_REMOVE(&snd_pcm_syncgroups, sg, pcmchan_syncgroup, link); + free(sg, M_DEVBUF); + } + + if (wrch) + wrch->sm = NULL; + if (rdch) + rdch->sm = NULL; + } + + if (wrch) + CHN_UNLOCK(wrch); + if (rdch) + CHN_UNLOCK(rdch); + + PCM_SG_UNLOCK(); + + if (sg_ids[0]) + free_unr(pcmsg_unrhdr, sg_ids[0]); + if (sg_ids[1]) + free_unr(pcmsg_unrhdr, sg_ids[1]); + if (sg_ids[2]) + free_unr(pcmsg_unrhdr, sg_ids[2]); + + return (ret); +} + +/** + * @brief Launch a sync group into action + * + * Sync groups are established via SNDCTL_DSP_SYNCGROUP. This function + * iterates over all members, triggering them along the way. + * + * @note Caller must not hold any channel locks. + * + * @param sg_id sync group identifier + * + * @retval 0 success + * @retval non-zero error worthy of propagating upstream to user + */ +static int +dsp_oss_syncstart(int sg_id) +{ + struct pcmchan_syncmember *sm, *sm_tmp; + struct pcmchan_syncgroup *sg; + struct pcm_channel *c; + int ret, needlocks; + + /* Get the synclists lock */ + PCM_SG_LOCK(); + + do { + ret = 0; + needlocks = 0; + + /* Search for syncgroup by ID */ + SLIST_FOREACH(sg, &snd_pcm_syncgroups, link) { + if (sg->id == sg_id) + break; + } + + /* Return EINVAL if not found */ + if (sg == NULL) { + ret = EINVAL; + break; + } + + /* Any removals resulting in an empty group should've handled this */ + KASSERT(!SLIST_EMPTY(&sg->members), ("found empty syncgroup")); + + /* + * Attempt to lock all member channels - if any are already + * locked, unlock those acquired, sleep for a bit, and try + * again. + */ + SLIST_FOREACH(sm, &sg->members, link) { + if (CHN_TRYLOCK(sm->ch) == 0) { + int timo = hz * 5/1000; + if (timo < 1) + timo = 1; + + /* Release all locked channels so far, retry */ + SLIST_FOREACH(sm_tmp, &sg->members, link) { + /* sm is the member already locked */ + if (sm == sm_tmp) + break; + CHN_UNLOCK(sm_tmp->ch); + } + + /** @todo Is PRIBIO correct/ */ + ret = msleep(sm, &snd_pcm_syncgroups_mtx, + PRIBIO | PCATCH, "pcmsg", timo); + if (ret == EINTR || ret == ERESTART) + break; + + needlocks = 1; + ret = 0; /* Assumes ret == EAGAIN... */ + } + } + } while (needlocks && ret == 0); + + /* Proceed only if no errors encountered. */ + if (ret == 0) { + /* Launch channels */ + while((sm = SLIST_FIRST(&sg->members)) != NULL) { + SLIST_REMOVE_HEAD(&sg->members, link); + + c = sm->ch; + c->sm = NULL; + chn_start(c, 1); + c->flags &= ~CHN_F_NOTRIGGER; + CHN_UNLOCK(c); + + free(sm, M_DEVBUF); + } + + SLIST_REMOVE(&snd_pcm_syncgroups, sg, pcmchan_syncgroup, link); + free(sg, M_DEVBUF); + } + + PCM_SG_UNLOCK(); + + /* + * Free_unr() may sleep, so be sure to give up the syncgroup lock + * first. + */ + if (ret == 0) + free_unr(pcmsg_unrhdr, sg_id); + + return (ret); +} + +/** + * @brief Handler for SNDCTL_DSP_POLICY + * + * The SNDCTL_DSP_POLICY ioctl is a simpler interface to control fragment + * size and count like with SNDCTL_DSP_SETFRAGMENT. Instead of the user + * specifying those two parameters, s/he simply selects a number from 0..10 + * which corresponds to a buffer size. Smaller numbers request smaller + * buffers with lower latencies (at greater overhead from more frequent + * interrupts), while greater numbers behave in the opposite manner. + * + * The 4Front spec states that a value of 5 should be the default. However, + * this implementation deviates slightly by using a linear scale without + * consulting drivers. I.e., even though drivers may have different default + * buffer sizes, a policy argument of 5 will have the same result across + * all drivers. + * + * See http://manuals.opensound.com/developer/SNDCTL_DSP_POLICY.html for + * more information. + * + * @todo When SNDCTL_DSP_COOKEDMODE is supported, it'll be necessary to + * work with hardware drivers directly. + * + * @note PCM channel arguments must not be locked by caller. + * + * @param wrch Pointer to opened playback channel (optional; may be NULL) + * @param rdch " recording channel (optional; may be NULL) + * @param policy Integer from [0:10] + * + * @retval 0 constant (for now) + */ +static int +dsp_oss_policy(struct pcm_channel *wrch, struct pcm_channel *rdch, int policy) +{ + int ret; + + if (policy < CHN_POLICY_MIN || policy > CHN_POLICY_MAX) + return (EIO); + + /* Default: success */ + ret = 0; + + if (rdch) { + CHN_LOCK(rdch); + ret = chn_setlatency(rdch, policy); + CHN_UNLOCK(rdch); + } + + if (wrch && ret == 0) { + CHN_LOCK(wrch); + ret = chn_setlatency(wrch, policy); + CHN_UNLOCK(wrch); + } + + if (ret) + ret = EIO; + + return (ret); +} + +#ifdef OSSV4_EXPERIMENT +/** + * @brief Enable or disable "cooked" mode + * + * This is a handler for @c SNDCTL_DSP_COOKEDMODE. When in cooked mode, which + * is the default, the sound system handles rate and format conversions + * automatically (ex: user writing 11025Hz/8 bit/unsigned but card only + * operates with 44100Hz/16bit/signed samples). + * + * Disabling cooked mode is intended for applications wanting to mmap() + * a sound card's buffer space directly, bypassing the FreeBSD 2-stage + * feeder architecture, presumably to gain as much control over audio + * hardware as possible. + * + * See @c http://manuals.opensound.com/developer/SNDCTL_DSP_COOKEDMODE.html + * for more details. + * + * @note Currently, this function is just a stub that always returns EINVAL. + * + * @todo Figure out how to and actually implement this. + * + * @param wrch playback channel (optional; may be NULL) + * @param rdch recording channel (optional; may be NULL) + * @param enabled 0 = raw mode, 1 = cooked mode + * + * @retval EINVAL Operation not yet supported. + */ +static int +dsp_oss_cookedmode(struct pcm_channel *wrch, struct pcm_channel *rdch, int enabled) +{ + return (EINVAL); +} + +/** + * @brief Retrieve channel interleaving order + * + * This is the handler for @c SNDCTL_DSP_GET_CHNORDER. + * + * See @c http://manuals.opensound.com/developer/SNDCTL_DSP_GET_CHNORDER.html + * for more details. + * + * @note As the ioctl definition is still under construction, FreeBSD + * does not currently support SNDCTL_DSP_GET_CHNORDER. + * + * @param wrch playback channel (optional; may be NULL) + * @param rdch recording channel (optional; may be NULL) + * @param map channel map (result will be stored there) + * + * @retval EINVAL Operation not yet supported. + */ +static int +dsp_oss_getchnorder(struct pcm_channel *wrch, struct pcm_channel *rdch, unsigned long long *map) +{ + return (EINVAL); +} + +/** + * @brief Specify channel interleaving order + * + * This is the handler for @c SNDCTL_DSP_SET_CHNORDER. + * + * @note As the ioctl definition is still under construction, FreeBSD + * does not currently support @c SNDCTL_DSP_SET_CHNORDER. + * + * @param wrch playback channel (optional; may be NULL) + * @param rdch recording channel (optional; may be NULL) + * @param map channel map + * + * @retval EINVAL Operation not yet supported. + */ +static int +dsp_oss_setchnorder(struct pcm_channel *wrch, struct pcm_channel *rdch, unsigned long long *map) +{ + return (EINVAL); +} + +/** + * @brief Retrieve an audio device's label + * + * This is a handler for the @c SNDCTL_GETLABEL ioctl. + * + * See @c http://manuals.opensound.com/developer/SNDCTL_GETLABEL.html + * for more details. + * + * From Hannu@4Front: "For example ossxmix (just like some HW mixer + * consoles) can show variable "labels" for certain controls. By default + * the application name (say quake) is shown as the label but + * applications may change the labels themselves." + * + * @note As the ioctl definition is still under construction, FreeBSD + * does not currently support @c SNDCTL_GETLABEL. + * + * @param wrch playback channel (optional; may be NULL) + * @param rdch recording channel (optional; may be NULL) + * @param label label gets copied here + * + * @retval EINVAL Operation not yet supported. + */ +static int +dsp_oss_getlabel(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_label_t *label) +{ + return (EINVAL); +} + +/** + * @brief Specify an audio device's label + * + * This is a handler for the @c SNDCTL_SETLABEL ioctl. Please see the + * comments for @c dsp_oss_getlabel immediately above. + * + * See @c http://manuals.opensound.com/developer/SNDCTL_GETLABEL.html + * for more details. + * + * @note As the ioctl definition is still under construction, FreeBSD + * does not currently support SNDCTL_SETLABEL. + * + * @param wrch playback channel (optional; may be NULL) + * @param rdch recording channel (optional; may be NULL) + * @param label label gets copied from here + * + * @retval EINVAL Operation not yet supported. + */ +static int +dsp_oss_setlabel(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_label_t *label) +{ + return (EINVAL); +} + +/** + * @brief Retrieve name of currently played song + * + * This is a handler for the @c SNDCTL_GETSONG ioctl. Audio players could + * tell the system the name of the currently playing song, which would be + * visible in @c /dev/sndstat. + * + * See @c http://manuals.opensound.com/developer/SNDCTL_GETSONG.html + * for more details. + * + * @note As the ioctl definition is still under construction, FreeBSD + * does not currently support SNDCTL_GETSONG. + * + * @param wrch playback channel (optional; may be NULL) + * @param rdch recording channel (optional; may be NULL) + * @param song song name gets copied here + * + * @retval EINVAL Operation not yet supported. + */ +static int +dsp_oss_getsong(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_longname_t *song) +{ + return (EINVAL); +} + +/** + * @brief Retrieve name of currently played song + * + * This is a handler for the @c SNDCTL_SETSONG ioctl. Audio players could + * tell the system the name of the currently playing song, which would be + * visible in @c /dev/sndstat. + * + * See @c http://manuals.opensound.com/developer/SNDCTL_SETSONG.html + * for more details. + * + * @note As the ioctl definition is still under construction, FreeBSD + * does not currently support SNDCTL_SETSONG. + * + * @param wrch playback channel (optional; may be NULL) + * @param rdch recording channel (optional; may be NULL) + * @param song song name gets copied from here + * + * @retval EINVAL Operation not yet supported. + */ +static int +dsp_oss_setsong(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_longname_t *song) +{ + return (EINVAL); +} + +/** + * @brief Rename a device + * + * This is a handler for the @c SNDCTL_SETNAME ioctl. + * + * See @c http://manuals.opensound.com/developer/SNDCTL_SETNAME.html for + * more details. + * + * From Hannu@4Front: "This call is used to change the device name + * reported in /dev/sndstat and ossinfo. So instead of using some generic + * 'OSS loopback audio (MIDI) driver' the device may be given a meaningfull + * name depending on the current context (for example 'OSS virtual wave table + * synth' or 'VoIP link to London')." + * + * @note As the ioctl definition is still under construction, FreeBSD + * does not currently support SNDCTL_SETNAME. + * + * @param wrch playback channel (optional; may be NULL) + * @param rdch recording channel (optional; may be NULL) + * @param name new device name gets copied from here + * + * @retval EINVAL Operation not yet supported. + */ +static int +dsp_oss_setname(struct pcm_channel *wrch, struct pcm_channel *rdch, oss_longname_t *name) +{ + return (EINVAL); +} +#endif /* !OSSV4_EXPERIMENT */ --- sys/dev/sound/pcm/dsp.h.orig Sun Jan 30 09:00:04 2005 +++ sys/dev/sound/pcm/dsp.h Thu Jul 12 12:04:19 2007 @@ -23,7 +23,20 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $FreeBSD: src/sys/dev/sound/pcm/dsp.h,v 1.8.2.1 2005/01/30 01:00:04 imp Exp $ + * $FreeBSD: src/sys/dev/sound/pcm/dsp.h,v 1.13 2007/06/16 03:37:28 ariff Exp $ */ +#ifndef _PCMDSP_H_ +#define _PCMDSP_H_ + extern struct cdevsw dsp_cdevsw; + +struct dsp_cdevinfo; + +char *dsp_unit2name(char *, size_t, int); +int dsp_oss_audioinfo(struct cdev *, oss_audioinfo *); + +void dsp_cdevinfo_init(struct snddev_info *); +void dsp_cdevinfo_flush(struct snddev_info *); + +#endif /* !_PCMDSP_H_ */ --- sys/dev/sound/pcm/fake.c.orig Sun Jan 30 09:00:05 2005 +++ sys/dev/sound/pcm/fake.c Thu Jul 12 12:04:19 2007 @@ -26,9 +26,13 @@ #include -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pcm/fake.c,v 1.13.4.1 2005/01/30 01:00:05 imp Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pcm/fake.c,v 1.18 2007/03/15 18:19:01 ariff Exp $"); static u_int32_t fk_fmt[] = { + AFMT_MU_LAW, + AFMT_STEREO | AFMT_MU_LAW, + AFMT_A_LAW, + AFMT_STEREO | AFMT_A_LAW, AFMT_U8, AFMT_STEREO | AFMT_U8, AFMT_S8, @@ -41,6 +45,22 @@ AFMT_STEREO | AFMT_S16_BE, AFMT_U16_BE, AFMT_STEREO | AFMT_U16_BE, + AFMT_S24_LE, + AFMT_STEREO | AFMT_S24_LE, + AFMT_U24_LE, + AFMT_STEREO | AFMT_U24_LE, + AFMT_S24_BE, + AFMT_STEREO | AFMT_S24_BE, + AFMT_U24_BE, + AFMT_STEREO | AFMT_U24_BE, + AFMT_S32_LE, + AFMT_STEREO | AFMT_S32_LE, + AFMT_U32_LE, + AFMT_STEREO | AFMT_U32_LE, + AFMT_S32_BE, + AFMT_STEREO | AFMT_S32_BE, + AFMT_U32_BE, + AFMT_STEREO | AFMT_U32_BE, 0 }; static struct pcmchan_caps fk_caps = {0, 1000000, fk_fmt, 0}; @@ -117,9 +137,16 @@ struct snddev_info *d = device_get_softc(dev); struct pcm_channel *c; - c = malloc(sizeof(*c), M_DEVBUF, M_WAITOK); + c = malloc(sizeof(*c), M_DEVBUF, M_WAITOK | M_ZERO); c->methods = kobj_create(&fkchan_class, M_DEVBUF, M_WAITOK); c->parentsnddev = d; + /* + * Fake channel is such a blessing in disguise. Using this, + * we can keep track prefered virtual channel speed / format without + * querying kernel hint repetitively (see vchan_create / vchan.c). + */ + c->speed = 0; + c->format = 0; snprintf(c->name, CHN_NAMELEN, "%s:fake", device_get_nameunit(dev)); return c; --- sys/dev/sound/pcm/feeder.c.orig Sun Jan 30 09:00:05 2005 +++ sys/dev/sound/pcm/feeder.c Thu Jul 12 12:04:19 2007 @@ -28,13 +28,50 @@ #include "feeder_if.h" -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pcm/feeder.c,v 1.32.4.1 2005/01/30 01:00:05 imp Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pcm/feeder.c,v 1.44 2007/06/17 15:53:11 ariff Exp $"); MALLOC_DEFINE(M_FEEDER, "feeder", "pcm feeder"); #define MAXFEEDERS 256 #undef FEEDER_DEBUG +int feeder_buffersize = FEEDBUFSZ; +TUNABLE_INT("hw.snd.feeder_buffersize", &feeder_buffersize); + +#ifdef SND_DEBUG +static int +sysctl_hw_snd_feeder_buffersize(SYSCTL_HANDLER_ARGS) +{ + int i, err, val; + + val = feeder_buffersize; + err = sysctl_handle_int(oidp, &val, 0, req); + + if (err != 0 || req->newptr == NULL) + return err; + + if (val < FEEDBUFSZ_MIN || val > FEEDBUFSZ_MAX) + return EINVAL; + + i = 0; + while (val >> i) + i++; + i = 1 << i; + if (i > val && (i >> 1) > 0 && (i >> 1) >= ((val * 3) >> 2)) + i >>= 1; + + feeder_buffersize = i; + + return err; +} +SYSCTL_PROC(_hw_snd, OID_AUTO, feeder_buffersize, CTLTYPE_INT | CTLFLAG_RW, + 0, sizeof(int), sysctl_hw_snd_feeder_buffersize, "I", + "feeder buffer size"); +#else +SYSCTL_INT(_hw_snd, OID_AUTO, feeder_buffersize, CTLFLAG_RD, + &feeder_buffersize, FEEDBUFSZ, "feeder buffer size"); +#endif + struct feedertab_entry { SLIST_ENTRY(feedertab_entry) link; struct feeder_class *feederclass; @@ -72,6 +109,55 @@ SLIST_INSERT_HEAD(&feedertab, fte, link); feedercnt++; + /* initialize global variables */ + + if (snd_verbose < 0 || snd_verbose > 4) + snd_verbose = 1; + + /* initialize unit numbering */ + snd_unit_init(); + if (snd_unit < 0 || snd_unit > PCMMAXUNIT) + snd_unit = -1; + + if (snd_maxautovchans < 0 || + snd_maxautovchans > SND_MAXVCHANS) + snd_maxautovchans = 0; + + if (chn_latency < CHN_LATENCY_MIN || + chn_latency > CHN_LATENCY_MAX) + chn_latency = CHN_LATENCY_DEFAULT; + + if (chn_latency_profile < CHN_LATENCY_PROFILE_MIN || + chn_latency_profile > CHN_LATENCY_PROFILE_MAX) + chn_latency_profile = CHN_LATENCY_PROFILE_DEFAULT; + + if (feeder_buffersize < FEEDBUFSZ_MIN || + feeder_buffersize > FEEDBUFSZ_MAX) + feeder_buffersize = FEEDBUFSZ; + + if (feeder_rate_min < FEEDRATE_MIN || + feeder_rate_max < FEEDRATE_MIN || + feeder_rate_min > FEEDRATE_MAX || + feeder_rate_max > FEEDRATE_MAX || + !(feeder_rate_min < feeder_rate_max)) { + feeder_rate_min = FEEDRATE_RATEMIN; + feeder_rate_max = FEEDRATE_RATEMAX; + } + + if (feeder_rate_round < FEEDRATE_ROUNDHZ_MIN || + feeder_rate_round > FEEDRATE_ROUNDHZ_MAX) + feeder_rate_round = FEEDRATE_ROUNDHZ; + + if (bootverbose) + printf("%s: snd_unit=%d snd_maxautovchans=%d " + "latency=%d feeder_buffersize=%d " + "feeder_rate_min=%d feeder_rate_max=%d " + "feeder_rate_round=%d\n", + __func__, snd_unit, snd_maxautovchans, + chn_latency, feeder_buffersize, + feeder_rate_min, feeder_rate_max, + feeder_rate_round); + /* we've got our root feeder so don't veto pcm loading anymore */ pcm_veto_load = 0; @@ -259,37 +345,339 @@ return 1; } +/* + * See feeder_fmtchain() for the mumbo-jumbo ridiculous explanation + * of what the heck is this FMT_Q_* + */ +#define FMT_Q_UP 1 +#define FMT_Q_DOWN 2 +#define FMT_Q_EQ 3 +#define FMT_Q_MULTI 4 + +/* + * 14bit format scoring + * -------------------- + * + * 13 12 11 10 9 8 2 1 0 offset + * +---+---+---+---+---+---+-------------+---+---+ + * | X | X | X | X | X | X | X X X X X X | X | X | + * +---+---+---+---+---+---+-------------+---+---+ + * | | | | | | | | | + * | | | | | | | | +--> signed? + * | | | | | | | | + * | | | | | | | +------> bigendian? + * | | | | | | | + * | | | | | | +---------------> total channels + * | | | | | | + * | | | | | +------------------------> AFMT_A_LAW + * | | | | | + * | | | | +----------------------------> AFMT_MU_LAW + * | | | | + * | | | +--------------------------------> AFMT_8BIT + * | | | + * | | +------------------------------------> AFMT_16BIT + * | | + * | +----------------------------------------> AFMT_24BIT + * | + * +--------------------------------------------> AFMT_32BIT + */ +#define score_signeq(s1, s2) (((s1) & 0x1) == ((s2) & 0x1)) +#define score_endianeq(s1, s2) (((s1) & 0x2) == ((s2) & 0x2)) +#define score_cheq(s1, s2) (((s1) & 0xfc) == ((s2) & 0xfc)) +#define score_val(s1) ((s1) & 0x3f00) +#define score_cse(s1) ((s1) & 0x7f) + +u_int32_t +chn_fmtscore(u_int32_t fmt) +{ + u_int32_t ret; + + ret = 0; + if (fmt & AFMT_SIGNED) + ret |= 1 << 0; + if (fmt & AFMT_BIGENDIAN) + ret |= 1 << 1; + if (fmt & AFMT_STEREO) + ret |= (2 & 0x3f) << 2; + else + ret |= (1 & 0x3f) << 2; + if (fmt & AFMT_A_LAW) + ret |= 1 << 8; + else if (fmt & AFMT_MU_LAW) + ret |= 1 << 9; + else if (fmt & AFMT_8BIT) + ret |= 1 << 10; + else if (fmt & AFMT_16BIT) + ret |= 1 << 11; + else if (fmt & AFMT_24BIT) + ret |= 1 << 12; + else if (fmt & AFMT_32BIT) + ret |= 1 << 13; + + return ret; +} + +static u_int32_t +chn_fmtbestfunc(u_int32_t fmt, u_int32_t *fmts, int cheq) +{ + u_int32_t best, score, score2, oldscore; + int i; + + if (fmt == 0 || fmts == NULL || fmts[0] == 0) + return 0; + + if (fmtvalid(fmt, fmts)) + return fmt; + + best = 0; + score = chn_fmtscore(fmt); + oldscore = 0; + for (i = 0; fmts[i] != 0; i++) { + score2 = chn_fmtscore(fmts[i]); + if (cheq && !score_cheq(score, score2)) + continue; + if (oldscore == 0 || + (score_val(score2) == score_val(score)) || + (score_val(score2) == score_val(oldscore)) || + (score_val(score2) > score_val(oldscore) && + score_val(score2) < score_val(score)) || + (score_val(score2) < score_val(oldscore) && + score_val(score2) > score_val(score)) || + (score_val(oldscore) < score_val(score) && + score_val(score2) > score_val(oldscore))) { + if (score_val(oldscore) != score_val(score2) || + score_cse(score) == score_cse(score2) || + ((score_cse(oldscore) != score_cse(score) && + !score_endianeq(score, oldscore) && + (score_endianeq(score, score2) || + (!score_signeq(score, oldscore) && + score_signeq(score, score2)))))) { + best = fmts[i]; + oldscore = score2; + } + } + } + return best; +} + +u_int32_t +chn_fmtbestbit(u_int32_t fmt, u_int32_t *fmts) +{ + return chn_fmtbestfunc(fmt, fmts, 0); +} + +u_int32_t +chn_fmtbeststereo(u_int32_t fmt, u_int32_t *fmts) +{ + return chn_fmtbestfunc(fmt, fmts, 1); +} + +u_int32_t +chn_fmtbest(u_int32_t fmt, u_int32_t *fmts) +{ + u_int32_t best1, best2; + u_int32_t score, score1, score2; + + if (fmtvalid(fmt, fmts)) + return fmt; + + best1 = chn_fmtbeststereo(fmt, fmts); + best2 = chn_fmtbestbit(fmt, fmts); + + if (best1 != 0 && best2 != 0 && best1 != best2) { + if (fmt & AFMT_STEREO) + return best1; + else { + score = score_val(chn_fmtscore(fmt)); + score1 = score_val(chn_fmtscore(best1)); + score2 = score_val(chn_fmtscore(best2)); + if (score1 == score2 || score1 == score) + return best1; + else if (score2 == score) + return best2; + else if (score1 > score2) + return best1; + return best2; + } + } else if (best2 == 0) + return best1; + else + return best2; +} + static struct pcm_feeder * feeder_fmtchain(u_int32_t *to, struct pcm_feeder *source, struct pcm_feeder *stop, int maxdepth) { - struct feedertab_entry *fte; + struct feedertab_entry *fte, *ftebest; struct pcm_feeder *try, *ret; + uint32_t fl, qout, qsrc, qdst; + int qtype; + + if (to == NULL || to[0] == 0) + return NULL; - /* printf("trying %s (%x -> %x)...\n", source->class->name, source->desc->in, source->desc->out); */ + DEB(printf("trying %s (0x%08x -> 0x%08x)...\n", source->class->name, source->desc->in, source->desc->out)); if (fmtvalid(source->desc->out, to)) { - /* printf("got it\n"); */ + DEB(printf("got it\n")); return source; } if (maxdepth < 0) return NULL; + /* + * WARNING: THIS IS _NOT_ FOR THE FAINT HEART + * Disclaimer: I don't expect anybody could understand this + * without deep logical and mathematical analysis + * involving various unnamed probability theorem. + * + * This "Best Fit Random Chain Selection" (BLEHBLEHWHATEVER) algorithm + * is **extremely** difficult to digest especially when applied to + * large sets / numbers of random chains (feeders), each with + * unique characteristic providing different sets of in/out format. + * + * Basically, our FEEDER_FMT (see feeder_fmt.c) chains characteristic: + * 1) Format chains + * 1.1 "8bit to any, not to 8bit" + * 1.1.1 sign can remain consistent, e.g: u8 -> u16[le|be] + * 1.1.2 sign can be changed, e.g: u8 -> s16[le|be] + * 1.1.3 endian can be changed, e.g: u8 -> u16[le|be] + * 1.1.4 both can be changed, e.g: u8 -> [u|s]16[le|be] + * 1.2 "Any to 8bit, not from 8bit" + * 1.2.1 sign can remain consistent, e.g: s16le -> s8 + * 1.2.2 sign can be changed, e.g: s16le -> u8 + * 1.2.3 source endian can be anything e.g: s16[le|be] -> s8 + * 1.2.4 source endian / sign can be anything e.g: [u|s]16[le|be] -> u8 + * 1.3 "Any to any where BOTH input and output either 8bit or non-8bit" + * 1.3.1 endian MUST remain consistent + * 1.3.2 sign CAN be changed + * 1.4 "Long jump" is allowed, e.g: from 16bit to 32bit, excluding + * 16bit to 24bit . + * 2) Channel chains (mono <-> stereo) + * 2.1 Both endian and sign MUST remain consistent + * 3) Endian chains (big endian <-> little endian) + * 3.1 Channels and sign MUST remain consistent + * 4) Sign chains (signed <-> unsigned) + * 4.1 Channels and endian MUST remain consistent + * + * .. and the mother of all chaining rules: + * + * Rules 0: Source and destination MUST not contain multiple selections. + * (qtype != FMT_Q_MULTI) + * + * First of all, our caller ( chn_fmtchain() ) will reduce the possible + * multiple from/to formats to a single best format using chn_fmtbest(). + * Then, using chn_fmtscore(), we determine the chaining characteristic. + * Our main goal is to narrow it down until it reach FMT_Q_EQ chaining + * type while still adhering above chaining rules. + * + * The need for this complicated chaining procedures is inevitable, + * since currently we have more than 200 different types of FEEDER_FMT + * doing various unique format conversion. Without this (the old way), + * it is possible to generate broken chain since it doesn't do any + * sanity checking to ensure that the output format is "properly aligned" + * with the direction of conversion (quality up/down/equal). + * + * Conversion: s24le to s32le + * Possible chain: 1) s24le -> s32le (correct, optimized) + * 2) s24le -> s16le -> s32le + * (since we have feeder_24to16 and feeder_16to32) + * +-- obviously broken! + * + * Using scoring mechanisme, this will ensure that the chaining + * process do the right thing, or at least, give the best chain + * possible without causing quality (the 'Q') degradation. + */ + + qdst = chn_fmtscore(to[0]); + qsrc = chn_fmtscore(source->desc->out); + +#define score_q(s1) score_val(s1) +#define score_8bit(s1) ((s1) & 0x700) +#define score_non8bit(s1) (!score_8bit(s1)) +#define score_across8bit(s1, s2) ((score_8bit(s1) && score_non8bit(s2)) || \ + (score_8bit(s2) && score_non8bit(s1))) + +#define FMT_CHAIN_Q_UP(s1, s2) (score_q(s1) < score_q(s2)) +#define FMT_CHAIN_Q_DOWN(s1, s2) (score_q(s1) > score_q(s2)) +#define FMT_CHAIN_Q_EQ(s1, s2) (score_q(s1) == score_q(s2)) +#define FMT_Q_DOWN_FLAGS(s1, s2) (0x1 | (score_across8bit(s1, s2) ? \ + 0x2 : 0x0)) +#define FMT_Q_UP_FLAGS(s1, s2) FMT_Q_DOWN_FLAGS(s1, s2) +#define FMT_Q_EQ_FLAGS(s1, s2) (0x3ffc | \ + ((score_cheq(s1, s2) && \ + score_endianeq(s1, s2)) ? \ + 0x1 : 0x0) | \ + ((score_cheq(s1, s2) && \ + score_signeq(s1, s2)) ? \ + 0x2 : 0x0)) + + /* Determine chaining direction and set matching flag */ + fl = 0x3fff; + if (to[1] != 0) { + qtype = FMT_Q_MULTI; + printf("%s: WARNING: FMT_Q_MULTI chaining. Expect the unexpected.\n", __func__); + } else if (FMT_CHAIN_Q_DOWN(qsrc, qdst)) { + qtype = FMT_Q_DOWN; + fl = FMT_Q_DOWN_FLAGS(qsrc, qdst); + } else if (FMT_CHAIN_Q_UP(qsrc, qdst)) { + qtype = FMT_Q_UP; + fl = FMT_Q_UP_FLAGS(qsrc, qdst); + } else { + qtype = FMT_Q_EQ; + fl = FMT_Q_EQ_FLAGS(qsrc, qdst); + } + + ftebest = NULL; + SLIST_FOREACH(fte, &feedertab, link) { if (fte->desc == NULL) continue; if (fte->desc->type != FEEDER_FMT) continue; - if (fte->desc->in == source->desc->out) { + qout = chn_fmtscore(fte->desc->out); +#define FMT_Q_MULTI_VALIDATE(qt) ((qt) == FMT_Q_MULTI) +#define FMT_Q_FL_MATCH(qfl, s1, s2) (((s1) & (qfl)) == ((s2) & (qfl))) +#define FMT_Q_UP_VALIDATE(qt, s1, s2, s3) ((qt) == FMT_Q_UP && \ + score_q(s3) >= score_q(s1) && \ + score_q(s3) <= score_q(s2)) +#define FMT_Q_DOWN_VALIDATE(qt, s1, s2, s3) ((qt) == FMT_Q_DOWN && \ + score_q(s3) <= score_q(s1) && \ + score_q(s3) >= score_q(s2)) +#define FMT_Q_EQ_VALIDATE(qt, s1, s2) ((qt) == FMT_Q_EQ && \ + score_q(s1) == score_q(s2)) + if (fte->desc->in == source->desc->out && + (FMT_Q_MULTI_VALIDATE(qtype) || + (FMT_Q_FL_MATCH(fl, qout, qdst) && + (FMT_Q_UP_VALIDATE(qtype, qsrc, qdst, qout) || + FMT_Q_DOWN_VALIDATE(qtype, qsrc, qdst, qout) || + FMT_Q_EQ_VALIDATE(qtype, qdst, qout))))) { try = feeder_create(fte->feederclass, fte->desc); if (try) { try->source = source; - ret = chainok(try, stop)? feeder_fmtchain(to, try, stop, maxdepth - 1) : NULL; + ret = chainok(try, stop) ? feeder_fmtchain(to, try, stop, maxdepth - 1) : NULL; if (ret != NULL) return ret; feeder_destroy(try); } + } else if (fte->desc->in == source->desc->out) { + /* XXX quality must be considered! */ + if (ftebest == NULL) + ftebest = fte; } } + + if (ftebest != NULL) { + try = feeder_create(ftebest->feederclass, ftebest->desc); + if (try) { + try->source = source; + ret = chainok(try, stop) ? feeder_fmtchain(to, try, stop, maxdepth - 1) : NULL; + if (ret != NULL) + return ret; + feeder_destroy(try); + } + } + /* printf("giving up %s...\n", source->class->name); */ return NULL; @@ -299,7 +687,7 @@ chn_fmtchain(struct pcm_channel *c, u_int32_t *to) { struct pcm_feeder *try, *del, *stop; - u_int32_t tmpfrom[2], best, *from; + u_int32_t tmpfrom[2], tmpto[2], best, *from; int i, max, bestmax; KASSERT(c != NULL, ("c == NULL")); @@ -307,44 +695,81 @@ KASSERT(to != NULL, ("to == NULL")); KASSERT(to[0] != 0, ("to[0] == 0")); + if (c == NULL || c->feeder == NULL || to == NULL || to[0] == 0) + return 0; + stop = c->feeder; + best = 0; if (c->direction == PCMDIR_REC && c->feeder->desc->type == FEEDER_ROOT) { from = chn_getcaps(c)->fmtlist; + if (from[1] != 0) { + best = chn_fmtbest(to[0], from); + if (best != 0) { + tmpfrom[0] = best; + tmpfrom[1] = 0; + from = tmpfrom; + } + } } else { tmpfrom[0] = c->feeder->desc->out; tmpfrom[1] = 0; from = tmpfrom; + if (to[1] != 0) { + best = chn_fmtbest(from[0], to); + if (best != 0) { + tmpto[0] = best; + tmpto[1] = 0; + to = tmpto; + } + } } - i = 0; - best = 0; - bestmax = 100; - while (from[i] != 0) { - c->feeder->desc->out = from[i]; - try = NULL; +#define FEEDER_FMTCHAIN_MAXDEPTH 8 + + try = NULL; + + if (to[0] != 0 && from[0] != 0 && + to[1] == 0 && from[1] == 0) { max = 0; - while (try == NULL && max < 8) { + best = from[0]; + c->feeder->desc->out = best; + do { try = feeder_fmtchain(to, c->feeder, stop, max); - if (try == NULL) - max++; - } - if (try != NULL && max < bestmax) { - bestmax = max; - best = from[i]; - } - while (try != NULL && try != stop) { - del = try; - try = try->source; - feeder_destroy(del); + DEB(if (try != NULL) { + printf("%s: 0x%08x -> 0x%08x (maxdepth: %d)\n", + __func__, from[0], to[0], max); + }); + } while (try == NULL && max++ < FEEDER_FMTCHAIN_MAXDEPTH); + } else { + printf("%s: Using the old-way format chaining!\n", __func__); + i = 0; + best = 0; + bestmax = 100; + while (from[i] != 0) { + c->feeder->desc->out = from[i]; + try = NULL; + max = 0; + do { + try = feeder_fmtchain(to, c->feeder, stop, max); + } while (try == NULL && max++ < FEEDER_FMTCHAIN_MAXDEPTH); + if (try != NULL && max < bestmax) { + bestmax = max; + best = from[i]; + } + while (try != NULL && try != stop) { + del = try; + try = try->source; + feeder_destroy(del); + } + i++; } - i++; - } - if (best == 0) - return 0; + if (best == 0) + return 0; - c->feeder->desc->out = best; - try = feeder_fmtchain(to, c->feeder, stop, bestmax); + c->feeder->desc->out = best; + try = feeder_fmtchain(to, c->feeder, stop, bestmax); + } if (try == NULL) return 0; @@ -371,7 +796,16 @@ printf("%s [%d]\n", try->class->name, try->desc->idx); #endif - return (c->direction == PCMDIR_REC)? best : c->feeder->desc->out; + if (c->direction == PCMDIR_REC) { + try = c->feeder; + while (try != NULL) { + if (try->desc->type == FEEDER_ROOT) + return try->desc->out; + try = try->source; + } + return best; + } else + return c->feeder->desc->out; } void @@ -394,28 +828,54 @@ feed_root(struct pcm_feeder *feeder, struct pcm_channel *ch, u_int8_t *buffer, u_int32_t count, void *source) { struct snd_dbuf *src = source; - int l; - u_int8_t x; + int l, offset; KASSERT(count > 0, ("feed_root: count == 0")); /* count &= ~((1 << ch->align) - 1); */ KASSERT(count > 0, ("feed_root: aligned count == 0 (align = %d)", ch->align)); + if (++ch->feedcount == 0) + ch->feedcount = 2; + l = min(count, sndbuf_getready(src)); - sndbuf_dispose(src, buffer, l); /* When recording only return as much data as available */ - if (ch->direction == PCMDIR_REC) + if (ch->direction == PCMDIR_REC) { + sndbuf_dispose(src, buffer, l); return l; + } -/* - if (l < count) - printf("appending %d bytes\n", count - l); -*/ - - x = (sndbuf_getfmt(src) & AFMT_SIGNED)? 0 : 0x80; - while (l < count) - buffer[l++] = x; + + offset = count - l; + + if (offset > 0) { + if (snd_verbose > 3) + printf("%s: (%s) %spending %d bytes " + "(count=%d l=%d feed=%d)\n", + __func__, + (ch->flags & CHN_F_VIRTUAL) ? "virtual" : "hardware", + (ch->feedcount == 1) ? "pre" : "ap", + offset, count, l, ch->feedcount); + + if (ch->feedcount == 1) { + memset(buffer, + sndbuf_zerodata(sndbuf_getfmt(src)), + offset); + if (l > 0) + sndbuf_dispose(src, buffer + offset, l); + else + ch->feedcount--; + } else { + if (l > 0) + sndbuf_dispose(src, buffer, l); + memset(buffer + l, + sndbuf_zerodata(sndbuf_getfmt(src)), + offset); + if (!(ch->flags & CHN_F_CLOSING)) + ch->xruns++; + } + } else if (l > 0) + sndbuf_dispose(src, buffer, l); return count; } @@ -434,8 +894,3 @@ }; SYSINIT(feeder_root, SI_SUB_DRIVERS, SI_ORDER_FIRST, feeder_register, &feeder_root_class); SYSUNINIT(feeder_root, SI_SUB_DRIVERS, SI_ORDER_FIRST, feeder_unregisterall, NULL); - - - - - --- sys/dev/sound/pcm/feeder.h.orig Sun Jan 30 09:00:05 2005 +++ sys/dev/sound/pcm/feeder.h Thu Jul 12 12:04:19 2007 @@ -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/pcm/feeder.h,v 1.11.4.1 2005/01/30 01:00:05 imp Exp $ + * $FreeBSD: src/sys/dev/sound/pcm/feeder.h,v 1.15 2007/03/16 17:15:33 ariff Exp $ */ struct pcm_feederdesc { @@ -53,6 +53,10 @@ void feeder_register(void *p); struct feeder_class *feeder_getclass(struct pcm_feederdesc *desc); +u_int32_t chn_fmtscore(u_int32_t fmt); +u_int32_t chn_fmtbestbit(u_int32_t fmt, u_int32_t *fmts); +u_int32_t chn_fmtbeststereo(u_int32_t fmt, u_int32_t *fmts); +u_int32_t chn_fmtbest(u_int32_t fmt, u_int32_t *fmts); u_int32_t chn_fmtchain(struct pcm_channel *c, u_int32_t *to); int chn_addfeeder(struct pcm_channel *c, struct feeder_class *fc, struct pcm_feederdesc *desc); int chn_removefeeder(struct pcm_channel *c); @@ -68,17 +72,45 @@ .desc = feeder ## _desc, \ .data = pdata, \ }; \ -SYSINIT(feeder, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, feeder_register, &feeder ## _class); +SYSINIT(feeder, SI_SUB_DRIVERS, SI_ORDER_ANY, feeder_register, &feeder ## _class); -#define FEEDER_ROOT 1 -#define FEEDER_FMT 2 -#define FEEDER_MIXER 3 -#define FEEDER_RATE 4 -#define FEEDER_FILTER 5 -#define FEEDER_VOLUME 6 -#define FEEDER_LAST FEEDER_VOLUME +enum { + FEEDER_ROOT, + FEEDER_FMT, + FEEDER_MIXER, + FEEDER_RATE, + FEEDER_FILTER, + FEEDER_VOLUME, + FEEDER_SWAPLR, + FEEDER_LAST +}; #define FEEDRATE_SRC 1 #define FEEDRATE_DST 2 +#define FEEDVOL_CLASS 1 + +#define FEEDRATE_RATEMIN 1 +#define FEEDRATE_RATEMAX 2016000 /* 48000 * 42 */ +#define FEEDRATE_MIN 1 +#define FEEDRATE_MAX 0x7fffff /* sign 24bit ~ 8ghz ! */ + +#define FEEDRATE_ROUNDHZ 25 +#define FEEDRATE_ROUNDHZ_MIN 0 +#define FEEDRATE_ROUNDHZ_MAX 500 + +/* + * Default buffer size for feeder processing. + * + * Big = less sndbuf_feed(), more memory usage. + * Small = aggresive sndbuf_feed() (perhaps too much), less memory usage. + */ +#define FEEDBUFSZ 16384 +#define FEEDBUFSZ_MIN 2048 +#define FEEDBUFSZ_MAX 131072 + +extern int feeder_rate_min; +extern int feeder_rate_max; +extern int feeder_rate_round; +extern int feeder_buffersize; --- sys/dev/sound/pcm/feeder_fmt.c.orig Sun Jan 30 09:00:05 2005 +++ sys/dev/sound/pcm/feeder_fmt.c Thu Jul 12 12:04:19 2007 @@ -1,5 +1,6 @@ /*- - * Copyright (c) 1999 Cameron Grant + * Copyright (c) 1999 Cameron Grant + * Copyright (c) 2005 Ariff Abdullah * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -24,52 +25,106 @@ * SUCH DAMAGE. */ -#include +/* + * *New* and rewritten soft format converter, supporting 24/32bit pcm data, + * simplified and optimized. + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * This new implementation is fully dedicated in memory of Cameron Grant, * + * the creator of the magnificent, highly addictive feeder infrastructure. * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * + */ +#include +#include +#include #include "feeder_if.h" -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pcm/feeder_fmt.c,v 1.13.4.1 2005/01/30 01:00:05 imp Exp $"); - -MALLOC_DEFINE(M_FMTFEEDER, "fmtfeed", "pcm format feeder"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pcm/feeder_fmt.c,v 1.23 2007/06/02 13:07:44 joel Exp $"); -#define FEEDBUFSZ 8192 - -static unsigned char ulaw_to_u8[] = { - 3, 7, 11, 15, 19, 23, 27, 31, - 35, 39, 43, 47, 51, 55, 59, 63, - 66, 68, 70, 72, 74, 76, 78, 80, - 82, 84, 86, 88, 90, 92, 94, 96, - 98, 99, 100, 101, 102, 103, 104, 105, - 106, 107, 108, 109, 110, 111, 112, 113, - 113, 114, 114, 115, 115, 116, 116, 117, - 117, 118, 118, 119, 119, 120, 120, 121, - 121, 121, 122, 122, 122, 122, 123, 123, - 123, 123, 124, 124, 124, 124, 125, 125, - 125, 125, 125, 125, 126, 126, 126, 126, - 126, 126, 126, 126, 127, 127, 127, 127, - 127, 127, 127, 127, 127, 127, 127, 127, - 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, - 253, 249, 245, 241, 237, 233, 229, 225, - 221, 217, 213, 209, 205, 201, 197, 193, - 190, 188, 186, 184, 182, 180, 178, 176, - 174, 172, 170, 168, 166, 164, 162, 160, - 158, 157, 156, 155, 154, 153, 152, 151, - 150, 149, 148, 147, 146, 145, 144, 143, - 143, 142, 142, 141, 141, 140, 140, 139, - 139, 138, 138, 137, 137, 136, 136, 135, - 135, 135, 134, 134, 134, 134, 133, 133, - 133, 133, 132, 132, 132, 132, 131, 131, - 131, 131, 131, 131, 130, 130, 130, 130, - 130, 130, 130, 130, 129, 129, 129, 129, - 129, 129, 129, 129, 129, 129, 129, 129, - 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, +static int feeder_fmt_stereodownmix = 0; +TUNABLE_INT("hw.snd.feeder_fmt_stereodownmix", &feeder_fmt_stereodownmix); +#ifdef SND_DEBUG +SYSCTL_INT(_hw_snd, OID_AUTO, feeder_fmt_stereodownmix, CTLFLAG_RW, + &feeder_fmt_stereodownmix, 1, "averaging stereo downmix"); +#endif + +#define FEEDFMT_RESERVOIR (PCM_32_BPS * SND_CHN_MAX) + +static uint8_t ulaw_to_u8_tbl[] = { + 3, 7, 11, 15, 19, 23, 27, 31, + 35, 39, 43, 47, 51, 55, 59, 63, + 66, 68, 70, 72, 74, 76, 78, 80, + 82, 84, 86, 88, 90, 92, 94, 96, + 98, 99, 100, 101, 102, 103, 104, 105, + 106, 107, 108, 109, 110, 111, 112, 113, + 113, 114, 114, 115, 115, 116, 116, 117, + 117, 118, 118, 119, 119, 120, 120, 121, + 121, 121, 122, 122, 122, 122, 123, 123, + 123, 123, 124, 124, 124, 124, 125, 125, + 125, 125, 125, 125, 126, 126, 126, 126, + 126, 126, 126, 126, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 253, 249, 245, 241, 237, 233, 229, 225, + 221, 217, 213, 209, 205, 201, 197, 193, + 190, 188, 186, 184, 182, 180, 178, 176, + 174, 172, 170, 168, 166, 164, 162, 160, + 158, 157, 156, 155, 154, 153, 152, 151, + 150, 149, 148, 147, 146, 145, 144, 143, + 143, 142, 142, 141, 141, 140, 140, 139, + 139, 138, 138, 137, 137, 136, 136, 135, + 135, 135, 134, 134, 134, 134, 133, 133, + 133, 133, 132, 132, 132, 132, 131, 131, + 131, 131, 131, 131, 130, 130, 130, 130, + 130, 130, 130, 130, 129, 129, 129, 129, + 129, 129, 129, 129, 129, 129, 129, 129, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, +}; + +static uint8_t alaw_to_u8_tbl[] = { + 108, 109, 106, 107, 112, 113, 110, 111, + 100, 101, 98, 99, 104, 105, 102, 103, + 118, 118, 117, 117, 120, 120, 119, 119, + 114, 114, 113, 113, 116, 116, 115, 115, + 43, 47, 35, 39, 59, 63, 51, 55, + 11, 15, 3, 7, 27, 31, 19, 23, + 86, 88, 82, 84, 94, 96, 90, 92, + 70, 72, 66, 68, 78, 80, 74, 76, + 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 123, 123, 123, 123, 124, 124, 124, 124, + 121, 121, 121, 121, 122, 122, 122, 122, + 126, 126, 126, 126, 126, 126, 126, 126, + 125, 125, 125, 125, 125, 125, 125, 125, + 148, 147, 150, 149, 144, 143, 146, 145, + 156, 155, 158, 157, 152, 151, 154, 153, + 138, 138, 139, 139, 136, 136, 137, 137, + 142, 142, 143, 143, 140, 140, 141, 141, + 213, 209, 221, 217, 197, 193, 205, 201, + 245, 241, 253, 249, 229, 225, 237, 233, + 170, 168, 174, 172, 162, 160, 166, 164, + 186, 184, 190, 188, 178, 176, 182, 180, + 129, 129, 129, 129, 129, 129, 129, 129, + 129, 129, 129, 129, 129, 129, 129, 129, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 133, 133, 133, 133, 132, 132, 132, 132, + 135, 135, 135, 135, 134, 134, 134, 134, + 130, 130, 130, 130, 130, 130, 130, 130, + 131, 131, 131, 131, 131, 131, 131, 131, }; -static unsigned char u8_to_ulaw[] = { +static uint8_t u8_to_ulaw_tbl[] = { 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, @@ -104,446 +159,1278 @@ 129, 129, 129, 129, 128, 128, 128, 128, }; -static unsigned char alaw_to_ulaw[] = { - 42, 43, 40, 41, 46, 47, 44, 45, - 34, 35, 32, 33, 38, 39, 36, 37, - 57, 58, 55, 56, 61, 62, 59, 60, - 49, 50, 48, 48, 53, 54, 51, 52, - 10, 11, 8, 9, 14, 15, 12, 13, - 2, 3, 0, 1, 6, 7, 4, 5, - 26, 27, 24, 25, 30, 31, 28, 29, - 18, 19, 16, 17, 22, 23, 20, 21, - 98, 99, 96, 97, 102, 103, 100, 101, - 93, 93, 92, 92, 95, 95, 94, 94, - 116, 118, 112, 114, 124, 126, 120, 122, - 106, 107, 104, 105, 110, 111, 108, 109, - 72, 73, 70, 71, 76, 77, 74, 75, - 64, 65, 63, 63, 68, 69, 66, 67, - 86, 87, 84, 85, 90, 91, 88, 89, - 79, 79, 78, 78, 82, 83, 80, 81, - 170, 171, 168, 169, 174, 175, 172, 173, - 162, 163, 160, 161, 166, 167, 164, 165, - 185, 186, 183, 184, 189, 190, 187, 188, - 177, 178, 176, 176, 181, 182, 179, 180, - 138, 139, 136, 137, 142, 143, 140, 141, - 130, 131, 128, 129, 134, 135, 132, 133, - 154, 155, 152, 153, 158, 159, 156, 157, - 146, 147, 144, 145, 150, 151, 148, 149, - 226, 227, 224, 225, 230, 231, 228, 229, - 221, 221, 220, 220, 223, 223, 222, 222, - 244, 246, 240, 242, 252, 254, 248, 250, - 234, 235, 232, 233, 238, 239, 236, 237, - 200, 201, 198, 199, 204, 205, 202, 203, - 192, 193, 191, 191, 196, 197, 194, 195, - 214, 215, 212, 213, 218, 219, 216, 217, - 207, 207, 206, 206, 210, 211, 208, 209, -}; - -static unsigned char ulaw_to_alaw[] = { - 42, 43, 40, 41, 46, 47, 44, 45, - 34, 35, 32, 33, 38, 39, 36, 37, - 58, 59, 56, 57, 62, 63, 60, 61, - 50, 51, 48, 49, 54, 55, 52, 53, - 10, 11, 8, 9, 14, 15, 12, 13, - 2, 3, 0, 1, 6, 7, 4, 5, - 27, 24, 25, 30, 31, 28, 29, 18, - 19, 16, 17, 22, 23, 20, 21, 106, - 104, 105, 110, 111, 108, 109, 98, 99, - 96, 97, 102, 103, 100, 101, 122, 120, - 126, 127, 124, 125, 114, 115, 112, 113, - 118, 119, 116, 117, 75, 73, 79, 77, - 66, 67, 64, 65, 70, 71, 68, 69, - 90, 91, 88, 89, 94, 95, 92, 93, - 82, 82, 83, 83, 80, 80, 81, 81, - 86, 86, 87, 87, 84, 84, 85, 85, - 170, 171, 168, 169, 174, 175, 172, 173, - 162, 163, 160, 161, 166, 167, 164, 165, - 186, 187, 184, 185, 190, 191, 188, 189, - 178, 179, 176, 177, 182, 183, 180, 181, - 138, 139, 136, 137, 142, 143, 140, 141, - 130, 131, 128, 129, 134, 135, 132, 133, - 155, 152, 153, 158, 159, 156, 157, 146, - 147, 144, 145, 150, 151, 148, 149, 234, - 232, 233, 238, 239, 236, 237, 226, 227, - 224, 225, 230, 231, 228, 229, 250, 248, - 254, 255, 252, 253, 242, 243, 240, 241, - 246, 247, 244, 245, 203, 201, 207, 205, - 194, 195, 192, 193, 198, 199, 196, 197, - 218, 219, 216, 217, 222, 223, 220, 221, - 210, 210, 211, 211, 208, 208, 209, 209, - 214, 214, 215, 215, 212, 212, 213, 213, -}; - -/*****************************************************************************/ - -static int -feed_8to16le(struct pcm_feeder *f, struct pcm_channel *c, u_int8_t *b, u_int32_t count, void *source) -{ - int i, j, k; - - k = FEEDER_FEED(f->source, c, b, count / 2, source); - j = k - 1; - i = j * 2 + 1; - while (i > 0 && j >= 0) { - b[i--] = b[j--]; - b[i--] = 0; +static uint8_t u8_to_alaw_tbl[] = { + 42, 42, 42, 42, 42, 43, 43, 43, + 43, 40, 40, 40, 40, 41, 41, 41, + 41, 46, 46, 46, 46, 47, 47, 47, + 47, 44, 44, 44, 44, 45, 45, 45, + 45, 34, 34, 34, 34, 35, 35, 35, + 35, 32, 32, 32, 32, 33, 33, 33, + 33, 38, 38, 38, 38, 39, 39, 39, + 39, 36, 36, 36, 36, 37, 37, 37, + 37, 58, 58, 59, 59, 56, 56, 57, + 57, 62, 62, 63, 63, 60, 60, 61, + 61, 50, 50, 51, 51, 48, 48, 49, + 49, 54, 54, 55, 55, 52, 52, 53, + 53, 10, 11, 8, 9, 14, 15, 12, + 13, 2, 3, 0, 1, 6, 7, 4, + 5, 24, 30, 28, 18, 16, 22, 20, + 106, 110, 98, 102, 122, 114, 75, 90, + 213, 197, 245, 253, 229, 225, 237, 233, + 149, 151, 145, 147, 157, 159, 153, 155, + 133, 132, 135, 134, 129, 128, 131, 130, + 141, 140, 143, 142, 137, 136, 139, 138, + 181, 181, 180, 180, 183, 183, 182, 182, + 177, 177, 176, 176, 179, 179, 178, 178, + 189, 189, 188, 188, 191, 191, 190, 190, + 185, 185, 184, 184, 187, 187, 186, 186, + 165, 165, 165, 165, 164, 164, 164, 164, + 167, 167, 167, 167, 166, 166, 166, 166, + 161, 161, 161, 161, 160, 160, 160, 160, + 163, 163, 163, 163, 162, 162, 162, 162, + 173, 173, 173, 173, 172, 172, 172, 172, + 175, 175, 175, 175, 174, 174, 174, 174, + 169, 169, 169, 169, 168, 168, 168, 168, + 171, 171, 171, 171, 170, 170, 170, 170, +}; + +static int +feed_table_8(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, + uint32_t count, void *source) +{ + int j, sign, k; + uint8_t *tbl = (uint8_t *)f->data; + + if (count < PCM_8_BPS) + return (0); + + k = FEEDER_FEED(f->source, c, b, count, source); + if (k < PCM_8_BPS) + return (0); + + j = k; + sign = (f->desc->out & AFMT_SIGNED) ? 0x80 : 0x00; + + do { + j--; + b[j] = tbl[b[j]] ^ sign; + } while (j != 0); + + return (k); +} + +static int +feed_table_16(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, + uint32_t count, void *source) +{ + int i, j, sign, k; + uint8_t *tbl = (uint8_t *)f->data; + + if (count < PCM_16_BPS) + return (0); + + k = FEEDER_FEED(f->source, c, b, count >> 1, source); + if (k < PCM_8_BPS) + return (0); + + i = k; + k <<= 1; + j = k; + sign = (f->desc->out & AFMT_SIGNED) ? 0x80 : 0x00; + + if (f->desc->out & AFMT_BIGENDIAN) { + do { + b[--j] = 0; + b[--j] = tbl[b[--i]] ^ sign; + } while (i != 0); + } else { + do { + b[--j] = tbl[b[--i]] ^ sign; + b[--j] = 0; + } while (i != 0); } - return k * 2; + + return (k); } -static struct pcm_feederdesc feeder_8to16le_desc[] = { - {FEEDER_FMT, AFMT_U8, AFMT_U16_LE, 0}, - {FEEDER_FMT, AFMT_U8 | AFMT_STEREO, AFMT_U16_LE | AFMT_STEREO, 0}, - {FEEDER_FMT, AFMT_S8, AFMT_S16_LE, 0}, - {FEEDER_FMT, AFMT_S8 | AFMT_STEREO, AFMT_S16_LE | AFMT_STEREO, 0}, - {0}, +static int +feed_table_xlaw(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, + uint32_t count, void *source) +{ + int j, sign, k; + uint8_t *tbl = (uint8_t *)f->data; + + if (count < PCM_8_BPS) + return (0); + + k = FEEDER_FEED(f->source, c, b, count, source); + if (k < PCM_8_BPS) + return (0); + + j = k ; + sign = (f->desc->in & AFMT_SIGNED) ? 0x80 : 0x00; + + do { + j--; + b[j] = tbl[b[j] ^ sign]; + } while (j != 0); + + return (k); +} + +static struct pcm_feederdesc feeder_ulawto8_desc[] = { + {FEEDER_FMT, AFMT_MU_LAW, AFMT_U8, 0}, + {FEEDER_FMT, AFMT_MU_LAW | AFMT_STEREO, AFMT_U8 | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_MU_LAW, AFMT_S8, 0}, + {FEEDER_FMT, AFMT_MU_LAW | AFMT_STEREO, AFMT_S8 | AFMT_STEREO, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_ulawto8_methods[] = { + KOBJMETHOD(feeder_feed, feed_table_8), + {0, 0} +}; +FEEDER_DECLARE(feeder_ulawto8, 0, ulaw_to_u8_tbl); + +static struct pcm_feederdesc feeder_alawto8_desc[] = { + {FEEDER_FMT, AFMT_A_LAW, AFMT_U8, 0}, + {FEEDER_FMT, AFMT_A_LAW | AFMT_STEREO, AFMT_U8 | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_A_LAW, AFMT_S8, 0}, + {FEEDER_FMT, AFMT_A_LAW | AFMT_STEREO, AFMT_S8 | AFMT_STEREO, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_alawto8_methods[] = { + KOBJMETHOD(feeder_feed, feed_table_8), + {0, 0} +}; +FEEDER_DECLARE(feeder_alawto8, 0, alaw_to_u8_tbl); + +static struct pcm_feederdesc feeder_ulawto16_desc[] = { + {FEEDER_FMT, AFMT_MU_LAW, AFMT_S16_LE, 0}, + {FEEDER_FMT, AFMT_MU_LAW | AFMT_STEREO, AFMT_S16_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_MU_LAW, AFMT_U16_LE, 0}, + {FEEDER_FMT, AFMT_MU_LAW | AFMT_STEREO, AFMT_U16_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_MU_LAW, AFMT_S16_BE, 0}, + {FEEDER_FMT, AFMT_MU_LAW | AFMT_STEREO, AFMT_S16_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_MU_LAW, AFMT_U16_BE, 0}, + {FEEDER_FMT, AFMT_MU_LAW | AFMT_STEREO, AFMT_U16_BE | AFMT_STEREO, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_ulawto16_methods[] = { + KOBJMETHOD(feeder_feed, feed_table_16), + {0, 0} +}; +FEEDER_DECLARE(feeder_ulawto16, 0, ulaw_to_u8_tbl); + +static struct pcm_feederdesc feeder_alawto16_desc[] = { + {FEEDER_FMT, AFMT_A_LAW, AFMT_S16_LE, 0}, + {FEEDER_FMT, AFMT_A_LAW | AFMT_STEREO, AFMT_S16_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_A_LAW, AFMT_U16_LE, 0}, + {FEEDER_FMT, AFMT_A_LAW | AFMT_STEREO, AFMT_U16_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_A_LAW, AFMT_S16_BE, 0}, + {FEEDER_FMT, AFMT_A_LAW | AFMT_STEREO, AFMT_S16_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_A_LAW, AFMT_U16_BE, 0}, + {FEEDER_FMT, AFMT_A_LAW | AFMT_STEREO, AFMT_U16_BE | AFMT_STEREO, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_alawto16_methods[] = { + KOBJMETHOD(feeder_feed, feed_table_16), + {0, 0} }; -static kobj_method_t feeder_8to16le_methods[] = { - KOBJMETHOD(feeder_feed, feed_8to16le), - { 0, 0 } +FEEDER_DECLARE(feeder_alawto16, 0, alaw_to_u8_tbl); + +static struct pcm_feederdesc feeder_8toulaw_desc[] = { + {FEEDER_FMT, AFMT_U8, AFMT_MU_LAW, 0}, + {FEEDER_FMT, AFMT_U8 | AFMT_STEREO, AFMT_MU_LAW | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S8, AFMT_MU_LAW, 0}, + {FEEDER_FMT, AFMT_S8 | AFMT_STEREO, AFMT_MU_LAW | AFMT_STEREO, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_8toulaw_methods[] = { + KOBJMETHOD(feeder_feed, feed_table_xlaw), + {0, 0} +}; +FEEDER_DECLARE(feeder_8toulaw, 0, u8_to_ulaw_tbl); + +static struct pcm_feederdesc feeder_8toalaw_desc[] = { + {FEEDER_FMT, AFMT_U8, AFMT_A_LAW, 0}, + {FEEDER_FMT, AFMT_U8 | AFMT_STEREO, AFMT_A_LAW | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S8, AFMT_A_LAW, 0}, + {FEEDER_FMT, AFMT_S8 | AFMT_STEREO, AFMT_A_LAW | AFMT_STEREO, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_8toalaw_methods[] = { + KOBJMETHOD(feeder_feed, feed_table_xlaw), + {0, 0} }; -FEEDER_DECLARE(feeder_8to16le, 0, NULL); +FEEDER_DECLARE(feeder_8toalaw, 0, u8_to_alaw_tbl); -/*****************************************************************************/ +/* + * All conversion done in byte level to preserve endianess. + */ + +#define FEEDFMT_SWAP_SIGN(f) (((((f)->desc->in & AFMT_SIGNED) == 0) != \ + (((f)->desc->out & AFMT_SIGNED) == 0)) \ + ? 0x80 : 0x00) + +/* + * Bit conversion + */ + +#define FBIT_DATA(i, o, c) ((intptr_t)((((c) & 0x3f) << 8) | \ + (((i) & 0xf) << 4) | ((o) & 0xf))) +#define FBIT_OUTBPS(m) ((m) & 0xf) +#define FBIT_INBPS(m) FBIT_OUTBPS((m) >> 4) +#define FBIT_CHANNELS(m) (((m) >> 8) & 0x3f) static int -feed_16to8_init(struct pcm_feeder *f) +feed_updownbit_init(struct pcm_feeder *f) { - f->data = malloc(FEEDBUFSZ, M_FMTFEEDER, M_NOWAIT | M_ZERO); - if (f->data == NULL) - return ENOMEM; - return 0; + int ibps, obps, channels; + + if (f->desc->in == f->desc->out || (f->desc->in & AFMT_STEREO) != + (f->desc->out & AFMT_STEREO)) + return (-1); + + channels = (f->desc->in & AFMT_STEREO) ? 2 : 1; + + if (f->desc->in & AFMT_32BIT) + ibps = PCM_32_BPS; + else if (f->desc->in & AFMT_24BIT) + ibps = PCM_24_BPS; + else if (f->desc->in & AFMT_16BIT) + ibps = PCM_16_BPS; + else + ibps = PCM_8_BPS; + + if (f->desc->out & AFMT_32BIT) + obps = PCM_32_BPS; + else if (f->desc->out & AFMT_24BIT) + obps = PCM_24_BPS; + else if (f->desc->out & AFMT_16BIT) + obps = PCM_16_BPS; + else + obps = PCM_8_BPS; + + f->data = (void *)FBIT_DATA(ibps, obps, channels); + + return (0); } static int -feed_16to8_free(struct pcm_feeder *f) +feed_upbit(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, + uint32_t count, void *source) { - if (f->data) - free(f->data, M_FMTFEEDER); - f->data = NULL; - return 0; + int i, j, k, sign, ibps, ialign, obps, oalign, pad; + uint8_t *src, *dst; + + ibps = FBIT_INBPS((intptr_t)f->data); + obps = FBIT_OUTBPS((intptr_t)f->data); + + ialign = ibps * FBIT_CHANNELS((intptr_t)f->data); + oalign = obps * FBIT_CHANNELS((intptr_t)f->data); + + if (count < oalign) + return (0); + + k = FEEDER_FEED(f->source, c, b, (count / oalign) * ialign, source); + if (k < ialign) + return (0); + + k -= k % ialign; + j = (k / ibps) * obps; + pad = obps - ibps; + src = b + k; + dst = b + j; + sign = FEEDFMT_SWAP_SIGN(f); + + if (f->desc->out & AFMT_BIGENDIAN) { + do { + i = pad; + do { + *--dst = 0; + } while (--i != 0); + i = ibps; + while (--i != 0) + *--dst = *--src; + *--dst = *--src ^ sign; + } while (dst != b); + } else { + do { + *--dst = *--src ^ sign; + i = ibps; + while (--i != 0) + *--dst = *--src; + i = pad; + do { + *--dst = 0; + } while (--i != 0); + } while (dst != b); + } + + return (j); } +static struct pcm_feederdesc feeder_8to16_desc[] = { + {FEEDER_FMT, AFMT_U8, AFMT_U16_LE, 0}, + {FEEDER_FMT, AFMT_U8 | AFMT_STEREO, AFMT_U16_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S8, AFMT_S16_LE, 0}, + {FEEDER_FMT, AFMT_S8 | AFMT_STEREO, AFMT_S16_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U8, AFMT_U16_BE, 0}, + {FEEDER_FMT, AFMT_U8 | AFMT_STEREO, AFMT_U16_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S8, AFMT_S16_BE, 0}, + {FEEDER_FMT, AFMT_S8 | AFMT_STEREO, AFMT_S16_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U8, AFMT_S16_LE, 0}, + {FEEDER_FMT, AFMT_U8 | AFMT_STEREO, AFMT_S16_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S8, AFMT_U16_LE, 0}, + {FEEDER_FMT, AFMT_S8 | AFMT_STEREO, AFMT_U16_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U8, AFMT_S16_BE, 0}, + {FEEDER_FMT, AFMT_U8 | AFMT_STEREO, AFMT_S16_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S8, AFMT_U16_BE, 0}, + {FEEDER_FMT, AFMT_S8 | AFMT_STEREO, AFMT_U16_BE | AFMT_STEREO, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_8to16_methods[] = { + KOBJMETHOD(feeder_init, feed_updownbit_init), + KOBJMETHOD(feeder_feed, feed_upbit), + {0, 0} +}; +FEEDER_DECLARE(feeder_8to16, 0, NULL); + +static struct pcm_feederdesc feeder_8to24_desc[] = { + {FEEDER_FMT, AFMT_U8, AFMT_U24_LE, 0}, + {FEEDER_FMT, AFMT_U8 | AFMT_STEREO, AFMT_U24_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S8, AFMT_S24_LE, 0}, + {FEEDER_FMT, AFMT_S8 | AFMT_STEREO, AFMT_S24_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U8, AFMT_U24_BE, 0}, + {FEEDER_FMT, AFMT_U8 | AFMT_STEREO, AFMT_U24_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S8, AFMT_S24_BE, 0}, + {FEEDER_FMT, AFMT_S8 | AFMT_STEREO, AFMT_S24_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U8, AFMT_S24_LE, 0}, + {FEEDER_FMT, AFMT_U8 | AFMT_STEREO, AFMT_S24_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S8, AFMT_U24_LE, 0}, + {FEEDER_FMT, AFMT_S8 | AFMT_STEREO, AFMT_U24_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U8, AFMT_S24_BE, 0}, + {FEEDER_FMT, AFMT_U8 | AFMT_STEREO, AFMT_S24_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S8, AFMT_U24_BE, 0}, + {FEEDER_FMT, AFMT_S8 | AFMT_STEREO, AFMT_U24_BE | AFMT_STEREO, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_8to24_methods[] = { + KOBJMETHOD(feeder_init, feed_updownbit_init), + KOBJMETHOD(feeder_feed, feed_upbit), + {0, 0} +}; +FEEDER_DECLARE(feeder_8to24, 0, NULL); + +static struct pcm_feederdesc feeder_8to32_desc[] = { + {FEEDER_FMT, AFMT_U8, AFMT_U32_LE, 0}, + {FEEDER_FMT, AFMT_U8 | AFMT_STEREO, AFMT_U32_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S8, AFMT_S32_LE, 0}, + {FEEDER_FMT, AFMT_S8 | AFMT_STEREO, AFMT_S32_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U8, AFMT_U32_BE, 0}, + {FEEDER_FMT, AFMT_U8 | AFMT_STEREO, AFMT_U32_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S8, AFMT_S32_BE, 0}, + {FEEDER_FMT, AFMT_S8 | AFMT_STEREO, AFMT_S32_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U8, AFMT_S32_LE, 0}, + {FEEDER_FMT, AFMT_U8 | AFMT_STEREO, AFMT_S32_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S8, AFMT_U32_LE, 0}, + {FEEDER_FMT, AFMT_S8 | AFMT_STEREO, AFMT_U32_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U8, AFMT_S32_BE, 0}, + {FEEDER_FMT, AFMT_U8 | AFMT_STEREO, AFMT_S32_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S8, AFMT_U32_BE, 0}, + {FEEDER_FMT, AFMT_S8 | AFMT_STEREO, AFMT_U32_BE | AFMT_STEREO, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_8to32_methods[] = { + KOBJMETHOD(feeder_init, feed_updownbit_init), + KOBJMETHOD(feeder_feed, feed_upbit), + {0, 0} +}; +FEEDER_DECLARE(feeder_8to32, 0, NULL); + +static struct pcm_feederdesc feeder_16to24_desc[] = { + {FEEDER_FMT, AFMT_U16_LE, AFMT_U24_LE, 0}, + {FEEDER_FMT, AFMT_U16_LE | AFMT_STEREO, AFMT_U24_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S16_LE, AFMT_S24_LE, 0}, + {FEEDER_FMT, AFMT_S16_LE | AFMT_STEREO, AFMT_S24_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U16_BE, AFMT_U24_BE, 0}, + {FEEDER_FMT, AFMT_U16_BE | AFMT_STEREO, AFMT_U24_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S16_BE, AFMT_S24_BE, 0}, + {FEEDER_FMT, AFMT_S16_BE | AFMT_STEREO, AFMT_S24_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U16_LE, AFMT_S24_LE, 0}, + {FEEDER_FMT, AFMT_U16_LE | AFMT_STEREO, AFMT_S24_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S16_LE, AFMT_U24_LE, 0}, + {FEEDER_FMT, AFMT_S16_LE | AFMT_STEREO, AFMT_U24_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U16_BE, AFMT_S24_BE, 0}, + {FEEDER_FMT, AFMT_U16_BE | AFMT_STEREO, AFMT_S24_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S16_BE, AFMT_U24_BE, 0}, + {FEEDER_FMT, AFMT_S16_BE | AFMT_STEREO, AFMT_U24_BE | AFMT_STEREO, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_16to24_methods[] = { + KOBJMETHOD(feeder_init, feed_updownbit_init), + KOBJMETHOD(feeder_feed, feed_upbit), + {0, 0} +}; +FEEDER_DECLARE(feeder_16to24, 0, NULL); + +static struct pcm_feederdesc feeder_16to32_desc[] = { + {FEEDER_FMT, AFMT_U16_LE, AFMT_U32_LE, 0}, + {FEEDER_FMT, AFMT_U16_LE | AFMT_STEREO, AFMT_U32_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S16_LE, AFMT_S32_LE, 0}, + {FEEDER_FMT, AFMT_S16_LE | AFMT_STEREO, AFMT_S32_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U16_BE, AFMT_U32_BE, 0}, + {FEEDER_FMT, AFMT_U16_BE | AFMT_STEREO, AFMT_U32_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S16_BE, AFMT_S32_BE, 0}, + {FEEDER_FMT, AFMT_S16_BE | AFMT_STEREO, AFMT_S32_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U16_LE, AFMT_S32_LE, 0}, + {FEEDER_FMT, AFMT_U16_LE | AFMT_STEREO, AFMT_S32_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S16_LE, AFMT_U32_LE, 0}, + {FEEDER_FMT, AFMT_S16_LE | AFMT_STEREO, AFMT_U32_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U16_BE, AFMT_S32_BE, 0}, + {FEEDER_FMT, AFMT_U16_BE | AFMT_STEREO, AFMT_S32_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S16_BE, AFMT_U32_BE, 0}, + {FEEDER_FMT, AFMT_S16_BE | AFMT_STEREO, AFMT_U32_BE | AFMT_STEREO, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_16to32_methods[] = { + KOBJMETHOD(feeder_init, feed_updownbit_init), + KOBJMETHOD(feeder_feed, feed_upbit), + {0, 0} +}; +FEEDER_DECLARE(feeder_16to32, 0, NULL); + +static struct pcm_feederdesc feeder_24to32_desc[] = { + {FEEDER_FMT, AFMT_U24_LE, AFMT_U32_LE, 0}, + {FEEDER_FMT, AFMT_U24_LE | AFMT_STEREO, AFMT_U32_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S24_LE, AFMT_S32_LE, 0}, + {FEEDER_FMT, AFMT_S24_LE | AFMT_STEREO, AFMT_S32_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U24_BE, AFMT_U32_BE, 0}, + {FEEDER_FMT, AFMT_U24_BE | AFMT_STEREO, AFMT_U32_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S24_BE, AFMT_S32_BE, 0}, + {FEEDER_FMT, AFMT_S24_BE | AFMT_STEREO, AFMT_S32_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U24_LE, AFMT_S32_LE, 0}, + {FEEDER_FMT, AFMT_U24_LE | AFMT_STEREO, AFMT_S32_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S24_LE, AFMT_U32_LE, 0}, + {FEEDER_FMT, AFMT_S24_LE | AFMT_STEREO, AFMT_U32_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U24_BE, AFMT_S32_BE, 0}, + {FEEDER_FMT, AFMT_U24_BE | AFMT_STEREO, AFMT_S32_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S24_BE, AFMT_U32_BE, 0}, + {FEEDER_FMT, AFMT_S24_BE | AFMT_STEREO, AFMT_U32_BE | AFMT_STEREO, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_24to32_methods[] = { + KOBJMETHOD(feeder_init, feed_updownbit_init), + KOBJMETHOD(feeder_feed, feed_upbit), + {0, 0} +}; +FEEDER_DECLARE(feeder_24to32, 0, NULL); + static int -feed_16leto8(struct pcm_feeder *f, struct pcm_channel *c, u_int8_t *b, u_int32_t count, void *source) +feed_downbit(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, + uint32_t count, void *source) { - u_int32_t i = 0, toget = count * 2; - int j = 1, k; + int i, j, k, sign, be, ibps, ialign, obps, oalign,dump; + uint8_t *src, *dst, *end; + uint8_t reservoir[FEEDFMT_RESERVOIR]; + + ibps = FBIT_INBPS((intptr_t)f->data); + obps = FBIT_OUTBPS((intptr_t)f->data); + + ialign = ibps * FBIT_CHANNELS((intptr_t)f->data); + oalign = obps * FBIT_CHANNELS((intptr_t)f->data); + + if (count < oalign) + return (0); + + dst = b; + dump = ibps - obps; + sign = FEEDFMT_SWAP_SIGN(f); + be = (f->desc->in & AFMT_BIGENDIAN) ? 1 : 0; + k = count - (count % oalign); + + do { + if (k < oalign) + break; + + if (k < ialign) { + src = reservoir; + j = ialign; + } else { + src = dst; + j = k; + } + + j = FEEDER_FEED(f->source, c, src, j - (j % ialign), source); + if (j < ialign) + break; + + j -= j % ialign; + j *= obps; + j /= ibps; + end = dst + j; + + if (be != 0) { + do { + *dst++ = *src++ ^ sign; + i = obps; + while (--i != 0) + *dst++ = *src++; + src += dump; + } while (dst != end); + } else { + do { + src += dump; + i = obps; + while (--i != 0) + *dst++ = *src++; + *dst++ = *src++ ^ sign; + } while (dst != end); + } - k = FEEDER_FEED(f->source, c, f->data, min(toget, FEEDBUFSZ), source); - while (j < k) { - b[i++] = ((u_int8_t *)f->data)[j]; - j += 2; - } - return i; + k -= j; + } while (k != 0); + + return (dst - b); } -static struct pcm_feederdesc feeder_16leto8_desc[] = { +static struct pcm_feederdesc feeder_16to8_desc[] = { {FEEDER_FMT, AFMT_U16_LE, AFMT_U8, 0}, {FEEDER_FMT, AFMT_U16_LE | AFMT_STEREO, AFMT_U8 | AFMT_STEREO, 0}, {FEEDER_FMT, AFMT_S16_LE, AFMT_S8, 0}, {FEEDER_FMT, AFMT_S16_LE | AFMT_STEREO, AFMT_S8 | AFMT_STEREO, 0}, - {0}, -}; -static kobj_method_t feeder_16leto8_methods[] = { - KOBJMETHOD(feeder_init, feed_16to8_init), - KOBJMETHOD(feeder_free, feed_16to8_free), - KOBJMETHOD(feeder_feed, feed_16leto8), - { 0, 0 } -}; -FEEDER_DECLARE(feeder_16leto8, 1, NULL); - -/*****************************************************************************/ + {FEEDER_FMT, AFMT_U16_BE, AFMT_U8, 0}, + {FEEDER_FMT, AFMT_U16_BE | AFMT_STEREO, AFMT_U8 | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S16_BE, AFMT_S8, 0}, + {FEEDER_FMT, AFMT_S16_BE | AFMT_STEREO, AFMT_S8 | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U16_LE, AFMT_S8, 0}, + {FEEDER_FMT, AFMT_U16_LE | AFMT_STEREO, AFMT_S8 | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S16_LE, AFMT_U8, 0}, + {FEEDER_FMT, AFMT_S16_LE | AFMT_STEREO, AFMT_U8 | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U16_BE, AFMT_S8, 0}, + {FEEDER_FMT, AFMT_U16_BE | AFMT_STEREO, AFMT_S8 | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S16_BE, AFMT_U8, 0}, + {FEEDER_FMT, AFMT_S16_BE | AFMT_STEREO, AFMT_U8 | AFMT_STEREO, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_16to8_methods[] = { + KOBJMETHOD(feeder_init, feed_updownbit_init), + KOBJMETHOD(feeder_feed, feed_downbit), + {0, 0} +}; +FEEDER_DECLARE(feeder_16to8, 0, NULL); + +static struct pcm_feederdesc feeder_24to8_desc[] = { + {FEEDER_FMT, AFMT_U24_LE, AFMT_U8, 0}, + {FEEDER_FMT, AFMT_U24_LE | AFMT_STEREO, AFMT_U8 | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S24_LE, AFMT_S8, 0}, + {FEEDER_FMT, AFMT_S24_LE | AFMT_STEREO, AFMT_S8 | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U24_BE, AFMT_U8, 0}, + {FEEDER_FMT, AFMT_U24_BE | AFMT_STEREO, AFMT_U8 | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S24_BE, AFMT_S8, 0}, + {FEEDER_FMT, AFMT_S24_BE | AFMT_STEREO, AFMT_S8 | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U24_LE, AFMT_S8, 0}, + {FEEDER_FMT, AFMT_U24_LE | AFMT_STEREO, AFMT_S8 | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S24_LE, AFMT_U8, 0}, + {FEEDER_FMT, AFMT_S24_LE | AFMT_STEREO, AFMT_U8 | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U24_BE, AFMT_S8, 0}, + {FEEDER_FMT, AFMT_U24_BE | AFMT_STEREO, AFMT_S8 | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S24_BE, AFMT_U8, 0}, + {FEEDER_FMT, AFMT_S24_BE | AFMT_STEREO, AFMT_U8 | AFMT_STEREO, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_24to8_methods[] = { + KOBJMETHOD(feeder_init, feed_updownbit_init), + KOBJMETHOD(feeder_feed, feed_downbit), + {0, 0} +}; +FEEDER_DECLARE(feeder_24to8, 0, NULL); + +static struct pcm_feederdesc feeder_24to16_desc[] = { + {FEEDER_FMT, AFMT_U24_LE, AFMT_U16_LE, 0}, + {FEEDER_FMT, AFMT_U24_LE | AFMT_STEREO, AFMT_U16_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S24_LE, AFMT_S16_LE, 0}, + {FEEDER_FMT, AFMT_S24_LE | AFMT_STEREO, AFMT_S16_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U24_BE, AFMT_U16_BE, 0}, + {FEEDER_FMT, AFMT_U24_BE | AFMT_STEREO, AFMT_U16_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S24_BE, AFMT_S16_BE, 0}, + {FEEDER_FMT, AFMT_S24_BE | AFMT_STEREO, AFMT_S16_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U24_LE, AFMT_S16_LE, 0}, + {FEEDER_FMT, AFMT_U24_LE | AFMT_STEREO, AFMT_S16_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S24_LE, AFMT_U16_LE, 0}, + {FEEDER_FMT, AFMT_S24_LE | AFMT_STEREO, AFMT_U16_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U24_BE, AFMT_S16_BE, 0}, + {FEEDER_FMT, AFMT_U24_BE | AFMT_STEREO, AFMT_S16_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S24_BE, AFMT_U16_BE, 0}, + {FEEDER_FMT, AFMT_S24_BE | AFMT_STEREO, AFMT_U16_BE | AFMT_STEREO, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_24to16_methods[] = { + KOBJMETHOD(feeder_init, feed_updownbit_init), + KOBJMETHOD(feeder_feed, feed_downbit), + {0, 0} +}; +FEEDER_DECLARE(feeder_24to16, 0, NULL); + +static struct pcm_feederdesc feeder_32to8_desc[] = { + {FEEDER_FMT, AFMT_U32_LE, AFMT_U8, 0}, + {FEEDER_FMT, AFMT_U32_LE | AFMT_STEREO, AFMT_U8 | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S32_LE, AFMT_S8, 0}, + {FEEDER_FMT, AFMT_S32_LE | AFMT_STEREO, AFMT_S8 | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U32_BE, AFMT_U8, 0}, + {FEEDER_FMT, AFMT_U32_BE | AFMT_STEREO, AFMT_U8 | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S32_BE, AFMT_S8, 0}, + {FEEDER_FMT, AFMT_S32_BE | AFMT_STEREO, AFMT_S8 | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U32_LE, AFMT_S8, 0}, + {FEEDER_FMT, AFMT_U32_LE | AFMT_STEREO, AFMT_S8 | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S32_LE, AFMT_U8, 0}, + {FEEDER_FMT, AFMT_S32_LE | AFMT_STEREO, AFMT_U8 | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U32_BE, AFMT_S8, 0}, + {FEEDER_FMT, AFMT_U32_BE | AFMT_STEREO, AFMT_S8 | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S32_BE, AFMT_U8, 0}, + {FEEDER_FMT, AFMT_S32_BE | AFMT_STEREO, AFMT_U8 | AFMT_STEREO, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_32to8_methods[] = { + KOBJMETHOD(feeder_init, feed_updownbit_init), + KOBJMETHOD(feeder_feed, feed_downbit), + {0, 0} +}; +FEEDER_DECLARE(feeder_32to8, 0, NULL); + +static struct pcm_feederdesc feeder_32to16_desc[] = { + {FEEDER_FMT, AFMT_U32_LE, AFMT_U16_LE, 0}, + {FEEDER_FMT, AFMT_U32_LE | AFMT_STEREO, AFMT_U16_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S32_LE, AFMT_S16_LE, 0}, + {FEEDER_FMT, AFMT_S32_LE | AFMT_STEREO, AFMT_S16_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U32_BE, AFMT_U16_BE, 0}, + {FEEDER_FMT, AFMT_U32_BE | AFMT_STEREO, AFMT_U16_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S32_BE, AFMT_S16_BE, 0}, + {FEEDER_FMT, AFMT_S32_BE | AFMT_STEREO, AFMT_S16_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U32_LE, AFMT_S16_LE, 0}, + {FEEDER_FMT, AFMT_U32_LE | AFMT_STEREO, AFMT_S16_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S32_LE, AFMT_U16_LE, 0}, + {FEEDER_FMT, AFMT_S32_LE | AFMT_STEREO, AFMT_U16_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U32_BE, AFMT_S16_BE, 0}, + {FEEDER_FMT, AFMT_U32_BE | AFMT_STEREO, AFMT_S16_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S32_BE, AFMT_U16_BE, 0}, + {FEEDER_FMT, AFMT_S32_BE | AFMT_STEREO, AFMT_U16_BE | AFMT_STEREO, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_32to16_methods[] = { + KOBJMETHOD(feeder_init, feed_updownbit_init), + KOBJMETHOD(feeder_feed, feed_downbit), + {0, 0} +}; +FEEDER_DECLARE(feeder_32to16, 0, NULL); + +static struct pcm_feederdesc feeder_32to24_desc[] = { + {FEEDER_FMT, AFMT_U32_LE, AFMT_U24_LE, 0}, + {FEEDER_FMT, AFMT_U32_LE | AFMT_STEREO, AFMT_U24_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S32_LE, AFMT_S24_LE, 0}, + {FEEDER_FMT, AFMT_S32_LE | AFMT_STEREO, AFMT_S24_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U32_BE, AFMT_U24_BE, 0}, + {FEEDER_FMT, AFMT_U32_BE | AFMT_STEREO, AFMT_U24_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S32_BE, AFMT_S24_BE, 0}, + {FEEDER_FMT, AFMT_S32_BE | AFMT_STEREO, AFMT_S24_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U32_LE, AFMT_S24_LE, 0}, + {FEEDER_FMT, AFMT_U32_LE | AFMT_STEREO, AFMT_S24_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S32_LE, AFMT_U24_LE, 0}, + {FEEDER_FMT, AFMT_S32_LE | AFMT_STEREO, AFMT_U24_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U32_BE, AFMT_S24_BE, 0}, + {FEEDER_FMT, AFMT_U32_BE | AFMT_STEREO, AFMT_S24_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S32_BE, AFMT_U24_BE, 0}, + {FEEDER_FMT, AFMT_S32_BE | AFMT_STEREO, AFMT_U24_BE | AFMT_STEREO, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_32to24_methods[] = { + KOBJMETHOD(feeder_init, feed_updownbit_init), + KOBJMETHOD(feeder_feed, feed_downbit), + {0, 0} +}; +FEEDER_DECLARE(feeder_32to24, 0, NULL); +/* + * Bit conversion end + */ +/* + * Channel conversion (mono -> stereo) + */ static int -feed_monotostereo8(struct pcm_feeder *f, struct pcm_channel *c, u_int8_t *b, u_int32_t count, void *source) +feed_monotostereo(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, + uint32_t count, void *source) { - int i, j, k = FEEDER_FEED(f->source, c, b, count / 2, source); + int bps, i, j, k, l; + uint8_t v; - j = k - 1; - i = j * 2 + 1; - while (i > 0 && j >= 0) { - b[i--] = b[j]; - b[i--] = b[j]; - j--; - } - return k * 2; + bps = (int)((intptr_t)f->data); + if (count < (bps << 1)) + return (0); + + j = FEEDER_FEED(f->source, c, b, (count - (count % bps)) >> 1, source); + if (j < bps) + return (0); + + j -= j % bps; + i = j << 1; + l = i; + + do { + k = bps; + do { + v = b[--j]; + b[--i] = v; + b[i - bps] = v; + } while (--k != 0); + i -= bps; + } while (i != 0); + + return (l); } static struct pcm_feederdesc feeder_monotostereo8_desc[] = { {FEEDER_FMT, AFMT_U8, AFMT_U8 | AFMT_STEREO, 0}, {FEEDER_FMT, AFMT_S8, AFMT_S8 | AFMT_STEREO, 0}, - {0}, + {FEEDER_FMT, AFMT_MU_LAW, AFMT_MU_LAW | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_A_LAW, AFMT_A_LAW | AFMT_STEREO, 0}, + {0, 0, 0, 0}, }; static kobj_method_t feeder_monotostereo8_methods[] = { - KOBJMETHOD(feeder_feed, feed_monotostereo8), - { 0, 0 } + KOBJMETHOD(feeder_feed, feed_monotostereo), + {0, 0} }; -FEEDER_DECLARE(feeder_monotostereo8, 0, NULL); - -/*****************************************************************************/ - -static int -feed_monotostereo16(struct pcm_feeder *f, struct pcm_channel *c, u_int8_t *b, u_int32_t count, void *source) -{ - int i, j, k = FEEDER_FEED(f->source, c, b, count / 2, source); - u_int8_t x, y; - - j = k - 1; - i = j * 2 + 1; - while (i >= 3 && j >= 1) { - x = b[j--]; - y = b[j--]; - b[i--] = x; - b[i--] = y; - b[i--] = x; - b[i--] = y; - } - return k * 2; -} +FEEDER_DECLARE(feeder_monotostereo8, 0, (void *)PCM_8_BPS); static struct pcm_feederdesc feeder_monotostereo16_desc[] = { {FEEDER_FMT, AFMT_U16_LE, AFMT_U16_LE | AFMT_STEREO, 0}, {FEEDER_FMT, AFMT_S16_LE, AFMT_S16_LE | AFMT_STEREO, 0}, {FEEDER_FMT, AFMT_U16_BE, AFMT_U16_BE | AFMT_STEREO, 0}, {FEEDER_FMT, AFMT_S16_BE, AFMT_S16_BE | AFMT_STEREO, 0}, - {0}, + {0, 0, 0, 0}, }; static kobj_method_t feeder_monotostereo16_methods[] = { - KOBJMETHOD(feeder_feed, feed_monotostereo16), - { 0, 0 } + KOBJMETHOD(feeder_feed, feed_monotostereo), + {0, 0} }; -FEEDER_DECLARE(feeder_monotostereo16, 0, NULL); +FEEDER_DECLARE(feeder_monotostereo16, 0, (void *)PCM_16_BPS); -/*****************************************************************************/ +static struct pcm_feederdesc feeder_monotostereo24_desc[] = { + {FEEDER_FMT, AFMT_U24_LE, AFMT_U24_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S24_LE, AFMT_S24_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U24_BE, AFMT_U24_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S24_BE, AFMT_S24_BE | AFMT_STEREO, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_monotostereo24_methods[] = { + KOBJMETHOD(feeder_feed, feed_monotostereo), + {0, 0} +}; +FEEDER_DECLARE(feeder_monotostereo24, 0, (void *)PCM_24_BPS); + +static struct pcm_feederdesc feeder_monotostereo32_desc[] = { + {FEEDER_FMT, AFMT_U32_LE, AFMT_U32_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S32_LE, AFMT_S32_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U32_BE, AFMT_U32_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S32_BE, AFMT_S32_BE | AFMT_STEREO, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_monotostereo32_methods[] = { + KOBJMETHOD(feeder_feed, feed_monotostereo), + {0, 0} +}; +FEEDER_DECLARE(feeder_monotostereo32, 0, (void *)PCM_32_BPS); +/* + * Channel conversion (mono -> stereo) end + */ -static int -feed_stereotomono8_init(struct pcm_feeder *f) -{ - f->data = malloc(FEEDBUFSZ, M_FMTFEEDER, M_NOWAIT | M_ZERO); - if (f->data == NULL) - return ENOMEM; - return 0; +/* + * Channel conversion (stereo -> mono) + */ +#define FEEDER_FMT_STEREODOWNMIX(FMTBIT, SIGN, SIGNS, ENDIAN, ENDIANS) \ +static void \ +SIGNS##FMTBIT##ENDIANS##e_stereodownmix(uint8_t *dst, uint8_t *sx, uint8_t *sy) \ +{ \ + intpcm_t v; \ + \ + v = (INTPCM##FMTBIT##_T(PCM_READ_##SIGN##FMTBIT##_##ENDIAN##E(sx)) + \ + PCM_READ_##SIGN##FMTBIT##_##ENDIAN##E(sy)) >> 1; \ + PCM_WRITE_##SIGN##FMTBIT##_##ENDIAN##E(dst, v); \ } -static int -feed_stereotomono8_free(struct pcm_feeder *f) +FEEDER_FMT_STEREODOWNMIX(8, S, s, N, n) +FEEDER_FMT_STEREODOWNMIX(16, S, s, L, l) +FEEDER_FMT_STEREODOWNMIX(24, S, s, L, l) +FEEDER_FMT_STEREODOWNMIX(32, S, s, L, l) +FEEDER_FMT_STEREODOWNMIX(16, S, s, B, b) +FEEDER_FMT_STEREODOWNMIX(24, S, s, B, b) +FEEDER_FMT_STEREODOWNMIX(32, S, s, B, b) +FEEDER_FMT_STEREODOWNMIX(8, U, u, N, n) +FEEDER_FMT_STEREODOWNMIX(16, U, u, L, l) +FEEDER_FMT_STEREODOWNMIX(24, U, u, L, l) +FEEDER_FMT_STEREODOWNMIX(32, U, u, L, l) +FEEDER_FMT_STEREODOWNMIX(16, U, u, B, b) +FEEDER_FMT_STEREODOWNMIX(24, U, u, B, b) +FEEDER_FMT_STEREODOWNMIX(32, U, u, B, b) + +static void +ulaw_stereodownmix(uint8_t *dst, uint8_t *sx, uint8_t *sy) { - if (f->data) - free(f->data, M_FMTFEEDER); - f->data = NULL; - return 0; + uint8_t v; + + v = ((uint32_t)ulaw_to_u8_tbl[*sx] + ulaw_to_u8_tbl[*sy]) >> 1; + *dst = u8_to_ulaw_tbl[v]; } -static int -feed_stereotomono8(struct pcm_feeder *f, struct pcm_channel *c, u_int8_t *b, u_int32_t count, void *source) +static void +alaw_stereodownmix(uint8_t *dst, uint8_t *sx, uint8_t *sy) { - u_int32_t i = 0, toget = count * 2; - int j = 0, k; + uint8_t v; - k = FEEDER_FEED(f->source, c, f->data, min(toget, FEEDBUFSZ), source); - while (j < k) { - b[i++] = ((u_int8_t *)f->data)[j]; - j += 2; - } - return i; + v = ((uint32_t)alaw_to_u8_tbl[*sx] + alaw_to_u8_tbl[*sy]) >> 1; + *dst = u8_to_alaw_tbl[v]; } -static struct pcm_feederdesc feeder_stereotomono8_desc[] = { - {FEEDER_FMT, AFMT_U8 | AFMT_STEREO, AFMT_U8, 0}, - {FEEDER_FMT, AFMT_S8 | AFMT_STEREO, AFMT_S8, 0}, - {0}, -}; -static kobj_method_t feeder_stereotomono8_methods[] = { - KOBJMETHOD(feeder_init, feed_stereotomono8_init), - KOBJMETHOD(feeder_free, feed_stereotomono8_free), - KOBJMETHOD(feeder_feed, feed_stereotomono8), - { 0, 0 } -}; -FEEDER_DECLARE(feeder_stereotomono8, 1, NULL); +typedef void (*feed_fmt_stereodownmix_filter)(uint8_t *, + uint8_t *, uint8_t *); -/*****************************************************************************/ +struct feed_fmt_stereodownmix_info { + uint32_t format; + int bps; + feed_fmt_stereodownmix_filter func[2]; +}; + +static struct feed_fmt_stereodownmix_info feed_fmt_stereodownmix_tbl[] = { + { AFMT_S8, PCM_8_BPS, { NULL, s8ne_stereodownmix }}, + { AFMT_S16_LE, PCM_16_BPS, { NULL, s16le_stereodownmix }}, + { AFMT_S16_BE, PCM_16_BPS, { NULL, s16be_stereodownmix }}, + { AFMT_S24_LE, PCM_24_BPS, { NULL, s24le_stereodownmix }}, + { AFMT_S24_BE, PCM_24_BPS, { NULL, s24be_stereodownmix }}, + { AFMT_S32_LE, PCM_32_BPS, { NULL, s32le_stereodownmix }}, + { AFMT_S32_BE, PCM_32_BPS, { NULL, s32be_stereodownmix }}, + { AFMT_U8, PCM_8_BPS, { NULL, u8ne_stereodownmix }}, + { AFMT_A_LAW, PCM_8_BPS, { NULL, alaw_stereodownmix }}, + { AFMT_MU_LAW, PCM_8_BPS, { NULL, ulaw_stereodownmix }}, + { AFMT_U16_LE, PCM_16_BPS, { NULL, u16le_stereodownmix }}, + { AFMT_U16_BE, PCM_16_BPS, { NULL, u16be_stereodownmix }}, + { AFMT_U24_LE, PCM_24_BPS, { NULL, u24le_stereodownmix }}, + { AFMT_U24_BE, PCM_24_BPS, { NULL, u24be_stereodownmix }}, + { AFMT_U32_LE, PCM_32_BPS, { NULL, u32le_stereodownmix }}, + { AFMT_U32_BE, PCM_32_BPS, { NULL, u32be_stereodownmix }}, +}; + +#define FSM_DATA(i, j) ((intptr_t)((((i) & 0x3f) << 1) | ((j) & 0x1))) +#define FSM_INFOIDX(m) (((m) >> 1) & 0x3f) +#define FSM_FUNCIDX(m) ((m) & 0x1) static int -feed_stereotomono16_init(struct pcm_feeder *f) +feed_stereotomono_init(struct pcm_feeder *f) { - f->data = malloc(FEEDBUFSZ, M_FMTFEEDER, M_NOWAIT | M_ZERO); - if (f->data == NULL) - return ENOMEM; - return 0; -} + int i, funcidx; -static int -feed_stereotomono16_free(struct pcm_feeder *f) -{ - if (f->data) - free(f->data, M_FMTFEEDER); - f->data = NULL; - return 0; + if (!(f->desc->in & AFMT_STEREO) || (f->desc->out & AFMT_STEREO)) + return (-1); + + funcidx = (feeder_fmt_stereodownmix != 0) ? 1 : 0; + + for (i = 0; i < sizeof(feed_fmt_stereodownmix_tbl) / + sizeof(feed_fmt_stereodownmix_tbl[0]); i++) { + if (f->desc->out == feed_fmt_stereodownmix_tbl[i].format) { + f->data = (void *)FSM_DATA(i, funcidx); + return (0); + } + } + + return (-1); } static int -feed_stereotomono16(struct pcm_feeder *f, struct pcm_channel *c, u_int8_t *b, u_int32_t count, void *source) +feed_stereotomono(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, + uint32_t count, void *source) { - u_int32_t i = 0, toget = count * 2; - int j = 0, k; + struct feed_fmt_stereodownmix_info *info; + feed_fmt_stereodownmix_filter stereodownmix; + int i, j, k, ibps, obps; + uint8_t *src, *dst, *end; + uint8_t reservoir[FEEDFMT_RESERVOIR]; + + info = &feed_fmt_stereodownmix_tbl[FSM_INFOIDX((intptr_t)f->data)]; + obps = info->bps; + + if (count < obps) + return (0); + + stereodownmix = info->func[FSM_FUNCIDX((intptr_t)f->data)]; + ibps = obps << 1; + dst = b; + k = count - (count % obps); + + do { + if (k < obps) + break; + + if (k < ibps) { + src = reservoir; + j = ibps; + } else { + src = dst; + j = k; + } + + j = FEEDER_FEED(f->source, c, src, j - (j % ibps), source); + if (j < ibps) + break; + + j -= j % ibps; + j >>= 1; + end = dst + j; + + if (stereodownmix != NULL) { + do { + stereodownmix(dst, src, src + obps); + dst += obps; + src += ibps; + } while (dst != end); + } else { + do { + i = obps; + do { + *dst++ = *src++; + } while (--i != 0); + src += obps; + } while (dst != end); + } - k = FEEDER_FEED(f->source, c, f->data, min(toget, FEEDBUFSZ), source); - while (j < k) { - b[i++] = ((u_int8_t *)f->data)[j]; - b[i++] = ((u_int8_t *)f->data)[j + 1]; - j += 4; - } - return i; + k -= j; + } while (k != 0); + + return (dst - b); } +static struct pcm_feederdesc feeder_stereotomono8_desc[] = { + {FEEDER_FMT, AFMT_U8 | AFMT_STEREO, AFMT_U8, 0}, + {FEEDER_FMT, AFMT_S8 | AFMT_STEREO, AFMT_S8, 0}, + {FEEDER_FMT, AFMT_MU_LAW | AFMT_STEREO, AFMT_MU_LAW, 0}, + {FEEDER_FMT, AFMT_A_LAW | AFMT_STEREO, AFMT_A_LAW, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_stereotomono8_methods[] = { + KOBJMETHOD(feeder_init, feed_stereotomono_init), + KOBJMETHOD(feeder_feed, feed_stereotomono), + {0, 0} +}; +FEEDER_DECLARE(feeder_stereotomono8, 0, NULL); + static struct pcm_feederdesc feeder_stereotomono16_desc[] = { {FEEDER_FMT, AFMT_U16_LE | AFMT_STEREO, AFMT_U16_LE, 0}, {FEEDER_FMT, AFMT_S16_LE | AFMT_STEREO, AFMT_S16_LE, 0}, {FEEDER_FMT, AFMT_U16_BE | AFMT_STEREO, AFMT_U16_BE, 0}, {FEEDER_FMT, AFMT_S16_BE | AFMT_STEREO, AFMT_S16_BE, 0}, - {0}, + {0, 0, 0, 0}, }; static kobj_method_t feeder_stereotomono16_methods[] = { - KOBJMETHOD(feeder_init, feed_stereotomono16_init), - KOBJMETHOD(feeder_free, feed_stereotomono16_free), - KOBJMETHOD(feeder_feed, feed_stereotomono16), - { 0, 0 } -}; -FEEDER_DECLARE(feeder_stereotomono16, 1, NULL); - -/*****************************************************************************/ + KOBJMETHOD(feeder_init, feed_stereotomono_init), + KOBJMETHOD(feeder_feed, feed_stereotomono), + {0, 0} +}; +FEEDER_DECLARE(feeder_stereotomono16, 0, NULL); + +static struct pcm_feederdesc feeder_stereotomono24_desc[] = { + {FEEDER_FMT, AFMT_U24_LE | AFMT_STEREO, AFMT_U24_LE, 0}, + {FEEDER_FMT, AFMT_S24_LE | AFMT_STEREO, AFMT_S24_LE, 0}, + {FEEDER_FMT, AFMT_U24_BE | AFMT_STEREO, AFMT_U24_BE, 0}, + {FEEDER_FMT, AFMT_S24_BE | AFMT_STEREO, AFMT_S24_BE, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_stereotomono24_methods[] = { + KOBJMETHOD(feeder_init, feed_stereotomono_init), + KOBJMETHOD(feeder_feed, feed_stereotomono), + {0, 0} +}; +FEEDER_DECLARE(feeder_stereotomono24, 0, NULL); + +static struct pcm_feederdesc feeder_stereotomono32_desc[] = { + {FEEDER_FMT, AFMT_U32_LE | AFMT_STEREO, AFMT_U32_LE, 0}, + {FEEDER_FMT, AFMT_S32_LE | AFMT_STEREO, AFMT_S32_LE, 0}, + {FEEDER_FMT, AFMT_U32_BE | AFMT_STEREO, AFMT_U32_BE, 0}, + {FEEDER_FMT, AFMT_S32_BE | AFMT_STEREO, AFMT_S32_BE, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_stereotomono32_methods[] = { + KOBJMETHOD(feeder_init, feed_stereotomono_init), + KOBJMETHOD(feeder_feed, feed_stereotomono), + {0, 0} +}; +FEEDER_DECLARE(feeder_stereotomono32, 0, NULL); +/* + * Channel conversion (stereo -> mono) end + */ +/* + * Sign conversion + */ static int -feed_endian(struct pcm_feeder *f, struct pcm_channel *c, u_int8_t *b, u_int32_t count, void *source) +feed_sign(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, + uint32_t count, void *source) { - u_int8_t t; - int i = 0, j = FEEDER_FEED(f->source, c, b, count, source); - - while (i < j) { - t = b[i]; - b[i] = b[i + 1]; - b[i + 1] = t; - i += 2; - } - return i; -} - -static struct pcm_feederdesc feeder_endian_desc[] = { - {FEEDER_FMT, AFMT_U16_LE, AFMT_U16_BE, 0}, - {FEEDER_FMT, AFMT_U16_LE | AFMT_STEREO, AFMT_U16_BE | AFMT_STEREO, 0}, - {FEEDER_FMT, AFMT_S16_LE, AFMT_S16_BE, 0}, - {FEEDER_FMT, AFMT_S16_LE | AFMT_STEREO, AFMT_S16_BE | AFMT_STEREO, 0}, - {FEEDER_FMT, AFMT_U16_BE, AFMT_U16_LE, 0}, - {FEEDER_FMT, AFMT_U16_BE | AFMT_STEREO, AFMT_U16_LE | AFMT_STEREO, 0}, - {FEEDER_FMT, AFMT_S16_BE, AFMT_S16_LE, 0}, - {FEEDER_FMT, AFMT_S16_BE | AFMT_STEREO, AFMT_S16_LE | AFMT_STEREO, 0}, - {0}, -}; -static kobj_method_t feeder_endian_methods[] = { - KOBJMETHOD(feeder_feed, feed_endian), - { 0, 0 } -}; -FEEDER_DECLARE(feeder_endian, 0, NULL); + int i, j, bps, ofs; -/*****************************************************************************/ + bps = (int)((intptr_t)f->data); + if (count < bps) + return (0); + + i = FEEDER_FEED(f->source, c, b, count - (count % bps), source); + if (i < bps) + return (0); + + i -= i % bps; + j = i; + ofs = (f->desc->in & AFMT_BIGENDIAN) ? bps : 1; + + do { + b[i - ofs] ^= 0x80; + i -= bps; + } while (i != 0); -static int -feed_sign(struct pcm_feeder *f, struct pcm_channel *c, u_int8_t *b, u_int32_t count, void *source) -{ - int i = 0, j = FEEDER_FEED(f->source, c, b, count, source); - intptr_t ssz = (intptr_t)f->data, ofs = ssz - 1; - - while (i < j) { - b[i + ofs] ^= 0x80; - i += ssz; - } - return i; + return (j); } - static struct pcm_feederdesc feeder_sign8_desc[] = { {FEEDER_FMT, AFMT_U8, AFMT_S8, 0}, {FEEDER_FMT, AFMT_U8 | AFMT_STEREO, AFMT_S8 | AFMT_STEREO, 0}, {FEEDER_FMT, AFMT_S8, AFMT_U8, 0}, {FEEDER_FMT, AFMT_S8 | AFMT_STEREO, AFMT_U8 | AFMT_STEREO, 0}, - {0}, + {0, 0, 0, 0}, }; static kobj_method_t feeder_sign8_methods[] = { - KOBJMETHOD(feeder_feed, feed_sign), - { 0, 0 } + KOBJMETHOD(feeder_feed, feed_sign), + {0, 0} }; -FEEDER_DECLARE(feeder_sign8, 0, (void *)1); +FEEDER_DECLARE(feeder_sign8, 0, (void *)PCM_8_BPS); -static struct pcm_feederdesc feeder_sign16le_desc[] = { +static struct pcm_feederdesc feeder_sign16_desc[] = { {FEEDER_FMT, AFMT_U16_LE, AFMT_S16_LE, 0}, {FEEDER_FMT, AFMT_U16_LE | AFMT_STEREO, AFMT_S16_LE | AFMT_STEREO, 0}, {FEEDER_FMT, AFMT_S16_LE, AFMT_U16_LE, 0}, {FEEDER_FMT, AFMT_S16_LE | AFMT_STEREO, AFMT_U16_LE | AFMT_STEREO, 0}, - {0}, + {FEEDER_FMT, AFMT_U16_BE, AFMT_S16_BE, 0}, + {FEEDER_FMT, AFMT_U16_BE | AFMT_STEREO, AFMT_S16_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S16_BE, AFMT_U16_BE, 0}, + {FEEDER_FMT, AFMT_S16_BE | AFMT_STEREO, AFMT_U16_BE | AFMT_STEREO, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_sign16_methods[] = { + KOBJMETHOD(feeder_feed, feed_sign), + {0, 0} +}; +FEEDER_DECLARE(feeder_sign16, 0, (void *)PCM_16_BPS); + +static struct pcm_feederdesc feeder_sign24_desc[] = { + {FEEDER_FMT, AFMT_U24_LE, AFMT_S24_LE, 0}, + {FEEDER_FMT, AFMT_U24_LE | AFMT_STEREO, AFMT_S24_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S24_LE, AFMT_U24_LE, 0}, + {FEEDER_FMT, AFMT_S24_LE | AFMT_STEREO, AFMT_U24_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U24_BE, AFMT_S24_BE, 0}, + {FEEDER_FMT, AFMT_U24_BE | AFMT_STEREO, AFMT_S24_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S24_BE, AFMT_U24_BE, 0}, + {FEEDER_FMT, AFMT_S24_BE | AFMT_STEREO, AFMT_U24_BE | AFMT_STEREO, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_sign24_methods[] = { + KOBJMETHOD(feeder_feed, feed_sign), + {0, 0} +}; +FEEDER_DECLARE(feeder_sign24, 0, (void *)PCM_24_BPS); + +static struct pcm_feederdesc feeder_sign32_desc[] = { + {FEEDER_FMT, AFMT_U32_LE, AFMT_S32_LE, 0}, + {FEEDER_FMT, AFMT_U32_LE | AFMT_STEREO, AFMT_S32_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S32_LE, AFMT_U32_LE, 0}, + {FEEDER_FMT, AFMT_S32_LE | AFMT_STEREO, AFMT_U32_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U32_BE, AFMT_S32_BE, 0}, + {FEEDER_FMT, AFMT_U32_BE | AFMT_STEREO, AFMT_S32_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S32_BE, AFMT_U32_BE, 0}, + {FEEDER_FMT, AFMT_S32_BE | AFMT_STEREO, AFMT_U32_BE | AFMT_STEREO, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_sign32_methods[] = { + KOBJMETHOD(feeder_feed, feed_sign), + {0, 0} }; -static kobj_method_t feeder_sign16le_methods[] = { - KOBJMETHOD(feeder_feed, feed_sign), - { 0, 0 } -}; -FEEDER_DECLARE(feeder_sign16le, -1, (void *)2); - -/*****************************************************************************/ +FEEDER_DECLARE(feeder_sign32, 0, (void *)PCM_32_BPS); +/* + * Endian conversion + */ static int -feed_table(struct pcm_feeder *f, struct pcm_channel *c, u_int8_t *b, u_int32_t count, void *source) +feed_endian(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, + uint32_t count, void *source) { - int i = 0, j = FEEDER_FEED(f->source, c, b, count, source); - - while (i < j) { - b[i] = ((u_int8_t *)f->data)[b[i]]; - i++; - } - return i; -} - -static struct pcm_feederdesc feeder_ulawtou8_desc[] = { - {FEEDER_FMT, AFMT_MU_LAW, AFMT_U8, 0}, - {FEEDER_FMT, AFMT_MU_LAW | AFMT_STEREO, AFMT_U8 | AFMT_STEREO, 0}, - {0}, -}; -static kobj_method_t feeder_ulawtou8_methods[] = { - KOBJMETHOD(feeder_feed, feed_table), - { 0, 0 } -}; -FEEDER_DECLARE(feeder_ulawtou8, 0, ulaw_to_u8); + int i, j, k, bps; + uint8_t *buf, v; -static struct pcm_feederdesc feeder_u8toulaw_desc[] = { - {FEEDER_FMT, AFMT_U8, AFMT_MU_LAW, 0}, - {FEEDER_FMT, AFMT_U8 | AFMT_STEREO, AFMT_MU_LAW | AFMT_STEREO, 0}, - {0}, -}; -static kobj_method_t feeder_u8toulaw_methods[] = { - KOBJMETHOD(feeder_feed, feed_table), - { 0, 0 } -}; -FEEDER_DECLARE(feeder_u8toulaw, 0, u8_to_ulaw); + bps = (int)((intptr_t)f->data); + if (count < bps) + return (0); + + k = FEEDER_FEED(f->source, c, b, count - (count % bps), source); + if (k < bps) + return (0); + + k -= k % bps; + j = bps >> 1; + buf = b + k; + + do { + buf -= bps; + i = j; + do { + v = buf[--i]; + buf[i] = buf[bps - i - 1]; + buf[bps - i - 1] = v; + } while (i != 0); + } while (buf != b); -static struct pcm_feederdesc feeder_alawtoulaw_desc[] = { - {FEEDER_FMT, AFMT_A_LAW, AFMT_MU_LAW, 0}, - {FEEDER_FMT, AFMT_A_LAW | AFMT_STEREO, AFMT_MU_LAW | AFMT_STEREO, 0}, - {0}, -}; -static kobj_method_t feeder_alawtoulaw_methods[] = { - KOBJMETHOD(feeder_feed, feed_table), - { 0, 0 } + return (k); +} +static struct pcm_feederdesc feeder_endian16_desc[] = { + {FEEDER_FMT, AFMT_U16_LE, AFMT_U16_BE, 0}, + {FEEDER_FMT, AFMT_U16_LE | AFMT_STEREO, AFMT_U16_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S16_LE, AFMT_S16_BE, 0}, + {FEEDER_FMT, AFMT_S16_LE | AFMT_STEREO, AFMT_S16_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U16_BE, AFMT_U16_LE, 0}, + {FEEDER_FMT, AFMT_U16_BE | AFMT_STEREO, AFMT_U16_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S16_BE, AFMT_S16_LE, 0}, + {FEEDER_FMT, AFMT_S16_BE | AFMT_STEREO, AFMT_S16_LE | AFMT_STEREO, 0}, + {0, 0, 0, 0}, }; -FEEDER_DECLARE(feeder_alawtoulaw, 0, alaw_to_ulaw); +static kobj_method_t feeder_endian16_methods[] = { + KOBJMETHOD(feeder_feed, feed_endian), + {0, 0} +}; +FEEDER_DECLARE(feeder_endian16, 0, (void *)PCM_16_BPS); + +static struct pcm_feederdesc feeder_endian24_desc[] = { + {FEEDER_FMT, AFMT_U24_LE, AFMT_U24_BE, 0}, + {FEEDER_FMT, AFMT_U24_LE | AFMT_STEREO, AFMT_U24_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S24_LE, AFMT_S24_BE, 0}, + {FEEDER_FMT, AFMT_S24_LE | AFMT_STEREO, AFMT_S24_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U24_BE, AFMT_U24_LE, 0}, + {FEEDER_FMT, AFMT_U24_BE | AFMT_STEREO, AFMT_U24_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S24_BE, AFMT_S24_LE, 0}, + {FEEDER_FMT, AFMT_S24_BE | AFMT_STEREO, AFMT_S24_LE | AFMT_STEREO, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_endian24_methods[] = { + KOBJMETHOD(feeder_feed, feed_endian), + {0, 0} +}; +FEEDER_DECLARE(feeder_endian24, 0, (void *)PCM_24_BPS); + +static struct pcm_feederdesc feeder_endian32_desc[] = { + {FEEDER_FMT, AFMT_U32_LE, AFMT_U32_BE, 0}, + {FEEDER_FMT, AFMT_U32_LE | AFMT_STEREO, AFMT_U32_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S32_LE, AFMT_S32_BE, 0}, + {FEEDER_FMT, AFMT_S32_LE | AFMT_STEREO, AFMT_S32_BE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_U32_BE, AFMT_U32_LE, 0}, + {FEEDER_FMT, AFMT_U32_BE | AFMT_STEREO, AFMT_U32_LE | AFMT_STEREO, 0}, + {FEEDER_FMT, AFMT_S32_BE, AFMT_S32_LE, 0}, + {FEEDER_FMT, AFMT_S32_BE | AFMT_STEREO, AFMT_S32_LE | AFMT_STEREO, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_endian32_methods[] = { + KOBJMETHOD(feeder_feed, feed_endian), + {0, 0} +}; +FEEDER_DECLARE(feeder_endian32, 0, (void *)PCM_32_BPS); +/* + * Endian conversion end + */ -static struct pcm_feederdesc feeder_ulawtoalaw_desc[] = { - {FEEDER_FMT, AFMT_MU_LAW, AFMT_A_LAW, 0}, - {FEEDER_FMT, AFMT_MU_LAW | AFMT_STEREO, AFMT_A_LAW | AFMT_STEREO, 0}, - {0}, -}; -static kobj_method_t feeder_ulawtoalaw_methods[] = { - KOBJMETHOD(feeder_feed, feed_table), - { 0, 0 } -}; -FEEDER_DECLARE(feeder_ulawtoalaw, 0, ulaw_to_alaw); +/* + * L/R swap conversion + */ +static int +feed_swaplr(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, + uint32_t count, void *source) +{ + int i, j, bps, smpsz; + uint8_t *buf, v; + bps = (int)((intptr_t)f->data); + smpsz = bps << 1; + if (count < smpsz) + return (0); + + j = FEEDER_FEED(f->source, c, b, count - (count % smpsz), source); + if (j < smpsz) + return (0); + + j -= j % smpsz; + buf = b + j; + + do { + buf -= smpsz; + i = bps; + do { + v = buf[--i]; + buf[i] = buf[bps + i]; + buf[bps + i] = v; + } while (i != 0); + } while (buf != b); + return (j); +} +static struct pcm_feederdesc feeder_swaplr8_desc[] = { + {FEEDER_SWAPLR, AFMT_S8 | AFMT_STEREO, AFMT_S8 | AFMT_STEREO, 0}, + {FEEDER_SWAPLR, AFMT_U8 | AFMT_STEREO, AFMT_U8 | AFMT_STEREO, 0}, + {FEEDER_SWAPLR, AFMT_A_LAW | AFMT_STEREO, AFMT_A_LAW | AFMT_STEREO, 0}, + {FEEDER_SWAPLR, AFMT_MU_LAW | AFMT_STEREO, AFMT_A_LAW | AFMT_STEREO, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_swaplr8_methods[] = { + KOBJMETHOD(feeder_feed, feed_swaplr), + {0, 0} +}; +FEEDER_DECLARE(feeder_swaplr8, -1, (void *)PCM_8_BPS); + +static struct pcm_feederdesc feeder_swaplr16_desc[] = { + {FEEDER_SWAPLR, AFMT_S16_LE | AFMT_STEREO, AFMT_S16_LE | AFMT_STEREO, 0}, + {FEEDER_SWAPLR, AFMT_S16_BE | AFMT_STEREO, AFMT_S16_BE | AFMT_STEREO, 0}, + {FEEDER_SWAPLR, AFMT_U16_LE | AFMT_STEREO, AFMT_U16_LE | AFMT_STEREO, 0}, + {FEEDER_SWAPLR, AFMT_U16_BE | AFMT_STEREO, AFMT_U16_BE | AFMT_STEREO, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_swaplr16_methods[] = { + KOBJMETHOD(feeder_feed, feed_swaplr), + {0, 0} +}; +FEEDER_DECLARE(feeder_swaplr16, -1, (void *)PCM_16_BPS); + +static struct pcm_feederdesc feeder_swaplr24_desc[] = { + {FEEDER_SWAPLR, AFMT_S24_LE | AFMT_STEREO, AFMT_S24_LE | AFMT_STEREO, 0}, + {FEEDER_SWAPLR, AFMT_S24_BE | AFMT_STEREO, AFMT_S24_BE | AFMT_STEREO, 0}, + {FEEDER_SWAPLR, AFMT_U24_LE | AFMT_STEREO, AFMT_U24_LE | AFMT_STEREO, 0}, + {FEEDER_SWAPLR, AFMT_U24_BE | AFMT_STEREO, AFMT_U24_BE | AFMT_STEREO, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_swaplr24_methods[] = { + KOBJMETHOD(feeder_feed, feed_swaplr), + {0, 0} +}; +FEEDER_DECLARE(feeder_swaplr24, -1, (void *)PCM_24_BPS); + +static struct pcm_feederdesc feeder_swaplr32_desc[] = { + {FEEDER_SWAPLR, AFMT_S32_LE | AFMT_STEREO, AFMT_S32_LE | AFMT_STEREO, 0}, + {FEEDER_SWAPLR, AFMT_S32_BE | AFMT_STEREO, AFMT_S32_BE | AFMT_STEREO, 0}, + {FEEDER_SWAPLR, AFMT_U32_LE | AFMT_STEREO, AFMT_U32_LE | AFMT_STEREO, 0}, + {FEEDER_SWAPLR, AFMT_U32_BE | AFMT_STEREO, AFMT_U32_BE | AFMT_STEREO, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_swaplr32_methods[] = { + KOBJMETHOD(feeder_feed, feed_swaplr), + {0, 0} +}; +FEEDER_DECLARE(feeder_swaplr32, -1, (void *)PCM_32_BPS); +/* + * L/R swap conversion end + */ --- sys/dev/sound/pcm/feeder_if.m.orig Sun Jan 30 09:00:05 2005 +++ sys/dev/sound/pcm/feeder_if.m Thu Jan 6 09:43:21 2005 @@ -25,7 +25,7 @@ # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF # SUCH DAMAGE. # -# $FreeBSD: src/sys/dev/sound/pcm/feeder_if.m,v 1.5.8.1 2005/01/30 01:00:05 imp Exp $ +# $FreeBSD: src/sys/dev/sound/pcm/feeder_if.m,v 1.6 2005/01/06 01:43:21 imp Exp $ # #include --- sys/dev/sound/pcm/feeder_rate.c.orig Sun Jan 30 09:00:05 2005 +++ sys/dev/sound/pcm/feeder_rate.c Thu Jul 12 12:04:19 2007 @@ -1,5 +1,7 @@ /*- - * Copyright (c) 2003 Orion Hodson + * Copyright (c) 1999 Cameron Grant + * Copyright (c) 2003 Orion Hodson + * Copyright (c) 2005 Ariff Abdullah * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -22,8 +24,37 @@ * 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. + */ + +/* + * 2006-02-21: + * ========== + * + * Major cleanup and overhaul to remove much redundant codes. + * Highlights: + * 1) Support for signed / unsigned 16, 24 and 32 bit, + * big / little endian, + * 2) Unlimited channels. + * + * 2005-06-11: + * ========== * - * MAINTAINER: Orion Hodson + * *New* and rewritten soft sample rate converter supporting arbitrary sample + * rates, fine grained scaling/coefficients and a unified up/down stereo + * converter. Most of the disclaimers from orion's notes also applies + * here, regarding linear interpolation deficiencies and pre/post + * anti-aliasing filtering issues. This version comes with a much simpler and + * tighter interface, although it works almost exactly like the older one. + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * This new implementation is fully dedicated in memory of Cameron Grant, * + * the creator of the magnificent, highly addictive feeder infrastructure. * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * + * Orion's notes: + * ============= * * This rate conversion code uses linear interpolation without any * pre- or post- interpolation filtering to combat aliasing. This @@ -31,201 +62,317 @@ * stage in the future. * * Since this accuracy of interpolation is sensitive and examination - * of the algorithm output is harder from the kernel, th code is + * of the algorithm output is harder from the kernel, the code is * designed to be compiled in the kernel and in a userland test * harness. This is done by selectively including and excluding code * with several portions based on whether _KERNEL is defined. It's a * little ugly, but exceedingly useful. The testsuite and its * revisions can be found at: - * http://people.freebsd.org/~orion/feedrate/ + * http://people.freebsd.org/~orion/files/feedrate/ * * Special thanks to Ken Marx for exposing flaws in the code and for * testing revisions. */ -#ifdef _KERNEL - #include +#include #include "feeder_if.h" -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pcm/feeder_rate.c,v 1.10.6.1 2005/01/30 01:00:05 imp Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pcm/feeder_rate.c,v 1.23 2007/06/16 03:37:28 ariff Exp $"); -#endif /* _KERNEL */ +#define RATE_ASSERT(x, y) /* KASSERT(x,y) */ +#define RATE_TEST(x, y) /* if (!(x)) printf y */ +#define RATE_TRACE(x...) /* printf(x) */ MALLOC_DEFINE(M_RATEFEEDER, "ratefeed", "pcm rate feeder"); -#ifndef RATE_ASSERT -#define RATE_ASSERT(x, y) /* KASSERT(x) */ -#endif /* RATE_ASSERT */ - -#ifndef RATE_TRACE -#define RATE_TRACE(x...) /* printf(x) */ -#endif - -/*****************************************************************************/ - -/* The following coefficients are coupled. They are chosen to be - * guarantee calculable factors for the interpolation routine. They - * have been tested over the range of RATEMIN-RATEMAX Hz. Decreasing - * the granularity increases the required buffer size and affects the - * gain values at different points in the space. These values were - * found by running the test program with -p (probe) and some trial - * and error. - * - * ROUNDHZ the granularity of sample rates (fits n*11025 and n*8000). - * FEEDBUFSZ the amount of buffer space. - * MINGAIN the minimum acceptable gain in coefficients search. +/* + * Don't overflow 32bit integer, since everything is done + * within 32bit arithmetic. */ -#define ROUNDHZ 25 -#define FEEDBUFSZ 8192 -#define MINGAIN 92 - -#define RATEMIN 4000 -#define RATEMAX 48000 +#define RATE_FACTOR_MIN 1 +#define RATE_FACTOR_MAX PCM_S24_MAX +#define RATE_FACTOR_SAFE(val) (!((val) < RATE_FACTOR_MIN || \ + (val) > RATE_FACTOR_MAX)) struct feed_rate_info; -typedef int (*rate_convert_method)(struct feed_rate_info *, - uint32_t, uint32_t, int16_t *); +typedef uint32_t (*feed_rate_converter)(struct feed_rate_info *, uint8_t *, + uint32_t); + +struct feed_rate_info { + uint32_t src, dst; /* rounded source / destination rates */ + uint32_t rsrc, rdst; /* original source / destination rates */ + uint32_t gx, gy; /* interpolation / decimation ratio */ + uint32_t alpha; /* interpolation distance */ + uint32_t pos, bpos; /* current sample / buffer positions */ + uint32_t bufsz; /* total buffer size limit */ + uint32_t bufsz_init; /* allocated buffer size */ + uint32_t channels; /* total channels */ + uint32_t bps; /* bytes-per-sample */ +#ifdef FEEDRATE_STRAY + uint32_t stray; /* stray bytes */ +#endif + uint8_t *buffer; + feed_rate_converter convert; +}; -static int -convert_stereo_up(struct feed_rate_info *info, - uint32_t src_ticks, uint32_t dst_ticks, int16_t *dst); +int feeder_rate_min = FEEDRATE_RATEMIN; +int feeder_rate_max = FEEDRATE_RATEMAX; +int feeder_rate_round = FEEDRATE_ROUNDHZ; + +TUNABLE_INT("hw.snd.feeder_rate_min", &feeder_rate_min); +TUNABLE_INT("hw.snd.feeder_rate_max", &feeder_rate_max); +TUNABLE_INT("hw.snd.feeder_rate_round", &feeder_rate_round); static int -convert_stereo_down(struct feed_rate_info *info, - uint32_t src_ticks, uint32_t dst_ticks, int16_t *dst); +sysctl_hw_snd_feeder_rate_min(SYSCTL_HANDLER_ARGS) +{ + int err, val; -struct feed_rate_info { - uint32_t src, dst; /* source and destination rates */ - uint16_t buffer_ticks; /* number of available samples in buffer */ - uint16_t buffer_pos; /* next available sample in buffer */ - uint16_t rounds; /* maximum number of cycle rounds w buffer */ - uint16_t alpha; /* interpolation distance */ - uint16_t sscale; /* src clock scale */ - uint16_t dscale; /* dst clock scale */ - uint16_t mscale; /* scale factor to avoid divide per sample */ - uint16_t mroll; /* roll to again avoid divide per sample */ - uint16_t channels; /* 1 = mono, 2 = stereo */ + val = feeder_rate_min; + err = sysctl_handle_int(oidp, &val, 0, req); + if (err != 0 || req->newptr == NULL) + return (err); + if (RATE_FACTOR_SAFE(val) && val < feeder_rate_max) + feeder_rate_min = val; + else + err = EINVAL; + return (err); +} +SYSCTL_PROC(_hw_snd, OID_AUTO, feeder_rate_min, CTLTYPE_INT | CTLFLAG_RW, + 0, sizeof(int), sysctl_hw_snd_feeder_rate_min, "I", + "minimum allowable rate"); - rate_convert_method convert; - int16_t buffer[FEEDBUFSZ]; -}; +static int +sysctl_hw_snd_feeder_rate_max(SYSCTL_HANDLER_ARGS) +{ + int err, val; -#define bytes_per_sample 2 -#define src_ticks_per_cycle(info) (info->dscale * info->rounds) -#define dst_ticks_per_cycle(info) (info->sscale * info->rounds) -#define bytes_per_tick(info) (info->channels * bytes_per_sample) -#define src_bytes_per_cycle(info) \ - (src_ticks_per_cycle(info) * bytes_per_tick(info)) -#define dst_bytes_per_cycle(info) \ - (dst_ticks_per_cycle(info) * bytes_per_tick(info)) + val = feeder_rate_max; + err = sysctl_handle_int(oidp, &val, 0, req); + if (err != 0 || req->newptr == NULL) + return (err); + if (RATE_FACTOR_SAFE(val) && val > feeder_rate_min) + feeder_rate_max = val; + else + err = EINVAL; + return (err); +} +SYSCTL_PROC(_hw_snd, OID_AUTO, feeder_rate_max, CTLTYPE_INT | CTLFLAG_RW, + 0, sizeof(int), sysctl_hw_snd_feeder_rate_max, "I", + "maximum allowable rate"); -static uint32_t -gcd(uint32_t x, uint32_t y) +static int +sysctl_hw_snd_feeder_rate_round(SYSCTL_HANDLER_ARGS) { - uint32_t w; + int err, val; + + val = feeder_rate_round; + err = sysctl_handle_int(oidp, &val, 0, req); + if (err != 0 || req->newptr == NULL) + return (err); + if (val < FEEDRATE_ROUNDHZ_MIN || val > FEEDRATE_ROUNDHZ_MAX) + err = EINVAL; + else + feeder_rate_round = val - (val % FEEDRATE_ROUNDHZ); + return (err); +} +SYSCTL_PROC(_hw_snd, OID_AUTO, feeder_rate_round, CTLTYPE_INT | CTLFLAG_RW, + 0, sizeof(int), sysctl_hw_snd_feeder_rate_round, "I", + "sample rate converter rounding threshold"); + +#define FEEDER_RATE_CONVERT(FMTBIT, SIGN, SIGNS, ENDIAN, ENDIANS) \ +static uint32_t \ +feed_convert_##SIGNS##FMTBIT##ENDIANS##e(struct feed_rate_info *info, \ + uint8_t *dst, uint32_t max) \ +{ \ + uint32_t ret, smpsz, ch, pos, bpos, gx, gy, alpha, d1, d2; \ + intpcm_t x, y; \ + int i; \ + uint8_t *src, *sx, *sy; \ + \ + ret = 0; \ + alpha = info->alpha; \ + gx = info->gx; \ + gy = info->gy; \ + pos = info->pos; \ + bpos = info->bpos; \ + src = info->buffer + pos; \ + ch = info->channels; \ + smpsz = PCM_##FMTBIT##_BPS * ch; \ + for (;;) { \ + if (alpha < gx) { \ + alpha += gy; \ + pos += smpsz; \ + if (pos == bpos) \ + break; \ + src += smpsz; \ + } else { \ + alpha -= gx; \ + d1 = (alpha << PCM_FXSHIFT) / gy; \ + d2 = (1U << PCM_FXSHIFT) - d1; \ + sx = src - smpsz; \ + sy = src; \ + i = ch; \ + do { \ + x = PCM_READ_##SIGN##FMTBIT##_##ENDIAN##E(sx); \ + y = PCM_READ_##SIGN##FMTBIT##_##ENDIAN##E(sy); \ + x = ((INTPCM##FMTBIT##_T(x) * d1) + \ + (INTPCM##FMTBIT##_T(y) * d2)) >> \ + PCM_FXSHIFT; \ + PCM_WRITE_##SIGN##FMTBIT##_##ENDIAN##E(dst, x); \ + dst += PCM_##FMTBIT##_BPS; \ + sx += PCM_##FMTBIT##_BPS; \ + sy += PCM_##FMTBIT##_BPS; \ + ret += PCM_##FMTBIT##_BPS; \ + } while (--i != 0); \ + if (ret == max) \ + break; \ + } \ + } \ + info->alpha = alpha; \ + info->pos = pos; \ + return (ret); \ +} + +FEEDER_RATE_CONVERT(8, S, s, N, n) +FEEDER_RATE_CONVERT(16, S, s, L, l) +FEEDER_RATE_CONVERT(24, S, s, L, l) +FEEDER_RATE_CONVERT(32, S, s, L, l) +FEEDER_RATE_CONVERT(16, S, s, B, b) +FEEDER_RATE_CONVERT(24, S, s, B, b) +FEEDER_RATE_CONVERT(32, S, s, B, b) +FEEDER_RATE_CONVERT(8, U, u, N, n) +FEEDER_RATE_CONVERT(16, U, u, L, l) +FEEDER_RATE_CONVERT(24, U, u, L, l) +FEEDER_RATE_CONVERT(32, U, u, L, l) +FEEDER_RATE_CONVERT(16, U, u, B, b) +FEEDER_RATE_CONVERT(24, U, u, B, b) +FEEDER_RATE_CONVERT(32, U, u, B, b) + +static void +feed_speed_ratio(uint32_t src, uint32_t dst, uint32_t *gx, uint32_t *gy) +{ + uint32_t w, x = src, y = dst; + while (y != 0) { w = x % y; x = y; y = w; } - return x; + *gx = src / x; + *gy = dst / x; +} + +static void +feed_rate_reset(struct feed_rate_info *info) +{ + info->src = info->rsrc - (info->rsrc % + ((feeder_rate_round > 0) ? feeder_rate_round : 1)); + info->dst = info->rdst - (info->rdst % + ((feeder_rate_round > 0) ? feeder_rate_round : 1)); + info->gx = 1; + info->gy = 1; + info->alpha = 0; + info->channels = 1; + info->bps = PCM_8_BPS; + info->convert = NULL; + info->bufsz = info->bufsz_init; + info->pos = 1; + info->bpos = 2; +#ifdef FEEDRATE_STRAY + info->stray = 0; +#endif } static int feed_rate_setup(struct pcm_feeder *f) { struct feed_rate_info *info = f->data; - uint32_t mscale, mroll, l, r, g; - - /* Beat sample rates down by greatest common divisor */ - g = gcd(info->src, info->dst); - info->sscale = info->dst / g; - info->dscale = info->src / g; - - info->alpha = 0; - info->buffer_ticks = 0; - info->buffer_pos = 0; - - /* Pick suitable conversion routine */ - if (info->src > info->dst) { - info->convert = convert_stereo_down; - } else { - info->convert = convert_stereo_up; + static const struct { + uint32_t format; /* pcm / audio format */ + uint32_t bps; /* bytes-per-sample, regardless of + total channels */ + feed_rate_converter convert; + } convtbl[] = { + { AFMT_S8, PCM_8_BPS, feed_convert_s8ne }, + { AFMT_S16_LE, PCM_16_BPS, feed_convert_s16le }, + { AFMT_S24_LE, PCM_24_BPS, feed_convert_s24le }, + { AFMT_S32_LE, PCM_32_BPS, feed_convert_s32le }, + { AFMT_S16_BE, PCM_16_BPS, feed_convert_s16be }, + { AFMT_S24_BE, PCM_24_BPS, feed_convert_s24be }, + { AFMT_S32_BE, PCM_32_BPS, feed_convert_s32be }, + { AFMT_U8, PCM_8_BPS, feed_convert_u8ne }, + { AFMT_U16_LE, PCM_16_BPS, feed_convert_u16le }, + { AFMT_U24_LE, PCM_24_BPS, feed_convert_u24le }, + { AFMT_U32_LE, PCM_32_BPS, feed_convert_u32le }, + { AFMT_U16_BE, PCM_16_BPS, feed_convert_u16be }, + { AFMT_U24_BE, PCM_24_BPS, feed_convert_u24be }, + { AFMT_U32_BE, PCM_32_BPS, feed_convert_u32be }, + { 0, 0, NULL }, + }; + uint32_t i; + + feed_rate_reset(info); + + if (info->src != info->dst) + feed_speed_ratio(info->src, info->dst, &info->gx, &info->gy); + + if (!(RATE_FACTOR_SAFE(info->gx) && RATE_FACTOR_SAFE(info->gy))) + return (-1); + + for (i = 0; i < sizeof(convtbl) / sizeof(convtbl[0]); i++) { + if (convtbl[i].format == 0) + return (-1); + if ((f->desc->out & ~AFMT_STEREO) == convtbl[i].format) { + info->bps = convtbl[i].bps; + info->convert = convtbl[i].convert; + break; + } } /* - * Determine number of conversion rounds that will fit into - * buffer. NB Must set info->rounds to one before using - * src_ticks_per_cycle here since it used by src_ticks_per_cycle. + * No need to interpolate/decimate, just do plain copy. */ - info->rounds = 1; - r = (FEEDBUFSZ - bytes_per_tick(info)) / - (src_ticks_per_cycle(info) * bytes_per_tick(info)); - if (r == 0) { - RATE_TRACE("Insufficient buffer space for conversion %d -> %d " - "(%d < %d)\n", info->src, info->dst, FEEDBUFSZ, - src_ticks_per_cycle(info) * bytes_per_tick(info)); - return -1; - } - info->rounds = r; - - /* - * Find scale and roll combination that allows us to trade - * costly divide operations in the main loop for multiply-rolls. - */ - for (l = 96; l >= MINGAIN; l -= 3) { - for (mroll = 0; mroll < 16; mroll ++) { - mscale = (1 << mroll) / info->sscale; - - r = (mscale * info->sscale * 100) >> mroll; - if (r > l && r <= 100) { - info->mscale = mscale; - info->mroll = mroll; - RATE_TRACE("Converting %d to %d with " - "mscale = %d and mroll = %d " - "(gain = %d / 100)\n", - info->src, info->dst, - info->mscale, info->mroll, r); - return 0; - } - } - } - - RATE_TRACE("Failed to find a converter within %d%% gain for " - "%d to %d.\n", l, info->src, info->dst); + if (info->gx == info->gy) + info->convert = NULL; + + info->channels = (f->desc->out & AFMT_STEREO) ? 2 : 1; + info->pos = info->bps * info->channels; + info->bpos = info->pos << 1; + info->bufsz -= info->bufsz % info->pos; + + memset(info->buffer, sndbuf_zerodata(f->desc->out), info->bpos); + + RATE_TRACE("%s: %u (%u) -> %u (%u) [%u/%u] , " + "format=0x%08x, channels=%u, bufsz=%u\n", + __func__, info->src, info->rsrc, info->dst, info->rdst, + info->gx, info->gy, f->desc->out, info->channels, + info->bufsz - info->pos); - return -2; + return (0); } static int -feed_rate_set(struct pcm_feeder *f, int what, int value) +feed_rate_set(struct pcm_feeder *f, int what, int32_t value) { struct feed_rate_info *info = f->data; - int rvalue; - - if (value < RATEMIN || value > RATEMAX) { - return -1; - } - - rvalue = (value / ROUNDHZ) * ROUNDHZ; - if (value - rvalue > ROUNDHZ / 2) { - rvalue += ROUNDHZ; - } - - switch(what) { + + if (value < feeder_rate_min || value > feeder_rate_max) + return (-1); + + switch (what) { case FEEDRATE_SRC: - info->src = rvalue; + info->rsrc = value; break; case FEEDRATE_DST: - info->dst = rvalue; + info->rdst = value; break; default: - return -1; + return (-1); } - - return feed_rate_setup(f); + return (feed_rate_setup(f)); } static int @@ -233,15 +380,15 @@ { struct feed_rate_info *info = f->data; - switch(what) { + switch (what) { case FEEDRATE_SRC: - return info->src; + return (info->rsrc); case FEEDRATE_DST: - return info->dst; + return (info->rdst); default: - return -1; + return (-1); } - return -1; + return (-1); } static int @@ -249,15 +396,26 @@ { struct feed_rate_info *info; + if (f->desc->out != f->desc->in) + return (EINVAL); + info = malloc(sizeof(*info), M_RATEFEEDER, M_NOWAIT | M_ZERO); if (info == NULL) - return ENOMEM; - info->src = DSP_DEFAULT_SPEED; - info->dst = DSP_DEFAULT_SPEED; - info->channels = 2; - + return (ENOMEM); + /* + * bufsz = sample from last cycle + conversion space + */ + info->bufsz_init = 8 + feeder_buffersize; + info->buffer = malloc(info->bufsz_init, M_RATEFEEDER, + M_NOWAIT | M_ZERO); + if (info->buffer == NULL) { + free(info, M_RATEFEEDER); + return (ENOMEM); + } + info->rsrc = DSP_DEFAULT_SPEED; + info->rdst = DSP_DEFAULT_SPEED; f->data = info; - return 0; + return (feed_rate_setup(f)); } static int @@ -265,225 +423,220 @@ { struct feed_rate_info *info = f->data; - if (info) { + if (info != NULL) { + if (info->buffer != NULL) + free(info->buffer, M_RATEFEEDER); free(info, M_RATEFEEDER); } f->data = NULL; - return 0; + return (0); } static int -convert_stereo_up(struct feed_rate_info *info, - uint32_t src_ticks, - uint32_t dst_ticks, - int16_t *dst) -{ - uint32_t max_dst_ticks; - int32_t alpha, dalpha, malpha, mroll, sp, dp, se, de, x, o; - int16_t *src; - - sp = info->buffer_pos * 2; - se = sp + src_ticks * 2; - - src = info->buffer; - alpha = info->alpha * info->mscale; - dalpha = info->dscale * info->mscale; /* Alpha increment */ - malpha = info->sscale * info->mscale; /* Maximum allowed alpha value */ - mroll = info->mroll; +feed_rate(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, + uint32_t count, void *source) +{ + struct feed_rate_info *info = f->data; + uint32_t i, smpsz; + int32_t fetch, slot; - /* - * For efficiency the main conversion loop should only depend on - * one variable. We use the state to work out the maximum number - * of output samples that are available and eliminate the checking of - * sp from the loop. - */ - max_dst_ticks = src_ticks * info->dst / info->src - alpha / dalpha; - if (max_dst_ticks < dst_ticks) { - dst_ticks = max_dst_ticks; - } + if (info->convert == NULL) + return (FEEDER_FEED(f->source, c, b, count, source)); - dp = 0; - de = dst_ticks * 2; /* - * Unrolling this loop manually does not help much here because - * of the alpha, malpha comparison. + * This loop has been optimized to generalize both up / down + * sampling without causing missing samples or excessive buffer + * feeding. The tricky part is to calculate *precise* (slot) value + * needed for the entire conversion space since we are bound to + * return and fill up the buffer according to the requested 'count'. + * Too much feeding will cause the extra buffer stay within temporary + * circular buffer forever and always manifest itself as a truncated + * sound during end of playback / recording. Too few, and we end up + * with possible underruns and waste of cpu cycles. + * + * 'Stray' management exist to combat with possible unaligned + * buffering by the caller. */ - while (dp < de) { - o = malpha - alpha; - x = alpha * src[sp + 2] + o * src[sp]; - dst[dp++] = x >> mroll; - x = alpha * src[sp + 3] + o * src[sp + 1]; - dst[dp++] = x >> mroll; - alpha += dalpha; - if (alpha >= malpha) { - alpha -= malpha; - sp += 2; - } - } - RATE_ASSERT(sp <= se, ("%s: Source overrun\n", __func__)); - - info->buffer_pos = sp / info->channels; - info->alpha = alpha / info->mscale; - - return dp / info->channels; -} - -static int -convert_stereo_down(struct feed_rate_info *info, - uint32_t src_ticks, - uint32_t dst_ticks, - int16_t *dst) -{ - int32_t alpha, dalpha, malpha, mroll, sp, dp, se, de, x, o, m, - mdalpha, mstep; - int16_t *src; - - sp = info->buffer_pos * 2; - se = sp + src_ticks * 2; - - src = info->buffer; - alpha = info->alpha * info->mscale; - dalpha = info->dscale * info->mscale; /* Alpha increment */ - malpha = info->sscale * info->mscale; /* Maximum allowed alpha value */ - mroll = info->mroll; - - dp = 0; - de = dst_ticks * 2; - - m = dalpha / malpha; - mstep = m * 2; - mdalpha = dalpha - m * malpha; - + smpsz = info->bps * info->channels; + RATE_TEST(count >= smpsz && (count % smpsz) == 0, + ("%s: Count size not sample integral (%d)\n", __func__, count)); + if (count < smpsz) + return (0); + count -= count % smpsz; /* - * TODO: eliminate sp or dp from this loop comparison for a few - * extra % performance. + * This slot count formula will stay here for the next million years + * to come. This is the key of our circular buffering precision. */ - while (sp < se && dp < de) { - o = malpha - alpha; - x = alpha * src[sp + 2] + o * src[sp]; - dst[dp++] = x >> mroll; - x = alpha * src[sp + 3] + o * src[sp + 1]; - dst[dp++] = x >> mroll; - - alpha += mdalpha; - sp += mstep; - if (alpha >= malpha) { - alpha -= malpha; - sp += 2; - } - } - - info->buffer_pos = sp / 2; - info->alpha = alpha / info->mscale; - - RATE_ASSERT(info->buffer_pos <= info->buffer_ticks, - ("%s: Source overrun\n", __func__)); - - return dp / 2; -} - -static int -feed_rate(struct pcm_feeder *f, - struct pcm_channel *c, - uint8_t *b, - uint32_t count, - void *source) -{ - struct feed_rate_info *info = f->data; - - uint32_t done, s_ticks, d_ticks; - done = 0; - - RATE_ASSERT(info->channels == 2, - ("%s: channels (%d) != 2", __func__, info->channels)); - - while (done < count) { - /* Slurp in more data if input buffer is not full */ - while (info->buffer_ticks < src_ticks_per_cycle(info)) { - uint8_t *u8b; - int fetch; - fetch = src_bytes_per_cycle(info) - - info->buffer_ticks * bytes_per_tick(info); - u8b = (uint8_t*)info->buffer + - (info->buffer_ticks + 1) * - bytes_per_tick(info); - fetch = FEEDER_FEED(f->source, c, u8b, fetch, source); - RATE_ASSERT(fetch % bytes_per_tick(info) == 0, - ("%s: fetched unaligned bytes (%d)", - __func__, fetch)); - info->buffer_ticks += fetch / bytes_per_tick(info); - RATE_ASSERT(src_ticks_per_cycle(info) >= - info->buffer_ticks, - ("%s: buffer overfilled (%d > %d).", - __func__, info->buffer_ticks, - src_ticks_per_cycle(info))); + slot = (((info->gx * (count / smpsz)) + info->gy - info->alpha - 1) / + info->gy) * smpsz; + RATE_TEST((slot % smpsz) == 0, + ("%s: Slot count not sample integral (%d)\n", __func__, slot)); +#ifdef FEEDRATE_STRAY + RATE_TEST(info->stray == 0, ("%s: [1] Stray bytes: %u\n", __func__, + info->stray)); +#endif + if (info->pos != smpsz && info->bpos - info->pos == smpsz && + info->bpos + slot > info->bufsz) { + /* + * Copy last unit sample and its previous to + * beginning of buffer. + */ + bcopy(info->buffer + info->pos - smpsz, info->buffer, + smpsz << 1); + info->pos = smpsz; + info->bpos = smpsz << 1; + } + RATE_ASSERT(slot >= 0, ("%s: Negative Slot: %d\n", __func__, slot)); + i = 0; + for (;;) { + for (;;) { + fetch = info->bufsz - info->bpos; +#ifdef FEEDRATE_STRAY + fetch -= info->stray; +#endif + RATE_ASSERT(fetch >= 0, + ("%s: [1] Buffer overrun: %d > %d\n", __func__, + info->bpos, info->bufsz)); + if (slot < fetch) + fetch = slot; +#ifdef FEEDRATE_STRAY + if (fetch < 1) +#else + if (fetch < smpsz) +#endif + break; + RATE_ASSERT((int)(info->bpos +#ifdef FEEDRATE_STRAY + - info->stray +#endif + ) >= 0 && + (info->bpos - info->stray) < info->bufsz, + ("%s: DANGER - BUFFER OVERRUN! bufsz=%d, pos=%d\n", + __func__, info->bufsz, info->bpos +#ifdef FEEDRATE_STRAY + - info->stray +#endif + )); + fetch = FEEDER_FEED(f->source, c, + info->buffer + info->bpos +#ifdef FEEDRATE_STRAY + - info->stray +#endif + , fetch, source); +#ifdef FEEDRATE_STRAY + info->stray = 0; if (fetch == 0) +#else + if (fetch < smpsz) +#endif + break; + RATE_TEST((fetch % smpsz) == 0, + ("%s: Fetch size not sample integral (%d)\n", + __func__, fetch)); +#ifdef FEEDRATE_STRAY + info->stray += fetch % smpsz; + RATE_TEST(info->stray == 0, + ("%s: Stray bytes detected (%d)\n", __func__, + info->stray)); +#endif + fetch -= fetch % smpsz; + info->bpos += fetch; + slot -= fetch; + RATE_ASSERT(slot >= 0, ("%s: Negative Slot: %d\n", + __func__, slot)); + if (slot == 0 || info->bpos == info->bufsz) break; } - - /* Find amount of input buffer data that should be processed */ - d_ticks = (count - done) / bytes_per_tick(info); - s_ticks = info->buffer_ticks - info->buffer_pos; - if (info->buffer_ticks != src_ticks_per_cycle(info)) { - if (s_ticks > 8) - s_ticks -= 8; - else - s_ticks = 0; - } - - d_ticks = info->convert(info, s_ticks, d_ticks, - (int16_t*)(b + done)); - if (d_ticks == 0) + if (info->pos == info->bpos) { + RATE_TEST(info->pos == smpsz, + ("%s: EOF while in progress\n", __func__)); break; - done += d_ticks * bytes_per_tick(info); - - RATE_ASSERT(info->buffer_pos <= info->buffer_ticks, - ("%s: buffer_ticks too big\n", __func__)); - RATE_ASSERT(info->buffer_ticks <= src_ticks_per_cycle(info), - ("too many ticks %d / %d\n", - info->buffer_ticks, src_ticks_per_cycle(info))); - RATE_TRACE("%s: ticks %5d / %d pos %d\n", __func__, - info->buffer_ticks, src_ticks_per_cycle(info), - info->buffer_pos); - - if (src_ticks_per_cycle(info) <= info->buffer_pos) { - /* End of cycle reached, copy last samples to start */ - uint8_t *u8b; - u8b = (uint8_t*)info->buffer; - bcopy(u8b + src_bytes_per_cycle(info), u8b, - bytes_per_tick(info)); - - RATE_ASSERT(info->alpha == 0, - ("%s: completed cycle with " - "alpha non-zero", __func__, info->alpha)); - - info->buffer_pos = 0; - info->buffer_ticks = 0; } + RATE_ASSERT(info->pos <= info->bpos, + ("%s: [2] Buffer overrun: %d > %d\n", __func__, info->pos, + info->bpos)); + RATE_ASSERT(info->pos < info->bpos, + ("%s: Zero buffer!\n", __func__)); + RATE_ASSERT(((info->bpos - info->pos) % smpsz) == 0, + ("%s: Buffer not sample integral (%d)\n", __func__, + info->bpos - info->pos)); + i += info->convert(info, b + i, count - i); + RATE_ASSERT(info->pos <= info->bpos, + ("%s: [3] Buffer overrun: %d > %d\n", __func__, info->pos, + info->bpos)); + if (info->pos == info->bpos) { + /* + * End of buffer cycle. Copy last unit sample + * to beginning of buffer so next cycle can + * interpolate using it. + */ +#ifdef FEEDRATE_STRAY + RATE_TEST(info->stray == 0, + ("%s: [2] Stray bytes: %u\n", __func__, + info->stray)); +#endif + bcopy(info->buffer + info->pos - smpsz, info->buffer, + smpsz); + info->bpos = smpsz; + info->pos = smpsz; + } + if (i == count) + break; } - - RATE_ASSERT(count >= done, - ("%s: generated too many bytes of data (%d > %d).", - __func__, done, count)); - if (done != count) { - RATE_TRACE("Only did %d of %d\n", done, count); - } + RATE_TEST((slot == 0 && count == i) || (slot > 0 && count > i && + info->pos == info->bpos && info->pos == smpsz), + ("%s: Inconsistent slot/count! " + "Count Expect: %u , Got: %u, Slot Left: %d\n", __func__, count, i, + slot)); + +#ifdef FEEDRATE_STRAY + RATE_TEST(info->stray == 0, ("%s: [3] Stray bytes: %u\n", __func__, + info->stray)); +#endif - return done; + return (i); } static struct pcm_feederdesc feeder_rate_desc[] = { + {FEEDER_RATE, AFMT_S8, AFMT_S8, 0}, + {FEEDER_RATE, AFMT_S16_LE, AFMT_S16_LE, 0}, + {FEEDER_RATE, AFMT_S24_LE, AFMT_S24_LE, 0}, + {FEEDER_RATE, AFMT_S32_LE, AFMT_S32_LE, 0}, + {FEEDER_RATE, AFMT_S16_BE, AFMT_S16_BE, 0}, + {FEEDER_RATE, AFMT_S24_BE, AFMT_S24_BE, 0}, + {FEEDER_RATE, AFMT_S32_BE, AFMT_S32_BE, 0}, + {FEEDER_RATE, AFMT_S8 | AFMT_STEREO, AFMT_S8 | AFMT_STEREO, 0}, {FEEDER_RATE, AFMT_S16_LE | AFMT_STEREO, AFMT_S16_LE | AFMT_STEREO, 0}, + {FEEDER_RATE, AFMT_S24_LE | AFMT_STEREO, AFMT_S24_LE | AFMT_STEREO, 0}, + {FEEDER_RATE, AFMT_S32_LE | AFMT_STEREO, AFMT_S32_LE | AFMT_STEREO, 0}, + {FEEDER_RATE, AFMT_S16_BE | AFMT_STEREO, AFMT_S16_BE | AFMT_STEREO, 0}, + {FEEDER_RATE, AFMT_S24_BE | AFMT_STEREO, AFMT_S24_BE | AFMT_STEREO, 0}, + {FEEDER_RATE, AFMT_S32_BE | AFMT_STEREO, AFMT_S32_BE | AFMT_STEREO, 0}, + {FEEDER_RATE, AFMT_U8, AFMT_U8, 0}, + {FEEDER_RATE, AFMT_U16_LE, AFMT_U16_LE, 0}, + {FEEDER_RATE, AFMT_U24_LE, AFMT_U24_LE, 0}, + {FEEDER_RATE, AFMT_U32_LE, AFMT_U32_LE, 0}, + {FEEDER_RATE, AFMT_U16_BE, AFMT_U16_BE, 0}, + {FEEDER_RATE, AFMT_U24_BE, AFMT_U24_BE, 0}, + {FEEDER_RATE, AFMT_U32_BE, AFMT_U32_BE, 0}, + {FEEDER_RATE, AFMT_U8 | AFMT_STEREO, AFMT_U8 | AFMT_STEREO, 0}, + {FEEDER_RATE, AFMT_U16_LE | AFMT_STEREO, AFMT_U16_LE | AFMT_STEREO, 0}, + {FEEDER_RATE, AFMT_U24_LE | AFMT_STEREO, AFMT_U24_LE | AFMT_STEREO, 0}, + {FEEDER_RATE, AFMT_U32_LE | AFMT_STEREO, AFMT_U32_LE | AFMT_STEREO, 0}, + {FEEDER_RATE, AFMT_U16_BE | AFMT_STEREO, AFMT_U16_BE | AFMT_STEREO, 0}, + {FEEDER_RATE, AFMT_U24_BE | AFMT_STEREO, AFMT_U24_BE | AFMT_STEREO, 0}, + {FEEDER_RATE, AFMT_U32_BE | AFMT_STEREO, AFMT_U32_BE | AFMT_STEREO, 0}, {0, 0, 0, 0}, }; + static kobj_method_t feeder_rate_methods[] = { - KOBJMETHOD(feeder_init, feed_rate_init), - KOBJMETHOD(feeder_free, feed_rate_free), - KOBJMETHOD(feeder_set, feed_rate_set), - KOBJMETHOD(feeder_get, feed_rate_get), - KOBJMETHOD(feeder_feed, feed_rate), + KOBJMETHOD(feeder_init, feed_rate_init), + KOBJMETHOD(feeder_free, feed_rate_free), + KOBJMETHOD(feeder_set, feed_rate_set), + KOBJMETHOD(feeder_get, feed_rate_get), + KOBJMETHOD(feeder_feed, feed_rate), {0, 0} }; -FEEDER_DECLARE(feeder_rate, 2, NULL); +FEEDER_DECLARE(feeder_rate, 2, NULL); --- sys/dev/sound/pcm/feeder_volume.c.orig Thu Jan 1 07:30:00 1970 +++ sys/dev/sound/pcm/feeder_volume.c Thu Jul 12 12:04:19 2007 @@ -0,0 +1,240 @@ +/*- + * 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. + */ + +/* feeder_volume, a long 'Lost Technology' rather than a new feature. */ + +#include +#include +#include "feeder_if.h" + +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pcm/feeder_volume.c,v 1.6 2007/06/16 20:36:39 ariff Exp $"); + +typedef int (*feed_volume_filter)(uint8_t *, int *, int, int); + +#define FVOL_CALC_8(s, v) SND_VOL_CALC_SAMPLE((int32_t)(s), v) +#define FVOL_CALC_16(s, v) SND_VOL_CALC_SAMPLE((int32_t)(s), v) +#define FVOL_CALC_24(s, v) SND_VOL_CALC_SAMPLE((int64_t)(s), v) +#define FVOL_CALC_32(s, v) SND_VOL_CALC_SAMPLE((int64_t)(s), v) + +#define FEEDER_VOLUME_FILTER(FMTBIT, SIGN, SIGNS, ENDIAN, ENDIANS) \ +static int \ +feed_volume_filter_##SIGNS##FMTBIT##ENDIANS##e(uint8_t *b, int *vol, \ + int channels, int count) \ +{ \ + intpcm##FMTBIT##_t k; \ + intpcm_t j; \ + int i; \ + \ + i = count; \ + b += i; \ + \ + do { \ + b -= PCM_##FMTBIT##_BPS; \ + i -= PCM_##FMTBIT##_BPS; \ + j = PCM_READ_##SIGN##FMTBIT##_##ENDIAN##E(b); \ + k = FVOL_CALC_##FMTBIT(j, \ + vol[(i / PCM_##FMTBIT##_BPS) % channels]); \ + j = PCM_CLAMP_##SIGN##FMTBIT(k); \ + _PCM_WRITE_##SIGN##FMTBIT##_##ENDIAN##E(b, j); \ + } while (i != 0); \ + \ + return (count); \ +} + +FEEDER_VOLUME_FILTER(8, S, s, N, n) +FEEDER_VOLUME_FILTER(16, S, s, L, l) +FEEDER_VOLUME_FILTER(24, S, s, L, l) +FEEDER_VOLUME_FILTER(32, S, s, L, l) +FEEDER_VOLUME_FILTER(16, S, s, B, b) +FEEDER_VOLUME_FILTER(24, S, s, B, b) +FEEDER_VOLUME_FILTER(32, S, s, B, b) +FEEDER_VOLUME_FILTER(8, U, u, N, n) +FEEDER_VOLUME_FILTER(16, U, u, L, l) +FEEDER_VOLUME_FILTER(24, U, u, L, l) +FEEDER_VOLUME_FILTER(32, U, u, L, l) +FEEDER_VOLUME_FILTER(16, U, u, B, b) +FEEDER_VOLUME_FILTER(24, U, u, B, b) +FEEDER_VOLUME_FILTER(32, U, u, B, b) + +struct feed_volume_info { + uint32_t format; + int bps; + feed_volume_filter filter; +}; + +static struct feed_volume_info feed_volume_tbl[] = { + { AFMT_S8, PCM_8_BPS, feed_volume_filter_s8ne }, + { AFMT_S16_LE, PCM_16_BPS, feed_volume_filter_s16le }, + { AFMT_S24_LE, PCM_24_BPS, feed_volume_filter_s24le }, + { AFMT_S32_LE, PCM_32_BPS, feed_volume_filter_s32le }, + { AFMT_S16_BE, PCM_16_BPS, feed_volume_filter_s16be }, + { AFMT_S24_BE, PCM_24_BPS, feed_volume_filter_s24be }, + { AFMT_S32_BE, PCM_32_BPS, feed_volume_filter_s32be }, + { AFMT_U8, PCM_8_BPS, feed_volume_filter_u8ne }, + { AFMT_U16_LE, PCM_16_BPS, feed_volume_filter_u16le }, + { AFMT_U24_LE, PCM_24_BPS, feed_volume_filter_u24le }, + { AFMT_U32_LE, PCM_32_BPS, feed_volume_filter_u32le }, + { AFMT_U16_BE, PCM_16_BPS, feed_volume_filter_u16be }, + { AFMT_U24_BE, PCM_24_BPS, feed_volume_filter_u24be }, + { AFMT_U32_BE, PCM_32_BPS, feed_volume_filter_u32be }, +}; + +#define FVOL_DATA(vc, i, c) ((intptr_t)((((vc) & 0x3f) << 12) | \ + (((i) & 0x3f) << 6) | ((c) & 0x3f))) +#define FVOL_CLASS(m) (((m) >> 12) & 0x3f) +#define FVOL_INFOIDX(m) (((m) >> 6) & 0x3f) +#define FVOL_CHANNELS(m) ((m) & 0x3f) + +static int +feed_volume_setup(struct pcm_feeder *f, int vc) +{ + int i, channels; + + if (f->desc->in != f->desc->out || vc < SND_VOL_C_BEGIN || + vc > SND_VOL_C_END) + return (EINVAL); + + /* For now, this is mandatory! */ + if (!(f->desc->out & AFMT_STEREO)) + return (EINVAL); + + channels = 2; + + /* XXX Future interleave multichannel check yada yada.. */ + if (channels < SND_CHN_MIN || channels > SND_CHN_MAX) + return (EINVAL); + + for (i = 0; i < (sizeof(feed_volume_tbl) / sizeof(feed_volume_tbl[0])); + i++) { + if ((f->desc->out & ~AFMT_STEREO) == + feed_volume_tbl[i].format) { + f->data = (void *)FVOL_DATA(vc, i, channels); + return (0); + } + } + + return (EINVAL); +} + +static int +feed_volume_init(struct pcm_feeder *f) +{ + return (feed_volume_setup(f, SND_VOL_C_PCM)); +} + +static int +feed_volume_set(struct pcm_feeder *f, int what, int value) +{ + switch (what) { + case FEEDVOL_CLASS: + return (feed_volume_setup(f, value)); + break; + default: + break; + } + + return (EINVAL); +} + +static int +feed_volume_get(struct pcm_feeder *f, int what) +{ + switch (what) { + case FEEDVOL_CLASS: + return (FVOL_CLASS((intptr_t)f->data)); + break; + default: + break; + } + + return (-1); +} + +static int +feed_volume(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, + uint32_t count, void *source) +{ + struct feed_volume_info *info; + int vol[SND_CHN_MAX]; + int j, k, v, smpsz, channels; + snd_volume_class_t vcv; + + channels = FVOL_CHANNELS((intptr_t)f->data); + vcv = SND_VOL_C_VAL(FVOL_CLASS((intptr_t)f->data)); + j = channels - 1; + k = 0; + + do { + KASSERT(c->matrix[j] != SND_CHN_T_MAX, + ("%s(): invalid matrix j=%d", __func__, j)); + v = c->volume[vcv][c->matrix[j]]; + if (v != SND_VOL_FLAT) + k = 1; + vol[j] = v; + } while (j-- != 0); + + if (k == 0) + return (FEEDER_FEED(f->source, c, b, count, source)); + + info = &feed_volume_tbl[FVOL_INFOIDX((intptr_t)f->data)]; + smpsz = info->bps * channels; + if (count < smpsz) + return (0); + + k = FEEDER_FEED(f->source, c, b, count - (count % smpsz), source); + if (k < smpsz) + return (0); + + k -= k % smpsz; + + return (info->filter(b, vol, channels, k)); +} + +static struct pcm_feederdesc feeder_volume_desc[] = { + {FEEDER_VOLUME, AFMT_S8 | AFMT_STEREO, AFMT_S8 | AFMT_STEREO, 0}, + {FEEDER_VOLUME, AFMT_S16_LE | AFMT_STEREO, AFMT_S16_LE | AFMT_STEREO, 0}, + {FEEDER_VOLUME, AFMT_S24_LE | AFMT_STEREO, AFMT_S24_LE | AFMT_STEREO, 0}, + {FEEDER_VOLUME, AFMT_S32_LE | AFMT_STEREO, AFMT_S32_LE | AFMT_STEREO, 0}, + {FEEDER_VOLUME, AFMT_S16_BE | AFMT_STEREO, AFMT_S16_BE | AFMT_STEREO, 0}, + {FEEDER_VOLUME, AFMT_S24_BE | AFMT_STEREO, AFMT_S24_BE | AFMT_STEREO, 0}, + {FEEDER_VOLUME, AFMT_S32_BE | AFMT_STEREO, AFMT_S32_BE | AFMT_STEREO, 0}, + {FEEDER_VOLUME, AFMT_U8 | AFMT_STEREO, AFMT_U8 | AFMT_STEREO, 0}, + {FEEDER_VOLUME, AFMT_U16_LE | AFMT_STEREO, AFMT_U16_LE | AFMT_STEREO, 0}, + {FEEDER_VOLUME, AFMT_U24_LE | AFMT_STEREO, AFMT_U24_LE | AFMT_STEREO, 0}, + {FEEDER_VOLUME, AFMT_U32_LE | AFMT_STEREO, AFMT_U32_LE | AFMT_STEREO, 0}, + {FEEDER_VOLUME, AFMT_U16_BE | AFMT_STEREO, AFMT_U16_BE | AFMT_STEREO, 0}, + {FEEDER_VOLUME, AFMT_U24_BE | AFMT_STEREO, AFMT_U24_BE | AFMT_STEREO, 0}, + {FEEDER_VOLUME, AFMT_U32_BE | AFMT_STEREO, AFMT_U32_BE | AFMT_STEREO, 0}, + {0, 0, 0, 0}, +}; +static kobj_method_t feeder_volume_methods[] = { + KOBJMETHOD(feeder_init, feed_volume_init), + KOBJMETHOD(feeder_set, feed_volume_set), + KOBJMETHOD(feeder_get, feed_volume_get), + KOBJMETHOD(feeder_feed, feed_volume), + {0, 0} +}; +FEEDER_DECLARE(feeder_volume, 2, NULL); --- sys/dev/sound/pcm/matrix.h.orig Thu Jan 1 07:30:00 1970 +++ sys/dev/sound/pcm/matrix.h Thu Jul 12 12:04:19 2007 @@ -0,0 +1,99 @@ +/*- + * 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$ + */ + +#ifndef _SND_MATRIX_H_ +#define _SND_MATRIX_H_ + +typedef enum { + SND_CHN_T_FL, /* Front Left */ + SND_CHN_T_FR, /* Front Right */ + SND_CHN_T_FC, /* Front Center */ + SND_CHN_T_LF, /* Low Frequency */ + SND_CHN_T_BL, /* Back Left */ + SND_CHN_T_BR, /* Back Right */ + SND_CHN_T_FLC, /* Front Left Center */ + SND_CHN_T_FRC, /* Front Right Center */ + SND_CHN_T_BC, /* Back Center */ + SND_CHN_T_SL, /* Side Left */ + SND_CHN_T_SR, /* Side Right */ + SND_CHN_T_TC, /* Top Center */ + SND_CHN_T_TFL, /* Top Front Left */ + SND_CHN_T_TFC, /* Top Front Center */ + SND_CHN_T_TFR, /* Top Front Right */ + SND_CHN_T_TBL, /* Top Back Left */ + SND_CHN_T_TBC, /* Top Back Center */ + SND_CHN_T_TBR, /* Top Back Right */ + SND_CHN_T_MAX, + SND_CHN_T_VOL_0DB = SND_CHN_T_MAX, + SND_CHN_T_VOL_MAX +} snd_channel_t; + +#define SND_CHN_T_BEGIN SND_CHN_T_FL +#define SND_CHN_T_END SND_CHN_T_FR +#define SND_CHN_T_STEP 1 + +#define SND_CHN_MIN 1 +#define SND_CHN_MAX 2 + +/* + * Multichannel interleaved volume matrix. Each calculated value relative + * to master and 0db will be stored in each CLASS + 1 as long as + * chn_setvolume_matrix() or the equivalent CHN_SETVOLUME() macros is + * used (see channel.c). + */ +typedef enum { + SND_VOL_C_MASTER, + SND_VOL_C_PCM, + SND_VOL_C_PCM_VAL, + SND_VOL_C_MAX +} snd_volume_class_t; + +#define SND_VOL_C_BEGIN SND_VOL_C_PCM +#define SND_VOL_C_END SND_VOL_C_PCM +#define SND_VOL_C_STEP 2 + +#define SND_VOL_C_VAL(x) ((x) + 1) + +#define SND_VOL_0DB_MIN 1 +#define SND_VOL_0DB_MAX 100 + +#define SND_VOL_0DB_MASTER 100 +#define SND_VOL_0DB_PCM 45 + +#define SND_VOL_RESOLUTION 8 +#define SND_VOL_FLAT (1 << SND_VOL_RESOLUTION) + +#define SND_VOL_CALC_SAMPLE(x, y) (((x) * (y)) >> SND_VOL_RESOLUTION) + +#define SND_VOL_CALC_VAL(x, y, z) \ + (((((x)[y][z] << SND_VOL_RESOLUTION) / \ + (x)[y][SND_CHN_T_VOL_0DB]) * \ + (x)[SND_VOL_C_MASTER][z]) / \ + (x)[SND_VOL_C_MASTER][SND_CHN_T_VOL_0DB]) \ + +#endif /* !_SND_MATRIX_H_ */ --- sys/dev/sound/pcm/mixer.c.orig Sun Jan 30 09:00:05 2005 +++ sys/dev/sound/pcm/mixer.c Thu Jul 12 12:04:19 2007 @@ -28,26 +28,43 @@ #include "mixer_if.h" -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pcm/mixer.c,v 1.40.2.1 2005/01/30 01:00:05 imp Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pcm/mixer.c,v 1.61 2007/06/16 03:37:28 ariff Exp $"); MALLOC_DEFINE(M_MIXER, "mixer", "mixer"); +static int mixer_bypass = 1; +TUNABLE_INT("hw.snd.vpc_mixer_bypass", &mixer_bypass); +SYSCTL_INT(_hw_snd, OID_AUTO, vpc_mixer_bypass, CTLFLAG_RW, + &mixer_bypass, 0, + "control channel pcm/rec volume, bypassing real mixer device"); + #define MIXER_NAMELEN 16 struct snd_mixer { KOBJ_FIELDS; - const char *type; void *devinfo; int busy; int hwvol_muted; int hwvol_mixer; int hwvol_step; + int type; + device_t dev; u_int32_t hwvol_mute_level; u_int32_t devs; u_int32_t recdevs; u_int32_t recsrc; u_int16_t level[32]; + u_int8_t parent[32]; + u_int32_t child[32]; + u_int8_t realdev[32]; char name[MIXER_NAMELEN]; struct mtx *lock; + oss_mixer_enuminfo enuminfo; + /** + * Counter is incremented when applications change any of this + * mixer's controls. A change in value indicates that persistent + * mixer applications should update their displays. + */ + int modify_counter; }; static u_int16_t snd_mixerdefaults[SOUND_MIXER_NRDEVICES] = { @@ -60,6 +77,7 @@ [SOUND_MIXER_LINE] = 75, [SOUND_MIXER_MIC] = 0, [SOUND_MIXER_CD] = 75, + [SOUND_MIXER_IGAIN] = 0, [SOUND_MIXER_LINE1] = 75, [SOUND_MIXER_VIDEO] = 75, [SOUND_MIXER_RECLEV] = 0, @@ -71,19 +89,23 @@ static d_open_t mixer_open; static d_close_t mixer_close; +static d_ioctl_t mixer_ioctl; static struct cdevsw mixer_cdevsw = { .d_version = D_VERSION, - .d_flags = D_NEEDGIANT, .d_open = mixer_open, .d_close = mixer_close, .d_ioctl = mixer_ioctl, .d_name = "mixer", - .d_maj = SND_CDEV_MAJOR, }; +/** + * Keeps a count of mixer devices; used only by OSSv4 SNDCTL_SYSINFO ioctl. + */ +int mixer_count = 0; + #ifdef USING_DEVFS -static eventhandler_tag mixer_ehtag; +static eventhandler_tag mixer_ehtag = NULL; #endif static struct cdev * @@ -110,23 +132,157 @@ } #endif +#define MIXER_SET_UNLOCK(x, y) do { \ + if ((y) != 0) \ + snd_mtxunlock((x)->lock); \ +} while(0) + +#define MIXER_SET_LOCK(x, y) do { \ + if ((y) != 0) \ + snd_mtxlock((x)->lock); \ +} while(0) + +static int +mixer_set_softpcmvol(struct snd_mixer *m, struct snddev_info *d, + unsigned left, unsigned right) +{ + struct pcm_channel *c; + int dropmtx, acquiremtx; + + if (!PCM_REGISTERED(d)) + return (EINVAL); + + if (mtx_owned(m->lock)) + dropmtx = 1; + else + dropmtx = 0; + + if (!(d->flags & SD_F_MPSAFE) || mtx_owned(d->lock) != 0) + acquiremtx = 0; + else + acquiremtx = 1; + + /* + * Be careful here. If we're coming from cdev ioctl, it is OK to + * not doing locking AT ALL (except on individual channel) since + * we've been heavily guarded by pcm cv, or if we're still + * under Giant influence. Since we also have mix_* calls, we cannot + * assume such protection and just do the lock as usuall. + */ + MIXER_SET_UNLOCK(m, dropmtx); + MIXER_SET_LOCK(d, acquiremtx); + + if (CHN_EMPTY(d, channels.pcm.busy)) { + CHN_FOREACH(c, d, channels.pcm) { + CHN_LOCK(c); + if (c->direction == PCMDIR_PLAY && + (c->feederflags & (1 << FEEDER_VOLUME))) { + CHN_SETVOLUME(c, + SND_VOL_C_MASTER, SND_CHN_T_FL, left); + CHN_SETVOLUME(c, + SND_VOL_C_MASTER, SND_CHN_T_FR, right); + } + CHN_UNLOCK(c); + } + } else { + CHN_FOREACH(c, d, channels.pcm.busy) { + CHN_LOCK(c); + if (c->direction == PCMDIR_PLAY && + (c->feederflags & (1 << FEEDER_VOLUME))) { + CHN_SETVOLUME(c, + SND_VOL_C_MASTER, SND_CHN_T_FL, left); + CHN_SETVOLUME(c, + SND_VOL_C_MASTER, SND_CHN_T_FR, right); + } + CHN_UNLOCK(c); + } + } + + MIXER_SET_UNLOCK(d, acquiremtx); + MIXER_SET_LOCK(m, dropmtx); + + return (0); +} + static int -mixer_set(struct snd_mixer *mixer, unsigned dev, unsigned lev) +mixer_set(struct snd_mixer *m, unsigned dev, unsigned lev) { - unsigned l, r; - int v; + struct snddev_info *d; + unsigned l, r, tl, tr; + u_int32_t parent = SOUND_MIXER_NONE, child = 0; + u_int32_t realdev; + int i, dropmtx; - if ((dev >= SOUND_MIXER_NRDEVICES) || (0 == (mixer->devs & (1 << dev)))) + if (m == NULL || dev >= SOUND_MIXER_NRDEVICES || + (0 == (m->devs & (1 << dev)))) return -1; l = min((lev & 0x00ff), 100); r = min(((lev & 0xff00) >> 8), 100); + realdev = m->realdev[dev]; - v = MIXER_SET(mixer, dev, l, r); - if (v < 0) + d = device_get_softc(m->dev); + if (d == NULL) return -1; - mixer->level[dev] = l | (r << 8); + /* It is safe to drop this mutex due to Giant. */ + if (!(d->flags & SD_F_MPSAFE) && mtx_owned(m->lock) != 0) + dropmtx = 1; + else + dropmtx = 0; + + MIXER_SET_UNLOCK(m, dropmtx); + + /* TODO: recursive handling */ + parent = m->parent[dev]; + if (parent >= SOUND_MIXER_NRDEVICES) + parent = SOUND_MIXER_NONE; + if (parent == SOUND_MIXER_NONE) + child = m->child[dev]; + + if (parent != SOUND_MIXER_NONE) { + tl = (l * (m->level[parent] & 0x00ff)) / 100; + tr = (r * ((m->level[parent] & 0xff00) >> 8)) / 100; + if (dev == SOUND_MIXER_PCM && (d->flags & SD_F_SOFTPCMVOL)) + (void)mixer_set_softpcmvol(m, d, tl, tr); + else if (realdev != SOUND_MIXER_NONE && + MIXER_SET(m, realdev, tl, tr) < 0) { + MIXER_SET_LOCK(m, dropmtx); + return -1; + } + } else if (child != 0) { + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { + if (!(child & (1 << i)) || m->parent[i] != dev) + continue; + realdev = m->realdev[i]; + tl = (l * (m->level[i] & 0x00ff)) / 100; + tr = (r * ((m->level[i] & 0xff00) >> 8)) / 100; + if (i == SOUND_MIXER_PCM && + (d->flags & SD_F_SOFTPCMVOL)) + (void)mixer_set_softpcmvol(m, d, tl, tr); + else if (realdev != SOUND_MIXER_NONE) + MIXER_SET(m, realdev, tl, tr); + } + realdev = m->realdev[dev]; + if (realdev != SOUND_MIXER_NONE && + MIXER_SET(m, realdev, l, r) < 0) { + MIXER_SET_LOCK(m, dropmtx); + return -1; + } + } else { + if (dev == SOUND_MIXER_PCM && (d->flags & SD_F_SOFTPCMVOL)) + (void)mixer_set_softpcmvol(m, d, l, r); + else if (realdev != SOUND_MIXER_NONE && + MIXER_SET(m, realdev, l, r) < 0) { + MIXER_SET_LOCK(m, dropmtx); + return -1; + } + } + + m->level[dev] = l | (r << 8); + + MIXER_SET_LOCK(m, dropmtx); + return 0; } @@ -135,16 +291,30 @@ { if ((dev < SOUND_MIXER_NRDEVICES) && (mixer->devs & (1 << dev))) return mixer->level[dev]; - else return -1; + else + return -1; } static int mixer_setrecsrc(struct snd_mixer *mixer, u_int32_t src) { + struct snddev_info *d; + int dropmtx; + + d = device_get_softc(mixer->dev); + if (d == NULL) + return -1; + if (!(d->flags & SD_F_MPSAFE) && mtx_owned(mixer->lock) != 0) + dropmtx = 1; + else + dropmtx = 0; src &= mixer->recdevs; if (src == 0) src = SOUND_MASK_MIC; + /* It is safe to drop this mutex due to Giant. */ + MIXER_SET_UNLOCK(mixer, dropmtx); mixer->recsrc = MIXER_SETRECSRC(mixer, src); + MIXER_SET_LOCK(mixer, dropmtx); return 0; } @@ -154,18 +324,219 @@ return mixer->recsrc; } +/** + * @brief Retrieve the route number of the current recording device + * + * OSSv4 assigns routing numbers to recording devices, unlike the previous + * API which relied on a fixed table of device numbers and names. This + * function returns the routing number of the device currently selected + * for recording. + * + * For now, this function is kind of a goofy compatibility stub atop the + * existing sound system. (For example, in theory, the old sound system + * allows multiple recording devices to be specified via a bitmask.) + * + * @param m mixer context container thing + * + * @retval 0 success + * @retval EIDRM no recording device found (generally not possible) + * @todo Ask about error code + */ +static int +mixer_get_recroute(struct snd_mixer *m, int *route) +{ + int i, cnt; + + cnt = 0; + + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { + /** @todo can user set a multi-device mask? (== or &?) */ + if ((1 << i) == m->recsrc) + break; + if ((1 << i) & m->recdevs) + ++cnt; + } + + if (i == SOUND_MIXER_NRDEVICES) + return EIDRM; + + *route = cnt; + return 0; +} + +/** + * @brief Select a device for recording + * + * This function sets a recording source based on a recording device's + * routing number. Said number is translated to an old school recdev + * mask and passed over mixer_setrecsrc. + * + * @param m mixer context container thing + * + * @retval 0 success(?) + * @retval EINVAL User specified an invalid device number + * @retval otherwise error from mixer_setrecsrc + */ +static int +mixer_set_recroute(struct snd_mixer *m, int route) +{ + int i, cnt, ret; + + ret = 0; + cnt = 0; + + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { + if ((1 << i) & m->recdevs) { + if (route == cnt) + break; + ++cnt; + } + } + + if (i == SOUND_MIXER_NRDEVICES) + ret = EINVAL; + else + ret = mixer_setrecsrc(m, (1 << i)); + + return ret; +} + void mix_setdevs(struct snd_mixer *m, u_int32_t v) { + struct snddev_info *d; + int i; + + if (m == NULL) + return; + + d = device_get_softc(m->dev); + if (d != NULL && (d->flags & SD_F_SOFTPCMVOL)) + v |= SOUND_MASK_PCM; + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { + if (m->parent[i] < SOUND_MIXER_NRDEVICES) + v |= 1 << m->parent[i]; + v |= m->child[i]; + } m->devs = v; } +/** + * @brief Record mask of available recording devices + * + * Calling functions are responsible for defining the mask of available + * recording devices. This function records that value in a structure + * used by the rest of the mixer code. + * + * This function also populates a structure used by the SNDCTL_DSP_*RECSRC* + * family of ioctls that are part of OSSV4. All recording device labels + * are concatenated in ascending order corresponding to their routing + * numbers. (Ex: a system might have 0 => 'vol', 1 => 'cd', 2 => 'line', + * etc.) For now, these labels are just the standard recording device + * names (cd, line1, etc.), but will eventually be fully dynamic and user + * controlled. + * + * @param m mixer device context container thing + * @param v mask of recording devices + */ void mix_setrecdevs(struct snd_mixer *m, u_int32_t v) { + oss_mixer_enuminfo *ei; + char *loc; + int i, nvalues, nwrote, nleft, ncopied; + + ei = &m->enuminfo; + + nvalues = 0; + nwrote = 0; + nleft = sizeof(ei->strings); + loc = ei->strings; + + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { + if ((1 << i) & v) { + ei->strindex[nvalues] = nwrote; + ncopied = strlcpy(loc, snd_mixernames[i], nleft) + 1; + /* strlcpy retval doesn't include terminator */ + + nwrote += ncopied; + nleft -= ncopied; + nvalues++; + + /* + * XXX I don't think this should ever be possible. + * Even with a move to dynamic device/channel names, + * each label is limited to ~16 characters, so that'd + * take a LOT to fill this buffer. + */ + if ((nleft <= 0) || (nvalues >= OSS_ENUM_MAXVALUE)) { + device_printf(m->dev, + "mix_setrecdevs: Not enough room to store device names--please file a bug report.\n"); + device_printf(m->dev, + "mix_setrecdevs: Please include details about your sound hardware, OS version, etc.\n"); + break; + } + + loc = &ei->strings[nwrote]; + } + } + + /* + * NB: The SNDCTL_DSP_GET_RECSRC_NAMES ioctl ignores the dev + * and ctrl fields. + */ + ei->nvalues = nvalues; m->recdevs = v; } +void +mix_setparentchild(struct snd_mixer *m, u_int32_t parent, u_int32_t childs) +{ + u_int32_t mask = 0; + int i; + + if (m == NULL || parent >= SOUND_MIXER_NRDEVICES) + return; + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { + if (i == parent) + continue; + if (childs & (1 << i)) { + mask |= 1 << i; + if (m->parent[i] < SOUND_MIXER_NRDEVICES) + m->child[m->parent[i]] &= ~(1 << i); + m->parent[i] = parent; + m->child[i] = 0; + } + } + mask &= ~(1 << parent); + m->child[parent] = mask; +} + +void +mix_setrealdev(struct snd_mixer *m, u_int32_t dev, u_int32_t realdev) +{ + if (m == NULL || dev >= SOUND_MIXER_NRDEVICES || + !(realdev == SOUND_MIXER_NONE || realdev < SOUND_MIXER_NRDEVICES)) + return; + m->realdev[dev] = realdev; +} + +u_int32_t +mix_getparent(struct snd_mixer *m, u_int32_t dev) +{ + if (m == NULL || dev >= SOUND_MIXER_NRDEVICES) + return SOUND_MIXER_NONE; + return m->parent[dev]; +} + +u_int32_t +mix_getchild(struct snd_mixer *m, u_int32_t dev) +{ + if (m == NULL || dev >= SOUND_MIXER_NRDEVICES) + return 0; + return m->child[dev]; +} + u_int32_t mix_getdevs(struct snd_mixer *m) { @@ -184,6 +555,80 @@ return m->devinfo; } +static struct snd_mixer * +mixer_obj_create(device_t dev, kobj_class_t cls, void *devinfo, + int type, const char *desc) +{ + struct snd_mixer *m; + int i; + + KASSERT(dev != NULL && cls != NULL && devinfo != NULL, + ("%s(): NULL data dev=%p cls=%p devinfo=%p", + __func__, dev, cls, devinfo)); + KASSERT(type == MIXER_TYPE_PRIMARY || type == MIXER_TYPE_SECONDARY, + ("invalid mixer type=%d", type)); + + m = (struct snd_mixer *)kobj_create(cls, M_MIXER, M_WAITOK | M_ZERO); + snprintf(m->name, sizeof(m->name), "%s:mixer", + device_get_nameunit(dev)); + if (desc != NULL) { + strlcat(m->name, ":", sizeof(m->name)); + strlcat(m->name, desc, sizeof(m->name)); + } + m->lock = snd_mtxcreate(m->name, (type == MIXER_TYPE_PRIMARY) ? + "primary pcm mixer" : "secondary pcm mixer"); + m->type = type; + m->devinfo = devinfo; + m->busy = 0; + m->dev = dev; + for (i = 0; i < (sizeof(m->parent) / sizeof(m->parent[0])); i++) { + m->parent[i] = SOUND_MIXER_NONE; + m->child[i] = 0; + m->realdev[i] = i; + } + + if (MIXER_INIT(m)) { + snd_mtxlock(m->lock); + snd_mtxfree(m->lock); + kobj_delete((kobj_t)m, M_MIXER); + return (NULL); + } + + return (m); +} + +int +mixer_delete(struct snd_mixer *m) +{ + KASSERT(m != NULL, ("NULL snd_mixer")); + KASSERT(m->type == MIXER_TYPE_SECONDARY, + ("%s(): illegal mixer type=%d", __func__, m->type)); + + snd_mtxlock(m->lock); + + MIXER_UNINIT(m); + + snd_mtxfree(m->lock); + kobj_delete((kobj_t)m, M_MIXER); + + --mixer_count; + + return (0); +} + +struct snd_mixer * +mixer_create(device_t dev, kobj_class_t cls, void *devinfo, const char *desc) +{ + struct snd_mixer *m; + + m = mixer_obj_create(dev, cls, devinfo, MIXER_TYPE_SECONDARY, desc); + + if (m != NULL) + ++mixer_count; + + return (m); +} + int mixer_init(device_t dev, kobj_class_t cls, void *devinfo) { @@ -191,17 +636,11 @@ struct snd_mixer *m; u_int16_t v; struct cdev *pdev; - int i, unit, val; + int i, unit, devunit, val; - m = (struct snd_mixer *)kobj_create(cls, M_MIXER, M_WAITOK | M_ZERO); - snprintf(m->name, MIXER_NAMELEN, "%s:mixer", device_get_nameunit(dev)); - m->lock = snd_mtxcreate(m->name, "pcm mixer"); - m->type = cls->name; - m->devinfo = devinfo; - m->busy = 0; - - if (MIXER_INIT(m)) - goto bad; + m = mixer_obj_create(dev, cls, devinfo, MIXER_TYPE_PRIMARY, NULL); + if (m == NULL) + return (-1); for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { v = snd_mixerdefaults[i]; @@ -219,30 +658,60 @@ mixer_setrecsrc(m, SOUND_MASK_MIC); unit = device_get_unit(dev); - pdev = make_dev(&mixer_cdevsw, PCMMKMINOR(unit, SND_DEV_CTL, 0), + devunit = snd_mkunit(unit, SND_DEV_CTL, 0); + pdev = make_dev(&mixer_cdevsw, unit2minor(devunit), UID_ROOT, GID_WHEEL, 0666, "mixer%d", unit); pdev->si_drv1 = m; snddev = device_get_softc(dev); snddev->mixer_dev = pdev; - return 0; + ++mixer_count; -bad: - snd_mtxlock(m->lock); - snd_mtxfree(m->lock); - kobj_delete((kobj_t)m, M_MIXER); - return -1; + if (bootverbose) { + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { + if (!(m->devs & (1 << i))) + continue; + if (m->realdev[i] != i) { + device_printf(dev, "Mixer \"%s\" -> \"%s\":", + snd_mixernames[i], + (m->realdev[i] < SOUND_MIXER_NRDEVICES) ? + snd_mixernames[m->realdev[i]] : "none"); + } else { + device_printf(dev, "Mixer \"%s\":", + snd_mixernames[i]); + } + if (m->parent[i] < SOUND_MIXER_NRDEVICES) + printf(" parent=\"%s\"", + snd_mixernames[m->parent[i]]); + if (m->child[i] != 0) + printf(" child=0x%08x", m->child[i]); + printf("\n"); + } + if (snddev->flags & SD_F_SOFTPCMVOL) + device_printf(dev, "Soft PCM mixer ENABLED\n"); + } + + return (0); } int mixer_uninit(device_t dev) { int i; + struct snddev_info *d; struct snd_mixer *m; struct cdev *pdev; + d = device_get_softc(dev); pdev = mixer_get_devt(dev); + if (d == NULL || pdev == NULL || pdev->si_drv1 == NULL) + return EBADF; + m = pdev->si_drv1; + KASSERT(m != NULL, ("NULL snd_mixer")); + KASSERT(m->type == MIXER_TYPE_PRIMARY, + ("%s(): illegal mixer type=%d", __func__, m->type)); + snd_mtxlock(m->lock); if (m->busy) { @@ -263,6 +732,10 @@ snd_mtxfree(m->lock); kobj_delete((kobj_t)m, M_MIXER); + d->mixer_dev = NULL; + + --mixer_count; + return 0; } @@ -302,7 +775,7 @@ m = oidp->oid_arg1; snd_mtxlock(m->lock); - strncpy(devname, snd_mixernames[m->hwvol_mixer], sizeof(devname)); + strlcpy(devname, snd_mixernames[m->hwvol_mixer], sizeof(devname)); snd_mtxunlock(m->lock); error = sysctl_handle_string(oidp, &devname[0], sizeof(devname), req); snd_mtxlock(m->lock); @@ -334,9 +807,11 @@ m->hwvol_mixer = SOUND_MIXER_VOLUME; m->hwvol_step = 5; #ifdef SND_DYNSYSCTL - SYSCTL_ADD_INT(snd_sysctl_tree(dev), SYSCTL_CHILDREN(snd_sysctl_tree_top(dev)), + SYSCTL_ADD_INT(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "hwvol_step", CTLFLAG_RW, &m->hwvol_step, 0, ""); - SYSCTL_ADD_PROC(snd_sysctl_tree(dev), SYSCTL_CHILDREN(snd_sysctl_tree_top(dev)), + SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "hwvol_mixer", CTLTYPE_STRING | CTLFLAG_RW, m, 0, sysctl_hw_snd_hwvol_mixer, "A", ""); #endif @@ -392,69 +867,303 @@ snd_mtxunlock(m->lock); } +int +mixer_busy(struct snd_mixer *m) +{ + KASSERT(m != NULL, ("NULL snd_mixer")); + + return (m->busy); +} + +int +mix_set(struct snd_mixer *m, u_int dev, u_int left, u_int right) +{ + int ret; + + KASSERT(m != NULL, ("NULL snd_mixer")); + + snd_mtxlock(m->lock); + ret = mixer_set(m, dev, left | (right << 8)); + snd_mtxunlock(m->lock); + + return ((ret != 0) ? ENXIO : 0); +} + +int +mix_get(struct snd_mixer *m, u_int dev) +{ + int ret; + + KASSERT(m != NULL, ("NULL snd_mixer")); + + snd_mtxlock(m->lock); + ret = mixer_get(m, dev); + snd_mtxunlock(m->lock); + + return (ret); +} + +int +mix_setrecsrc(struct snd_mixer *m, u_int32_t src) +{ + int ret; + + KASSERT(m != NULL, ("NULL snd_mixer")); + + snd_mtxlock(m->lock); + ret = mixer_setrecsrc(m, src); + snd_mtxunlock(m->lock); + + return ((ret != 0) ? ENXIO : 0); +} + +u_int32_t +mix_getrecsrc(struct snd_mixer *m) +{ + u_int32_t ret; + + KASSERT(m != NULL, ("NULL snd_mixer")); + + snd_mtxlock(m->lock); + ret = mixer_getrecsrc(m); + snd_mtxunlock(m->lock); + + return (ret); +} + +int +mix_get_type(struct snd_mixer *m) +{ + KASSERT(m != NULL, ("NULL snd_mixer")); + + return (m->type); +} + /* ----------------------------------------------------------------------- */ static int mixer_open(struct cdev *i_dev, int flags, int mode, struct thread *td) { + struct snddev_info *d; struct snd_mixer *m; - intrmask_t s; + + + if (i_dev == NULL || i_dev->si_drv1 == NULL) + return (EBADF); m = i_dev->si_drv1; - s = spltty(); - snd_mtxlock(m->lock); + d = device_get_softc(m->dev); + if (!PCM_REGISTERED(d)) + return (EBADF); - m->busy++; + /* XXX Need Giant magic entry ??? */ + snd_mtxlock(m->lock); + m->busy = 1; snd_mtxunlock(m->lock); - splx(s); - return 0; + + return (0); } static int mixer_close(struct cdev *i_dev, int flags, int mode, struct thread *td) { + struct snddev_info *d; struct snd_mixer *m; - intrmask_t s; + int ret; + + if (i_dev == NULL || i_dev->si_drv1 == NULL) + return (EBADF); m = i_dev->si_drv1; - s = spltty(); + d = device_get_softc(m->dev); + if (!PCM_REGISTERED(d)) + return (EBADF); + + /* XXX Need Giant magic entry ??? */ + snd_mtxlock(m->lock); + ret = (m->busy == 0) ? EBADF : 0; + m->busy = 0; + snd_mtxunlock(m->lock); + + return (ret); +} - if (!m->busy) { +static int +mixer_ioctl_channel(struct cdev *dev, u_long cmd, caddr_t arg, int mode, + struct thread *td, int from) +{ + struct snddev_info *d; + struct snd_mixer *m; + struct pcm_channel *c, *rdch, *wrch; + pid_t pid; + int j, ret; + + if (td == NULL || td->td_proc == NULL) + return (-1); + + m = dev->si_drv1; + d = device_get_softc(m->dev); + j = cmd & 0xff; + + switch (j) { + case SOUND_MIXER_PCM: + case SOUND_MIXER_RECLEV: + case SOUND_MIXER_DEVMASK: + case SOUND_MIXER_CAPS: + case SOUND_MIXER_STEREODEVS: + break; + default: + return (-1); + break; + } + + pid = td->td_proc->p_pid; + rdch = NULL; + wrch = NULL; + c = NULL; + ret = -1; + + /* + * This is unfair. Imagine single proc opening multiple + * instances of same direction. What we do right now + * is looking for the first matching proc/pid, and just + * that. Nothing more. Consider it done. + * + * The better approach of controlling specific channel + * pcm or rec volume is by doing mixer ioctl + * (SNDCTL_DSP_[SET|GET][PLAY|REC]VOL / SOUND_MIXER_[PCM|RECLEV] + * on its open fd, rather than cracky mixer bypassing here. + */ + CHN_FOREACH(c, d, channels.pcm.opened) { + CHN_LOCK(c); + if (c->pid != pid || + !(c->feederflags & (1 << FEEDER_VOLUME))) { + CHN_UNLOCK(c); + continue; + } + if (rdch == NULL && c->direction == PCMDIR_REC) { + rdch = c; + if (j == SOUND_MIXER_RECLEV) + goto mixer_ioctl_channel_proc; + } else if (wrch == NULL && c->direction == PCMDIR_PLAY) { + wrch = c; + if (j == SOUND_MIXER_PCM) + goto mixer_ioctl_channel_proc; + } + CHN_UNLOCK(c); + if (rdch != NULL && wrch != NULL) + break; + } + + if (rdch == NULL && wrch == NULL) + return (-1); + + if ((j == SOUND_MIXER_DEVMASK || j == SOUND_MIXER_CAPS || + j == SOUND_MIXER_STEREODEVS) && + (cmd & MIXER_READ(0)) == MIXER_READ(0)) { + snd_mtxlock(m->lock); + *(int *)arg = mix_getdevs(m); snd_mtxunlock(m->lock); - splx(s); - return EBADF; + if (rdch != NULL) + *(int *)arg |= SOUND_MASK_RECLEV; + if (wrch != NULL) + *(int *)arg |= SOUND_MASK_PCM; + ret = 0; } - m->busy--; - snd_mtxunlock(m->lock); - splx(s); - return 0; + return (ret); + +mixer_ioctl_channel_proc: + + KASSERT(c != NULL, ("%s(): NULL channel", __func__)); + CHN_LOCKASSERT(c); + + if ((cmd & MIXER_WRITE(0)) == MIXER_WRITE(0)) { + CHN_SETVOLUME(c, SND_VOL_C_PCM, SND_CHN_T_FL, + *(int *)arg & 0x7f); + CHN_SETVOLUME(c, SND_VOL_C_PCM, SND_CHN_T_FR, + (*(int *)arg >> 8) & 0x7f); + } else if ((cmd & MIXER_READ(0)) == MIXER_READ(0)) { + *(int *)arg = CHN_GETVOLUME(c, SND_VOL_C_PCM, SND_CHN_T_FL); + *(int *)arg |= + CHN_GETVOLUME(c, SND_VOL_C_PCM, SND_CHN_T_FR) << 8; + } + + CHN_UNLOCK(c); + + return (0); } +static int +mixer_ioctl(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode, + struct thread *td) +{ + struct snddev_info *d; + int ret; + + if (i_dev == NULL || i_dev->si_drv1 == NULL) + return (EBADF); + + d = device_get_softc(((struct snd_mixer *)i_dev->si_drv1)->dev); + if (!PCM_REGISTERED(d)) + return (EBADF); + + PCM_GIANT_ENTER(d); + PCM_ACQUIRE_QUICK(d); + + ret = -1; + + if (mixer_bypass != 0 && (d->flags & SD_F_VPC)) + ret = mixer_ioctl_channel(i_dev, cmd, arg, mode, td, + MIXER_CMD_CDEV); + + if (ret == -1) + ret = mixer_ioctl_cmd(i_dev, cmd, arg, mode, td, + MIXER_CMD_CDEV); + + PCM_RELEASE_QUICK(d); + PCM_GIANT_LEAVE(d); + + return (ret); +} + +/* + * XXX Make sure you can guarantee concurrency safety before calling this + * function, be it through Giant, PCM_*, etc ! + */ int -mixer_ioctl(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode, struct thread *td) +mixer_ioctl_cmd(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode, + struct thread *td, int from) { struct snd_mixer *m; - intrmask_t s; int ret, *arg_i = (int *)arg; int v = -1, j = cmd & 0xff; m = i_dev->si_drv1; - if (!m->busy) - return EBADF; - s = spltty(); + if (m == NULL) + return (EBADF); + snd_mtxlock(m->lock); + if (from == MIXER_CMD_CDEV && !m->busy) { + snd_mtxunlock(m->lock); + return (EBADF); + } + + if (cmd == SNDCTL_MIXERINFO) { + snd_mtxunlock(m->lock); + return (mixer_oss_mixerinfo(i_dev, (oss_mixerinfo *)arg)); + } + if ((cmd & MIXER_WRITE(0)) == MIXER_WRITE(0)) { if (j == SOUND_MIXER_RECSRC) ret = mixer_setrecsrc(m, *arg_i); else ret = mixer_set(m, j, *arg_i); snd_mtxunlock(m->lock); - splx(s); - return (ret == 0)? 0 : ENXIO; + return ((ret == 0) ? 0 : ENXIO); } if ((cmd & MIXER_READ(0)) == MIXER_READ(0)) { @@ -478,43 +1187,225 @@ } *arg_i = v; snd_mtxunlock(m->lock); - return (v != -1)? 0 : ENXIO; + return ((v != -1) ? 0 : ENXIO); + } + + ret = 0; + + switch (cmd) { + /** @todo Double check return values, error codes. */ + case SNDCTL_SYSINFO: + snd_mtxunlock(m->lock); + sound_oss_sysinfo((oss_sysinfo *)arg); + return (ret); + break; + case SNDCTL_AUDIOINFO: + snd_mtxunlock(m->lock); + return (dsp_oss_audioinfo(i_dev, (oss_audioinfo *)arg)); + break; + case SNDCTL_DSP_GET_RECSRC_NAMES: + bcopy((void *)&m->enuminfo, arg, sizeof(oss_mixer_enuminfo)); + break; + case SNDCTL_DSP_GET_RECSRC: + ret = mixer_get_recroute(m, arg_i); + break; + case SNDCTL_DSP_SET_RECSRC: + ret = mixer_set_recroute(m, *arg_i); + break; + case OSS_GETVERSION: + *arg_i = SOUND_VERSION; + break; + default: + ret = ENXIO; + break; } + snd_mtxunlock(m->lock); - splx(s); - return ENXIO; + + return (ret); } #ifdef USING_DEVFS static void -mixer_clone(void *arg, char *name, int namelen, struct cdev **dev) +mixer_clone(void *arg, +#if __FreeBSD_version >= 600034 + struct ucred *cred, +#endif + char *name, int namelen, struct cdev **dev) { - struct snddev_info *sd; + struct snddev_info *d; if (*dev != NULL) return; if (strcmp(name, "mixer") == 0) { - sd = devclass_get_softc(pcm_devclass, snd_unit); - if (sd != NULL) - *dev = sd->mixer_dev; + d = devclass_get_softc(pcm_devclass, snd_unit); + if (PCM_REGISTERED(d) && d->mixer_dev != NULL) { + *dev = d->mixer_dev; + dev_ref(*dev); + } } } static void mixer_sysinit(void *p) { + if (mixer_ehtag != NULL) + return; mixer_ehtag = EVENTHANDLER_REGISTER(dev_clone, mixer_clone, 0, 1000); } static void mixer_sysuninit(void *p) { - if (mixer_ehtag != NULL) - EVENTHANDLER_DEREGISTER(dev_clone, mixer_ehtag); + if (mixer_ehtag == NULL) + return; + EVENTHANDLER_DEREGISTER(dev_clone, mixer_ehtag); + mixer_ehtag = NULL; } SYSINIT(mixer_sysinit, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, mixer_sysinit, NULL); SYSUNINIT(mixer_sysuninit, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, mixer_sysuninit, NULL); #endif +/** + * @brief Handler for SNDCTL_MIXERINFO + * + * This function searches for a mixer based on the numeric ID stored + * in oss_miserinfo::dev. If set to -1, then information about the + * current mixer handling the request is provided. Note, however, that + * this ioctl may be made with any sound device (audio, mixer, midi). + * + * @note Caller must not hold any PCM device, channel, or mixer locks. + * + * See http://manuals.opensound.com/developer/SNDCTL_MIXERINFO.html for + * more information. + * + * @param i_dev character device on which the ioctl arrived + * @param arg user argument (oss_mixerinfo *) + * + * @retval EINVAL oss_mixerinfo::dev specified a bad value + * @retval 0 success + */ +int +mixer_oss_mixerinfo(struct cdev *i_dev, oss_mixerinfo *mi) +{ + struct snddev_info *d; + struct snd_mixer *m; + int nmix, i; + + /* + * If probing the device handling the ioctl, make sure it's a mixer + * device. (This ioctl is valid on audio, mixer, and midi devices.) + */ + if (mi->dev == -1 && i_dev->si_devsw != &mixer_cdevsw) + return (EINVAL); + + d = NULL; + m = NULL; + nmix = 0; + + /* + * There's a 1:1 relationship between mixers and PCM devices, so + * begin by iterating over PCM devices and search for our mixer. + */ + for (i = 0; pcm_devclass != NULL && + i < devclass_get_maxunit(pcm_devclass); i++) { + d = devclass_get_softc(pcm_devclass, i); + if (!PCM_REGISTERED(d)) + continue; + + /* XXX Need Giant magic entry */ + + /* See the note in function docblock. */ + mtx_assert(d->lock, MA_NOTOWNED); + pcm_lock(d); + + if (d->mixer_dev != NULL && d->mixer_dev->si_drv1 != NULL && + ((mi->dev == -1 && d->mixer_dev == i_dev) || + mi->dev == nmix)) { + m = d->mixer_dev->si_drv1; + mtx_lock(m->lock); + + /* + * At this point, the following synchronization stuff + * has happened: + * - a specific PCM device is locked. + * - a specific mixer device has been locked, so be + * sure to unlock when existing. + */ + bzero((void *)mi, sizeof(*mi)); + mi->dev = nmix; + snprintf(mi->id, sizeof(mi->id), "mixer%d", i); + strlcpy(mi->name, m->name, sizeof(mi->name)); + mi->modify_counter = m->modify_counter; + mi->card_number = i; + /* + * Currently, FreeBSD assumes 1:1 relationship between + * a pcm and mixer devices, so this is hardcoded to 0. + */ + mi->port_number = 0; + + /** + * @todo Fill in @sa oss_mixerinfo::mixerhandle. + * @note From 4Front: "mixerhandle is an arbitrary + * string that identifies the mixer better than + * the device number (mixerinfo.dev). Device + * numbers may change depending on the order the + * drivers are loaded. However the handle should + * remain the same provided that the sound card + * is not moved to another PCI slot." + */ + + /** + * @note + * @sa oss_mixerinfo::magic is a reserved field. + * + * @par + * From 4Front: "magic is usually 0. However some + * devices may have dedicated setup utilities and the + * magic field may contain an unique driver specific + * value (managed by [4Front])." + */ + + mi->enabled = device_is_attached(m->dev) ? 1 : 0; + /** + * The only flag for @sa oss_mixerinfo::caps is + * currently MIXER_CAP_VIRTUAL, which I'm not sure we + * really worry about. + */ + /** + * Mixer extensions currently aren't supported, so + * leave @sa oss_mixerinfo::nrext blank for now. + */ + /** + * @todo Fill in @sa oss_mixerinfo::priority (requires + * touching drivers?) + * @note The priority field is for mixer applets to + * determine which mixer should be the default, with 0 + * being least preferred and 10 being most preferred. + * From 4Front: "OSS drivers like ICH use higher + * values (10) because such chips are known to be used + * only on motherboards. Drivers for high end pro + * devices use 0 because they will never be the + * default mixer. Other devices use values 1 to 9 + * depending on the estimated probability of being the + * default device. + * + * XXX Described by Hannu@4Front, but not found in + * soundcard.h. + strlcpy(mi->devnode, d->mixer_dev->si_name, + sizeof(mi->devnode)); + mi->legacy_device = i; + */ + mtx_unlock(m->lock); + } else + ++nmix; + pcm_unlock(d); + + if (m != NULL) + return (0); + } + + return (EINVAL); +} --- sys/dev/sound/pcm/mixer.h.orig Sun Jan 30 09:00:05 2005 +++ sys/dev/sound/pcm/mixer.h Thu Jul 12 12:04:19 2007 @@ -23,28 +23,53 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $FreeBSD: src/sys/dev/sound/pcm/mixer.h,v 1.13.2.1 2005/01/30 01:00:05 imp Exp $ + * $FreeBSD: src/sys/dev/sound/pcm/mixer.h,v 1.19 2007/06/16 03:37:28 ariff Exp $ */ +struct snd_mixer *mixer_create(device_t dev, kobj_class_t cls, void *devinfo, + const char *desc); +int mixer_delete(struct snd_mixer *m); int mixer_init(device_t dev, kobj_class_t cls, void *devinfo); int mixer_uninit(device_t dev); int mixer_reinit(device_t dev); -int mixer_ioctl(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode, struct thread *td); +int mixer_ioctl_cmd(struct cdev *i_dev, u_long cmd, caddr_t arg, int mode, struct thread *td, int from); +int mixer_oss_mixerinfo(struct cdev *i_dev, oss_mixerinfo *mi); int mixer_hwvol_init(device_t dev); void mixer_hwvol_mute(device_t dev); void mixer_hwvol_step(device_t dev, int left_step, int right_step); +int mixer_busy(struct snd_mixer *m); + +int mix_set(struct snd_mixer *m, u_int dev, u_int left, u_int right); +int mix_get(struct snd_mixer *m, u_int dev); +int mix_setrecsrc(struct snd_mixer *m, u_int32_t src); +u_int32_t mix_getrecsrc(struct snd_mixer *m); +int mix_get_type(struct snd_mixer *m); + void mix_setdevs(struct snd_mixer *m, u_int32_t v); void mix_setrecdevs(struct snd_mixer *m, u_int32_t v); u_int32_t mix_getdevs(struct snd_mixer *m); u_int32_t mix_getrecdevs(struct snd_mixer *m); +void mix_setparentchild(struct snd_mixer *m, u_int32_t parent, u_int32_t childs); +void mix_setrealdev(struct snd_mixer *m, u_int32_t dev, u_int32_t realdev); +u_int32_t mix_getparent(struct snd_mixer *m, u_int32_t dev); +u_int32_t mix_getchild(struct snd_mixer *m, u_int32_t dev); void *mix_getdevinfo(struct snd_mixer *m); +extern int mixer_count; + +#define MIXER_CMD_DIRECT 0 /* send command within driver */ +#define MIXER_CMD_CDEV 1 /* send command from cdev/ioctl */ + +#define MIXER_TYPE_PRIMARY 0 /* mixer_init() */ +#define MIXER_TYPE_SECONDARY 1 /* mixer_create() */ + /* * this is a kludge to allow hiding of the struct snd_mixer definition * 512 should be enough for all architectures */ -#define MIXER_SIZE (512 + sizeof(struct kobj)) +#define MIXER_SIZE (512 + sizeof(struct kobj) + \ + sizeof(oss_mixer_enuminfo)) -#define MIXER_DECLARE(name) DEFINE_CLASS(name, name ## _methods, MIXER_SIZE) +#define MIXER_DECLARE(name) static DEFINE_CLASS(name, name ## _methods, MIXER_SIZE) --- sys/dev/sound/pcm/mixer_if.m.orig Sun Jan 30 09:00:05 2005 +++ sys/dev/sound/pcm/mixer_if.m Thu Jan 6 09:43:21 2005 @@ -25,7 +25,7 @@ # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF # SUCH DAMAGE. # -# $FreeBSD: src/sys/dev/sound/pcm/mixer_if.m,v 1.5.8.1 2005/01/30 01:00:05 imp Exp $ +# $FreeBSD: src/sys/dev/sound/pcm/mixer_if.m,v 1.6 2005/01/06 01:43:21 imp Exp $ # #include --- sys/dev/sound/pcm/pcm.h.orig Thu Jan 1 07:30:00 1970 +++ sys/dev/sound/pcm/pcm.h Thu Jul 12 12:04:19 2007 @@ -0,0 +1,374 @@ +/*- + * 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$ + */ + +#ifndef _SND_PCM_H_ +#define _SND_PCM_H_ + +#include + +/* + * Macros for reading/writing PCM sample / int values from bytes array. + * Since every process is done using signed integer (and to make our life + * less miserable), unsigned sample will be converted to its signed + * counterpart and restored during writing back. To avoid overflow, + * we truncate 32bit (and only 32bit) samples down to 24bit (see below + * for the reason), unless PCM_USE_64BIT_ARITH is defined. + */ + +/* + * Automatically turn on 64bit arithmetic on suitable archs + * (amd64 64bit, ia64, etc..) for wider 32bit samples / integer processing. + */ +#if LONG_BIT >= 64 +#undef PCM_USE_64BIT_ARITH +#define PCM_USE_64BIT_ARITH 1 +#endif + +typedef int32_t intpcm_t; + +typedef int32_t intpcm8_t; +typedef int32_t intpcm16_t; +typedef int32_t intpcm24_t; +#ifdef PCM_USE_64BIT_ARITH +typedef int64_t intpcm32_t; +#else +typedef int32_t intpcm32_t; +#endif + +/* 32bit fixed point shift */ +#define PCM_FXSHIFT 8 + +#define PCM_S8_MAX 0x7f +#define PCM_S8_MIN -0x80 +#define PCM_S16_MAX 0x7fff +#define PCM_S16_MIN -0x8000 +#define PCM_S24_MAX 0x7fffff +#define PCM_S24_MIN -0x800000 +#ifdef PCM_USE_64BIT_ARITH +#if LONG_BIT >= 64 +#define PCM_S32_MAX 0x7fffffffL +#define PCM_S32_MIN -0x80000000L +#else +#define PCM_S32_MAX 0x7fffffffLL +#define PCM_S32_MIN -0x80000000LL +#endif +#else +#define PCM_S32_MAX 0x7fffffff +#define PCM_S32_MIN (-0x7fffffff - 1) +#endif + +/* Bytes-per-sample definition */ +#define PCM_8_BPS 1 +#define PCM_16_BPS 2 +#define PCM_24_BPS 3 +#define PCM_32_BPS 4 + +#define INTPCM_T(v) ((intpcm_t)(v)) +#define INTPCM8_T(v) ((intpcm8_t)(v)) +#define INTPCM16_T(v) ((intpcm16_t)(v)) +#define INTPCM24_T(v) ((intpcm24_t)(v)) +#define INTPCM32_T(v) ((intpcm32_t)(v)) + +#if BYTE_ORDER == LITTLE_ENDIAN +#define _PCM_READ_S16_LE(b8) INTPCM_T(*((int16_t *)(b8))) +#define _PCM_READ_S32_LE(b8) INTPCM_T(*((int32_t *)(b8))) +#define _PCM_READ_S16_BE(b8) \ + INTPCM_T((b8)[1] | (((int8_t)((b8)[0])) << 8)) +#define _PCM_READ_S32_BE(b8) \ + INTPCM_T((b8)[3] | ((b8)[2] << 8) | ((b8)[1] << 16) | \ + (((int8_t)((b8)[0])) << 24)) + +#define _PCM_WRITE_S16_LE(b8, val) do { \ + *((int16_t *)(b8)) = (val); \ +} while(0) +#define _PCM_WRITE_S32_LE(b8, val) do { \ + *((int32_t *)(b8)) = (val); \ +} while(0) +#define _PCM_WRITE_S16_BE(bb8, vval) do { \ + intpcm_t val = (vval); \ + uint8_t *b8 = (bb8); \ + b8[1] = val; \ + b8[0] = val >> 8; \ +} while(0) +#define _PCM_WRITE_S32_BE(bb8, vval) do { \ + intpcm_t val = (vval); \ + uint8_t *b8 = (bb8); \ + b8[3] = val; \ + b8[2] = val >> 8; \ + b8[1] = val >> 16; \ + b8[0] = val >> 24; \ +} while(0) + +#define _PCM_READ_U16_LE(b8) \ + INTPCM_T((int16_t)(*((uint16_t *)(b8)) ^ 0x8000)) +#define _PCM_READ_U32_LE(b8) \ + INTPCM_T((int32_t)(*((uint32_t *)(b8)) ^ 0x80000000)) +#define _PCM_READ_U16_BE(b8) \ + INTPCM_T((b8)[1] | (((int8_t)((b8)[0] ^ 0x80)) << 8)) +#define _PCM_READ_U32_BE(b8) \ + INTPCM_T((b8)[3] | ((b8)[2] << 8) | ((b8)[1] << 16) | \ + (((int8_t)((b8)[0] ^ 0x80)) << 24)) + +#define _PCM_WRITE_U16_LE(b8, val) do { \ + *((uint16_t *)(b8)) = (val) ^ 0x8000; \ +} while(0) +#define _PCM_WRITE_U32_LE(b8, val) do { \ + *((uint32_t *)(b8)) = (val) ^ 0x80000000; \ +} while(0) +#define _PCM_WRITE_U16_BE(bb8, vval) do { \ + intpcm_t val = (vval); \ + uint8_t *b8 = (bb8); \ + b8[1] = val; \ + b8[0] = (val >> 8) ^ 0x80; \ +} while(0) +#define _PCM_WRITE_U32_BE(bb8, vval) do { \ + intpcm_t val = (vval); \ + uint8_t *b8 = (bb8); \ + b8[3] = val; \ + b8[2] = val >> 8; \ + b8[1] = val >> 16; \ + b8[0] = (val >> 24) ^ 0x80; \ +} while(0) +#else /* !LITTLE_ENDIAN */ +#define _PCM_READ_S16_LE(b8) \ + INTPCM_T((b8)[0] | (((int8_t)((b8)[1])) << 8)) +#define _PCM_READ_S32_LE(b8) \ + INTPCM_T((b8)[0] | ((b8)[1] << 8) | ((b8)[2] << 16) | \ + (((int8_t)((b8)[3])) << 24)) +#define _PCM_READ_S16_BE(b8) INTPCM_T(*((int16_t *)(b8))) +#define _PCM_READ_S32_BE(b8) INTPCM_T(*((int32_t *)(b8))) + +#define _PCM_WRITE_S16_LE(bb8, vval) do { \ + intpcm_t val = (vval); \ + uint8_t *b8 = (bb8); \ + b8[0] = val; \ + b8[1] = val >> 8; \ +} while(0) +#define _PCM_WRITE_S32_LE(bb8, vval) do { \ + intpcm_t val = (vval); \ + uint8_t *b8 = (bb8); \ + b8[0] = val; \ + b8[1] = val >> 8; \ + b8[2] = val >> 16; \ + b8[3] = val >> 24; \ +} while(0) +#define _PCM_WRITE_S16_BE(b8, val) do { \ + *((int16_t *)(b8)) = (val); \ +} while(0) +#define _PCM_WRITE_S32_BE(b8, val) do { \ + *((int32_t *)(b8)) = (val); \ +} while(0) + +#define _PCM_READ_U16_LE(b8) \ + INTPCM_T((b8)[0] | (((int8_t)((b8)[1] ^ 0x80)) << 8)) +#define _PCM_READ_U32_LE(b8) \ + INTPCM_T((b8)[0] | ((b8)[1] << 8) | ((b8)[2] << 16) | \ + (((int8_t)((b8)[3] ^ 0x80)) << 24)) +#define _PCM_READ_U16_BE(b8) \ + INTPCM_T((int16_t)(*((uint16_t *)(b8)) ^ 0x8000)) +#define _PCM_READ_U32_BE(b8) \ + INTPCM_T((int32_t)(*((uint32_t *)(b8)) ^ 0x80000000)) + +#define _PCM_WRITE_U16_LE(bb8, vval) do { \ + intpcm_t val = (vval); \ + uint8_t *b8 = (bb8); \ + b8[0] = val; \ + b8[1] = (val >> 8) ^ 0x80; \ +} while(0) +#define _PCM_WRITE_U32_LE(bb8, vval) do { \ + intpcm_t val = (vval); \ + uint8_t *b8 = (bb8); \ + b8[0] = val; \ + b8[1] = val >> 8; \ + b8[2] = val >> 16; \ + b8[3] = (val >> 24) ^ 0x80; \ +} while(0) +#define _PCM_WRITE_U16_BE(b8, val) do { \ + *((uint16_t *)(b8)) = (val) ^ 0x8000; \ +} while(0) +#define _PCM_WRITE_U32_BE(b8, val) do { \ + *((uint32_t *)(b8)) = (val) ^ 0x80000000; \ +} while(0) +#endif + +#define _PCM_READ_S24_LE(b8) \ + INTPCM_T((b8)[0] | ((b8)[1] << 8) | (((int8_t)((b8)[2])) << 16)) +#define _PCM_READ_S24_BE(b8) \ + INTPCM_T((b8)[2] | ((b8)[1] << 8) | (((int8_t)((b8)[0])) << 16)) + +#define _PCM_WRITE_S24_LE(bb8, vval) do { \ + intpcm_t val = (vval); \ + uint8_t *b8 = (bb8); \ + b8[0] = val; \ + b8[1] = val >> 8; \ + b8[2] = val >> 16; \ +} while(0) +#define _PCM_WRITE_S24_BE(bb8, vval) do { \ + intpcm_t val = (vval); \ + uint8_t *b8 = (bb8); \ + b8[2] = val; \ + b8[1] = val >> 8; \ + b8[0] = val >> 16; \ +} while(0) + +#define _PCM_READ_U24_LE(b8) \ + INTPCM_T((b8)[0] | ((b8)[1] << 8) | \ + (((int8_t)((b8)[2] ^ 0x80)) << 16)) +#define _PCM_READ_U24_BE(b8) \ + INTPCM_T((b8)[2] | ((b8)[1] << 8) | \ + (((int8_t)((b8)[0] ^ 0x80)) << 16)) + +#define _PCM_WRITE_U24_LE(bb8, vval) do { \ + intpcm_t val = (vval); \ + uint8_t *b8 = (bb8); \ + b8[0] = val; \ + b8[1] = val >> 8; \ + b8[2] = (val >> 16) ^ 0x80; \ +} while(0) +#define _PCM_WRITE_U24_BE(bb8, vval) do { \ + intpcm_t val = (vval); \ + uint8_t *b8 = (bb8); \ + b8[2] = val; \ + b8[1] = val >> 8; \ + b8[0] = (val >> 16) ^ 0x80; \ +} while(0) + +/* + * 8bit sample is pretty much useless since it doesn't provide + * sufficient dynamic range throughout our filtering process. + * For the sake of completeness, declare it anyway. + */ +#define _PCM_READ_S8_NE(b8) INTPCM_T(*((int8_t *)(b8))) +#define _PCM_READ_U8_NE(b8) \ + INTPCM_T((int8_t)(*((uint8_t *)(b8)) ^ 0x80)) + +#define _PCM_WRITE_S8_NE(b8, val) do { \ + *((int8_t *)(b8)) = (val); \ +} while(0) +#define _PCM_WRITE_U8_NE(b8, val) do { \ + *((uint8_t *)(b8)) = (val) ^ 0x80; \ +} while(0) + +/* + * Common macross. Use this instead of "_", unless we want + * the real sample value. + */ + +/* 8bit */ +#define PCM_READ_S8_NE(b8) _PCM_READ_S8_NE(b8) +#define PCM_READ_U8_NE(b8) _PCM_READ_U8_NE(b8) +#define PCM_WRITE_S8_NE(b8, val) _PCM_WRITE_S8_NE(b8, val) +#define PCM_WRITE_U8_NE(b8, val) _PCM_WRITE_U8_NE(b8, val) + +/* 16bit */ +#define PCM_READ_S16_LE(b8) _PCM_READ_S16_LE(b8) +#define PCM_READ_S16_BE(b8) _PCM_READ_S16_BE(b8) +#define PCM_READ_U16_LE(b8) _PCM_READ_U16_LE(b8) +#define PCM_READ_U16_BE(b8) _PCM_READ_U16_BE(b8) + +#define PCM_WRITE_S16_LE(b8, val) _PCM_WRITE_S16_LE(b8, val) +#define PCM_WRITE_S16_BE(b8, val) _PCM_WRITE_S16_BE(b8, val) +#define PCM_WRITE_U16_LE(b8, val) _PCM_WRITE_U16_LE(b8, val) +#define PCM_WRITE_U16_BE(b8, val) _PCM_WRITE_U16_BE(b8, val) + +/* 24bit */ +#define PCM_READ_S24_LE(b8) _PCM_READ_S24_LE(b8) +#define PCM_READ_S24_BE(b8) _PCM_READ_S24_BE(b8) +#define PCM_READ_U24_LE(b8) _PCM_READ_U24_LE(b8) +#define PCM_READ_U24_BE(b8) _PCM_READ_U24_BE(b8) + +#define PCM_WRITE_S24_LE(b8, val) _PCM_WRITE_S24_LE(b8, val) +#define PCM_WRITE_S24_BE(b8, val) _PCM_WRITE_S24_BE(b8, val) +#define PCM_WRITE_U24_LE(b8, val) _PCM_WRITE_U24_LE(b8, val) +#define PCM_WRITE_U24_BE(b8, val) _PCM_WRITE_U24_BE(b8, val) + +/* 32bit */ +#ifdef PCM_USE_64BIT_ARITH +#define PCM_READ_S32_LE(b8) _PCM_READ_S32_LE(b8) +#define PCM_READ_S32_BE(b8) _PCM_READ_S32_BE(b8) +#define PCM_READ_U32_LE(b8) _PCM_READ_U32_LE(b8) +#define PCM_READ_U32_BE(b8) _PCM_READ_U32_BE(b8) + +#define PCM_WRITE_S32_LE(b8, val) _PCM_WRITE_S32_LE(b8, val) +#define PCM_WRITE_S32_BE(b8, val) _PCM_WRITE_S32_BE(b8, val) +#define PCM_WRITE_U32_LE(b8, val) _PCM_WRITE_U32_LE(b8, val) +#define PCM_WRITE_U32_BE(b8, val) _PCM_WRITE_U32_BE(b8, val) +#else /* !PCM_USE_64BIT_ARITH */ +/* + * 24bit integer ?!? This is quite unfortunate, eh? Get the fact straight: + * Dynamic range for: + * 1) Human =~ 140db + * 2) 16bit = 96db (close enough) + * 3) 24bit = 144db (perfect) + * 4) 32bit = 196db (way too much) + * 5) Bugs Bunny = Gazillion!@%$Erbzzztt-EINVAL db + * Since we're not Bugs Bunny ..uh..err.. avoiding 64bit arithmetic, 24bit + * is pretty much sufficient for our signed integer processing. + */ +#define PCM_READ_S32_LE(b8) (_PCM_READ_S32_LE(b8) >> PCM_FXSHIFT) +#define PCM_READ_S32_BE(b8) (_PCM_READ_S32_BE(b8) >> PCM_FXSHIFT) +#define PCM_READ_U32_LE(b8) (_PCM_READ_U32_LE(b8) >> PCM_FXSHIFT) +#define PCM_READ_U32_BE(b8) (_PCM_READ_U32_BE(b8) >> PCM_FXSHIFT) + +#define PCM_WRITE_S32_LE(b8, val) \ + _PCM_WRITE_S32_LE(b8, (val) << PCM_FXSHIFT) +#define PCM_WRITE_S32_BE(b8, val) \ + _PCM_WRITE_S32_BE(b8, (val) << PCM_FXSHIFT) +#define PCM_WRITE_U32_LE(b8, val) \ + _PCM_WRITE_U32_LE(b8, (val) << PCM_FXSHIFT) +#define PCM_WRITE_U32_BE(b8, val) \ + _PCM_WRITE_U32_BE(b8, (val) << PCM_FXSHIFT) +#endif + +#define PCM_CLAMP_S8(val) \ + (((val) > PCM_S8_MAX) ? PCM_S8_MAX : \ + (((val) < PCM_S8_MIN) ? PCM_S8_MIN : (val))) +#define PCM_CLAMP_S16(val) \ + (((val) > PCM_S16_MAX) ? PCM_S16_MAX : \ + (((val) < PCM_S16_MIN) ? PCM_S16_MIN : (val))) +#define PCM_CLAMP_S24(val) \ + (((val) > PCM_S24_MAX) ? PCM_S24_MAX : \ + (((val) < PCM_S24_MIN) ? PCM_S24_MIN : (val))) + +#ifdef PCM_USE_64BIT_ARITH +#define PCM_CLAMP_S32(val) \ + (((val) > PCM_S32_MAX) ? PCM_S32_MAX : \ + (((val) < PCM_S32_MIN) ? PCM_S32_MIN : (val))) +#else +#define PCM_CLAMP_S32(val) \ + (((val) > PCM_S24_MAX) ? PCM_S32_MAX : \ + (((val) < PCM_S24_MIN) ? PCM_S32_MIN : \ + ((val) << PCM_FXSHIFT))) +#endif + +#define PCM_CLAMP_U8(val) PCM_CLAMP_S8(val) +#define PCM_CLAMP_U16(val) PCM_CLAMP_S16(val) +#define PCM_CLAMP_U24(val) PCM_CLAMP_S24(val) +#define PCM_CLAMP_U32(val) PCM_CLAMP_S32(val) + +#endif /* !_SND_PCM_H_ */ --- sys/dev/sound/pcm/sndstat.c.orig Wed Sep 21 10:47:12 2005 +++ sys/dev/sound/pcm/sndstat.c Thu Jul 12 12:04:19 2007 @@ -25,12 +25,13 @@ */ #include -#include +#include +#include #ifdef USING_MUTEX #include #endif -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pcm/sndstat.c,v 1.17.2.3 2005/09/21 02:47:12 yongari Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pcm/sndstat.c,v 1.28 2007/06/16 03:37:28 ariff Exp $"); #define SS_TYPE_MODULE 0 #define SS_TYPE_FIRST 1 @@ -45,12 +46,10 @@ static struct cdevsw sndstat_cdevsw = { .d_version = D_VERSION, - .d_flags = D_NEEDGIANT, .d_open = sndstat_open, .d_close = sndstat_close, .d_read = sndstat_read, .d_name = "sndstat", - .d_maj = SND_CDEV_MAJOR, }; struct sndstat_entry { @@ -62,22 +61,55 @@ }; #ifdef USING_MUTEX -static struct sx sndstat_lock; +static struct mtx sndstat_lock; #endif static struct sbuf sndstat_sbuf; -static struct cdev *sndstat_dev = 0; -static int sndstat_isopen = 0; -static int sndstat_bufptr; +static struct cdev *sndstat_dev = NULL; +static int sndstat_bufptr = -1; static int sndstat_maxunit = -1; static int sndstat_files = 0; +#define SNDSTAT_PID(x) ((pid_t)((intptr_t)((x)->si_drv1))) +#define SNDSTAT_PID_SET(x, y) (x)->si_drv1 = (void *)((intptr_t)(y)) +#define SNDSTAT_FLUSH() do { \ + if (sndstat_bufptr != -1) { \ + sbuf_delete(&sndstat_sbuf); \ + sndstat_bufptr = -1; \ + } \ +} while(0) + static SLIST_HEAD(, sndstat_entry) sndstat_devlist = SLIST_HEAD_INITIALIZER(none); -static int sndstat_verbose = 1; +int snd_verbose = 1; #ifdef USING_MUTEX -TUNABLE_INT("hw.snd.verbose", &sndstat_verbose); +TUNABLE_INT("hw.snd.verbose", &snd_verbose); #else -TUNABLE_INT_DECL("hw.snd.verbose", 1, sndstat_verbose); +TUNABLE_INT_DECL("hw.snd.verbose", 1, snd_verbose); +#endif + +#ifdef SND_DEBUG +static int +sysctl_hw_snd_sndstat_pid(SYSCTL_HANDLER_ARGS) +{ + int err, val; + + if (sndstat_dev == NULL) + return (EINVAL); + + mtx_lock(&sndstat_lock); + val = (int)SNDSTAT_PID(sndstat_dev); + mtx_unlock(&sndstat_lock); + err = sysctl_handle_int(oidp, &val, 0, req); + if (err == 0 && req->newptr != NULL && val == 0) { + mtx_lock(&sndstat_lock); + SNDSTAT_FLUSH(); + SNDSTAT_PID_SET(sndstat_dev, 0); + mtx_unlock(&sndstat_lock); + } + return (err); +} +SYSCTL_PROC(_hw_snd, OID_AUTO, sndstat_pid, CTLTYPE_INT | CTLFLAG_RW, + 0, sizeof(int), sysctl_hw_snd_sndstat_pid, "I", "sndstat busy pid"); #endif static int sndstat_prepare(struct sbuf *s); @@ -85,98 +117,96 @@ static int sysctl_hw_sndverbose(SYSCTL_HANDLER_ARGS) { - intrmask_t s; int error, verbose; - verbose = sndstat_verbose; - error = sysctl_handle_int(oidp, &verbose, sizeof(verbose), req); + verbose = snd_verbose; + error = sysctl_handle_int(oidp, &verbose, 0, req); if (error == 0 && req->newptr != NULL) { - s = spltty(); - sx_xlock(&sndstat_lock); - if (verbose < 0 || verbose > 3) + mtx_lock(&sndstat_lock); + if (verbose < 0 || verbose > 4) error = EINVAL; else - sndstat_verbose = verbose; - sx_xunlock(&sndstat_lock); - splx(s); + snd_verbose = verbose; + mtx_unlock(&sndstat_lock); } return error; } SYSCTL_PROC(_hw_snd, OID_AUTO, verbose, CTLTYPE_INT | CTLFLAG_RW, - 0, sizeof(int), sysctl_hw_sndverbose, "I", ""); + 0, sizeof(int), sysctl_hw_sndverbose, "I", "verbosity level"); static int sndstat_open(struct cdev *i_dev, int flags, int mode, struct thread *td) { - intrmask_t s; - int error; + if (sndstat_dev == NULL || i_dev != sndstat_dev) + return EBADF; - s = spltty(); - sx_xlock(&sndstat_lock); - if (sndstat_isopen) { - sx_xunlock(&sndstat_lock); - splx(s); + mtx_lock(&sndstat_lock); + if (SNDSTAT_PID(i_dev) != 0) { + mtx_unlock(&sndstat_lock); return EBUSY; } - sndstat_isopen = 1; - sx_xunlock(&sndstat_lock); - splx(s); - if (sbuf_new(&sndstat_sbuf, NULL, 4096, 0) == NULL) { - error = ENXIO; - goto out; + SNDSTAT_PID_SET(i_dev, td->td_proc->p_pid); + mtx_unlock(&sndstat_lock); + if (sbuf_new(&sndstat_sbuf, NULL, 4096, SBUF_AUTOEXTEND) == NULL) { + mtx_lock(&sndstat_lock); + SNDSTAT_PID_SET(i_dev, 0); + mtx_unlock(&sndstat_lock); + return ENXIO; } sndstat_bufptr = 0; - error = (sndstat_prepare(&sndstat_sbuf) > 0) ? 0 : ENOMEM; -out: - if (error) { - s = spltty(); - sx_xlock(&sndstat_lock); - sndstat_isopen = 0; - sx_xunlock(&sndstat_lock); - splx(s); - } - return (error); + return 0; } static int sndstat_close(struct cdev *i_dev, int flags, int mode, struct thread *td) { - intrmask_t s; + if (sndstat_dev == NULL || i_dev != sndstat_dev) + return EBADF; - s = spltty(); - sx_xlock(&sndstat_lock); - if (!sndstat_isopen) { - sx_xunlock(&sndstat_lock); - splx(s); + mtx_lock(&sndstat_lock); + if (SNDSTAT_PID(i_dev) == 0) { + mtx_unlock(&sndstat_lock); return EBADF; } - sbuf_delete(&sndstat_sbuf); - sndstat_isopen = 0; - sx_xunlock(&sndstat_lock); - splx(s); + SNDSTAT_FLUSH(); + SNDSTAT_PID_SET(i_dev, 0); + + mtx_unlock(&sndstat_lock); + return 0; } static int sndstat_read(struct cdev *i_dev, struct uio *buf, int flag) { - intrmask_t s; int l, err; - s = spltty(); - sx_xlock(&sndstat_lock); - if (!sndstat_isopen) { - sx_xunlock(&sndstat_lock); - splx(s); + if (sndstat_dev == NULL || i_dev != sndstat_dev) + return EBADF; + + mtx_lock(&sndstat_lock); + if (SNDSTAT_PID(i_dev) != buf->uio_td->td_proc->p_pid || + sndstat_bufptr == -1) { + mtx_unlock(&sndstat_lock); return EBADF; } + mtx_unlock(&sndstat_lock); + + if (sndstat_bufptr == 0) { + err = (sndstat_prepare(&sndstat_sbuf) > 0) ? 0 : ENOMEM; + if (err) { + mtx_lock(&sndstat_lock); + SNDSTAT_FLUSH(); + mtx_unlock(&sndstat_lock); + return err; + } + } + l = min(buf->uio_resid, sbuf_len(&sndstat_sbuf) - sndstat_bufptr); err = (l > 0)? uiomove(sbuf_data(&sndstat_sbuf) + sndstat_bufptr, l, buf) : 0; sndstat_bufptr += l; - sx_xunlock(&sndstat_lock); - splx(s); return err; } @@ -196,9 +226,40 @@ } int +sndstat_acquire(struct thread *td) +{ + if (sndstat_dev == NULL) + return EBADF; + + mtx_lock(&sndstat_lock); + if (SNDSTAT_PID(sndstat_dev) != 0) { + mtx_unlock(&sndstat_lock); + return EBUSY; + } + SNDSTAT_PID_SET(sndstat_dev, td->td_proc->p_pid); + mtx_unlock(&sndstat_lock); + return 0; +} + +int +sndstat_release(struct thread *td) +{ + if (sndstat_dev == NULL) + return EBADF; + + mtx_lock(&sndstat_lock); + if (SNDSTAT_PID(sndstat_dev) != td->td_proc->p_pid) { + mtx_unlock(&sndstat_lock); + return EBADF; + } + SNDSTAT_PID_SET(sndstat_dev, 0); + mtx_unlock(&sndstat_lock); + return 0; +} + +int sndstat_register(device_t dev, char *str, sndstat_handler handler) { - intrmask_t s; struct sndstat_entry *ent; const char *devtype; int type, unit; @@ -219,24 +280,19 @@ unit = -1; } - ent = malloc(sizeof *ent, M_DEVBUF, M_ZERO | M_WAITOK); - if (!ent) - return ENOSPC; - + ent = malloc(sizeof *ent, M_DEVBUF, M_WAITOK | M_ZERO); ent->dev = dev; ent->str = str; ent->type = type; ent->unit = unit; ent->handler = handler; - s = spltty(); - sx_xlock(&sndstat_lock); + mtx_lock(&sndstat_lock); SLIST_INSERT_HEAD(&sndstat_devlist, ent, link); if (type == SS_TYPE_MODULE) sndstat_files++; sndstat_maxunit = (unit > sndstat_maxunit)? unit : sndstat_maxunit; - sx_xunlock(&sndstat_lock); - splx(s); + mtx_unlock(&sndstat_lock); return 0; } @@ -250,23 +306,19 @@ int sndstat_unregister(device_t dev) { - intrmask_t s; struct sndstat_entry *ent; - s = spltty(); - sx_xlock(&sndstat_lock); + mtx_lock(&sndstat_lock); SLIST_FOREACH(ent, &sndstat_devlist, link) { if (ent->dev == dev) { SLIST_REMOVE(&sndstat_devlist, ent, sndstat_entry, link); - sx_xunlock(&sndstat_lock); - splx(s); + mtx_unlock(&sndstat_lock); free(ent, M_DEVBUF); return 0; } } - sx_xunlock(&sndstat_lock); - splx(s); + mtx_unlock(&sndstat_lock); return ENXIO; } @@ -274,24 +326,20 @@ int sndstat_unregisterfile(char *str) { - intrmask_t s; struct sndstat_entry *ent; - s = spltty(); - sx_xlock(&sndstat_lock); + mtx_lock(&sndstat_lock); SLIST_FOREACH(ent, &sndstat_devlist, link) { if (ent->dev == NULL && ent->str == str) { SLIST_REMOVE(&sndstat_devlist, ent, sndstat_entry, link); sndstat_files--; - sx_xunlock(&sndstat_lock); - splx(s); + mtx_unlock(&sndstat_lock); free(ent, M_DEVBUF); return 0; } } - sx_xunlock(&sndstat_lock); - splx(s); + mtx_unlock(&sndstat_lock); return ENXIO; } @@ -302,9 +350,11 @@ sndstat_prepare(struct sbuf *s) { struct sndstat_entry *ent; + struct snddev_info *d; int i, j; - sbuf_printf(s, "FreeBSD Audio Driver (newpcm)\n"); + sbuf_printf(s, "FreeBSD Audio Driver (newpcm: %ubit %d/%s)\n", + (u_int)sizeof(intpcm32_t) << 3, SND_DRV_VERSION, MACHINE_ARCH); if (SLIST_EMPTY(&sndstat_devlist)) { sbuf_printf(s, "No devices installed.\n"); sbuf_finish(s); @@ -318,18 +368,25 @@ ent = sndstat_find(j, i); if (!ent) continue; + d = device_get_softc(ent->dev); + if (!PCM_REGISTERED(d)) + continue; + /* XXX Need Giant magic entry ??? */ + PCM_ACQUIRE_QUICK(d); sbuf_printf(s, "%s:", device_get_nameunit(ent->dev)); sbuf_printf(s, " <%s>", device_get_desc(ent->dev)); - sbuf_printf(s, " %s", ent->str); + sbuf_printf(s, " %s [%s]", ent->str, + (d->flags & SD_F_MPSAFE) ? "MPSAFE" : "GIANT"); if (ent->handler) - ent->handler(s, ent->dev, sndstat_verbose); + ent->handler(s, ent->dev, snd_verbose); else sbuf_printf(s, " [no handler]"); sbuf_printf(s, "\n"); + PCM_RELEASE_QUICK(d); } } - if (sndstat_verbose >= 3 && sndstat_files > 0) { + if (snd_verbose >= 3 && sndstat_files > 0) { sbuf_printf(s, "\nFile Versions:\n"); SLIST_FOREACH(ent, &sndstat_devlist, link) { @@ -345,39 +402,35 @@ static int sndstat_init(void) { - sx_init(&sndstat_lock, "sndstat"); - sndstat_dev = make_dev(&sndstat_cdevsw, SND_DEV_STATUS, UID_ROOT, GID_WHEEL, 0444, "sndstat"); - - return (sndstat_dev != 0)? 0 : ENXIO; + if (sndstat_dev != NULL) + return EINVAL; + mtx_init(&sndstat_lock, "sndstat", "sndstat lock", MTX_DEF); + sndstat_dev = make_dev(&sndstat_cdevsw, SND_DEV_STATUS, + UID_ROOT, GID_WHEEL, 0444, "sndstat"); + return 0; } static int sndstat_uninit(void) { - intrmask_t s; + if (sndstat_dev == NULL) + return EINVAL; - s = spltty(); - sx_xlock(&sndstat_lock); - if (sndstat_isopen) { - sx_xunlock(&sndstat_lock); - splx(s); + mtx_lock(&sndstat_lock); + if (SNDSTAT_PID(sndstat_dev) != curthread->td_proc->p_pid) { + mtx_unlock(&sndstat_lock); return EBUSY; } - if (sndstat_dev) - destroy_dev(sndstat_dev); - sndstat_dev = 0; - - splx(s); - sx_xunlock(&sndstat_lock); - sx_destroy(&sndstat_lock); - return 0; -} + SNDSTAT_FLUSH(); -int -sndstat_busy(void) -{ - return (sndstat_isopen); + mtx_unlock(&sndstat_lock); + + destroy_dev(sndstat_dev); + sndstat_dev = NULL; + + mtx_destroy(&sndstat_lock); + return 0; } static void @@ -397,5 +450,3 @@ SYSINIT(sndstat_sysinit, SI_SUB_DRIVERS, SI_ORDER_FIRST, sndstat_sysinit, NULL); SYSUNINIT(sndstat_sysuninit, SI_SUB_DRIVERS, SI_ORDER_FIRST, sndstat_sysuninit, NULL); - - --- sys/dev/sound/pcm/sndstat.h.orig Thu Jan 1 07:30:00 1970 +++ sys/dev/sound/pcm/sndstat.h Thu Jul 12 12:04:19 2007 @@ -0,0 +1,147 @@ +/*- + * 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$ + */ + +#ifndef _SND_SNDSTAT_H_ +#define _SND_SNDSTAT_H_ + +#define SNDSTAT_PREPARE_PCM_ARGS \ + struct sbuf *s, device_t dev, int verbose + +#define SNDSTAT_PREPARE_PCM_BEGIN() do { \ + struct snddev_info *d; \ + struct pcm_channel *c; \ + struct pcm_feeder *f; \ + \ + if (verbose < 1) \ + return (0); \ + \ + d = device_get_softc(dev); \ + PCM_BUSYASSERT(d); \ + \ + if (CHN_EMPTY(d, channels.pcm)) { \ + sbuf_printf(s, " (mixer only)"); \ + return (0); \ + } \ + \ + sbuf_printf(s, " (%dp:%dv/%dr:%dv channels %splex%s)", \ + d->playcount, d->pvchancount, d->reccount, d->rvchancount, \ + (d->flags & SD_F_SIMPLEX) ? "sim" : "du", \ + (device_get_unit(dev) == snd_unit) ? " default" : "") + + +#define SNDSTAT_PREPARE_PCM_END() \ + if (verbose <= 1) \ + return (0); \ + \ + CHN_FOREACH(c, d, channels.pcm) { \ + \ + KASSERT(c->bufhard != NULL && c->bufsoft != NULL, \ + ("hosed pcm channel setup")); \ + \ + sbuf_printf(s, "\n\t"); \ + \ + sbuf_printf(s, "%s[%s]: ", \ + (c->parentchannel != NULL) ? \ + c->parentchannel->name : "", c->name); \ + sbuf_printf(s, "spd %d", c->speed); \ + if (c->speed != sndbuf_getspd(c->bufhard)) \ + sbuf_printf(s, "/%d", \ + sndbuf_getspd(c->bufhard)); \ + sbuf_printf(s, ", fmt 0x%08x", c->format); \ + if (c->format != sndbuf_getfmt(c->bufhard)) \ + sbuf_printf(s, "/0x%08x", \ + sndbuf_getfmt(c->bufhard)); \ + sbuf_printf(s, ", flags 0x%08x, 0x%08x", \ + c->flags, c->feederflags); \ + if (c->pid != -1) \ + sbuf_printf(s, ", pid %d (%s)", \ + c->pid, c->comm); \ + sbuf_printf(s, "\n\t"); \ + \ + sbuf_printf(s, "interrupts %d, ", c->interrupts); \ + \ + if (c->direction == PCMDIR_REC) \ + sbuf_printf(s, \ + "overruns %d, feed %u, hfree %d, " \ + "sfree %d [b:%d/%d/%d|bs:%d/%d/%d]", \ + c->xruns, c->feedcount, \ + sndbuf_getfree(c->bufhard), \ + sndbuf_getfree(c->bufsoft), \ + sndbuf_getsize(c->bufhard), \ + sndbuf_getblksz(c->bufhard), \ + sndbuf_getblkcnt(c->bufhard), \ + sndbuf_getsize(c->bufsoft), \ + sndbuf_getblksz(c->bufsoft), \ + sndbuf_getblkcnt(c->bufsoft)); \ + else \ + sbuf_printf(s, \ + "underruns %d, feed %u, ready %d " \ + "[b:%d/%d/%d|bs:%d/%d/%d]", \ + c->xruns, c->feedcount, \ + sndbuf_getready(c->bufsoft), \ + sndbuf_getsize(c->bufhard), \ + sndbuf_getblksz(c->bufhard), \ + sndbuf_getblkcnt(c->bufhard), \ + sndbuf_getsize(c->bufsoft), \ + sndbuf_getblksz(c->bufsoft), \ + sndbuf_getblkcnt(c->bufsoft)); \ + sbuf_printf(s, "\n\t"); \ + \ + sbuf_printf(s, "{%s}", \ + (c->direction == PCMDIR_REC) ? "hardware" : \ + "userland"); \ + sbuf_printf(s, " -> "); \ + f = c->feeder; \ + while (f->source != NULL) \ + f = f->source; \ + while (f != NULL) { \ + sbuf_printf(s, "%s", f->class->name); \ + if (f->desc->type == FEEDER_FMT) \ + sbuf_printf(s, "(0x%08x -> 0x%08x)", \ + f->desc->in, f->desc->out); \ + if (f->desc->type == FEEDER_RATE) \ + sbuf_printf(s, "(%d -> %d)", \ + FEEDER_GET(f, FEEDRATE_SRC), \ + FEEDER_GET(f, FEEDRATE_DST)); \ + if (f->desc->type == FEEDER_ROOT || \ + f->desc->type == FEEDER_MIXER || \ + f->desc->type == FEEDER_VOLUME) \ + sbuf_printf(s, "(0x%08x)", \ + f->desc->out); \ + sbuf_printf(s, " -> "); \ + f = f->parent; \ + } \ + sbuf_printf(s, "{%s}", \ + (c->direction == PCMDIR_REC) ? "userland" : \ + "hardware"); \ + } \ + \ + return (0); \ +} while(0) + +#endif /* !_SND_SNDSTAT_H_ */ --- sys/dev/sound/pcm/sound.c.orig Sun Jan 30 09:00:05 2005 +++ sys/dev/sound/pcm/sound.c Thu Jul 12 12:04:19 2007 @@ -1,6 +1,6 @@ /*- * Copyright (c) 1999 Cameron Grant - * (C) 1997 Luigi Rizzo (luigi@iet.unipi.it) + * (C) 1997 Luigi Rizzo * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -26,44 +26,59 @@ */ #include +#include #include #include +#include +#include +#include #include #include "feeder_if.h" -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pcm/sound.c,v 1.92.2.1 2005/01/30 01:00:05 imp Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pcm/sound.c,v 1.119 2007/06/17 19:02:05 ariff Exp $"); devclass_t pcm_devclass; int pcm_veto_load = 1; #ifdef USING_DEVFS -int snd_unit = 0; -TUNABLE_INT("hw.snd.unit", &snd_unit); +int snd_unit = -1; +TUNABLE_INT("hw.snd.default_unit", &snd_unit); #endif -int snd_maxautovchans = 0; +static int snd_unit_auto = 0; +TUNABLE_INT("hw.snd.default_auto", &snd_unit_auto); +SYSCTL_INT(_hw_snd, OID_AUTO, default_auto, CTLFLAG_RW, + &snd_unit_auto, 0, "assign default unit to a newly attached device"); + +int snd_maxautovchans = 16; +/* XXX: a tunable implies that we may need more than one sound channel before + the system can change a sysctl (/etc/sysctl.conf), do we really need + this? */ TUNABLE_INT("hw.snd.maxautovchans", &snd_maxautovchans); SYSCTL_NODE(_hw, OID_AUTO, snd, CTLFLAG_RD, 0, "Sound driver"); -static int sndstat_prepare_pcm(struct sbuf *s, device_t dev, int verbose); - -struct sysctl_ctx_list * -snd_sysctl_tree(device_t dev) -{ - struct snddev_info *d = device_get_softc(dev); +/* + * XXX I've had enough with people not telling proper version/arch + * while reporting problems, not after 387397913213th questions/requests. + */ +static const char snd_driver_version[] = + __XSTRING(SND_DRV_VERSION)"/"MACHINE_ARCH; +SYSCTL_STRING(_hw_snd, OID_AUTO, version, CTLFLAG_RD, &snd_driver_version, + 0, "driver version/arch"); - return &d->sysctl_tree; -} +/** + * @brief Unit number allocator for syncgroup IDs + */ +struct unrhdr *pcmsg_unrhdr = NULL; -struct sysctl_oid * -snd_sysctl_tree_top(device_t dev) +static int +sndstat_prepare_pcm(SNDSTAT_PREPARE_PCM_ARGS) { - struct snddev_info *d = device_get_softc(dev); - - return d->sysctl_tree_top; + SNDSTAT_PREPARE_PCM_BEGIN(); + SNDSTAT_PREPARE_PCM_END(); } void * @@ -73,8 +88,6 @@ struct mtx *m; m = malloc(sizeof(*m), M_DEVBUF, M_WAITOK | M_ZERO); - if (m == NULL) - return NULL; mtx_init(m, desc, type, MTX_DEF); return m; #else @@ -129,13 +142,22 @@ int snd_setup_intr(device_t dev, struct resource *res, int flags, driver_intr_t hand, void *param, void **cookiep) { + struct snddev_info *d; #ifdef USING_MUTEX flags &= INTR_MPSAFE; flags |= INTR_TYPE_AV; #else flags = INTR_TYPE_AV; #endif - return bus_setup_intr(dev, res, flags, hand, param, cookiep); + d = device_get_softc(dev); + if (d != NULL && (flags & INTR_MPSAFE)) + d->flags |= SD_F_MPSAFE; + + return bus_setup_intr(dev, res, flags, +#if __FreeBSD_version >= 700031 + NULL, +#endif + hand, param, cookiep); } #ifndef PCM_DEBUG_MTX @@ -158,157 +180,299 @@ return d->fakechan; } -/* return a locked channel */ -struct pcm_channel * -pcm_chnalloc(struct snddev_info *d, int direction, pid_t pid, int chnum) +static void +pcm_clonereset(struct snddev_info *d) +{ + int cmax; + + PCM_BUSYASSERT(d); + + cmax = d->playcount + d->reccount - 1; + if (d->pvchancount > 0) + cmax += MAX(d->pvchancount, snd_maxautovchans) - 1; + if (d->rvchancount > 0) + cmax += MAX(d->rvchancount, snd_maxautovchans) - 1; + if (cmax > PCMMAXCLONE) + cmax = PCMMAXCLONE; + (void)snd_clone_gc(d->clones); + (void)snd_clone_setmaxunit(d->clones, cmax); +} + +static int +pcm_setvchans(struct snddev_info *d, int direction, int newcnt, int num) +{ + struct pcm_channel *c, *ch, *nch; + int err, vcnt; + + PCM_BUSYASSERT(d); + + if ((direction == PCMDIR_PLAY && d->playcount < 1) || + (direction == PCMDIR_REC && d->reccount < 1)) + return (ENODEV); + + if (!(d->flags & SD_F_AUTOVCHAN)) + return (EINVAL); + + if (newcnt < 0 || newcnt > SND_MAXVCHANS) + return (E2BIG); + + if (direction == PCMDIR_PLAY) + vcnt = d->pvchancount; + else if (direction == PCMDIR_REC) + vcnt = d->rvchancount; + else + return (EINVAL); + + if (newcnt > vcnt) { + KASSERT(num == -1 || + (num >= 0 && num < SND_MAXVCHANS && (newcnt - 1) == vcnt), + ("bogus vchan_create() request num=%d newcnt=%d vcnt=%d", + num, newcnt, vcnt)); + /* add new vchans - find a parent channel first */ + ch = NULL; + CHN_FOREACH(c, d, channels.pcm) { + CHN_LOCK(c); + if (c->direction == direction && + ((c->flags & CHN_F_HAS_VCHAN) || (vcnt == 0 && + c->refcount < 1 && + !(c->flags & (CHN_F_BUSY | CHN_F_VIRTUAL))))) { + ch = c; + break; + } + CHN_UNLOCK(c); + } + if (ch == NULL) + return (EBUSY); + ch->flags |= CHN_F_BUSY; + err = 0; + while (err == 0 && newcnt > vcnt) { + err = vchan_create(ch, num); + if (err == 0) + vcnt++; + else if (err == E2BIG && newcnt > vcnt) + device_printf(d->dev, + "%s: err=%d Maximum channel reached.\n", + __func__, err); + } + if (vcnt == 0) + ch->flags &= ~CHN_F_BUSY; + CHN_UNLOCK(ch); + if (err != 0) + return (err); + else + pcm_clonereset(d); + } else if (newcnt < vcnt) { + KASSERT(num == -1, + ("bogus vchan_destroy() request num=%d", num)); + CHN_FOREACH(c, d, channels.pcm) { + CHN_LOCK(c); + if (c->direction != direction || + CHN_EMPTY(c, children) || + !(c->flags & CHN_F_HAS_VCHAN)) { + CHN_UNLOCK(c); + continue; + } + CHN_FOREACH_SAFE(ch, c, nch, children) { + CHN_LOCK(ch); + if (vcnt == 1 && c->refcount > 0) { + CHN_UNLOCK(ch); + break; + } if (!(ch->flags & CHN_F_BUSY) && + ch->refcount < 1) { + err = vchan_destroy(ch); + if (err == 0) + vcnt--; + } else + CHN_UNLOCK(ch); + if (vcnt == newcnt) + break; + } + CHN_UNLOCK(c); + break; + } + pcm_clonereset(d); + } + + return (0); +} + +/* return error status and a locked channel */ +int +pcm_chnalloc(struct snddev_info *d, struct pcm_channel **ch, int direction, + pid_t pid, char *comm, int devunit) { struct pcm_channel *c; - struct snddev_channel *sce; - int err; + int err, vchancount, vchan_num; - snd_mtxassert(d->lock); + KASSERT(d != NULL && ch != NULL && (devunit == -1 || + !(devunit & ~(SND_U_MASK | SND_D_MASK | SND_C_MASK))) && + (direction == PCMDIR_PLAY || direction == PCMDIR_REC), + ("%s(): invalid d=%p ch=%p direction=%d pid=%d devunit=%d", + __func__, d, ch, direction, pid, devunit)); + PCM_BUSYASSERT(d); + + /* Double check again. */ + if (devunit != -1) { + switch (snd_unit2d(devunit)) { + case SND_DEV_DSPHW_PLAY: + case SND_DEV_DSPHW_VPLAY: + if (direction != PCMDIR_PLAY) + return (EOPNOTSUPP); + break; + case SND_DEV_DSPHW_REC: + case SND_DEV_DSPHW_VREC: + if (direction != PCMDIR_REC) + return (EOPNOTSUPP); + break; + default: + if (!(direction == PCMDIR_PLAY || + direction == PCMDIR_REC)) + return (EOPNOTSUPP); + break; + } + } + + *ch = NULL; + vchan_num = 0; + vchancount = (direction == PCMDIR_PLAY) ? d->pvchancount : + d->rvchancount; +retry_chnalloc: + err = EOPNOTSUPP; /* scan for a free channel */ - SLIST_FOREACH(sce, &d->channels, link) { - c = sce->channel; + CHN_FOREACH(c, d, channels.pcm) { CHN_LOCK(c); - if ((c->direction == direction) && !(c->flags & CHN_F_BUSY)) { - if (chnum == -1 || c->num == chnum) { - c->flags |= CHN_F_BUSY; - c->pid = pid; - return c; + if (devunit == -1 && c->direction == direction && + (c->flags & CHN_F_VIRTUAL)) { + if (vchancount < snd_maxautovchans && + vchan_num < CHN_CHAN(c)) { + CHN_UNLOCK(c); + goto vchan_alloc; } + vchan_num++; } + if (c->direction == direction && !(c->flags & CHN_F_BUSY) && + (devunit == -1 || devunit == -2 || c->unit == devunit)) { + c->flags |= CHN_F_BUSY; + c->pid = pid; + strlcpy(c->comm, (comm != NULL) ? comm : + CHN_COMM_UNKNOWN, sizeof(c->comm)); + *ch = c; + return (0); + } else if (c->unit == devunit) { + if (c->direction != direction) + err = EOPNOTSUPP; + else if (c->flags & CHN_F_BUSY) + err = EBUSY; + else + err = EINVAL; + CHN_UNLOCK(c); + return (err); + } else if ((devunit == -1 || devunit == -2) && + c->direction == direction && (c->flags & CHN_F_BUSY)) + err = EBUSY; CHN_UNLOCK(c); } + if (devunit == -2) + return (err); + +vchan_alloc: /* no channel available */ - if (direction == PCMDIR_PLAY) { - if ((d->vchancount > 0) && (d->vchancount < snd_maxautovchans)) { - /* try to create a vchan */ - SLIST_FOREACH(sce, &d->channels, link) { - c = sce->channel; - CHN_LOCK(c); - if (!SLIST_EMPTY(&c->children)) { - err = vchan_create(c); - CHN_UNLOCK(c); - if (!err) - return pcm_chnalloc(d, direction, pid, -1); - else - device_printf(d->dev, "vchan_create(%s) == %d\n", c->name, err); - } else - CHN_UNLOCK(c); - } + if (devunit == -1 || snd_unit2d(devunit) == SND_DEV_DSPHW_VPLAY || + snd_unit2d(devunit) == SND_DEV_DSPHW_VREC) { + if (!(vchancount > 0 && vchancount < snd_maxautovchans) && + (devunit == -1 || snd_unit2c(devunit) < snd_maxautovchans)) + return (err); + err = pcm_setvchans(d, direction, vchancount + 1, + (devunit == -1) ? -1 : snd_unit2c(devunit)); + if (err == 0) { + if (devunit == -1) + devunit = -2; + goto retry_chnalloc; } } - return NULL; + return (err); } /* release a locked channel and unlock it */ int pcm_chnrelease(struct pcm_channel *c) { + PCM_BUSYASSERT(c->parentsnddev); CHN_LOCKASSERT(c); + c->flags &= ~CHN_F_BUSY; c->pid = -1; + strlcpy(c->comm, CHN_COMM_UNUSED, sizeof(c->comm)); CHN_UNLOCK(c); - return 0; + + return (0); } int pcm_chnref(struct pcm_channel *c, int ref) { - int r; - + PCM_BUSYASSERT(c->parentsnddev); CHN_LOCKASSERT(c); + c->refcount += ref; - r = c->refcount; - return r; + + return (c->refcount); } int pcm_inprog(struct snddev_info *d, int delta) { - int r; - - if (delta == 0) - return d->inprog; + snd_mtxassert(d->lock); - /* backtrace(); */ - pcm_lock(d); d->inprog += delta; - r = d->inprog; - pcm_unlock(d); - return r; + + return (d->inprog); } static void pcm_setmaxautovchans(struct snddev_info *d, int num) { - struct pcm_channel *c; - struct snddev_channel *sce; - int err, done; + PCM_BUSYASSERT(d); - if (num > 0 && d->vchancount == 0) { - SLIST_FOREACH(sce, &d->channels, link) { - c = sce->channel; - CHN_LOCK(c); - if ((c->direction == PCMDIR_PLAY) && !(c->flags & CHN_F_BUSY)) { - c->flags |= CHN_F_BUSY; - err = vchan_create(c); - if (err) { - c->flags &= ~CHN_F_BUSY; - CHN_UNLOCK(c); - device_printf(d->dev, "vchan_create(%s) == %d\n", c->name, err); - } else - CHN_UNLOCK(c); - return; - } - CHN_UNLOCK(c); - } - } - if (num == 0 && d->vchancount > 0) { - done = 0; - while (!done) { - done = 1; - SLIST_FOREACH(sce, &d->channels, link) { - c = sce->channel; - if ((c->flags & CHN_F_VIRTUAL) && !(c->flags & CHN_F_BUSY)) { - done = 0; - snd_mtxlock(d->lock); - err = vchan_destroy(c); - snd_mtxunlock(d->lock); - if (err) - device_printf(d->dev, "vchan_destroy(%s) == %d\n", c->name, err); - break; /* restart */ - } - } - } - } + if (num < 0) + return; + + if (num >= 0 && d->pvchancount > num) + (void)pcm_setvchans(d, PCMDIR_PLAY, num, -1); + else if (num > 0 && d->pvchancount == 0) + (void)pcm_setvchans(d, PCMDIR_PLAY, 1, -1); + + if (num >= 0 && d->rvchancount > num) + (void)pcm_setvchans(d, PCMDIR_REC, num, -1); + else if (num > 0 && d->rvchancount == 0) + (void)pcm_setvchans(d, PCMDIR_REC, 1, -1); + + pcm_clonereset(d); } #ifdef USING_DEVFS static int -sysctl_hw_snd_unit(SYSCTL_HANDLER_ARGS) +sysctl_hw_snd_default_unit(SYSCTL_HANDLER_ARGS) { struct snddev_info *d; int error, unit; unit = snd_unit; - error = sysctl_handle_int(oidp, &unit, sizeof(unit), req); + error = sysctl_handle_int(oidp, &unit, 0, req); if (error == 0 && req->newptr != NULL) { - if (unit < 0 || unit >= devclass_get_maxunit(pcm_devclass)) - return EINVAL; d = devclass_get_softc(pcm_devclass, unit); - if (d == NULL || SLIST_EMPTY(&d->channels)) + if (!PCM_REGISTERED(d) || CHN_EMPTY(d, channels.pcm)) return EINVAL; snd_unit = unit; } return (error); } -SYSCTL_PROC(_hw_snd, OID_AUTO, unit, CTLTYPE_INT | CTLFLAG_RW, - 0, sizeof(int), sysctl_hw_snd_unit, "I", ""); +/* XXX: do we need a way to let the user change the default unit? */ +SYSCTL_PROC(_hw_snd, OID_AUTO, default_unit, CTLTYPE_INT | CTLFLAG_RW, + 0, sizeof(int), sysctl_hw_snd_default_unit, "I", "default sound device"); #endif static int @@ -318,89 +482,140 @@ int i, v, error; v = snd_maxautovchans; - error = sysctl_handle_int(oidp, &v, sizeof(v), req); + error = sysctl_handle_int(oidp, &v, 0, req); if (error == 0 && req->newptr != NULL) { - if (v < 0 || v >= SND_MAXVCHANS || pcm_devclass == NULL) - return EINVAL; - if (v != snd_maxautovchans) { - for (i = 0; i < devclass_get_maxunit(pcm_devclass); i++) { - d = devclass_get_softc(pcm_devclass, i); - if (!d) - continue; - pcm_setmaxautovchans(d, v); - } - } + if (v < 0) + v = 0; + if (v > SND_MAXVCHANS) + v = SND_MAXVCHANS; snd_maxautovchans = v; + for (i = 0; pcm_devclass != NULL && + i < devclass_get_maxunit(pcm_devclass); i++) { + d = devclass_get_softc(pcm_devclass, i); + if (!PCM_REGISTERED(d)) + continue; + PCM_ACQUIRE_QUICK(d); + pcm_setmaxautovchans(d, v); + PCM_RELEASE_QUICK(d); + } } return (error); } SYSCTL_PROC(_hw_snd, OID_AUTO, maxautovchans, CTLTYPE_INT | CTLFLAG_RW, - 0, sizeof(int), sysctl_hw_snd_maxautovchans, "I", ""); + 0, sizeof(int), sysctl_hw_snd_maxautovchans, "I", "maximum virtual channel"); struct pcm_channel * -pcm_chn_create(struct snddev_info *d, struct pcm_channel *parent, kobj_class_t cls, int dir, void *devinfo) +pcm_chn_create(struct snddev_info *d, struct pcm_channel *parent, kobj_class_t cls, int dir, int num, void *devinfo) { struct pcm_channel *ch; - char *dirs; - int direction, err, *pnum; + int direction, err, rpnum, *pnum, max; + int udc, device, chan; + char *dirs, *devname, buf[CHN_NAMELEN]; + + PCM_BUSYASSERT(d); + snd_mtxassert(d->lock); + KASSERT(num >= -1, ("invalid num=%d", num)); + - switch(dir) { + switch (dir) { case PCMDIR_PLAY: dirs = "play"; direction = PCMDIR_PLAY; pnum = &d->playcount; + device = SND_DEV_DSPHW_PLAY; + max = SND_MAXHWCHAN; + break; + case PCMDIR_PLAY_VIRTUAL: + dirs = "virtual"; + direction = PCMDIR_PLAY; + pnum = &d->pvchancount; + device = SND_DEV_DSPHW_VPLAY; + max = SND_MAXVCHANS; break; - case PCMDIR_REC: dirs = "record"; direction = PCMDIR_REC; pnum = &d->reccount; + device = SND_DEV_DSPHW_REC; + max = SND_MAXHWCHAN; break; - - case PCMDIR_VIRTUAL: + case PCMDIR_REC_VIRTUAL: dirs = "virtual"; - direction = PCMDIR_PLAY; - pnum = &d->vchancount; + direction = PCMDIR_REC; + pnum = &d->rvchancount; + device = SND_DEV_DSPHW_VREC; + max = SND_MAXVCHANS; break; - default: - return NULL; + return (NULL); } - ch = malloc(sizeof(*ch), M_DEVBUF, M_WAITOK | M_ZERO); - if (!ch) - return NULL; + chan = (num == -1) ? 0 : num; - ch->methods = kobj_create(cls, M_DEVBUF, M_WAITOK); - if (!ch->methods) { - free(ch, M_DEVBUF); + if (*pnum >= max || chan >= max) + return (NULL); + + rpnum = 0; + + CHN_FOREACH(ch, d, channels.pcm) { + if (CHN_DEV(ch) != device) + continue; + if (chan == CHN_CHAN(ch)) { + if (num != -1) { + device_printf(d->dev, + "channel num=%d allocated!\n", chan); + return (NULL); + } + chan++; + if (chan >= max) { + device_printf(d->dev, + "chan=%d > %d\n", chan, max); + return (NULL); + } + } + rpnum++; + } - return NULL; + if (*pnum != rpnum) { + device_printf(d->dev, + "%s(): WARNING: pnum screwed : dirs=%s pnum=%d rpnum=%d\n", + __func__, dirs, *pnum, rpnum); + return (NULL); } - snd_mtxlock(d->lock); - ch->num = (*pnum)++; - snd_mtxunlock(d->lock); + udc = snd_mkunit(device_get_unit(d->dev), device, chan); + devname = dsp_unit2name(buf, sizeof(buf), udc); + if (devname == NULL) { + device_printf(d->dev, + "Failed to query device name udc=0x%08x\n", udc); + return (NULL); + } + + pcm_unlock(d); + ch = malloc(sizeof(*ch), M_DEVBUF, M_WAITOK | M_ZERO); + ch->methods = kobj_create(cls, M_DEVBUF, M_WAITOK | M_ZERO); + ch->unit = udc; ch->pid = -1; + strlcpy(ch->comm, CHN_COMM_UNUSED, sizeof(ch->comm)); ch->parentsnddev = d; ch->parentchannel = parent; ch->dev = d->dev; - snprintf(ch->name, 32, "%s:%s:%d", device_get_nameunit(ch->dev), dirs, ch->num); + ch->trigger = PCMTRIG_STOP; + snprintf(ch->name, sizeof(ch->name), "%s:%s:%s", + device_get_nameunit(ch->dev), dirs, devname); err = chn_init(ch, devinfo, dir, direction); + pcm_lock(d); if (err) { - device_printf(d->dev, "chn_init(%s) failed: err = %d\n", ch->name, err); + device_printf(d->dev, "chn_init(%s) failed: err = %d\n", + ch->name, err); kobj_delete(ch->methods, M_DEVBUF); free(ch, M_DEVBUF); - snd_mtxlock(d->lock); - (*pnum)--; - snd_mtxunlock(d->lock); - - return NULL; + return (NULL); } - return ch; + return (ch); } int @@ -410,167 +625,138 @@ int err; d = ch->parentsnddev; + PCM_BUSYASSERT(d); + err = chn_kill(ch); if (err) { - device_printf(d->dev, "chn_kill(%s) failed, err = %d\n", ch->name, err); - return err; + device_printf(ch->dev, "chn_kill(%s) failed, err = %d\n", + ch->name, err); + return (err); } kobj_delete(ch->methods, M_DEVBUF); free(ch, M_DEVBUF); - return 0; + return (0); } int pcm_chn_add(struct snddev_info *d, struct pcm_channel *ch) { - struct snddev_channel *sce, *tmp, *after; - int device = device_get_unit(d->dev); + PCM_BUSYASSERT(d); + snd_mtxassert(d->lock); + KASSERT(ch != NULL && (ch->direction == PCMDIR_PLAY || + ch->direction == PCMDIR_REC), ("Invalid pcm channel")); - /* - * Note it's confusing nomenclature. - * dev_t - * device -> pcm_device - * unit -> pcm_channel - * channel -> snddev_channel - * device_t - * unit -> pcm_device - */ + CHN_INSERT_SORT_ASCEND(d, ch, channels.pcm); - sce = malloc(sizeof(*sce), M_DEVBUF, M_WAITOK | M_ZERO); - if (!sce) { - return ENOMEM; + switch (CHN_DEV(ch)) { + case SND_DEV_DSPHW_PLAY: + d->playcount++; + break; + case SND_DEV_DSPHW_VPLAY: + d->pvchancount++; + break; + case SND_DEV_DSPHW_REC: + d->reccount++; + break; + case SND_DEV_DSPHW_VREC: + d->rvchancount++; + break; + default: + break; } - snd_mtxlock(d->lock); - sce->channel = ch; - sce->chan_num= d->devcount++; - if (SLIST_EMPTY(&d->channels)) { - SLIST_INSERT_HEAD(&d->channels, sce, link); - } else { - after = NULL; - SLIST_FOREACH(tmp, &d->channels, link) { - after = tmp; - } - SLIST_INSERT_AFTER(after, sce, link); - } - snd_mtxunlock(d->lock); - sce->dsp_devt= make_dev(&dsp_cdevsw, - PCMMKMINOR(device, SND_DEV_DSP, sce->chan_num), - UID_ROOT, GID_WHEEL, 0666, "dsp%d.%d", - device, sce->chan_num); - - sce->dspW_devt= make_dev(&dsp_cdevsw, - PCMMKMINOR(device, SND_DEV_DSP16, sce->chan_num), - UID_ROOT, GID_WHEEL, 0666, "dspW%d.%d", - device, sce->chan_num); - - sce->audio_devt= make_dev(&dsp_cdevsw, - PCMMKMINOR(device, SND_DEV_AUDIO, sce->chan_num), - UID_ROOT, GID_WHEEL, 0666, "audio%d.%d", - device, sce->chan_num); - - if (ch->direction == PCMDIR_REC) - sce->dspr_devt = make_dev(&dsp_cdevsw, - PCMMKMINOR(device, SND_DEV_DSPREC, - sce->chan_num), UID_ROOT, GID_WHEEL, - 0666, "dspr%d.%d", device, sce->chan_num); + d->devcount++; - return 0; + return (0); } int pcm_chn_remove(struct snddev_info *d, struct pcm_channel *ch) { - struct snddev_channel *sce; -#if 0 - int ourlock; + struct pcm_channel *tmp; - ourlock = 0; - if (!mtx_owned(d->lock)) { - snd_mtxlock(d->lock); - ourlock = 1; - } -#endif + PCM_BUSYASSERT(d); + snd_mtxassert(d->lock); - SLIST_FOREACH(sce, &d->channels, link) { - if (sce->channel == ch) - goto gotit; + tmp = NULL; + + CHN_FOREACH(tmp, d, channels.pcm) { + if (tmp == ch) + break; } -#if 0 - if (ourlock) - snd_mtxunlock(d->lock); -#endif - return EINVAL; -gotit: - SLIST_REMOVE(&d->channels, sce, snddev_channel, link); - if (ch->direction == PCMDIR_REC) - d->reccount--; - else if (ch->flags & CHN_F_VIRTUAL) - d->vchancount--; - else + if (tmp != ch) + return (EINVAL); + + CHN_REMOVE(d, ch, channels.pcm); + + switch (CHN_DEV(ch)) { + case SND_DEV_DSPHW_PLAY: d->playcount--; + break; + case SND_DEV_DSPHW_VPLAY: + d->pvchancount--; + break; + case SND_DEV_DSPHW_REC: + d->reccount--; + break; + case SND_DEV_DSPHW_VREC: + d->rvchancount--; + break; + default: + break; + } -#if 0 - if (ourlock) - snd_mtxunlock(d->lock); -#endif - free(sce, M_DEVBUF); + d->devcount--; - return 0; + return (0); } int pcm_addchan(device_t dev, int dir, kobj_class_t cls, void *devinfo) { - struct snddev_info *d = device_get_softc(dev); + struct snddev_info *d = device_get_softc(dev); struct pcm_channel *ch; - int err; + int err; + + PCM_BUSYASSERT(d); - ch = pcm_chn_create(d, NULL, cls, dir, devinfo); + pcm_lock(d); + ch = pcm_chn_create(d, NULL, cls, dir, -1, devinfo); if (!ch) { - device_printf(d->dev, "pcm_chn_create(%s, %d, %p) failed\n", cls->name, dir, devinfo); - return ENODEV; + device_printf(d->dev, "pcm_chn_create(%s, %d, %p) failed\n", + cls->name, dir, devinfo); + pcm_unlock(d); + return (ENODEV); } err = pcm_chn_add(d, ch); + pcm_unlock(d); if (err) { - device_printf(d->dev, "pcm_chn_add(%s) failed, err=%d\n", ch->name, err); + device_printf(d->dev, "pcm_chn_add(%s) failed, err=%d\n", + ch->name, err); pcm_chn_destroy(ch); - return err; - } - - CHN_LOCK(ch); - if (snd_maxautovchans > 0 && (d->flags & SD_F_AUTOVCHAN) && - ch->direction == PCMDIR_PLAY && d->vchancount == 0) { - ch->flags |= CHN_F_BUSY; - err = vchan_create(ch); - if (err) { - ch->flags &= ~CHN_F_BUSY; - CHN_UNLOCK(ch); - device_printf(d->dev, "vchan_create(%s) == %d\n", ch->name, err); - return err; - } } - CHN_UNLOCK(ch); - return err; + return (err); } static int pcm_killchan(device_t dev) { - struct snddev_info *d = device_get_softc(dev); - struct snddev_channel *sce; + struct snddev_info *d = device_get_softc(dev); struct pcm_channel *ch; - int error = 0; + int error; + + PCM_BUSYASSERT(d); - sce = SLIST_FIRST(&d->channels); - ch = sce->channel; + ch = CHN_FIRST(d, channels.pcm); - error = pcm_chn_remove(d, sce->channel); + pcm_lock(d); + error = pcm_chn_remove(d, ch); + pcm_unlock(d); if (error) return (error); return (pcm_chn_destroy(ch)); @@ -579,26 +765,54 @@ int pcm_setstatus(device_t dev, char *str) { - struct snddev_info *d = device_get_softc(dev); + struct snddev_info *d = device_get_softc(dev); - snd_mtxlock(d->lock); - strncpy(d->status, str, SND_STATUSLEN); - snd_mtxunlock(d->lock); - return 0; + PCM_BUSYASSERT(d); + + if (d->playcount == 0 || d->reccount == 0) + d->flags |= SD_F_SIMPLEX; + + if ((d->playcount > 0 || d->reccount > 0) && + !(d->flags & SD_F_AUTOVCHAN)) { + d->flags |= SD_F_AUTOVCHAN; + vchan_initsys(dev); + } + + pcm_setmaxautovchans(d, snd_maxautovchans); + + strlcpy(d->status, str, SND_STATUSLEN); + + pcm_lock(d); + + /* Last stage, enable cloning. */ + if (d->clones != NULL) + (void)snd_clone_enable(d->clones); + + /* Done, we're ready.. */ + d->flags |= SD_F_REGISTERED; + + PCM_RELEASE(d); + + pcm_unlock(d); + + if (snd_unit < 0 || snd_unit_auto != 0) + snd_unit = device_get_unit(dev); + + return (0); } -u_int32_t +uint32_t pcm_getflags(device_t dev) { - struct snddev_info *d = device_get_softc(dev); + struct snddev_info *d = device_get_softc(dev); return d->flags; } void -pcm_setflags(device_t dev, u_int32_t val) +pcm_setflags(device_t dev, uint32_t val) { - struct snddev_info *d = device_get_softc(dev); + struct snddev_info *d = device_get_softc(dev); d->flags = val; } @@ -606,24 +820,24 @@ void * pcm_getdevinfo(device_t dev) { - struct snddev_info *d = device_get_softc(dev); + struct snddev_info *d = device_get_softc(dev); return d->devinfo; } unsigned int -pcm_getbuffersize(device_t dev, unsigned int min, unsigned int deflt, unsigned int max) +pcm_getbuffersize(device_t dev, unsigned int minbufsz, unsigned int deflt, unsigned int maxbufsz) { - struct snddev_info *d = device_get_softc(dev); + struct snddev_info *d = device_get_softc(dev); int sz, x; sz = 0; if (resource_int_value(device_get_name(dev), device_get_unit(dev), "buffersize", &sz) == 0) { x = sz; - RANGE(sz, min, max); + RANGE(sz, minbufsz, maxbufsz); if (x != sz) - device_printf(dev, "'buffersize=%d' hint is out of range (%d-%d), using %d\n", x, min, max, sz); - x = min; + device_printf(dev, "'buffersize=%d' hint is out of range (%d-%d), using %d\n", x, minbufsz, maxbufsz, sz); + x = minbufsz; while (x < sz) x <<= 1; if (x > sz) @@ -641,10 +855,122 @@ return sz; } +#if defined(SND_DYNSYSCTL) && defined(SND_DEBUG) +static int +sysctl_dev_pcm_clone_flags(SYSCTL_HANDLER_ARGS) +{ + struct snddev_info *d; + uint32_t flags; + int err; + + d = oidp->oid_arg1; + if (!PCM_REGISTERED(d) || d->clones == NULL) + return (ENODEV); + + PCM_ACQUIRE_QUICK(d); + + flags = snd_clone_getflags(d->clones); + err = sysctl_handle_int(oidp, &flags, 0, req); + + if (err == 0 && req->newptr != NULL) { + if (flags & ~SND_CLONE_MASK) + err = EINVAL; + else + (void)snd_clone_setflags(d->clones, flags); + } + + PCM_RELEASE_QUICK(d); + + return (err); +} + +static int +sysctl_dev_pcm_clone_deadline(SYSCTL_HANDLER_ARGS) +{ + struct snddev_info *d; + int err, deadline; + + d = oidp->oid_arg1; + if (!PCM_REGISTERED(d) || d->clones == NULL) + return (ENODEV); + + PCM_ACQUIRE_QUICK(d); + + deadline = snd_clone_getdeadline(d->clones); + err = sysctl_handle_int(oidp, &deadline, 0, req); + + if (err == 0 && req->newptr != NULL) { + if (deadline < 0) + err = EINVAL; + else + (void)snd_clone_setdeadline(d->clones, deadline); + } + + PCM_RELEASE_QUICK(d); + + return (err); +} + +static int +sysctl_dev_pcm_clone_gc(SYSCTL_HANDLER_ARGS) +{ + struct snddev_info *d; + int err, val; + + d = oidp->oid_arg1; + if (!PCM_REGISTERED(d) || d->clones == NULL) + return (ENODEV); + + val = 0; + err = sysctl_handle_int(oidp, &val, 0, req); + + if (err == 0 && req->newptr != NULL && val != 0) { + PCM_ACQUIRE_QUICK(d); + val = snd_clone_gc(d->clones); + PCM_RELEASE_QUICK(d); + if (bootverbose != 0 || snd_verbose > 3) + device_printf(d->dev, "clone gc: pruned=%d\n", val); + } + + return (err); +} + +static int +sysctl_hw_snd_clone_gc(SYSCTL_HANDLER_ARGS) +{ + struct snddev_info *d; + int i, err, val; + + val = 0; + err = sysctl_handle_int(oidp, &val, 0, req); + + if (err == 0 && req->newptr != NULL && val != 0) { + for (i = 0; pcm_devclass != NULL && + i < devclass_get_maxunit(pcm_devclass); i++) { + d = devclass_get_softc(pcm_devclass, i); + if (!PCM_REGISTERED(d) || d->clones == NULL) + continue; + PCM_ACQUIRE_QUICK(d); + val = snd_clone_gc(d->clones); + PCM_RELEASE_QUICK(d); + if (bootverbose != 0 || snd_verbose > 3) + device_printf(d->dev, "clone gc: pruned=%d\n", + val); + } + } + + return (err); +} +SYSCTL_PROC(_hw_snd, OID_AUTO, clone_gc, CTLTYPE_INT | CTLFLAG_RW, + 0, sizeof(int), sysctl_hw_snd_clone_gc, "I", + "global clone garbage collector"); +#endif + int pcm_register(device_t dev, void *devinfo, int numplay, int numrec) { - struct snddev_info *d = device_get_softc(dev); + struct snddev_info *d; + int i; if (pcm_veto_load) { device_printf(dev, "disabled due to an error while initialising: %d\n", pcm_veto_load); @@ -652,335 +978,448 @@ return EINVAL; } - d->lock = snd_mtxcreate(device_get_nameunit(dev), "sound cdev"); + if (device_get_unit(dev) > PCMMAXUNIT) { + device_printf(dev, "PCMMAXUNIT reached : unit=%d > %d\n", + device_get_unit(dev), PCMMAXUNIT); + device_printf(dev, + "Use 'hw.snd.maxunit' tunable to raise the limit.\n"); + return ENODEV; + } - d->flags = 0; + d = device_get_softc(dev); d->dev = dev; + d->lock = snd_mtxcreate(device_get_nameunit(dev), "sound cdev"); + cv_init(&d->cv, device_get_nameunit(dev)); + PCM_ACQUIRE_QUICK(d); + dsp_cdevinfo_init(d); +#if 0 + /* + * d->flags should be cleared by the allocator of the softc. + * We cannot clear this field here because several devices set + * this flag before calling pcm_register(). + */ + d->flags = 0; +#endif + i = 0; + if (resource_int_value(device_get_name(dev), device_get_unit(dev), + "vpc_disabled", &i) != 0 || i == 0) + d->flags |= SD_F_VPC; d->devinfo = devinfo; d->devcount = 0; d->reccount = 0; d->playcount = 0; - d->vchancount = 0; + d->pvchancount = 0; + d->rvchancount = 0; + d->pvchanrate = 0; + d->pvchanformat = 0; + d->rvchanrate = 0; + d->rvchanformat = 0; d->inprog = 0; - SLIST_INIT(&d->channels); - SLIST_INIT(&d->channels); + /* + * Create clone manager, disabled by default. Cloning will be + * enabled during final stage of driver iniialization through + * pcm_setstatus(). + */ + d->clones = snd_clone_create(SND_U_MASK | SND_D_MASK, PCMMAXCLONE, + SND_CLONE_DEADLINE_DEFAULT, SND_CLONE_WAITOK | + SND_CLONE_GC_ENABLE | SND_CLONE_GC_UNREF | + SND_CLONE_GC_LASTREF | SND_CLONE_GC_EXPIRED); + + if (bootverbose != 0 || snd_verbose > 3) { + device_printf(dev, + "clone manager: deadline=%dms flags=0x%08x\n", + snd_clone_getdeadline(d->clones), + snd_clone_getflags(d->clones)); + } + + CHN_INIT(d, channels.pcm); + CHN_INIT(d, channels.pcm.busy); + CHN_INIT(d, channels.pcm.opened); - if (((numplay == 0) || (numrec == 0)) && (numplay != numrec)) + /* XXX This is incorrect, but lets play along for now. */ + if ((numplay == 0 || numrec == 0) && numplay != numrec) d->flags |= SD_F_SIMPLEX; d->fakechan = fkchan_setup(dev); chn_init(d->fakechan, NULL, 0, 0); #ifdef SND_DYNSYSCTL - sysctl_ctx_init(&d->sysctl_tree); - d->sysctl_tree_top = SYSCTL_ADD_NODE(&d->sysctl_tree, - SYSCTL_STATIC_CHILDREN(_hw_snd), OID_AUTO, - device_get_nameunit(dev), CTLFLAG_RD, 0, ""); - if (d->sysctl_tree_top == NULL) { - sysctl_ctx_free(&d->sysctl_tree); - goto no; - } - SYSCTL_ADD_INT(snd_sysctl_tree(dev), SYSCTL_CHILDREN(snd_sysctl_tree_top(dev)), - OID_AUTO, "buffersize", CTLFLAG_RD, &d->bufsz, 0, ""); + sysctl_ctx_init(&d->play_sysctl_ctx); + d->play_sysctl_tree = SYSCTL_ADD_NODE(&d->play_sysctl_ctx, + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "play", + CTLFLAG_RD, 0, "playback channels node"); + sysctl_ctx_init(&d->rec_sysctl_ctx); + d->rec_sysctl_tree = SYSCTL_ADD_NODE(&d->rec_sysctl_ctx, + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "rec", + CTLFLAG_RD, 0, "record channels node"); + /* XXX: an user should be able to set this with a control tool, the + sysadmin then needs min+max sysctls for this */ + SYSCTL_ADD_INT(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), + OID_AUTO, "buffersize", CTLFLAG_RD, &d->bufsz, 0, "allocated buffer size"); +#ifdef SND_DEBUG + SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, + "clone_flags", CTLTYPE_UINT | CTLFLAG_RW, d, sizeof(d), + sysctl_dev_pcm_clone_flags, "IU", + "clone flags"); + SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, + "clone_deadline", CTLTYPE_INT | CTLFLAG_RW, d, sizeof(d), + sysctl_dev_pcm_clone_deadline, "I", + "clone expiration deadline (ms)"); + SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, + "clone_gc", CTLTYPE_INT | CTLFLAG_RW, d, sizeof(d), + sysctl_dev_pcm_clone_gc, "I", + "clone garbage collector"); #endif - if (numplay > 0) - vchan_initsys(dev); - if (numplay == 1) +#endif + + if (numplay > 0 || numrec > 0) { d->flags |= SD_F_AUTOVCHAN; + vchan_initsys(dev); + } sndstat_register(dev, d->status, sndstat_prepare_pcm); - return 0; -no: - snd_mtxfree(d->lock); - return ENXIO; + + return 0; } int pcm_unregister(device_t dev) { - struct snddev_info *d = device_get_softc(dev); - struct snddev_channel *sce; + struct snddev_info *d; struct pcm_channel *ch; + struct thread *td; + int i; - snd_mtxlock(d->lock); - if (d->inprog) { - device_printf(dev, "unregister: operation in progress\n"); - snd_mtxunlock(d->lock); - return EBUSY; + td = curthread; + d = device_get_softc(dev); + + if (!PCM_ALIVE(d)) { + device_printf(dev, "unregister: device not configured\n"); + return (0); } - if (sndstat_busy() != 0) { + + if (sndstat_acquire(td) != 0) { device_printf(dev, "unregister: sndstat busy\n"); - snd_mtxunlock(d->lock); - return EBUSY; + return (EBUSY); } + pcm_lock(d); + PCM_WAIT(d); + + if (d->inprog != 0) { + device_printf(dev, "unregister: operation in progress\n"); + pcm_unlock(d); + sndstat_release(td); + return (EBUSY); + } - SLIST_FOREACH(sce, &d->channels, link) { - ch = sce->channel; + PCM_ACQUIRE(d); + pcm_unlock(d); + + CHN_FOREACH(ch, d, channels.pcm) { + CHN_LOCK(ch); if (ch->refcount > 0) { - device_printf(dev, "unregister: channel %s busy (pid %d)\n", ch->name, ch->pid); - snd_mtxunlock(d->lock); - return EBUSY; + device_printf(dev, + "unregister: channel %s busy (pid %d)\n", + ch->name, ch->pid); + CHN_UNLOCK(ch); + PCM_RELEASE_QUICK(d); + sndstat_release(td); + return (EBUSY); } + CHN_UNLOCK(ch); } - SLIST_FOREACH(sce, &d->channels, link) { - destroy_dev(sce->dsp_devt); - destroy_dev(sce->dspW_devt); - destroy_dev(sce->audio_devt); - if (sce->dspr_devt) - destroy_dev(sce->dspr_devt); + if (d->clones != NULL) { + if (snd_clone_busy(d->clones) != 0) { + device_printf(dev, "unregister: clone busy\n"); + PCM_RELEASE_QUICK(d); + sndstat_release(td); + return (EBUSY); + } else { + pcm_lock(d); + (void)snd_clone_disable(d->clones); + pcm_unlock(d); + } } - if (mixer_uninit(dev)) { + if (mixer_uninit(dev) == EBUSY) { device_printf(dev, "unregister: mixer busy\n"); - snd_mtxunlock(d->lock); - return EBUSY; + pcm_lock(d); + if (d->clones != NULL) + (void)snd_clone_enable(d->clones); + PCM_RELEASE(d); + pcm_unlock(d); + sndstat_release(td); + return (EBUSY); + } + + pcm_lock(d); + d->flags |= SD_F_DYING; + d->flags &= ~SD_F_REGISTERED; + pcm_unlock(d); + + /* + * No lock being held, so this thing can be flushed without + * stucking into devdrn oblivion. + */ + if (d->clones != NULL) { + snd_clone_destroy(d->clones); + d->clones = NULL; } #ifdef SND_DYNSYSCTL - d->sysctl_tree_top = NULL; - sysctl_ctx_free(&d->sysctl_tree); + if (d->play_sysctl_tree != NULL) { + sysctl_ctx_free(&d->play_sysctl_ctx); + d->play_sysctl_tree = NULL; + } + if (d->rec_sysctl_tree != NULL) { + sysctl_ctx_free(&d->rec_sysctl_ctx); + d->rec_sysctl_tree = NULL; + } #endif - while (!SLIST_EMPTY(&d->channels)) + + while (!CHN_EMPTY(d, channels.pcm)) pcm_killchan(dev); chn_kill(d->fakechan); fkchan_kill(d->fakechan); - sndstat_unregister(dev); - snd_mtxunlock(d->lock); + dsp_cdevinfo_flush(d); + + pcm_lock(d); + PCM_RELEASE(d); + cv_destroy(&d->cv); + pcm_unlock(d); snd_mtxfree(d->lock); - return 0; + sndstat_unregister(dev); + sndstat_release(td); + + if (snd_unit == device_get_unit(dev)) { + /* + * Reassign default unit to the next available dev, but + * first, reset snd_unit to something ridiculous. + */ + snd_unit = -1; + for (i = 0; pcm_devclass != NULL && + i < devclass_get_maxunit(pcm_devclass); i++) { + if (device_get_unit(dev) == i) + continue; + d = devclass_get_softc(pcm_devclass, i); + if (PCM_REGISTERED(d)) { + snd_unit = i; + break; + } + } + } + + return (0); } /************************************************************************/ -static int -sndstat_prepare_pcm(struct sbuf *s, device_t dev, int verbose) +#ifdef SND_DYNSYSCTL +int +sysctl_hw_snd_vchans(SYSCTL_HANDLER_ARGS) { - struct snddev_info *d; - struct snddev_channel *sce; - struct pcm_channel *c; - struct pcm_feeder *f; - int pc, rc, vc; + struct snddev_info *d; + int direction, vchancount; + int err, cnt; - if (verbose < 1) - return 0; + d = devclass_get_softc(pcm_devclass, VCHAN_SYSCTL_UNIT(oidp->oid_arg1)); + if (!PCM_REGISTERED(d) || !(d->flags & SD_F_AUTOVCHAN)) + return (EINVAL); - d = device_get_softc(dev); - if (!d) - return ENXIO; + pcm_lock(d); + PCM_WAIT(d); - snd_mtxlock(d->lock); - if (!SLIST_EMPTY(&d->channels)) { - pc = rc = vc = 0; - SLIST_FOREACH(sce, &d->channels, link) { - c = sce->channel; - if (c->direction == PCMDIR_PLAY) { - if (c->flags & CHN_F_VIRTUAL) - vc++; - else - pc++; - } else - rc++; - } - sbuf_printf(s, " (%dp/%dr/%dv channels%s%s)", d->playcount, d->reccount, d->vchancount, - (d->flags & SD_F_SIMPLEX)? "" : " duplex", -#ifdef USING_DEVFS - (device_get_unit(dev) == snd_unit)? " default" : "" -#else - "" -#endif - ); + switch (VCHAN_SYSCTL_DIR(oidp->oid_arg1)) { + case VCHAN_PLAY: + direction = PCMDIR_PLAY; + vchancount = d->pvchancount; + cnt = d->playcount; + break; + case VCHAN_REC: + direction = PCMDIR_REC; + vchancount = d->rvchancount; + cnt = d->reccount; + break; + default: + pcm_unlock(d); + return (EINVAL); + break; + } - if (verbose <= 1) { - snd_mtxunlock(d->lock); - return 0; - } - - SLIST_FOREACH(sce, &d->channels, link) { - c = sce->channel; - sbuf_printf(s, "\n\t"); - - /* it would be better to indent child channels */ - sbuf_printf(s, "%s[%s]: ", c->parentchannel? c->parentchannel->name : "", c->name); - sbuf_printf(s, "spd %d", c->speed); - if (c->speed != sndbuf_getspd(c->bufhard)) - sbuf_printf(s, "/%d", sndbuf_getspd(c->bufhard)); - sbuf_printf(s, ", fmt 0x%08x", c->format); - if (c->format != sndbuf_getfmt(c->bufhard)) - sbuf_printf(s, "/0x%08x", sndbuf_getfmt(c->bufhard)); - sbuf_printf(s, ", flags 0x%08x, 0x%08x", c->flags, c->feederflags); - if (c->pid != -1) - sbuf_printf(s, ", pid %d", c->pid); - sbuf_printf(s, "\n\t"); - - if (c->bufhard != NULL && c->bufsoft != NULL) { - sbuf_printf(s, "interrupts %d, ", c->interrupts); - if (c->direction == PCMDIR_REC) - sbuf_printf(s, "overruns %d, hfree %d, sfree %d", - c->xruns, sndbuf_getfree(c->bufhard), sndbuf_getfree(c->bufsoft)); - else - sbuf_printf(s, "underruns %d, ready %d", - c->xruns, sndbuf_getready(c->bufsoft)); - sbuf_printf(s, "\n\t"); - } + if (cnt < 1) { + pcm_unlock(d); + return (ENODEV); + } - sbuf_printf(s, "{%s}", (c->direction == PCMDIR_REC)? "hardware" : "userland"); - sbuf_printf(s, " -> "); - f = c->feeder; - while (f->source != NULL) - f = f->source; - while (f != NULL) { - sbuf_printf(s, "%s", f->class->name); - if (f->desc->type == FEEDER_FMT) - sbuf_printf(s, "(0x%08x -> 0x%08x)", f->desc->in, f->desc->out); - if (f->desc->type == FEEDER_RATE) - sbuf_printf(s, "(%d -> %d)", FEEDER_GET(f, FEEDRATE_SRC), FEEDER_GET(f, FEEDRATE_DST)); - if (f->desc->type == FEEDER_ROOT || f->desc->type == FEEDER_MIXER) - sbuf_printf(s, "(0x%08x)", f->desc->out); - sbuf_printf(s, " -> "); - f = f->parent; - } - sbuf_printf(s, "{%s}", (c->direction == PCMDIR_REC)? "userland" : "hardware"); - } - } else - sbuf_printf(s, " (mixer only)"); - snd_mtxunlock(d->lock); + PCM_ACQUIRE(d); + pcm_unlock(d); - return 0; + cnt = vchancount; + err = sysctl_handle_int(oidp, &cnt, 0, req); + + if (err == 0 && req->newptr != NULL && vchancount != cnt) { + if (cnt < 0) + cnt = 0; + if (cnt > SND_MAXVCHANS) + cnt = SND_MAXVCHANS; + err = pcm_setvchans(d, direction, cnt, -1); + } + + PCM_RELEASE_QUICK(d); + + return err; } +#endif /************************************************************************/ -#ifdef SND_DYNSYSCTL -int -sysctl_hw_snd_vchans(SYSCTL_HANDLER_ARGS) +/** + * @brief Handle OSSv4 SNDCTL_SYSINFO ioctl. + * + * @param si Pointer to oss_sysinfo struct where information about the + * sound subsystem will be written/copied. + * + * This routine returns information about the sound system, such as the + * current OSS version, number of audio, MIDI, and mixer drivers, etc. + * Also includes a bitmask showing which of the above types of devices + * are open (busy). + * + * @note + * Calling threads must not hold any snddev_info or pcm_channel locks. + * + * @author Ryan Beasley + */ +void +sound_oss_sysinfo(oss_sysinfo *si) { + static char si_product[] = "FreeBSD native OSS ABI"; + static char si_version[] = __XSTRING(__FreeBSD_version); + static int intnbits = sizeof(int) * 8; /* Better suited as macro? + Must pester a C guru. */ + struct snddev_info *d; - struct snddev_channel *sce; struct pcm_channel *c; - int err, newcnt, cnt, busy; - int x; + int i, j, ncards; + + ncards = 0; + + strlcpy(si->product, si_product, sizeof(si->product)); + strlcpy(si->version, si_version, sizeof(si->version)); + si->versionnum = SOUND_VERSION; - d = oidp->oid_arg1; + /* + * Iterate over PCM devices and their channels, gathering up data + * for the numaudios, ncards, and openedaudio fields. + */ + si->numaudios = 0; + bzero((void *)&si->openedaudio, sizeof(si->openedaudio)); - x = pcm_inprog(d, 1); - if (x != 1) { - pcm_inprog(d, -1); - return EINPROGRESS; - } + j = 0; - busy = 0; - cnt = 0; - SLIST_FOREACH(sce, &d->channels, link) { - c = sce->channel; - CHN_LOCK(c); - if ((c->direction == PCMDIR_PLAY) && (c->flags & CHN_F_VIRTUAL)) { - cnt++; - if (c->flags & CHN_F_BUSY) - busy++; - } - CHN_UNLOCK(c); - } + for (i = 0; pcm_devclass != NULL && + i < devclass_get_maxunit(pcm_devclass); i++) { + d = devclass_get_softc(pcm_devclass, i); + if (!PCM_REGISTERED(d)) + continue; - newcnt = cnt; + /* XXX Need Giant magic entry ??? */ - err = sysctl_handle_int(oidp, &newcnt, sizeof(newcnt), req); + /* See note in function's docblock */ + mtx_assert(d->lock, MA_NOTOWNED); + pcm_lock(d); - if (err == 0 && req->newptr != NULL) { + si->numaudios += d->devcount; + ++ncards; - if (newcnt < 0 || newcnt > SND_MAXVCHANS) { - pcm_inprog(d, -1); - return E2BIG; - } - - if (newcnt > cnt) { - /* add new vchans - find a parent channel first */ - SLIST_FOREACH(sce, &d->channels, link) { - c = sce->channel; - CHN_LOCK(c); - /* not a candidate if not a play channel */ - if (c->direction != PCMDIR_PLAY) - goto next; - /* not a candidate if a virtual channel */ - if (c->flags & CHN_F_VIRTUAL) - goto next; - /* not a candidate if it's in use */ - if (!(c->flags & CHN_F_BUSY) || - !(SLIST_EMPTY(&c->children))) - /* - * if we get here we're a nonvirtual - * play channel, and either - * 1) not busy - * 2) busy with children, not directly - * open - * - * thus we can add children - */ - goto addok; -next: - CHN_UNLOCK(c); - } - pcm_inprog(d, -1); - return EBUSY; -addok: - c->flags |= CHN_F_BUSY; - while (err == 0 && newcnt > cnt) { - err = vchan_create(c); - if (err == 0) - cnt++; - } - if (SLIST_EMPTY(&c->children)) - c->flags &= ~CHN_F_BUSY; + CHN_FOREACH(c, d, channels.pcm) { + mtx_assert(c->lock, MA_NOTOWNED); + CHN_LOCK(c); + if (c->flags & CHN_F_BUSY) + si->openedaudio[j / intnbits] |= + (1 << (j % intnbits)); CHN_UNLOCK(c); - } else if (newcnt < cnt) { - if (busy > newcnt) { - printf("cnt %d, newcnt %d, busy %d\n", cnt, newcnt, busy); - pcm_inprog(d, -1); - return EBUSY; - } - - snd_mtxlock(d->lock); - while (err == 0 && newcnt < cnt) { - SLIST_FOREACH(sce, &d->channels, link) { - c = sce->channel; - CHN_LOCK(c); - if ((c->flags & (CHN_F_BUSY | CHN_F_VIRTUAL)) == CHN_F_VIRTUAL) - goto remok; - - CHN_UNLOCK(c); - } - snd_mtxunlock(d->lock); - pcm_inprog(d, -1); - return EINVAL; -remok: - CHN_UNLOCK(c); - err = vchan_destroy(c); - if (err == 0) - cnt--; - } - snd_mtxunlock(d->lock); + j++; } + + pcm_unlock(d); } - pcm_inprog(d, -1); - return err; + + si->numsynths = 0; /* OSSv4 docs: this field is obsolete */ + /** + * @todo Collect num{midis,timers}. + * + * Need access to sound/midi/midi.c::midistat_lock in order + * to safely touch midi_devices and get a head count of, well, + * MIDI devices. midistat_lock is a global static (i.e., local to + * midi.c), but midi_devices is a regular global; should the mutex + * be publicized, or is there another way to get this information? + * + * NB: MIDI/sequencer stuff is currently on hold. + */ + si->nummidis = 0; + si->numtimers = 0; + si->nummixers = mixer_count; + si->numcards = ncards; + /* OSSv4 docs: Intended only for test apps; API doesn't + really have much of a concept of cards. Shouldn't be + used by applications. */ + + /** + * @todo Fill in "busy devices" fields. + * + * si->openedmidi = " MIDI devices + */ + bzero((void *)&si->openedmidi, sizeof(si->openedmidi)); + + /* + * Si->filler is a reserved array, but according to docs each + * element should be set to -1. + */ + for (i = 0; i < sizeof(si->filler)/sizeof(si->filler[0]); i++) + si->filler[i] = -1; } -#endif /************************************************************************/ -#if notyet static int sound_modevent(module_t mod, int type, void *data) { + int ret; +#if 0 return (midi_modevent(mod, type, data)); +#else + ret = 0; + + switch(type) { + case MOD_LOAD: + pcmsg_unrhdr = new_unrhdr(1, INT_MAX, NULL); + break; + case MOD_UNLOAD: + case MOD_SHUTDOWN: + ret = sndstat_acquire(curthread); + if (ret != 0) + break; + if (pcmsg_unrhdr != NULL) { + delete_unrhdr(pcmsg_unrhdr); + pcmsg_unrhdr = NULL; + } + break; + default: + ret = EOPNOTSUPP; + } + + return ret; +#endif } DEV_MODULE(sound, sound_modevent, NULL); -#else -DEV_MODULE(sound, NULL, NULL); -#endif /* notyet */ MODULE_VERSION(sound, SOUND_MODVER); --- sys/dev/sound/pcm/sound.h.orig Sun Jan 30 09:00:05 2005 +++ sys/dev/sound/pcm/sound.h Thu Jul 12 12:04:19 2007 @@ -24,7 +24,7 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $FreeBSD: src/sys/dev/sound/pcm/sound.h,v 1.61.2.1 2005/01/30 01:00:05 imp Exp $ + * $FreeBSD: src/sys/dev/sound/pcm/sound.h,v 1.78 2007/06/16 03:37:28 ariff Exp $ */ /* @@ -55,10 +55,10 @@ #if __FreeBSD_version < 500000 #include #endif -#include /* for DELAY */ #include #include #include +#include #include #include #include @@ -74,6 +74,7 @@ #if __FreeBSD_version > 500000 #include #include +#include #define USING_MUTEX #define USING_DEVFS @@ -93,50 +94,66 @@ #include #include #include +#include +#include +#include #define PCM_SOFTC_SIZE 512 #define SND_STATUSLEN 64 -#define SOUND_MODVER 1 +#define SOUND_MODVER 2 -#define SOUND_MINVER 1 +#define SOUND_MINVER SOUND_MODVER #define SOUND_PREFVER SOUND_MODVER -#define SOUND_MAXVER 1 +#define SOUND_MAXVER SOUND_MODVER /* -PROPOSAL: -each unit needs: -status, mixer, dsp, dspW, audio, sequencer, midi-in, seq2, sndproc = 9 devices -dspW and audio are deprecated. -dsp needs min 64 channels, will give it 256 - -minor = (unit << 20) + (dev << 16) + channel -currently minor = (channel << 16) + (unit << 4) + dev - -nomenclature: - /dev/pcmX/dsp.(0..255) - /dev/pcmX/dspW - /dev/pcmX/audio - /dev/pcmX/status - /dev/pcmX/mixer - [etc.] -*/ - -#define PCMMINOR(x) (minor(x)) -#define PCMCHAN(x) ((PCMMINOR(x) & 0x00ff0000) >> 16) -#define PCMUNIT(x) ((PCMMINOR(x) & 0x000000f0) >> 4) -#define PCMDEV(x) (PCMMINOR(x) & 0x0000000f) -#define PCMMKMINOR(u, d, c) ((((c) & 0xff) << 16) | (((u) & 0x0f) << 4) | ((d) & 0x0f)) + * We're abusing the fact that MAXMINOR still have enough room + * for our bit twiddling and nobody ever need 512 unique soundcards, + * 32 unique device types and 1024 unique cloneable devices for the + * next 100 years... + */ + +#define PCMMAXUNIT (snd_max_u()) +#define PCMMAXDEV (snd_max_d()) +#define PCMMAXCHAN (snd_max_c()) + +#define PCMMAXCLONE PCMMAXCHAN + +#define PCMUNIT(x) (snd_unit2u(dev2unit(x))) +#define PCMDEV(x) (snd_unit2d(dev2unit(x))) +#define PCMCHAN(x) (snd_unit2c(dev2unit(x))) + +/* + * By design, limit possible channels for each direction. + */ +#define SND_MAXHWCHAN 256 +#define SND_MAXVCHANS SND_MAXHWCHAN #define SD_F_SIMPLEX 0x00000001 -#define SD_F_AUTOVCHAN 0x00000002 +#define SD_F_AUTOVCHAN 0x00000002 +#define SD_F_SOFTPCMVOL 0x00000004 +#define SD_F_PSWAPLR 0x00000008 +#define SD_F_RSWAPLR 0x00000010 +#define SD_F_DYING 0x00000020 +#define SD_F_SUICIDE 0x00000040 +#define SD_F_BUSY 0x00000080 +#define SD_F_MPSAFE 0x00000100 +#define SD_F_REGISTERED 0x00000200 +#define SD_F_VPC 0x00000400 /* volume-per-channel */ + #define SD_F_PRIO_RD 0x10000000 #define SD_F_PRIO_WR 0x20000000 #define SD_F_PRIO_SET (SD_F_PRIO_RD | SD_F_PRIO_WR) #define SD_F_DIR_SET 0x40000000 #define SD_F_TRANSIENT 0xf0000000 +#define PCM_ALIVE(x) ((x) != NULL && (x)->lock != NULL && \ + !((x)->flags & SD_F_DYING)) +#define PCM_REGISTERED(x) (PCM_ALIVE(x) && \ + ((x)->flags & SD_F_REGISTERED)) + /* 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)) @@ -144,22 +161,18 @@ /* make figuring out what a format is easier. got AFMT_STEREO already */ #define AFMT_32BIT (AFMT_S32_LE | AFMT_S32_BE | AFMT_U32_LE | AFMT_U32_BE) +#define AFMT_24BIT (AFMT_S24_LE | AFMT_S24_BE | AFMT_U24_LE | AFMT_U24_BE) #define AFMT_16BIT (AFMT_S16_LE | AFMT_S16_BE | AFMT_U16_LE | AFMT_U16_BE) -#define AFMT_8BIT (AFMT_U8 | AFMT_S8) -#define AFMT_SIGNED (AFMT_S16_LE | AFMT_S16_BE | AFMT_S8) -#define AFMT_BIGENDIAN (AFMT_S16_BE | AFMT_U16_BE) +#define AFMT_8BIT (AFMT_MU_LAW | AFMT_A_LAW | AFMT_U8 | AFMT_S8) +#define AFMT_SIGNED (AFMT_S32_LE | AFMT_S32_BE | AFMT_S24_LE | AFMT_S24_BE | \ + AFMT_S16_LE | AFMT_S16_BE | AFMT_S8) +#define AFMT_BIGENDIAN (AFMT_S32_BE | AFMT_U32_BE | AFMT_S24_BE | AFMT_U24_BE | \ + AFMT_S16_BE | AFMT_U16_BE) struct pcm_channel *fkchan_setup(device_t dev); int fkchan_kill(struct pcm_channel *c); /* - * Major nuber for the sound driver. - */ -#define SND_CDEV_MAJOR 30 - -#define SND_MAXVCHANS 255 - -/* * Minor numbers for the sound driver. * * Unfortunately Creative called the codec chip of SB as a DSP. For this @@ -180,7 +193,27 @@ #define SND_DEV_SNDPROC 9 /* /dev/sndproc for programmable devices */ #define SND_DEV_PSS SND_DEV_SNDPROC /* ? */ #define SND_DEV_NORESET 10 -#define SND_DEV_DSPREC 11 /* recording channels */ + +#define SND_DEV_DSPHW_PLAY 11 /* specific playback channel */ +#define SND_DEV_DSPHW_VPLAY 12 /* specific virtual playback channel */ +#define SND_DEV_DSPHW_REC 13 /* specific record channel */ +#define SND_DEV_DSPHW_VREC 14 /* specific virtual record channel */ + +#define SND_DEV_DSPHW_CD 15 /* s16le/stereo 44100Hz CD */ + +/* + * OSSv4 compatible device. For now, it serve no purpose and + * the cloning itself will forward the request to ordinary /dev/dsp + * instead. + */ +#define SND_DEV_DSP_MMAP 16 /* /dev/dsp_mmap */ +#define SND_DEV_DSP_AC3 17 /* /dev/dsp_ac3 */ +#define SND_DEV_DSP_MULTICH 18 /* /dev/dsp_multich */ +#define SND_DEV_DSP_SPDIFOUT 19 /* /dev/dsp_spdifout */ +#define SND_DEV_DSP_SPDIFIN 20 /* /dev/dsp_spdifin */ + +#define SND_DEV_LAST SND_DEV_DSP_SPDIFIN +#define SND_DEV_MAX PCMMAXDEV #define DSP_DEFAULT_SPEED 8000 @@ -189,7 +222,10 @@ extern int pcm_veto_load; extern int snd_unit; +extern int snd_maxautovchans; +extern int snd_verbose; extern devclass_t pcm_devclass; +extern struct unrhdr *pcmsg_unrhdr; /* * some macros for debugging purposes @@ -204,22 +240,20 @@ SYSCTL_DECL(_hw_snd); -struct sysctl_ctx_list *snd_sysctl_tree(device_t dev); -struct sysctl_oid *snd_sysctl_tree_top(device_t dev); - struct pcm_channel *pcm_getfakechan(struct snddev_info *d); -struct pcm_channel *pcm_chnalloc(struct snddev_info *d, int direction, pid_t pid, int chnum); +int pcm_chnalloc(struct snddev_info *d, struct pcm_channel **ch, int direction, + pid_t pid, char *comm, int devunit); int pcm_chnrelease(struct pcm_channel *c); int pcm_chnref(struct pcm_channel *c, int ref); int pcm_inprog(struct snddev_info *d, int delta); -struct pcm_channel *pcm_chn_create(struct snddev_info *d, struct pcm_channel *parent, kobj_class_t cls, int dir, void *devinfo); +struct pcm_channel *pcm_chn_create(struct snddev_info *d, struct pcm_channel *parent, kobj_class_t cls, int dir, int num, void *devinfo); int pcm_chn_destroy(struct pcm_channel *ch); int pcm_chn_add(struct snddev_info *d, struct pcm_channel *ch); int pcm_chn_remove(struct snddev_info *d, struct pcm_channel *ch); int pcm_addchan(device_t dev, int dir, kobj_class_t cls, void *devinfo); -unsigned int pcm_getbuffersize(device_t dev, unsigned int min, unsigned int deflt, unsigned int max); +unsigned int pcm_getbuffersize(device_t dev, unsigned int minbufsz, unsigned int deflt, unsigned int maxbufsz); int pcm_register(device_t dev, void *devinfo, int numplay, int numrec); int pcm_unregister(device_t dev); int pcm_setstatus(device_t dev, char *str); @@ -240,11 +274,12 @@ int sysctl_hw_snd_vchans(SYSCTL_HANDLER_ARGS); typedef int (*sndstat_handler)(struct sbuf *s, device_t dev, int verbose); +int sndstat_acquire(struct thread *td); +int sndstat_release(struct thread *td); int sndstat_register(device_t dev, char *str, sndstat_handler handler); int sndstat_registerfile(char *str); int sndstat_unregister(device_t dev); int sndstat_unregisterfile(char *str); -int sndstat_busy(void); #define SND_DECLARE_FILE(version) \ _SND_DECLARE_FILE(__LINE__, version) @@ -273,33 +308,39 @@ * we also have to do this now makedev() has gone away. */ -struct snddev_channel { - SLIST_ENTRY(snddev_channel) link; - struct pcm_channel *channel; - int chan_num; - struct cdev *dsp_devt; - struct cdev *dspW_devt; - struct cdev *audio_devt; - struct cdev *dspr_devt; -}; - struct snddev_info { - SLIST_HEAD(, snddev_channel) channels; + struct { + struct { + SLIST_HEAD(, pcm_channel) head; + struct { + SLIST_HEAD(, pcm_channel) head; + } busy; + struct { + SLIST_HEAD(, pcm_channel) head; + } opened; + } pcm; + } channels; + TAILQ_HEAD(dsp_cdevinfo_linkhead, dsp_cdevinfo) dsp_cdevinfo_pool; + struct snd_clone *clones; struct pcm_channel *fakechan; - unsigned devcount, playcount, reccount, vchancount; + unsigned devcount, playcount, reccount, pvchancount, rvchancount ; unsigned flags; int inprog; unsigned int bufsz; void *devinfo; device_t dev; char status[SND_STATUSLEN]; - struct sysctl_ctx_list sysctl_tree; - struct sysctl_oid *sysctl_tree_top; struct mtx *lock; struct cdev *mixer_dev; - + uint32_t pvchanrate, pvchanformat; + uint32_t rvchanrate, rvchanformat; + struct sysctl_ctx_list play_sysctl_ctx, rec_sysctl_ctx; + struct sysctl_oid *play_sysctl_tree, *rec_sysctl_tree; + struct cv cv; }; +void sound_oss_sysinfo(oss_sysinfo *); + #ifdef PCM_DEBUG_MTX #define pcm_lock(d) mtx_lock(((struct snddev_info *)(d))->lock) #define pcm_unlock(d) mtx_unlock(((struct snddev_info *)(d))->lock) @@ -307,6 +348,196 @@ void pcm_lock(struct snddev_info *d); void pcm_unlock(struct snddev_info *d); #endif + +/* + * For PCM_[WAIT | ACQUIRE | RELEASE], be sure to surround these + * with pcm_lock/unlock() sequence, or I'll come to gnaw upon you! + */ +#ifdef SND_DIAGNOSTIC +#define PCM_WAIT(x) do { \ + if (mtx_owned((x)->lock) == 0) \ + panic("%s(%d): [PCM WAIT] Mutex not owned!", \ + __func__, __LINE__); \ + while ((x)->flags & SD_F_BUSY) { \ + if (snd_verbose > 3) \ + device_printf((x)->dev, \ + "%s(%d): [PCM WAIT] calling cv_wait().\n", \ + __func__, __LINE__); \ + cv_wait(&(x)->cv, (x)->lock); \ + } \ +} while(0) + +#define PCM_ACQUIRE(x) do { \ + if (mtx_owned((x)->lock) == 0) \ + panic("%s(%d): [PCM ACQUIRE] Mutex not owned!", \ + __func__, __LINE__); \ + if ((x)->flags & SD_F_BUSY) \ + panic("%s(%d): [PCM ACQUIRE] " \ + "Trying to acquire BUSY cv!", __func__, __LINE__); \ + (x)->flags |= SD_F_BUSY; \ +} while(0) + +#define PCM_RELEASE(x) do { \ + if (mtx_owned((x)->lock) == 0) \ + panic("%s(%d): [PCM RELEASE] Mutex not owned!", \ + __func__, __LINE__); \ + if ((x)->flags & SD_F_BUSY) { \ + (x)->flags &= ~SD_F_BUSY; \ + if ((x)->cv.cv_waiters != 0) { \ + if ((x)->cv.cv_waiters > 1 && snd_verbose > 3) \ + device_printf((x)->dev, \ + "%s(%d): [PCM RELEASE] " \ + "cv_waiters=%d > 1!\n", \ + __func__, __LINE__, \ + (x)->cv.cv_waiters); \ + cv_broadcast(&(x)->cv); \ + } \ + } else \ + panic("%s(%d): [PCM RELEASE] Releasing non-BUSY cv!", \ + __func__, __LINE__); \ +} while(0) + +/* Quick version, for shorter path. */ +#define PCM_ACQUIRE_QUICK(x) do { \ + if (mtx_owned((x)->lock) != 0) \ + panic("%s(%d): [PCM ACQUIRE QUICK] Mutex owned!", \ + __func__, __LINE__); \ + pcm_lock(x); \ + PCM_WAIT(x); \ + PCM_ACQUIRE(x); \ + pcm_unlock(x); \ +} while(0) + +#define PCM_RELEASE_QUICK(x) do { \ + if (mtx_owned((x)->lock) != 0) \ + panic("%s(%d): [PCM RELEASE QUICK] Mutex owned!", \ + __func__, __LINE__); \ + pcm_lock(x); \ + PCM_RELEASE(x); \ + pcm_unlock(x); \ +} while(0) + +#define PCM_BUSYASSERT(x) do { \ + if (!((x) != NULL && ((x)->flags & SD_F_BUSY))) \ + panic("%s(%d): [PCM BUSYASSERT] " \ + "Failed, snddev_info=%p", __func__, __LINE__, x); \ +} while(0) + +#define PCM_GIANT_ENTER(x) do { \ + int _pcm_giant = 0; \ + if (mtx_owned((x)->lock) != 0) \ + panic("%s(%d): [GIANT ENTER] PCM lock owned!", \ + __func__, __LINE__); \ + if (mtx_owned(&Giant) != 0 && snd_verbose > 3) \ + device_printf((x)->dev, \ + "%s(%d): [GIANT ENTER] Giant owned!\n", \ + __func__, __LINE__); \ + if (!((x)->flags & SD_F_MPSAFE) && mtx_owned(&Giant) == 0) \ + do { \ + mtx_lock(&Giant); \ + _pcm_giant = 1; \ + } while(0) + +#define PCM_GIANT_EXIT(x) do { \ + if (mtx_owned((x)->lock) != 0) \ + panic("%s(%d): [GIANT EXIT] PCM lock owned!", \ + __func__, __LINE__); \ + if (!(_pcm_giant == 0 || _pcm_giant == 1)) \ + panic("%s(%d): [GIANT EXIT] _pcm_giant screwed!", \ + __func__, __LINE__); \ + if ((x)->flags & SD_F_MPSAFE) { \ + if (_pcm_giant == 1) \ + panic("%s(%d): [GIANT EXIT] MPSAFE Giant?", \ + __func__, __LINE__); \ + if (mtx_owned(&Giant) != 0 && snd_verbose > 3) \ + device_printf((x)->dev, \ + "%s(%d): [GIANT EXIT] Giant owned!\n", \ + __func__, __LINE__); \ + } \ + if (_pcm_giant != 0) { \ + if (mtx_owned(&Giant) == 0) \ + panic("%s(%d): [GIANT EXIT] Giant not owned!", \ + __func__, __LINE__); \ + _pcm_giant = 0; \ + mtx_unlock(&Giant); \ + } \ +} while(0) +#else /* SND_DIAGNOSTIC */ +#define PCM_WAIT(x) do { \ + mtx_assert((x)->lock, MA_OWNED); \ + while ((x)->flags & SD_F_BUSY) \ + cv_wait(&(x)->cv, (x)->lock); \ +} while(0) + +#define PCM_ACQUIRE(x) do { \ + mtx_assert((x)->lock, MA_OWNED); \ + KASSERT(!((x)->flags & SD_F_BUSY), \ + ("%s(%d): [PCM ACQUIRE] Trying to acquire BUSY cv!", \ + __func__, __LINE__)); \ + (x)->flags |= SD_F_BUSY; \ +} while(0) + +#define PCM_RELEASE(x) do { \ + mtx_assert((x)->lock, MA_OWNED); \ + KASSERT((x)->flags & SD_F_BUSY, \ + ("%s(%d): [PCM RELEASE] Releasing non-BUSY cv!", \ + __func__, __LINE__)); \ + (x)->flags &= ~SD_F_BUSY; \ + if ((x)->cv.cv_waiters != 0) \ + cv_broadcast(&(x)->cv); \ +} while(0) + +/* Quick version, for shorter path. */ +#define PCM_ACQUIRE_QUICK(x) do { \ + mtx_assert((x)->lock, MA_NOTOWNED); \ + pcm_lock(x); \ + PCM_WAIT(x); \ + PCM_ACQUIRE(x); \ + pcm_unlock(x); \ +} while(0) + +#define PCM_RELEASE_QUICK(x) do { \ + mtx_assert((x)->lock, MA_NOTOWNED); \ + pcm_lock(x); \ + PCM_RELEASE(x); \ + pcm_unlock(x); \ +} while(0) + +#define PCM_BUSYASSERT(x) KASSERT(x != NULL && \ + ((x)->flags & SD_F_BUSY), \ + ("%s(%d): [PCM BUSYASSERT] " \ + "Failed, snddev_info=%p", \ + __func__, __LINE__, x)) + +#define PCM_GIANT_ENTER(x) do { \ + int _pcm_giant = 0; \ + mtx_assert((x)->lock, MA_NOTOWNED); \ + if (!((x)->flags & SD_F_MPSAFE) && mtx_owned(&Giant) == 0) \ + do { \ + mtx_lock(&Giant); \ + _pcm_giant = 1; \ + } while(0) + +#define PCM_GIANT_EXIT(x) do { \ + mtx_assert((x)->lock, MA_NOTOWNED); \ + KASSERT(_pcm_giant == 0 || _pcm_giant == 1, \ + ("%s(%d): [GIANT EXIT] _pcm_giant screwed!", \ + __func__, __LINE__)); \ + KASSERT(!((x)->flags & SD_F_MPSAFE) || \ + (((x)->flags & SD_F_MPSAFE) && _pcm_giant == 0), \ + ("%s(%d): [GIANT EXIT] MPSAFE Giant?", \ + __func__, __LINE__)); \ + if (_pcm_giant != 0) { \ + mtx_assert(&Giant, MA_OWNED); \ + _pcm_giant = 0; \ + mtx_unlock(&Giant); \ + } \ +} while(0) +#endif /* !SND_DIAGNOSTIC */ + +#define PCM_GIANT_LEAVE(x) \ + PCM_GIANT_EXIT(x); \ +} while(0) #ifdef KLD_MODULE #define PCM_KLDSTRING(a) ("kld " # a) --- sys/dev/sound/pcm/vchan.c.orig Sun Jan 30 09:00:05 2005 +++ sys/dev/sound/pcm/vchan.c Thu Jul 12 12:04:19 2007 @@ -1,5 +1,6 @@ /*- - * Copyright (c) 2001 Cameron Grant + * Copyright (c) 2001 Cameron Grant + * Copyright (c) 2006 Ariff Abdullah * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -24,328 +25,944 @@ * SUCH DAMAGE. */ +/* Almost entirely rewritten to add multi-format/channels mixing support. */ + #include +#include #include #include "feeder_if.h" -SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pcm/vchan.c,v 1.16.2.1 2005/01/30 01:00:05 imp Exp $"); +SND_DECLARE_FILE("$FreeBSD: src/sys/dev/sound/pcm/vchan.c,v 1.36 2007/06/16 03:37:28 ariff Exp $"); + +MALLOC_DEFINE(M_VCHANFEEDER, "vchanfeed", "pcm vchan feeder"); + +typedef uint32_t (*feed_vchan_mixer)(uint8_t *, uint8_t *, uint32_t); struct vchinfo { - u_int32_t spd, fmt, blksz, bps, run; - struct pcm_channel *channel, *parent; + struct pcm_channel *channel; struct pcmchan_caps caps; + uint32_t fmtlist[2]; + int trigger; +}; + +/* support everything (mono / stereo), except a-law / mu-law */ +static struct afmtstr_table vchan_supported_fmts[] = { + { "u8", AFMT_U8 }, { "s8", AFMT_S8 }, + { "s16le", AFMT_S16_LE }, { "s16be", AFMT_S16_BE }, + { "u16le", AFMT_U16_LE }, { "u16be", AFMT_U16_BE }, + { "s24le", AFMT_S24_LE }, { "s24be", AFMT_S24_BE }, + { "u24le", AFMT_U24_LE }, { "u24be", AFMT_U24_BE }, + { "s32le", AFMT_S32_LE }, { "s32be", AFMT_S32_BE }, + { "u32le", AFMT_U32_LE }, { "u32be", AFMT_U32_BE }, + { NULL, 0 }, +}; + +/* alias table, shorter. */ +static const struct { + char *alias, *fmtstr; +} vchan_fmtstralias[] = { + { "8", "u8" }, { "16", "s16le" }, + { "24", "s24le" }, { "32", "s32le" }, + { NULL, NULL }, +}; + +#define vchan_valid_format(fmt) \ + afmt2afmtstr(vchan_supported_fmts, fmt, NULL, 0, 0, \ + AFMTSTR_STEREO_RETURN) +#define vchan_valid_strformat(strfmt) \ + afmtstr2afmt(vchan_supported_fmts, strfmt, AFMTSTR_STEREO_RETURN); + +#define FEEDER_VCHAN_MIX(FMTBIT, SIGN, SIGNS, ENDIAN, ENDIANS) \ +static uint32_t \ +feed_vchan_mix_##SIGNS##FMTBIT##ENDIANS##e(uint8_t *to, uint8_t *tmp, \ + uint32_t count) \ +{ \ + intpcm##FMTBIT##_t z; \ + intpcm_t x, y; \ + int i; \ + \ + i = count; \ + tmp += i; \ + to += i; \ + \ + do { \ + tmp -= PCM_##FMTBIT##_BPS; \ + to -= PCM_##FMTBIT##_BPS; \ + i -= PCM_##FMTBIT##_BPS; \ + x = PCM_READ_##SIGN##FMTBIT##_##ENDIAN##E(tmp); \ + y = PCM_READ_##SIGN##FMTBIT##_##ENDIAN##E(to); \ + z = INTPCM##FMTBIT##_T(x) + y; \ + x = PCM_CLAMP_##SIGN##FMTBIT(z); \ + _PCM_WRITE_##SIGN##FMTBIT##_##ENDIAN##E(to, x); \ + } while (i != 0); \ + \ + return (count); \ +} + +FEEDER_VCHAN_MIX(8, S, s, N, n) +FEEDER_VCHAN_MIX(16, S, s, L, l) +FEEDER_VCHAN_MIX(24, S, s, L, l) +FEEDER_VCHAN_MIX(32, S, s, L, l) +FEEDER_VCHAN_MIX(16, S, s, B, b) +FEEDER_VCHAN_MIX(24, S, s, B, b) +FEEDER_VCHAN_MIX(32, S, s, B, b) +FEEDER_VCHAN_MIX(8, U, u, N, n) +FEEDER_VCHAN_MIX(16, U, u, L, l) +FEEDER_VCHAN_MIX(24, U, u, L, l) +FEEDER_VCHAN_MIX(32, U, u, L, l) +FEEDER_VCHAN_MIX(16, U, u, B, b) +FEEDER_VCHAN_MIX(24, U, u, B, b) +FEEDER_VCHAN_MIX(32, U, u, B, b) + +struct feed_vchan_info { + uint32_t format; + int bps; + feed_vchan_mixer mix; }; -static u_int32_t vchan_fmt[] = { - AFMT_STEREO | AFMT_S16_LE, - 0 +static struct feed_vchan_info feed_vchan_info_tbl[] = { + { AFMT_S8, PCM_8_BPS, feed_vchan_mix_s8ne }, + { AFMT_S16_LE, PCM_16_BPS, feed_vchan_mix_s16le }, + { AFMT_S24_LE, PCM_24_BPS, feed_vchan_mix_s24le }, + { AFMT_S32_LE, PCM_32_BPS, feed_vchan_mix_s32le }, + { AFMT_S16_BE, PCM_16_BPS, feed_vchan_mix_s16be }, + { AFMT_S24_BE, PCM_24_BPS, feed_vchan_mix_s24be }, + { AFMT_S32_BE, PCM_32_BPS, feed_vchan_mix_s32be }, + { AFMT_U8, PCM_8_BPS, feed_vchan_mix_u8ne }, + { AFMT_U16_LE, PCM_16_BPS, feed_vchan_mix_u16le }, + { AFMT_U24_LE, PCM_24_BPS, feed_vchan_mix_u24le }, + { AFMT_U32_LE, PCM_32_BPS, feed_vchan_mix_u32le }, + { AFMT_U16_BE, PCM_16_BPS, feed_vchan_mix_u16be }, + { AFMT_U24_BE, PCM_24_BPS, feed_vchan_mix_u24be }, + { AFMT_U32_BE, PCM_32_BPS, feed_vchan_mix_u32be }, }; +#define FVCHAN_DATA(i, c) ((intptr_t)((((i) & 0x1f) << 4) | ((c) & 0xf))) +#define FVCHAN_INFOIDX(m) (((m) >> 4) & 0x1f) +#define FVCHAN_CHANNELS(m) ((m) & 0xf) + static int -vchan_mix_s16(int16_t *to, int16_t *tmp, unsigned int count) +feed_vchan_init(struct pcm_feeder *f) +{ + int i, channels; + + if (f->desc->out != f->desc->in) + return (EINVAL); + + channels = (f->desc->out & AFMT_STEREO) ? 2 : 1; + + for (i = 0; i < sizeof(feed_vchan_info_tbl) / + sizeof(feed_vchan_info_tbl[0]); i++) { + if ((f->desc->out & ~AFMT_STEREO) == + feed_vchan_info_tbl[i].format) { + f->data = (void *)FVCHAN_DATA(i, channels); + return (0); + } + } + + return (-1); +} + +static __inline int +feed_vchan_rec(struct pcm_channel *c) { + struct pcm_channel *ch; + struct snd_dbuf *b, *bs; + int cnt, rdy; + + /* + * Reset ready and moving pointer. We're not using bufsoft + * anywhere since its sole purpose is to become the primary + * distributor for the recorded buffer and also as an interrupt + * threshold progress indicator. + */ + b = c->bufsoft; + b->rp = 0; + b->rl = 0; + cnt = sndbuf_getsize(b); + + do { + cnt = FEEDER_FEED(c->feeder->source, c, b->tmpbuf, cnt, + c->bufhard); + if (cnt != 0) { + sndbuf_acquire(b, b->tmpbuf, cnt); + cnt = sndbuf_getfree(b); + } + } while (cnt != 0); + + /* Not enough data */ + if (b->rl < sndbuf_getbps(b)) { + b->rl = 0; + return (0); + } + /* - * to is the output buffer, tmp is the input buffer - * count is the number of 16bit samples to mix + * Keep track of ready and moving pointer since we will use + * bufsoft over and over again, pretending nothing has happened. */ - int i; - int x; + rdy = b->rl; - for(i = 0; i < count; i++) { - x = to[i]; - x += tmp[i]; - if (x < -32768) { - /* printf("%d + %d = %d (u)\n", to[i], tmp[i], x); */ - x = -32768; - } - if (x > 32767) { - /* printf("%d + %d = %d (o)\n", to[i], tmp[i], x); */ - x = 32767; + CHN_FOREACH(ch, c, children.busy) { + CHN_LOCK(ch); + if (CHN_STOPPED(ch)) { + CHN_UNLOCK(ch); + continue; + } + bs = ch->bufsoft; + if (ch->flags & CHN_F_MAPPED) + sndbuf_dispose(bs, NULL, sndbuf_getready(bs)); + cnt = sndbuf_getfree(bs); + if (cnt < sndbuf_getbps(bs)) { + CHN_UNLOCK(ch); + continue; } - to[i] = x & 0x0000ffff; + do { + cnt = FEEDER_FEED(ch->feeder, ch, bs->tmpbuf, cnt, b); + if (cnt != 0) { + sndbuf_acquire(bs, bs->tmpbuf, cnt); + cnt = sndbuf_getfree(bs); + } + } while (cnt != 0); + /* + * Not entirely flushed out... + */ + if (b->rl != 0) + ch->xruns++; + CHN_UNLOCK(ch); + /* + * Rewind buffer position for next virtual channel. + */ + b->rp = 0; + b->rl = rdy; } - return 0; + + /* + * Set ready pointer to indicate that our children are ready + * to be woken up, also as an interrupt threshold progress + * indicator. + */ + b->rl = 1; + + /* + * Return 0 to bail out early from sndbuf_feed() loop. + * No need to increase feedcount counter since part of this + * feeder chains already include feed_root(). + */ + return (0); } static int -feed_vchan_s16(struct pcm_feeder *f, struct pcm_channel *c, u_int8_t *b, u_int32_t count, void *source) +feed_vchan(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, + uint32_t count, void *source) { - /* we're going to abuse things a bit */ + struct feed_vchan_info *info; struct snd_dbuf *src = source; - struct pcmchan_children *cce; struct pcm_channel *ch; - int16_t *tmp, *dst; - unsigned int cnt; + uint32_t cnt, mcnt, rcnt, sz; + uint8_t *tmp; - if (sndbuf_getsize(src) < count) - panic("feed_vchan_s16(%s): tmp buffer size %d < count %d, flags = 0x%x", - c->name, sndbuf_getsize(src), count, c->flags); - count &= ~1; - bzero(b, count); + if (c->direction == PCMDIR_REC) + return (feed_vchan_rec(c)); + + sz = sndbuf_getsize(src); + if (sz < count) + count = sz; + + info = &feed_vchan_info_tbl[FVCHAN_INFOIDX((intptr_t)f->data)]; + sz = info->bps * FVCHAN_CHANNELS((intptr_t)f->data); + count -= count % sz; + if (count < sz) + return (0); /* * we are going to use our source as a temporary buffer since it's * got no other purpose. we obtain our data by traversing the channel - * list of children and calling vchan_mix_* to mix count bytes from each - * into our destination buffer, b + * list of children and calling vchan_mix_* to mix count bytes from + * each into our destination buffer, b */ - dst = (int16_t *)b; - tmp = (int16_t *)sndbuf_getbuf(src); - bzero(tmp, count); - SLIST_FOREACH(cce, &c->children, link) { - ch = cce->channel; - CHN_LOCK(ch); - if (ch->flags & CHN_F_TRIGGERED) { - if (ch->flags & CHN_F_MAPPED) - sndbuf_acquire(ch->bufsoft, NULL, sndbuf_getfree(ch->bufsoft)); - cnt = FEEDER_FEED(ch->feeder, ch, (u_int8_t *)tmp, count, ch->bufsoft); - vchan_mix_s16(dst, tmp, cnt / 2); + tmp = sndbuf_getbuf(src); + rcnt = 0; + mcnt = 0; + + CHN_FOREACH(ch, c, children.busy) { + CHN_LOCK(ch); + if (CHN_STOPPED(ch)) { + CHN_UNLOCK(ch); + continue; + } + if ((ch->flags & CHN_F_MAPPED) && !(ch->flags & CHN_F_CLOSING)) + sndbuf_acquire(ch->bufsoft, NULL, + sndbuf_getfree(ch->bufsoft)); + if (rcnt == 0) { + rcnt = FEEDER_FEED(ch->feeder, ch, b, count, + ch->bufsoft); + rcnt -= rcnt % sz; + mcnt = count - rcnt; + } else { + cnt = FEEDER_FEED(ch->feeder, ch, tmp, count, + ch->bufsoft); + cnt -= cnt % sz; + if (cnt != 0) { + if (mcnt != 0) { + memset(b + rcnt, + sndbuf_zerodata(f->desc->out), + mcnt); + mcnt = 0; + } + cnt = info->mix(b, tmp, cnt); + if (cnt > rcnt) + rcnt = cnt; + } } - CHN_UNLOCK(ch); + CHN_UNLOCK(ch); } - return count; + if (++c->feedcount == 0) + c->feedcount = 2; + + return (rcnt); } -static struct pcm_feederdesc feeder_vchan_s16_desc[] = { +static struct pcm_feederdesc feeder_vchan_desc[] = { + {FEEDER_MIXER, AFMT_S8, AFMT_S8, 0}, + {FEEDER_MIXER, AFMT_S16_LE, AFMT_S16_LE, 0}, + {FEEDER_MIXER, AFMT_S24_LE, AFMT_S24_LE, 0}, + {FEEDER_MIXER, AFMT_S32_LE, AFMT_S32_LE, 0}, + {FEEDER_MIXER, AFMT_S16_BE, AFMT_S16_BE, 0}, + {FEEDER_MIXER, AFMT_S24_BE, AFMT_S24_BE, 0}, + {FEEDER_MIXER, AFMT_S32_BE, AFMT_S32_BE, 0}, + {FEEDER_MIXER, AFMT_S8 | AFMT_STEREO, AFMT_S8 | AFMT_STEREO, 0}, {FEEDER_MIXER, AFMT_S16_LE | AFMT_STEREO, AFMT_S16_LE | AFMT_STEREO, 0}, - {0}, + {FEEDER_MIXER, AFMT_S24_LE | AFMT_STEREO, AFMT_S24_LE | AFMT_STEREO, 0}, + {FEEDER_MIXER, AFMT_S32_LE | AFMT_STEREO, AFMT_S32_LE | AFMT_STEREO, 0}, + {FEEDER_MIXER, AFMT_S16_BE | AFMT_STEREO, AFMT_S16_BE | AFMT_STEREO, 0}, + {FEEDER_MIXER, AFMT_S24_BE | AFMT_STEREO, AFMT_S24_BE | AFMT_STEREO, 0}, + {FEEDER_MIXER, AFMT_S32_BE | AFMT_STEREO, AFMT_S32_BE | AFMT_STEREO, 0}, + {FEEDER_MIXER, AFMT_U8, AFMT_U8, 0}, + {FEEDER_MIXER, AFMT_U16_LE, AFMT_U16_LE, 0}, + {FEEDER_MIXER, AFMT_U24_LE, AFMT_U24_LE, 0}, + {FEEDER_MIXER, AFMT_U32_LE, AFMT_U32_LE, 0}, + {FEEDER_MIXER, AFMT_U16_BE, AFMT_U16_BE, 0}, + {FEEDER_MIXER, AFMT_U24_BE, AFMT_U24_BE, 0}, + {FEEDER_MIXER, AFMT_U32_BE, AFMT_U32_BE, 0}, + {FEEDER_MIXER, AFMT_U8 | AFMT_STEREO, AFMT_U8 | AFMT_STEREO, 0}, + {FEEDER_MIXER, AFMT_U16_LE | AFMT_STEREO, AFMT_U16_LE | AFMT_STEREO, 0}, + {FEEDER_MIXER, AFMT_U24_LE | AFMT_STEREO, AFMT_U24_LE | AFMT_STEREO, 0}, + {FEEDER_MIXER, AFMT_U32_LE | AFMT_STEREO, AFMT_U32_LE | AFMT_STEREO, 0}, + {FEEDER_MIXER, AFMT_U16_BE | AFMT_STEREO, AFMT_U16_BE | AFMT_STEREO, 0}, + {FEEDER_MIXER, AFMT_U24_BE | AFMT_STEREO, AFMT_U24_BE | AFMT_STEREO, 0}, + {FEEDER_MIXER, AFMT_U32_BE | AFMT_STEREO, AFMT_U32_BE | AFMT_STEREO, 0}, + {0, 0, 0, 0}, }; -static kobj_method_t feeder_vchan_s16_methods[] = { - KOBJMETHOD(feeder_feed, feed_vchan_s16), - { 0, 0 } +static kobj_method_t feeder_vchan_methods[] = { + KOBJMETHOD(feeder_init, feed_vchan_init), + KOBJMETHOD(feeder_feed, feed_vchan), + {0, 0} }; -FEEDER_DECLARE(feeder_vchan_s16, 2, NULL); +FEEDER_DECLARE(feeder_vchan, 2, NULL); /************************************************************/ static void * -vchan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) +vchan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, + struct pcm_channel *c, int dir) { struct vchinfo *ch; - struct pcm_channel *parent = devinfo; - KASSERT(dir == PCMDIR_PLAY, ("vchan_init: bad direction")); + KASSERT(dir == PCMDIR_PLAY || dir == PCMDIR_REC, + ("vchan_init: bad direction")); + KASSERT(c != NULL && c->parentchannel != NULL, + ("vchan_init: bad channels")); + ch = malloc(sizeof(*ch), M_DEVBUF, M_WAITOK | M_ZERO); - ch->parent = parent; ch->channel = c; - ch->fmt = AFMT_U8; - ch->spd = DSP_DEFAULT_SPEED; - ch->blksz = 2048; + ch->trigger = PCMTRIG_STOP; c->flags |= CHN_F_VIRTUAL; - return ch; + return (ch); } static int vchan_free(kobj_t obj, void *data) { - return 0; -} - -static int -vchan_setformat(kobj_t obj, void *data, u_int32_t format) -{ - struct vchinfo *ch = data; - struct pcm_channel *parent = ch->parent; - struct pcm_channel *channel = ch->channel; + free(data, M_DEVBUF); - ch->fmt = format; - ch->bps = 1; - ch->bps <<= (ch->fmt & AFMT_STEREO)? 1 : 0; - ch->bps <<= (ch->fmt & AFMT_16BIT)? 1 : 0; - ch->bps <<= (ch->fmt & AFMT_32BIT)? 2 : 0; - CHN_UNLOCK(channel); - chn_notify(parent, CHN_N_FORMAT); - CHN_LOCK(channel); - return 0; + return (0); } static int -vchan_setspeed(kobj_t obj, void *data, u_int32_t speed) +vchan_setformat(kobj_t obj, void *data, uint32_t format) { struct vchinfo *ch = data; - struct pcm_channel *parent = ch->parent; - struct pcm_channel *channel = ch->channel; - ch->spd = speed; - CHN_UNLOCK(channel); - chn_notify(parent, CHN_N_RATE); - CHN_LOCK(channel); - return speed; + if (fmtvalid(format, ch->fmtlist) == 0) + return (-1); + + return (0); } static int -vchan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) +vchan_setspeed(kobj_t obj, void *data, uint32_t speed) { struct vchinfo *ch = data; - struct pcm_channel *parent = ch->parent; - /* struct pcm_channel *channel = ch->channel; */ - int prate, crate; - - ch->blksz = blocksize; - /* CHN_UNLOCK(channel); */ - chn_notify(parent, CHN_N_BLOCKSIZE); - CHN_LOCK(parent); - /* CHN_LOCK(channel); */ - - crate = ch->spd * ch->bps; - prate = sndbuf_getspd(parent->bufhard) * sndbuf_getbps(parent->bufhard); - blocksize = sndbuf_getblksz(parent->bufhard); - CHN_UNLOCK(parent); - blocksize *= prate; - blocksize /= crate; + struct pcm_channel *p = ch->channel->parentchannel; - return blocksize; + return (sndbuf_getspd(p->bufsoft)); } static int vchan_trigger(kobj_t obj, void *data, int go) { struct vchinfo *ch = data; - struct pcm_channel *parent = ch->parent; - struct pcm_channel *channel = ch->channel; + struct pcm_channel *c, *p; + int err, otrigger; - if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) - return 0; + if (!PCMTRIG_COMMON(go) || go == ch->trigger) + return (0); - ch->run = (go == PCMTRIG_START)? 1 : 0; - CHN_UNLOCK(channel); - chn_notify(parent, CHN_N_TRIGGER); - CHN_LOCK(channel); + c = ch->channel; + p = c->parentchannel; + otrigger = ch->trigger; + ch->trigger = go; + + CHN_UNLOCK(c); + CHN_LOCK(p); + + switch (go) { + case PCMTRIG_START: + if (otrigger != PCMTRIG_START) + CHN_INSERT_HEAD(p, c, children.busy); + break; + case PCMTRIG_STOP: + case PCMTRIG_ABORT: + if (otrigger == PCMTRIG_START) + CHN_REMOVE(p, c, children.busy); + break; + default: + break; + } + + err = chn_notify(p, CHN_N_TRIGGER); + CHN_UNLOCK(p); + CHN_LOCK(c); - return 0; + return (err); } static struct pcmchan_caps * vchan_getcaps(kobj_t obj, void *data) { struct vchinfo *ch = data; + struct pcm_channel *c, *p; + uint32_t fmt; - ch->caps.minspeed = sndbuf_getspd(ch->parent->bufhard); + c = ch->channel; + p = c->parentchannel; + ch->caps.minspeed = sndbuf_getspd(p->bufsoft); ch->caps.maxspeed = ch->caps.minspeed; - ch->caps.fmtlist = vchan_fmt; ch->caps.caps = 0; + ch->fmtlist[1] = 0; + fmt = sndbuf_getfmt(p->bufsoft); + if (fmt != vchan_valid_format(fmt)) { + device_printf(c->dev, + "%s: WARNING: invalid vchan format! (0x%08x)\n", + __func__, fmt); + fmt = VCHAN_DEFAULT_AFMT; + } + ch->fmtlist[0] = fmt; + ch->caps.fmtlist = ch->fmtlist; - return &ch->caps; + return (&ch->caps); } static kobj_method_t vchan_methods[] = { - KOBJMETHOD(channel_init, vchan_init), - KOBJMETHOD(channel_free, vchan_free), - KOBJMETHOD(channel_setformat, vchan_setformat), - KOBJMETHOD(channel_setspeed, vchan_setspeed), - KOBJMETHOD(channel_setblocksize, vchan_setblocksize), - KOBJMETHOD(channel_trigger, vchan_trigger), - KOBJMETHOD(channel_getcaps, vchan_getcaps), - { 0, 0 } + KOBJMETHOD(channel_init, vchan_init), + KOBJMETHOD(channel_free, vchan_free), + KOBJMETHOD(channel_setformat, vchan_setformat), + KOBJMETHOD(channel_setspeed, vchan_setspeed), + KOBJMETHOD(channel_trigger, vchan_trigger), + KOBJMETHOD(channel_getcaps, vchan_getcaps), + {0, 0} }; CHANNEL_DECLARE(vchan); -/* virtual channel interface */ +/* + * On the fly vchan rate settings + */ +#ifdef SND_DYNSYSCTL +static int +sysctl_dev_pcm_vchanrate(SYSCTL_HANDLER_ARGS) +{ + struct snddev_info *d; + struct pcm_channel *c, *ch = NULL; + struct pcmchan_caps *caps; + int *vchanrate, vchancount, direction, err, newspd; + + d = devclass_get_softc(pcm_devclass, VCHAN_SYSCTL_UNIT(oidp->oid_arg1)); + if (!PCM_REGISTERED(d) || !(d->flags & SD_F_AUTOVCHAN)) + return (EINVAL); + + pcm_lock(d); + PCM_WAIT(d); + + switch (VCHAN_SYSCTL_DIR(oidp->oid_arg1)) { + case VCHAN_PLAY: + direction = PCMDIR_PLAY; + vchancount = d->pvchancount; + vchanrate = &d->pvchanrate; + break; + case VCHAN_REC: + direction = PCMDIR_REC; + vchancount = d->rvchancount; + vchanrate = &d->rvchanrate; + break; + default: + pcm_unlock(d); + return (EINVAL); + break; + } -int -vchan_create(struct pcm_channel *parent) + if (vchancount < 1) { + pcm_unlock(d); + return (EINVAL); + } + + PCM_ACQUIRE(d); + pcm_unlock(d); + + newspd = 0; + + CHN_FOREACH(c, d, channels.pcm) { + CHN_LOCK(c); + if (c->direction == direction) { + if (c->flags & CHN_F_VIRTUAL) { + /* Sanity check */ + if (ch != NULL && ch != c->parentchannel) { + CHN_UNLOCK(c); + PCM_RELEASE_QUICK(d); + return (EINVAL); + } + } else if (c->flags & CHN_F_HAS_VCHAN) { + /* No way!! */ + if (ch != NULL) { + CHN_UNLOCK(c); + PCM_RELEASE_QUICK(d); + return (EINVAL); + } + ch = c; + newspd = ch->speed; + } + } + CHN_UNLOCK(c); + } + if (ch == NULL) { + PCM_RELEASE_QUICK(d); + return (EINVAL); + } + + err = sysctl_handle_int(oidp, &newspd, 0, req); + if (err == 0 && req->newptr != NULL) { + if (newspd < 1 || newspd < feeder_rate_min || + newspd > feeder_rate_max) { + PCM_RELEASE_QUICK(d); + return (EINVAL); + } + CHN_LOCK(ch); + if (feeder_rate_round) { + caps = chn_getcaps(ch); + if (caps == NULL || newspd < caps->minspeed || + newspd > caps->maxspeed) { + CHN_UNLOCK(ch); + PCM_RELEASE_QUICK(d); + return (EINVAL); + } + } + if (CHN_STOPPED(ch) && newspd != ch->speed) { + err = chn_setspeed(ch, newspd); + /* + * Try to avoid FEEDER_RATE on parent channel if the + * requested value is not supported by the hardware. + */ + if (!err && feeder_rate_round && + (ch->feederflags & (1 << FEEDER_RATE))) { + newspd = sndbuf_getspd(ch->bufhard); + err = chn_setspeed(ch, newspd); + } + if (err == 0) + *vchanrate = newspd; + } + CHN_UNLOCK(ch); + } + + PCM_RELEASE_QUICK(d); + + return (err); +} + +static int +sysctl_dev_pcm_vchanformat(SYSCTL_HANDLER_ARGS) { - struct snddev_info *d = parent->parentsnddev; - struct pcmchan_children *pce; - struct pcm_channel *child; - int err, first; - - CHN_UNLOCK(parent); - - pce = malloc(sizeof(*pce), M_DEVBUF, M_WAITOK | M_ZERO); - if (!pce) { - CHN_LOCK(parent); - return ENOMEM; + struct snddev_info *d; + struct pcm_channel *c, *ch = NULL; + uint32_t newfmt, spd; + int *vchanformat, vchancount, direction, err, i; + char fmtstr[AFMTSTR_MAXSZ]; + + d = devclass_get_softc(pcm_devclass, VCHAN_SYSCTL_UNIT(oidp->oid_arg1)); + if (!PCM_REGISTERED(d) || !(d->flags & SD_F_AUTOVCHAN)) + return (EINVAL); + + pcm_lock(d); + PCM_WAIT(d); + + switch (VCHAN_SYSCTL_DIR(oidp->oid_arg1)) { + case VCHAN_PLAY: + direction = PCMDIR_PLAY; + vchancount = d->pvchancount; + vchanformat = &d->pvchanformat; + break; + case VCHAN_REC: + direction = PCMDIR_REC; + vchancount = d->rvchancount; + vchanformat = &d->rvchanformat; + break; + default: + pcm_unlock(d); + return (EINVAL); + break; } - /* create a new playback channel */ - child = pcm_chn_create(d, parent, &vchan_class, PCMDIR_VIRTUAL, parent); - if (!child) { - free(pce, M_DEVBUF); - CHN_LOCK(parent); - return ENODEV; + if (vchancount < 1) { + pcm_unlock(d); + return (EINVAL); + } + + PCM_ACQUIRE(d); + pcm_unlock(d); + + CHN_FOREACH(c, d, channels.pcm) { + CHN_LOCK(c); + if (c->direction == direction) { + if (c->flags & CHN_F_VIRTUAL) { + /* Sanity check */ + if (ch != NULL && ch != c->parentchannel) { + CHN_UNLOCK(c); + PCM_RELEASE_QUICK(d); + return (EINVAL); + } + } else if (c->flags & CHN_F_HAS_VCHAN) { + /* No way!! */ + if (ch != NULL) { + CHN_UNLOCK(c); + PCM_RELEASE_QUICK(d); + return (EINVAL); + } + ch = c; + if (ch->format != + afmt2afmtstr(vchan_supported_fmts, + ch->format, fmtstr, sizeof(fmtstr), + AFMTSTR_FULL, AFMTSTR_STEREO_RETURN)) { + strlcpy(fmtstr, VCHAN_DEFAULT_STRFMT, + sizeof(fmtstr)); + } + } + } + CHN_UNLOCK(c); + } + if (ch == NULL) { + PCM_RELEASE_QUICK(d); + return (EINVAL); } - CHN_LOCK(parent); + err = sysctl_handle_string(oidp, fmtstr, sizeof(fmtstr), req); + if (err == 0 && req->newptr != NULL) { + for (i = 0; vchan_fmtstralias[i].alias != NULL; i++) { + if (strcmp(fmtstr, vchan_fmtstralias[i].alias) == 0) { + strlcpy(fmtstr, vchan_fmtstralias[i].fmtstr, + sizeof(fmtstr)); + break; + } + } + newfmt = vchan_valid_strformat(fmtstr); + if (newfmt == 0) { + PCM_RELEASE_QUICK(d); + return (EINVAL); + } + CHN_LOCK(ch); + if (CHN_STOPPED(ch) && newfmt != ch->format) { + /* Get channel speed, before chn_reset() screw it. */ + spd = ch->speed; + err = chn_reset(ch, newfmt); + if (err == 0) + err = chn_setspeed(ch, spd); + if (err == 0) + *vchanformat = newfmt; + } + CHN_UNLOCK(ch); + } + + PCM_RELEASE_QUICK(d); + + return (err); +} +#endif + +/* virtual channel interface */ + +#define VCHAN_FMT_HINT(x) ((x) == PCMDIR_PLAY_VIRTUAL) ? \ + "play.vchanformat" : "rec.vchanformat" +#define VCHAN_SPD_HINT(x) ((x) == PCMDIR_PLAY_VIRTUAL) ? \ + "play.vchanrate" : "rec.vchanrate" + +int +vchan_create(struct pcm_channel *parent, int num) +{ + struct snddev_info *d = parent->parentsnddev; + struct pcm_channel *ch; + struct pcmchan_caps *parent_caps; + uint32_t vchanfmt; + int err, first, speed, r; + int direction; + + PCM_BUSYASSERT(d); + if (!(parent->flags & CHN_F_BUSY)) - return EBUSY; + return (EBUSY); - first = SLIST_EMPTY(&parent->children); - /* add us to our parent channel's children */ - pce->channel = child; - SLIST_INSERT_HEAD(&parent->children, pce, link); + if (parent->direction == PCMDIR_PLAY) { + direction = PCMDIR_PLAY_VIRTUAL; + vchanfmt = d->pvchanformat; + speed = d->pvchanrate; + } else if (parent->direction == PCMDIR_REC) { + direction = PCMDIR_REC_VIRTUAL; + vchanfmt = d->rvchanformat; + speed = d->rvchanrate; + } else + return (EINVAL); CHN_UNLOCK(parent); + /* create a new playback channel */ + pcm_lock(d); + ch = pcm_chn_create(d, parent, &vchan_class, direction, num, parent); + if (ch == NULL) { + pcm_unlock(d); + CHN_LOCK(parent); + return (ENODEV); + } + /* add us to our grandparent's channel list */ - /* - * XXX maybe we shouldn't always add the dev_t - */ - err = pcm_chn_add(d, child); + err = pcm_chn_add(d, ch); + pcm_unlock(d); if (err) { - pcm_chn_destroy(child); - free(pce, M_DEVBUF); + pcm_chn_destroy(ch); + CHN_LOCK(parent); + return (err); } - CHN_LOCK(parent); - /* XXX gross ugly hack, murder death kill */ - if (first && !err) { - err = chn_reset(parent, AFMT_STEREO | AFMT_S16_LE); - if (err) - printf("chn_reset: %d\n", err); - err = chn_setspeed(parent, 44100); - if (err) - printf("chn_setspeed: %d\n", err); + CHN_LOCK(parent); + /* + * Add us to our parent channel's children in reverse order + * so future destruction will pick the last (biggest number) + * channel. + */ + first = CHN_EMPTY(parent, children); + CHN_INSERT_SORT_DESCEND(parent, ch, children); + parent->flags |= CHN_F_HAS_VCHAN; + + if (first) { + parent_caps = chn_getcaps(parent); + if (parent_caps == NULL) + err = EINVAL; + + if (!err) { + if (vchanfmt == 0) { + const char *vfmt; + + CHN_UNLOCK(parent); + r = resource_string_value( + device_get_name(parent->dev), + device_get_unit(parent->dev), + VCHAN_FMT_HINT(direction), + &vfmt); + CHN_LOCK(parent); + if (r != 0) + vfmt = NULL; + if (vfmt != NULL) { + vchanfmt = vchan_valid_strformat(vfmt); + for (r = 0; vchanfmt == 0 && + vchan_fmtstralias[r].alias != NULL; + r++) { + if (strcmp(vfmt, vchan_fmtstralias[r].alias) == 0) { + vchanfmt = vchan_valid_strformat(vchan_fmtstralias[r].fmtstr); + break; + } + } + } + if (vchanfmt == 0) + vchanfmt = VCHAN_DEFAULT_AFMT; + } + err = chn_reset(parent, vchanfmt); + } + + if (!err) { + /* + * This is very sad. Few soundcards advertised as being + * able to do (insanely) higher/lower speed, but in + * reality, they simply can't. At least, we give user chance + * to set sane value via kernel hints or sysctl. + */ + if (speed < 1) { + CHN_UNLOCK(parent); + r = resource_int_value( + device_get_name(parent->dev), + device_get_unit(parent->dev), + VCHAN_SPD_HINT(direction), + &speed); + CHN_LOCK(parent); + if (r != 0) { + /* + * No saved value, no hint, NOTHING. + * + * Workaround for sb16 running + * poorly at 45k / 49k. + */ + switch (parent_caps->maxspeed) { + case 45000: + case 49000: + speed = 44100; + break; + default: + speed = VCHAN_DEFAULT_SPEED; + if (speed > parent_caps->maxspeed) + speed = parent_caps->maxspeed; + break; + } + if (speed < parent_caps->minspeed) + speed = parent_caps->minspeed; + } + } + + if (feeder_rate_round) { + /* + * Limit speed based on driver caps. + * This is supposed to help fixed rate, non-VRA + * AC97 cards, but.. (see below) + */ + if (speed < parent_caps->minspeed) + speed = parent_caps->minspeed; + if (speed > parent_caps->maxspeed) + speed = parent_caps->maxspeed; + } + + /* + * We still need to limit the speed between + * feeder_rate_min <-> feeder_rate_max. This is + * just an escape goat if all of the above failed + * miserably. + */ + if (speed < feeder_rate_min) + speed = feeder_rate_min; + if (speed > feeder_rate_max) + speed = feeder_rate_max; + + err = chn_setspeed(parent, speed); + /* + * Try to avoid FEEDER_RATE on parent channel if the + * requested value is not supported by the hardware. + */ + if (!err && feeder_rate_round && + (parent->feederflags & (1 << FEEDER_RATE))) { + speed = sndbuf_getspd(parent->bufhard); + err = chn_setspeed(parent, speed); + } + + if (!err) { + /* + * Save new value. + */ + CHN_UNLOCK(parent); + if (direction == PCMDIR_PLAY_VIRTUAL) { + d->pvchanformat = vchanfmt; + d->pvchanrate = speed; + } else { + d->rvchanformat = vchanfmt; + d->rvchanrate = speed; + } + CHN_LOCK(parent); + } + } + + if (err) { + CHN_REMOVE(parent, ch, children); + parent->flags &= ~CHN_F_HAS_VCHAN; + CHN_UNLOCK(parent); + pcm_lock(d); + if (pcm_chn_remove(d, ch) == 0) { + pcm_unlock(d); + pcm_chn_destroy(ch); + } else + pcm_unlock(d); + CHN_LOCK(parent); + return (err); + } } - return err; + return (0); } int vchan_destroy(struct pcm_channel *c) { - struct pcm_channel *parent = c->parentchannel; - struct snddev_info *d = parent->parentsnddev; - struct pcmchan_children *pce; - int err, last; + struct pcm_channel *parent; + struct snddev_info *d; + uint32_t spd; + int err; - CHN_LOCK(parent); - if (!(parent->flags & CHN_F_BUSY)) { - CHN_UNLOCK(parent); - return EBUSY; - } - if (SLIST_EMPTY(&parent->children)) { - CHN_UNLOCK(parent); - return EINVAL; - } + KASSERT(c != NULL && c->parentchannel != NULL && + c->parentsnddev != NULL, ("%s(): invalid channel=%p", + __func__, c)); + + CHN_LOCKASSERT(c); + + d = c->parentsnddev; + parent = c->parentchannel; + + PCM_BUSYASSERT(d); + CHN_LOCKASSERT(parent); + + CHN_UNLOCK(c); + + if (!(parent->flags & CHN_F_BUSY)) + return (EBUSY); + + if (CHN_EMPTY(parent, children)) + return (EINVAL); /* remove us from our parent's children list */ - SLIST_FOREACH(pce, &parent->children, link) { - if (pce->channel == c) - goto gotch; + CHN_REMOVE(parent, c, children); + + if (CHN_EMPTY(parent, children)) { + parent->flags &= ~(CHN_F_BUSY | CHN_F_HAS_VCHAN); + spd = parent->speed; + if (chn_reset(parent, parent->format) == 0) + chn_setspeed(parent, spd); } + CHN_UNLOCK(parent); - return EINVAL; -gotch: - SLIST_REMOVE(&parent->children, pce, pcmchan_children, link); - free(pce, M_DEVBUF); - - last = SLIST_EMPTY(&parent->children); - if (last) - parent->flags &= ~CHN_F_BUSY; /* remove us from our grandparent's channel list */ + pcm_lock(d); err = pcm_chn_remove(d, c); - if (err) - return err; + pcm_unlock(d); - CHN_UNLOCK(parent); /* destroy ourselves */ - err = pcm_chn_destroy(c); + if (!err) + err = pcm_chn_destroy(c); + + CHN_LOCK(parent); - return err; + return (err); } int @@ -353,14 +970,44 @@ { #ifdef SND_DYNSYSCTL struct snddev_info *d; + int unit; - d = device_get_softc(dev); - SYSCTL_ADD_PROC(snd_sysctl_tree(dev), SYSCTL_CHILDREN(snd_sysctl_tree_top(dev)), - OID_AUTO, "vchans", CTLTYPE_INT | CTLFLAG_RW, d, sizeof(d), - sysctl_hw_snd_vchans, "I", ""); + unit = device_get_unit(dev); + d = device_get_softc(dev); + + /* Play */ + SYSCTL_ADD_PROC(&d->play_sysctl_ctx, + SYSCTL_CHILDREN(d->play_sysctl_tree), + OID_AUTO, "vchans", CTLTYPE_INT | CTLFLAG_RW, + VCHAN_SYSCTL_DATA(unit, PLAY), VCHAN_SYSCTL_DATA_SIZE, + sysctl_hw_snd_vchans, "I", "total allocated virtual channel"); + SYSCTL_ADD_PROC(&d->play_sysctl_ctx, + SYSCTL_CHILDREN(d->play_sysctl_tree), + OID_AUTO, "vchanrate", CTLTYPE_INT | CTLFLAG_RW, + VCHAN_SYSCTL_DATA(unit, PLAY), VCHAN_SYSCTL_DATA_SIZE, + sysctl_dev_pcm_vchanrate, "I", "virtual channel mixing speed/rate"); + SYSCTL_ADD_PROC(&d->play_sysctl_ctx, + SYSCTL_CHILDREN(d->play_sysctl_tree), + OID_AUTO, "vchanformat", CTLTYPE_STRING | CTLFLAG_RW, + VCHAN_SYSCTL_DATA(unit, PLAY), VCHAN_SYSCTL_DATA_SIZE, + sysctl_dev_pcm_vchanformat, "A", "virtual channel format"); + /* Rec */ + SYSCTL_ADD_PROC(&d->rec_sysctl_ctx, + SYSCTL_CHILDREN(d->rec_sysctl_tree), + OID_AUTO, "vchans", CTLTYPE_INT | CTLFLAG_RW, + VCHAN_SYSCTL_DATA(unit, REC), VCHAN_SYSCTL_DATA_SIZE, + sysctl_hw_snd_vchans, "I", "total allocated virtual channel"); + SYSCTL_ADD_PROC(&d->rec_sysctl_ctx, + SYSCTL_CHILDREN(d->rec_sysctl_tree), + OID_AUTO, "vchanrate", CTLTYPE_INT | CTLFLAG_RW, + VCHAN_SYSCTL_DATA(unit, REC), VCHAN_SYSCTL_DATA_SIZE, + sysctl_dev_pcm_vchanrate, "I", "virtual channel base speed/rate"); + SYSCTL_ADD_PROC(&d->rec_sysctl_ctx, + SYSCTL_CHILDREN(d->rec_sysctl_tree), + OID_AUTO, "vchanformat", CTLTYPE_STRING | CTLFLAG_RW, + VCHAN_SYSCTL_DATA(unit, REC), VCHAN_SYSCTL_DATA_SIZE, + sysctl_dev_pcm_vchanformat, "A", "virtual channel format"); #endif - return 0; + return (0); } - - --- sys/dev/sound/pcm/vchan.h.orig Sun Jan 30 09:00:05 2005 +++ sys/dev/sound/pcm/vchan.h Thu Jul 12 12:04:19 2007 @@ -23,11 +23,30 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $FreeBSD: src/sys/dev/sound/pcm/vchan.h,v 1.3.4.1 2005/01/30 01:00:05 imp Exp $ + * $FreeBSD: src/sys/dev/sound/pcm/vchan.h,v 1.5 2007/05/31 18:43:32 ariff Exp $ */ -int vchan_create(struct pcm_channel *parent); +int vchan_create(struct pcm_channel *parent, int num); int vchan_destroy(struct pcm_channel *c); int vchan_initsys(device_t dev); +/* + * Default speed / format + */ +#define VCHAN_DEFAULT_SPEED 48000 +#define VCHAN_DEFAULT_AFMT (AFMT_S16_LE | AFMT_STEREO) +#define VCHAN_DEFAULT_STRFMT "s16le" + +#define VCHAN_PLAY 0 +#define VCHAN_REC 1 + +/* + * Offset by +/- 1 so we can distinguish bogus pointer. + */ +#define VCHAN_SYSCTL_DATA(x, y) \ + ((void *)((intptr_t)(((((x) + 1) & 0xfff) << 2) | \ + (((VCHAN_##y) + 1) & 0x3)))) +#define VCHAN_SYSCTL_DATA_SIZE sizeof(void *) +#define VCHAN_SYSCTL_UNIT(x) ((int)(((intptr_t)(x) >> 2) & 0xfff) - 1) +#define VCHAN_SYSCTL_DIR(x) ((int)((intptr_t)(x) & 0x3) - 1) --- sys/dev/sound/sbus/apcdmareg.h.orig Sun Jan 30 09:00:05 2005 +++ sys/dev/sound/sbus/apcdmareg.h Thu Jan 6 09:43:22 2005 @@ -1,4 +1,4 @@ -/* $FreeBSD: src/sys/dev/sound/sbus/apcdmareg.h,v 1.1.2.2 2005/01/30 01:00:05 imp Exp $ */ +/* $FreeBSD: src/sys/dev/sound/sbus/apcdmareg.h,v 1.2 2005/01/06 01:43:22 imp Exp $ */ /* $OpenBSD: apcdmareg.h,v 1.2 2003/06/02 18:53:18 jason Exp $ */ /*- --- sys/dev/sound/sbus/cs4231.c.orig Sun Jan 30 09:00:05 2005 +++ sys/dev/sound/sbus/cs4231.c Thu Jul 12 12:04:19 2007 @@ -37,7 +37,7 @@ */ #include -__FBSDID("$FreeBSD: src/sys/dev/sound/sbus/cs4231.c,v 1.1.2.2 2005/01/30 01:00:05 imp Exp $"); +__FBSDID("$FreeBSD: src/sys/dev/sound/sbus/cs4231.c,v 1.9 2007/06/17 06:10:43 ariff Exp $"); #include #include @@ -125,7 +125,6 @@ struct cs4231_channel sc_pch; struct cs4231_channel sc_rch; int sc_enabled; - int sc_rtype; int sc_nmres; int sc_nires; int sc_codecv; @@ -307,12 +306,14 @@ static int cs4231_bus_probe(device_t dev) { - const char *name; + const char *compat, *name; + compat = ofw_bus_get_compat(dev); name = ofw_bus_get_name(dev); - if (strcmp("SUNW,CS4231", name) == 0) { + if (strcmp("SUNW,CS4231", name) == 0 || + (compat != NULL && strcmp("SUNW,CS4231", compat) == 0)) { device_set_desc(dev, "Sun Audiocs"); - return (0); + return (BUS_PROBE_DEFAULT); } return (ENXIO); } @@ -320,16 +321,10 @@ static int cs4231_sbus_attach(device_t dev) { - struct snddev_info *d; struct cs4231_softc *sc; int burst; - d = device_get_softc(dev); - sc = malloc(sizeof(struct cs4231_softc), M_DEVBUF, M_NOWAIT | M_ZERO); - if (sc == NULL) { - device_printf(dev, "cannot allocate softc\n"); - return (ENOMEM); - } + sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO); sc->sc_dev = dev; /* * XXX @@ -345,7 +340,6 @@ else sc->sc_burst = 0; sc->sc_flags = CS4231_SBUS; - sc->sc_rtype = SYS_RES_MEMORY; sc->sc_nmres = 1; sc->sc_nires = 1; return cs4231_attach_common(sc); @@ -354,10 +348,8 @@ static int cs4231_ebus_attach(device_t dev) { - struct snddev_info *d; struct cs4231_softc *sc; - d = device_get_softc(dev); sc = malloc(sizeof(struct cs4231_softc), M_DEVBUF, M_NOWAIT | M_ZERO); if (sc == NULL) { device_printf(dev, "cannot allocate softc\n"); @@ -365,7 +357,6 @@ } sc->sc_dev = dev; sc->sc_burst = EBDCSR_BURST_1; - sc->sc_rtype = SYS_RES_IOPORT; sc->sc_nmres = CS4231_RES_MEM_MAX; sc->sc_nires = CS4231_RES_IRQ_MAX; sc->sc_flags = CS4231_EBUS; @@ -380,17 +371,12 @@ int i; sc->sc_lock = snd_mtxcreate(device_get_nameunit(sc->sc_dev), - "sound softc"); - if (sc->sc_lock == NULL) { - device_printf(sc->sc_dev, "cannot create mutex\n"); - free(sc, M_DEVBUF); - return (ENXIO); - } + "snd_cs4231 softc"); for (i = 0; i < sc->sc_nmres; i++) { sc->sc_rid[i] = i; if ((sc->sc_res[i] = bus_alloc_resource_any(sc->sc_dev, - sc->sc_rtype, &sc->sc_rid[i], RF_ACTIVE)) == NULL) { + SYS_RES_MEMORY, &sc->sc_rid[i], RF_ACTIVE)) == NULL) { device_printf(sc->sc_dev, "cannot map register %d\n", i); goto fail; @@ -439,7 +425,7 @@ CS4231_DEFAULT_BUF_SZ, CS4231_MAX_BUF_SZ); for (i = 0; i < sc->sc_nires; i++) { if (bus_dma_tag_create( - NULL, /* parent */ + bus_get_dma_tag(sc->sc_dev),/* parent */ 64, 0, /* alignment, boundary */ BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ @@ -718,7 +704,7 @@ } for (i = 0; i < sc->sc_nmres; i++) { if (sc->sc_res[i]) - bus_release_resource(sc->sc_dev, sc->sc_rtype, + bus_release_resource(sc->sc_dev, SYS_RES_MEMORY, sc->sc_rid[i], sc->sc_res[i]); } snd_mtxfree(sc->sc_lock); @@ -1046,7 +1032,7 @@ else dmat = sc->sc_dmat[0]; } - if (sndbuf_alloc(ch->buffer, dmat, sc->sc_bufsz) != 0) + if (sndbuf_alloc(ch->buffer, dmat, 0, sc->sc_bufsz) != 0) return (NULL); DPRINTF(("%s channel addr: 0x%lx\n", dir == PCMDIR_PLAY ? "playback" : "capture", sndbuf_getbufaddr(ch->buffer))); --- sys/dev/sound/sbus/cs4231.h.orig Mon Dec 13 19:12:49 2004 +++ sys/dev/sound/sbus/cs4231.h Thu Jan 6 09:43:22 2005 @@ -1,4 +1,4 @@ -/* $FreeBSD: src/sys/dev/sound/sbus/cs4231.h,v 1.1.2.1 2004/12/13 11:12:49 yongari Exp $ */ +/* $FreeBSD: src/sys/dev/sound/sbus/cs4231.h,v 1.2 2005/01/06 01:43:22 imp Exp $ */ /*- * Copyright (c) 1996 The NetBSD Foundation, Inc. * All rights reserved. @@ -35,7 +35,7 @@ * POSSIBILITY OF SUCH DAMAGE. */ -/* +/** * Register defs for Crystal Semiconductor CS4231 Audio Codec/mixer * chip, used on Gravis UltraSound MAX cards. * --- sys/dev/sound/unit.c.orig Thu Jan 1 07:30:00 1970 +++ sys/dev/sound/unit.c Thu Jul 12 12:04:19 2007 @@ -0,0 +1,194 @@ +/*- + * 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/unit.c,v 1.1 2007/05/31 18:35:24 ariff Exp $ + */ + +#include +#include + +#include + +/* + * Unit magic allocator for sound driver. + * + * 'u' = Unit of attached soundcards + * 'd' = Device type + * 'c' = Channel number + * + * eg: dsp0.p1 - u=0, d=p, c=1 + * dsp1.vp0 - u=1, d=vp, c=0 + * dsp0.10 - u=0, d=clone, c=allocated clone (see further explanation) + * + * Maximum unit of soundcards can be tuned through "hw.snd.maxunit", which + * is between SND_UNIT_UMIN (16) and SND_UNIT_UMAX (2048). By design, + * maximum allowable allocated channel is 256, with exception for clone + * devices which doesn't have any notion of channel numbering. The use of + * channel numbering in a clone device is simply to provide uniqueness among + * allocated clones. This also means that the maximum allowable clonable + * device is largely dependant and dynamically tuned depending on + * hw.snd.maxunit. + */ + +/* Default width */ +static int snd_u_shift = 9; /* 0 - 0x1ff : 512 distinct soundcards */ +static int snd_d_shift = 5; /* 0 - 0x1f : 32 distinct device types */ +static int snd_c_shift = 10; /* 0 - 0x3ff : 1024 distinct channels + (256 limit "by design", + except for clone devices) */ + +static int snd_unit_initialized = 0; + +#ifdef SND_DIAGNOSTIC +#define SND_UNIT_ASSERT() do { \ + if (snd_unit_initialized == 0) \ + panic("%s(): Uninitialized sound unit!", __func__); \ +} while(0) +#else +#define SND_UNIT_ASSERT() KASSERT(snd_unit_initialized != 0, \ + ("%s(): Uninitialized sound unit!", \ + __func__)) +#endif + +#define MKMASK(x) ((1 << snd_##x##_shift) - 1) + +int +snd_max_u(void) +{ + SND_UNIT_ASSERT(); + + return (MKMASK(u)); +} + +int +snd_max_d(void) +{ + SND_UNIT_ASSERT(); + + return (MKMASK(d)); +} + +int +snd_max_c(void) +{ + SND_UNIT_ASSERT(); + + return (MKMASK(c)); +} + +int +snd_unit2u(int unit) +{ + SND_UNIT_ASSERT(); + + return ((unit >> (snd_c_shift + snd_d_shift)) & MKMASK(u)); +} + +int +snd_unit2d(int unit) +{ + SND_UNIT_ASSERT(); + + return ((unit >> snd_c_shift) & MKMASK(d)); +} + +int +snd_unit2c(int unit) +{ + SND_UNIT_ASSERT(); + + return (unit & MKMASK(c)); +} + +int +snd_u2unit(int u) +{ + SND_UNIT_ASSERT(); + + return ((u & MKMASK(u)) << (snd_c_shift + snd_d_shift)); +} + +int +snd_d2unit(int d) +{ + SND_UNIT_ASSERT(); + + return ((d & MKMASK(d)) << snd_c_shift); +} + +int +snd_c2unit(int c) +{ + SND_UNIT_ASSERT(); + + return (c & MKMASK(c)); +} + +int +snd_mkunit(int u, int d, int c) +{ + SND_UNIT_ASSERT(); + + return ((c & MKMASK(c)) | ((d & MKMASK(d)) << snd_c_shift) | + ((u & MKMASK(u)) << (snd_c_shift + snd_d_shift))); +} + +/* + * This *must* be called first before any of the functions above!!! + */ +void +snd_unit_init(void) +{ + int i; + + if (snd_unit_initialized != 0) + return; + + snd_unit_initialized = 1; + + if (getenv_int("hw.snd.maxunit", &i) != 0) { + if (i < SND_UNIT_UMIN) + i = SND_UNIT_UMIN; + else if (i > SND_UNIT_UMAX) + i = SND_UNIT_UMAX; + else + i = roundup2(i, 2); + + for (snd_u_shift = 0; (i >> (snd_u_shift + 1)) != 0; + snd_u_shift++) + ; + + /* + * Make room for channels/clones allocation unit + * to fit within 24bit MAXMINOR limit. + */ + snd_c_shift = 24 - snd_u_shift - snd_d_shift; + } + + if (bootverbose != 0) + printf("%s() u=0x%08x [%d] d=0x%08x [%d] c=0x%08x [%d]\n", + __func__, SND_U_MASK, snd_max_u() + 1, + SND_D_MASK, snd_max_d() + 1, SND_C_MASK, snd_max_c() + 1); +} --- sys/dev/sound/unit.h.orig Thu Jan 1 07:30:00 1970 +++ sys/dev/sound/unit.h Thu Jul 12 12:04:19 2007 @@ -0,0 +1,52 @@ +/*- + * 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/unit.h,v 1.1 2007/05/31 18:35:24 ariff Exp $ + */ + +#ifndef _SND_UNIT_H_ +#define _SND_UNIT_H_ + +#define SND_UNIT_UMIN 16 +#define SND_UNIT_UMAX 2048 + +int snd_max_u(void); +int snd_max_d(void); +int snd_max_c(void); +int snd_unit2u(int); +int snd_unit2d(int); +int snd_unit2c(int); +int snd_u2unit(int); +int snd_d2unit(int); +int snd_c2unit(int); +int snd_mkunit(int, int, int); + +void snd_unit_init(void); + +#define SND_U_MASK (snd_u2unit(snd_max_u())) +#define SND_D_MASK (snd_d2unit(snd_max_d())) +#define SND_C_MASK (snd_c2unit(snd_max_c())) + +#endif /* !_SND_UNIT_H_ */ --- sys/dev/sound/usb/uaudio.c.orig Fri Apr 15 12:15:24 2005 +++ sys/dev/sound/usb/uaudio.c Thu Jul 12 12:04:19 2007 @@ -1,5 +1,5 @@ /* $NetBSD: uaudio.c,v 1.91 2004/11/05 17:46:14 kent Exp $ */ -/* $FreeBSD: src/sys/dev/sound/usb/uaudio.c,v 1.7.8.2 2005/04/15 04:15:24 julian Exp $: */ +/* $FreeBSD: src/sys/dev/sound/usb/uaudio.c,v 1.36 2007/06/20 05:11:37 imp Exp $ */ /*- * Copyright (c) 1999 The NetBSD Foundation, Inc. @@ -49,6 +49,17 @@ __KERNEL_RCSID(0, "$NetBSD: uaudio.c,v 1.91 2004/11/05 17:46:14 kent Exp $"); #endif +/* + * Also merged: + * $NetBSD: uaudio.c,v 1.94 2005/01/15 15:19:53 kent Exp $ + * $NetBSD: uaudio.c,v 1.95 2005/01/16 06:02:19 dsainty Exp $ + * $NetBSD: uaudio.c,v 1.96 2005/01/16 12:46:00 kent Exp $ + * $NetBSD: uaudio.c,v 1.97 2005/02/24 08:19:38 martin Exp $ + * $NetBSD: uaudio.c,v 1.102 2006/04/14 17:00:55 christos Exp $ + * $NetBSD: uaudio.c,v 1.103 2006/05/11 19:09:25 mrg Exp $ + * $NetBSD: uaudio.c,v 1.105 2006/10/04 16:00:15 christos Exp $ + */ + #include #include #include @@ -62,7 +73,6 @@ #include /* for bootverbose */ #include #include -#include #if defined(__NetBSD__) || defined(__OpenBSD__) #include #elif defined(__FreeBSD__) @@ -73,6 +83,7 @@ #include #if defined(__FreeBSD__) #include +#include #endif #if defined(__NetBSD__) || defined(__OpenBSD__) @@ -83,9 +94,12 @@ #include #elif defined(__FreeBSD__) #include /* XXXXX */ +#include #include +#include "feeder_if.h" #endif +#include #include #include #include @@ -105,8 +119,8 @@ #endif /* #define UAUDIO_MULTIPLE_ENDPOINTS */ #ifdef USB_DEBUG -#define DPRINTF(x) do { if (uaudiodebug) logprintf x; } while (0) -#define DPRINTFN(n,x) do { if (uaudiodebug>(n)) logprintf x; } while (0) +#define DPRINTF(x) do { if (uaudiodebug) printf x; } while (0) +#define DPRINTFN(n,x) do { if (uaudiodebug>(n)) printf x; } while (0) int uaudiodebug = 0; #if defined(__FreeBSD__) SYSCTL_NODE(_hw_usb, OID_AUTO, uaudio, CTLFLAG_RW, 0, "USB uaudio"); @@ -128,10 +142,10 @@ #define MIX_MAX_CHAN 8 struct mixerctl { - u_int16_t wValue[MIX_MAX_CHAN]; /* using nchan */ - u_int16_t wIndex; - u_int8_t nchan; - u_int8_t type; + uint16_t wValue[MIX_MAX_CHAN]; /* using nchan */ + uint16_t wIndex; + uint8_t nchan; + uint8_t type; #define MIX_ON_OFF 1 #define MIX_SIGNED_16 2 #define MIX_UNSIGNED_16 3 @@ -145,9 +159,9 @@ #if defined(__FreeBSD__) /* XXXXX */ unsigned ctl; #define MAX_SELECTOR_INPUT_PIN 256 - u_int8_t slctrtype[MAX_SELECTOR_INPUT_PIN]; + uint8_t slctrtype[MAX_SELECTOR_INPUT_PIN]; #endif - u_int8_t class; + uint8_t class; #if !defined(__FreeBSD__) char ctlname[MAX_AUDIO_DEV_LEN]; char *ctlunit; @@ -156,9 +170,9 @@ #define MAKE(h,l) (((h) << 8) | (l)) struct as_info { - u_int8_t alt; - u_int8_t encoding; - u_int8_t attributes; /* Copy of bmAttributes of + uint8_t alt; + uint8_t encoding; + uint8_t attributes; /* Copy of bmAttributes of * usb_audio_streaming_endpoint_descriptor */ usbd_interface_handle ifaceh; @@ -212,7 +226,7 @@ }; struct uaudio_softc { - USBBASEDEVICE sc_dev; /* base device */ + device_t sc_dev; /* base device */ usbd_device_handle sc_udev; /* USB device */ int sc_ac_iface; /* Audio Control interface */ usbd_interface_handle sc_ac_ifaceh; @@ -230,11 +244,20 @@ #define HAS_MULAW 0x10 #define UA_NOFRAC 0x20 /* don't do sample rate adjustment */ #define HAS_24 0x40 +#define HAS_32 0x80 int sc_mode; /* play/record capability */ struct mixerctl *sc_ctls; /* mixer controls */ int sc_nctls; /* # of mixer controls */ - device_ptr_t sc_audiodev; + device_t sc_audiodev; char sc_dying; +#if defined(__FreeBSD__) + struct sbuf uaudio_sndstat; + int uaudio_sndstat_flag; + int async; +#endif + int sc_vendor; + int sc_product; + int sc_release; }; struct terminal_list { @@ -273,140 +296,140 @@ #define AudioCrecord "record" #define AudioCequalization "equalization" #endif -Static const char *uac_names[] = { +static const char *uac_names[] = { AudioCoutputs, AudioCinputs, AudioCequalization, AudioCrecord, }; #endif -Static usbd_status uaudio_identify_ac +static usbd_status uaudio_identify_ac (struct uaudio_softc *, const usb_config_descriptor_t *); -Static usbd_status uaudio_identify_as +static usbd_status uaudio_identify_as (struct uaudio_softc *, const usb_config_descriptor_t *); -Static usbd_status uaudio_process_as +static usbd_status uaudio_process_as (struct uaudio_softc *, const char *, int *, int, const usb_interface_descriptor_t *); -Static void uaudio_add_alt(struct uaudio_softc *, const struct as_info *); +static void uaudio_add_alt(struct uaudio_softc *, const struct as_info *); -Static const usb_interface_descriptor_t *uaudio_find_iface +static const usb_interface_descriptor_t *uaudio_find_iface (const char *, int, int *, int); -Static void uaudio_mixer_add_ctl(struct uaudio_softc *, struct mixerctl *); +static void uaudio_mixer_add_ctl(struct uaudio_softc *, struct mixerctl *); #if defined(__NetBSD__) || defined(__OpenBSD__) -Static char *uaudio_id_name +static char *uaudio_id_name (struct uaudio_softc *, const struct io_terminal *, int); #endif #ifdef USB_DEBUG -Static void uaudio_dump_cluster(const struct usb_audio_cluster *); +static void uaudio_dump_cluster(const struct usb_audio_cluster *); #endif -Static struct usb_audio_cluster uaudio_get_cluster +static struct usb_audio_cluster uaudio_get_cluster (int, const struct io_terminal *); -Static void uaudio_add_input +static void uaudio_add_input (struct uaudio_softc *, const struct io_terminal *, int); -Static void uaudio_add_output +static void uaudio_add_output (struct uaudio_softc *, const struct io_terminal *, int); -Static void uaudio_add_mixer +static void uaudio_add_mixer (struct uaudio_softc *, const struct io_terminal *, int); -Static void uaudio_add_selector +static void uaudio_add_selector (struct uaudio_softc *, const struct io_terminal *, int); #ifdef USB_DEBUG -Static const char *uaudio_get_terminal_name(int); +static const char *uaudio_get_terminal_name(int); #endif -Static int uaudio_determine_class +static int uaudio_determine_class (const struct io_terminal *, struct mixerctl *); #if defined(__FreeBSD__) -Static const int uaudio_feature_name(const struct io_terminal *, - struct mixerctl *); +static int uaudio_feature_name(const struct io_terminal *, + struct mixerctl *); #else -Static const char *uaudio_feature_name +static const char *uaudio_feature_name (const struct io_terminal *, struct mixerctl *); #endif -Static void uaudio_add_feature +static void uaudio_add_feature (struct uaudio_softc *, const struct io_terminal *, int); -Static void uaudio_add_processing_updown +static void uaudio_add_processing_updown (struct uaudio_softc *, const struct io_terminal *, int); -Static void uaudio_add_processing +static void uaudio_add_processing (struct uaudio_softc *, const struct io_terminal *, int); -Static void uaudio_add_extension +static void uaudio_add_extension (struct uaudio_softc *, const struct io_terminal *, int); -Static struct terminal_list *uaudio_merge_terminal_list +static struct terminal_list *uaudio_merge_terminal_list (const struct io_terminal *); -Static struct terminal_list *uaudio_io_terminaltype +static struct terminal_list *uaudio_io_terminaltype (int, struct io_terminal *, int); -Static usbd_status uaudio_identify +static usbd_status uaudio_identify (struct uaudio_softc *, const usb_config_descriptor_t *); -Static int uaudio_signext(int, int); +static int uaudio_signext(int, int); #if defined(__NetBSD__) || defined(__OpenBSD__) -Static int uaudio_value2bsd(struct mixerctl *, int); +static int uaudio_value2bsd(struct mixerctl *, int); #endif -Static int uaudio_bsd2value(struct mixerctl *, int); -Static int uaudio_get(struct uaudio_softc *, int, int, int, int, int); +static int uaudio_bsd2value(struct mixerctl *, int); +static int uaudio_get(struct uaudio_softc *, int, int, int, int, int); #if defined(__NetBSD__) || defined(__OpenBSD__) -Static int uaudio_ctl_get +static int uaudio_ctl_get (struct uaudio_softc *, int, struct mixerctl *, int); #endif -Static void uaudio_set +static void uaudio_set (struct uaudio_softc *, int, int, int, int, int, int); -Static void uaudio_ctl_set +static void uaudio_ctl_set (struct uaudio_softc *, int, struct mixerctl *, int, int); -Static usbd_status uaudio_set_speed(struct uaudio_softc *, int, u_int); +static usbd_status uaudio_set_speed(struct uaudio_softc *, int, u_int); -Static usbd_status uaudio_chan_open(struct uaudio_softc *, struct chan *); -Static void uaudio_chan_close(struct uaudio_softc *, struct chan *); -Static usbd_status uaudio_chan_alloc_buffers +static usbd_status uaudio_chan_open(struct uaudio_softc *, struct chan *); +static void uaudio_chan_close(struct uaudio_softc *, struct chan *); +static usbd_status uaudio_chan_alloc_buffers (struct uaudio_softc *, struct chan *); -Static void uaudio_chan_free_buffers(struct uaudio_softc *, struct chan *); +static void uaudio_chan_free_buffers(struct uaudio_softc *, struct chan *); #if defined(__NetBSD__) || defined(__OpenBSD__) -Static void uaudio_chan_init +static void uaudio_chan_init (struct chan *, int, const struct audio_params *, int); -Static void uaudio_chan_set_param(struct chan *, u_char *, u_char *, int); +static void uaudio_chan_set_param(struct chan *, u_char *, u_char *, int); #endif -Static void uaudio_chan_ptransfer(struct chan *); -Static void uaudio_chan_pintr +static void uaudio_chan_ptransfer(struct chan *); +static void uaudio_chan_pintr (usbd_xfer_handle, usbd_private_handle, usbd_status); -Static void uaudio_chan_rtransfer(struct chan *); -Static void uaudio_chan_rintr +static void uaudio_chan_rtransfer(struct chan *); +static void uaudio_chan_rintr (usbd_xfer_handle, usbd_private_handle, usbd_status); #if defined(__NetBSD__) || defined(__OpenBSD__) -Static int uaudio_open(void *, int); -Static void uaudio_close(void *); -Static int uaudio_drain(void *); -Static int uaudio_query_encoding(void *, struct audio_encoding *); -Static void uaudio_get_minmax_rates +static int uaudio_open(void *, int); +static void uaudio_close(void *); +static int uaudio_drain(void *); +static int uaudio_query_encoding(void *, struct audio_encoding *); +static void uaudio_get_minmax_rates (int, const struct as_info *, const struct audio_params *, int, u_long *, u_long *); -Static int uaudio_match_alt_sub +static int uaudio_match_alt_sub (int, const struct as_info *, const struct audio_params *, int, u_long); -Static int uaudio_match_alt_chan +static int uaudio_match_alt_chan (int, const struct as_info *, struct audio_params *, int); -Static int uaudio_match_alt +static int uaudio_match_alt (int, const struct as_info *, struct audio_params *, int); -Static int uaudio_set_params +static int uaudio_set_params (void *, int, int, struct audio_params *, struct audio_params *); -Static int uaudio_round_blocksize(void *, int); -Static int uaudio_trigger_output +static int uaudio_round_blocksize(void *, int); +static int uaudio_trigger_output (void *, void *, void *, int, void (*)(void *), void *, struct audio_params *); -Static int uaudio_trigger_input +static int uaudio_trigger_input (void *, void *, void *, int, void (*)(void *), void *, struct audio_params *); -Static int uaudio_halt_in_dma(void *); -Static int uaudio_halt_out_dma(void *); -Static int uaudio_getdev(void *, struct audio_device *); -Static int uaudio_mixer_set_port(void *, mixer_ctrl_t *); -Static int uaudio_mixer_get_port(void *, mixer_ctrl_t *); -Static int uaudio_query_devinfo(void *, mixer_devinfo_t *); -Static int uaudio_get_props(void *); +static int uaudio_halt_in_dma(void *); +static int uaudio_halt_out_dma(void *); +static int uaudio_getdev(void *, struct audio_device *); +static int uaudio_mixer_set_port(void *, mixer_ctrl_t *); +static int uaudio_mixer_get_port(void *, mixer_ctrl_t *); +static int uaudio_query_devinfo(void *, mixer_devinfo_t *); +static int uaudio_get_props(void *); -Static const struct audio_hw_if uaudio_hw_if = { +static const struct audio_hw_if uaudio_hw_if = { uaudio_open, uaudio_close, uaudio_drain, @@ -436,15 +459,15 @@ NULL, }; -Static struct audio_device uaudio_device = { +static struct audio_device uaudio_device = { "USB audio", "", "uaudio" }; #elif defined(__FreeBSD__) -Static int audio_attach_mi(device_t); -Static int uaudio_init_params(struct uaudio_softc * sc, struct chan *ch, int mode); +static int audio_attach_mi(device_t); +static int uaudio_init_params(struct uaudio_softc * sc, struct chan *ch, int mode); /* for NetBSD compatibirity */ #define AUMODE_PLAY 0x01 @@ -483,7 +506,7 @@ usb_interface_descriptor_t *id; if (uaa->iface == NULL) - return (UMATCH_NONE); + return UMATCH_NONE; id = usbd_get_interface_descriptor(uaa->iface); /* Trigger on the control interface. */ @@ -491,9 +514,9 @@ id->bInterfaceClass != UICLASS_AUDIO || id->bInterfaceSubClass != UISUBCLASS_AUDIOCONTROL || (usbd_get_quirks(uaa->device)->uq_flags & UQ_BAD_AUDIO)) - return (UMATCH_NONE); + return UMATCH_NONE; - return (UMATCH_IFACECLASS_IFACESUBCLASS); + return UMATCH_IFACECLASS_IFACESUBCLASS; } USB_ATTACH(uaudio) @@ -501,35 +524,43 @@ USB_ATTACH_START(uaudio, sc, uaa); usb_interface_descriptor_t *id; usb_config_descriptor_t *cdesc; +#if !defined(__FreeBSD__) char devinfo[1024]; +#endif usbd_status err; int i, j, found; #if defined(__FreeBSD__) - usbd_devinfo(uaa->device, 0, devinfo); - USB_ATTACH_SETUP; + sc->sc_dev = self; #else usbd_devinfo(uaa->device, 0, devinfo, sizeof(devinfo)); -#endif - -#if !defined(__FreeBSD__) printf(": %s\n", devinfo); #endif sc->sc_udev = uaa->device; + sc->sc_vendor = uaa->vendor; + sc->sc_product = uaa->product; + sc->sc_release = uaa->release; +#if defined(__FreeBSD__) + if (resource_int_value(device_get_name(sc->sc_dev), + device_get_unit(sc->sc_dev), "async", &i) == 0 && i != 0) + sc->async = 1; + else + sc->async = 0; +#endif cdesc = usbd_get_config_descriptor(sc->sc_udev); if (cdesc == NULL) { printf("%s: failed to get configuration descriptor\n", - USBDEVNAME(sc->sc_dev)); - USB_ATTACH_ERROR_RETURN; + device_get_nameunit(sc->sc_dev)); + return ENXIO; } err = uaudio_identify(sc, cdesc); if (err) { printf("%s: audio descriptors make no sense, error=%d\n", - USBDEVNAME(sc->sc_dev), err); - USB_ATTACH_ERROR_RETURN; + device_get_nameunit(sc->sc_dev), err); + return ENXIO; } sc->sc_ac_ifaceh = uaa->iface; @@ -555,12 +586,12 @@ for (j = 0; j < sc->sc_nalts; j++) { if (sc->sc_alts[j].ifaceh == NULL) { printf("%s: alt %d missing AS interface(s)\n", - USBDEVNAME(sc->sc_dev), j); - USB_ATTACH_ERROR_RETURN; + device_get_nameunit(sc->sc_dev), j); + return ENXIO; } } - printf("%s: audio rev %d.%02x\n", USBDEVNAME(sc->sc_dev), + printf("%s: audio rev %d.%02x\n", device_get_nameunit(sc->sc_dev), sc->sc_audio_rev >> 8, sc->sc_audio_rev & 0xff); sc->sc_playchan.sc = sc->sc_recchan.sc = sc; @@ -573,7 +604,7 @@ #ifndef USB_DEBUG if (bootverbose) #endif - printf("%s: %d mixer controls\n", USBDEVNAME(sc->sc_dev), + printf("%s: %d mixer controls\n", device_get_nameunit(sc->sc_dev), sc->sc_nctls); #if !defined(__FreeBSD__) @@ -590,24 +621,31 @@ sc->sc_dying = 0; if (audio_attach_mi(sc->sc_dev)) { printf("audio_attach_mi failed\n"); - USB_ATTACH_ERROR_RETURN; + return ENXIO; } #endif - USB_ATTACH_SUCCESS_RETURN; +#if defined(__FreeBSD__) + SYSCTL_ADD_INT(device_get_sysctl_ctx(sc->sc_dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(sc->sc_dev)), + OID_AUTO, "async", CTLFLAG_RW, &sc->async, 0, + "Asynchronous USB request"); +#endif + return 0; } #if defined(__NetBSD__) || defined(__OpenBSD__) int -uaudio_activate(device_ptr_t self, enum devact act) +uaudio_activate(device_t self, enum devact act) { - struct uaudio_softc *sc = (struct uaudio_softc *)self; - int rv = 0; + struct uaudio_softc *sc; + int rv; + sc = (struct uaudio_softc *)self; + rv = 0; switch (act) { case DVACT_ACTIVATE: - return (EOPNOTSUPP); - break; + return EOPNOTSUPP; case DVACT_DEACTIVATE: if (sc->sc_audiodev != NULL) @@ -615,17 +653,19 @@ sc->sc_dying = 1; break; } - return (rv); + return rv; } #endif #if defined(__NetBSD__) || defined(__OpenBSD__) int -uaudio_detach(device_ptr_t self, int flags) +uaudio_detach(device_t self, int flags) { - struct uaudio_softc *sc = (struct uaudio_softc *)self; - int rv = 0; + struct uaudio_softc *sc; + int rv; + sc = (struct uaudio_softc *)self; + rv = 0; /* Wait for outstanding requests to complete. */ usbd_delay_ms(sc->sc_udev, UAUDIO_NCHANBUFS * UAUDIO_NFRAMES); @@ -635,14 +675,21 @@ usbd_add_drv_event(USB_EVENT_DRIVER_DETACH, sc->sc_udev, USBDEV(sc->sc_dev)); - return (rv); + return rv; } #elif defined(__FreeBSD__) USB_DETACH(uaudio) { + struct sndcard_func *func; + device_t *devlist = NULL; + int err, i, devcount; + USB_DETACH_START(uaudio, sc); + sbuf_delete(&(sc->uaudio_sndstat)); + sc->uaudio_sndstat_flag = 0; + sc->sc_dying = 1; #if 0 /* XXX */ @@ -650,24 +697,42 @@ usbd_delay_ms(sc->sc_udev, UAUDIO_NCHANBUFS * UAUDIO_NFRAMES); #endif - /* do nothing ? */ - return bus_generic_detach(sc->sc_dev); + err = bus_generic_detach(sc->sc_dev); + + if (err == 0) { + device_get_children(sc->sc_dev, &devlist, &devcount); + for (i = 0; devlist != NULL && i < devcount; i++) { + func = device_get_ivars(devlist[i]); + if (func != NULL && func->func == SCF_PCM && + func->varinfo == NULL) { + device_set_ivars(devlist[i], NULL); + free(func, M_DEVBUF); + device_delete_child(sc->sc_dev, devlist[i]); + } + } + if (devlist != NULL) + free(devlist, M_TEMP); + } + + return err; } #endif #if defined(__NetBSD__) || defined(__OpenBSD__) -Static int +static int uaudio_query_encoding(void *addr, struct audio_encoding *fp) { - struct uaudio_softc *sc = addr; - int flags = sc->sc_altflags; + struct uaudio_softc *sc; + int flags; int idx; + sc = addr; + flags = sc->sc_altflags; if (sc->sc_dying) - return (EIO); + return EIO; if (sc->sc_nalts == 0 || flags == 0) - return (ENXIO); + return ENXIO; idx = fp->index; switch (idx) { @@ -725,7 +790,7 @@ } #endif -Static const usb_interface_descriptor_t * +static const usb_interface_descriptor_t * uaudio_find_iface(const char *buf, int size, int *offsp, int subtype) { const usb_interface_descriptor_t *d; @@ -736,12 +801,12 @@ if (d->bDescriptorType == UDESC_INTERFACE && d->bInterfaceClass == UICLASS_AUDIO && d->bInterfaceSubClass == subtype) - return (d); + return d; } - return (NULL); + return NULL; } -Static void +static void uaudio_mixer_add_ctl(struct uaudio_softc *sc, struct mixerctl *mc) { int res; @@ -826,17 +891,18 @@ } #if defined(__NetBSD__) || defined(__OpenBSD__) -Static char * +static char * uaudio_id_name(struct uaudio_softc *sc, const struct io_terminal *iot, int id) { static char buf[32]; + snprintf(buf, sizeof(buf), "i%d", id); - return (buf); + return buf; } #endif #ifdef USB_DEBUG -Static void +static void uaudio_dump_cluster(const struct usb_audio_cluster *cl) { static const char *channel_names[16] = { @@ -848,21 +914,21 @@ int cc, i, first; cc = UGETW(cl->wChannelConfig); - logprintf("cluster: bNrChannels=%u wChannelConfig=0x%.4x", + printf("cluster: bNrChannels=%u wChannelConfig=0x%.4x", cl->bNrChannels, cc); first = TRUE; for (i = 0; cc != 0; i++) { if (cc & 1) { - logprintf("%c%s", first ? '<' : ',', channel_names[i]); + printf("%c%s", first ? '<' : ',', channel_names[i]); first = FALSE; } cc = cc >> 1; } - logprintf("> iChannelNames=%u", cl->iChannelNames); + printf("> iChannelNames=%u", cl->iChannelNames); } #endif -Static struct usb_audio_cluster +static struct usb_audio_cluster uaudio_get_cluster(int id, const struct io_terminal *iot) { struct usb_audio_cluster r; @@ -878,14 +944,14 @@ r.bNrChannels = iot[id].d.it->bNrChannels; USETW(r.wChannelConfig, UGETW(iot[id].d.it->wChannelConfig)); r.iChannelNames = iot[id].d.it->iChannelNames; - return (r); + return r; case UDESCSUB_AC_OUTPUT: id = iot[id].d.ot->bSourceId; break; case UDESCSUB_AC_MIXER: r = *(const struct usb_audio_cluster *) &iot[id].d.mu->baSourceId[iot[id].d.mu->bNrInPins]; - return (r); + return r; case UDESCSUB_AC_SELECTOR: /* XXX This is not really right */ id = iot[id].d.su->baSourceId[0]; @@ -896,11 +962,11 @@ case UDESCSUB_AC_PROCESSING: r = *(const struct usb_audio_cluster *) &iot[id].d.pu->baSourceId[iot[id].d.pu->bNrInPins]; - return (r); + return r; case UDESCSUB_AC_EXTENSION: r = *(const struct usb_audio_cluster *) &iot[id].d.eu->baSourceId[iot[id].d.eu->bNrInPins]; - return (r); + return r; default: goto bad; } @@ -908,11 +974,11 @@ bad: printf("uaudio_get_cluster: bad data\n"); memset(&r, 0, sizeof r); - return (r); + return r; } -Static void +static void uaudio_add_input(struct uaudio_softc *sc, const struct io_terminal *iot, int id) { #ifdef USB_DEBUG @@ -927,12 +993,13 @@ #endif } -Static void +static void uaudio_add_output(struct uaudio_softc *sc, const struct io_terminal *iot, int id) { #ifdef USB_DEBUG - const struct usb_audio_output_terminal *d = iot[id].d.ot; + const struct usb_audio_output_terminal *d; + d = iot[id].d.ot; DPRINTFN(2,("uaudio_add_output: bTerminalId=%d wTerminalType=0x%04x " "bAssocTerminal=%d bSourceId=%d iTerminal=%d\n", d->bTerminalId, UGETW(d->wTerminalType), d->bAssocTerminal, @@ -940,7 +1007,7 @@ #endif } -Static void +static void uaudio_add_mixer(struct uaudio_softc *sc, const struct io_terminal *iot, int id) { const struct usb_audio_mixer_unit *d = iot[id].d.mu; @@ -1009,10 +1076,10 @@ } -Static void +static void uaudio_add_selector(struct uaudio_softc *sc, const struct io_terminal *iot, int id) { - const struct usb_audio_selector_unit *d = iot[id].d.su; + const struct usb_audio_selector_unit *d; struct mixerctl mix; #if !defined(__FreeBSD__) int i, wp; @@ -1021,6 +1088,7 @@ struct mixerctl dummy; #endif + d = iot[id].d.su; DPRINTFN(2,("uaudio_add_selector: bUnitId=%d bNrInPins=%d\n", d->bUnitId, d->bNrInPins)); mix.wIndex = MAKE(d->bUnitId, sc->sc_ac_iface); @@ -1056,7 +1124,7 @@ } #ifdef USB_DEBUG -Static const char * +static const char * uaudio_get_terminal_name(int terminal_type) { static char buf[100]; @@ -1132,7 +1200,7 @@ } #endif -Static int +static int uaudio_determine_class(const struct io_terminal *iot, struct mixerctl *mix) { int terminal_type; @@ -1183,7 +1251,7 @@ } #if defined(__FreeBSD__) -const int +static int uaudio_feature_name(const struct io_terminal *iot, struct mixerctl *mix) { int terminal_type; @@ -1284,7 +1352,7 @@ return SOUND_MIXER_VOLUME; } #else -Static const char * +static const char * uaudio_feature_name(const struct io_terminal *iot, struct mixerctl *mix) { int terminal_type; @@ -1387,13 +1455,13 @@ } #endif -Static void +static void uaudio_add_feature(struct uaudio_softc *sc, const struct io_terminal *iot, int id) { - const struct usb_audio_feature_unit *d = iot[id].d.fu; - uByte *ctls = d->bmaControls; - int ctlsize = d->bControlSize; - int nchan = (d->bLength - 7) / ctlsize; + const struct usb_audio_feature_unit *d; + const uByte *ctls; + int ctlsize; + int nchan; u_int fumask, mmask, cmask; struct mixerctl mix; int chan, ctl, i, unit; @@ -1405,7 +1473,10 @@ #define GET(i) (ctls[(i)*ctlsize] | \ (ctlsize > 1 ? ctls[(i)*ctlsize+1] << 8 : 0)) - + d = iot[id].d.fu; + ctls = d->bmaControls; + ctlsize = d->bControlSize; + nchan = (d->bLength - 7) / ctlsize; mmask = GET(0); /* Figure out what we can control */ for (cmask = 0, chan = 1; chan < nchan; chan++) { @@ -1547,19 +1618,21 @@ } } -Static void +static void uaudio_add_processing_updown(struct uaudio_softc *sc, const struct io_terminal *iot, int id) { - const struct usb_audio_processing_unit *d = iot[id].d.pu; - const struct usb_audio_processing_unit_1 *d1 = - (const struct usb_audio_processing_unit_1 *)&d->baSourceId[d->bNrInPins]; - const struct usb_audio_processing_unit_updown *ud = - (const struct usb_audio_processing_unit_updown *) - &d1->bmControls[d1->bControlSize]; + const struct usb_audio_processing_unit *d; + const struct usb_audio_processing_unit_1 *d1; + const struct usb_audio_processing_unit_updown *ud; struct mixerctl mix; int i; + d = iot[id].d.pu; + d1 = (const struct usb_audio_processing_unit_1 *) + &d->baSourceId[d->bNrInPins]; + ud = (const struct usb_audio_processing_unit_updown *) + &d1->bmControls[d1->bControlSize]; DPRINTFN(2,("uaudio_add_processing_updown: bUnitId=%d bNrModes=%d\n", d->bUnitId, ud->bNrModes)); @@ -1586,15 +1659,18 @@ uaudio_mixer_add_ctl(sc, &mix); } -Static void +static void uaudio_add_processing(struct uaudio_softc *sc, const struct io_terminal *iot, int id) { - const struct usb_audio_processing_unit *d = iot[id].d.pu; - const struct usb_audio_processing_unit_1 *d1 = - (const struct usb_audio_processing_unit_1 *)&d->baSourceId[d->bNrInPins]; - int ptype = UGETW(d->wProcessType); + const struct usb_audio_processing_unit *d; + const struct usb_audio_processing_unit_1 *d1; + int ptype; struct mixerctl mix; + d = iot[id].d.pu; + d1 = (const struct usb_audio_processing_unit_1 *) + &d->baSourceId[d->bNrInPins]; + ptype = UGETW(d->wProcessType); DPRINTFN(2,("uaudio_add_processing: wProcessType=%d bUnitId=%d " "bNrInPins=%d\n", ptype, d->bUnitId, d->bNrInPins)); @@ -1630,14 +1706,16 @@ } } -Static void +static void uaudio_add_extension(struct uaudio_softc *sc, const struct io_terminal *iot, int id) { - const struct usb_audio_extension_unit *d = iot[id].d.eu; - const struct usb_audio_extension_unit_1 *d1 = - (const struct usb_audio_extension_unit_1 *)&d->baSourceId[d->bNrInPins]; + const struct usb_audio_extension_unit *d; + const struct usb_audio_extension_unit_1 *d1; struct mixerctl mix; + d = iot[id].d.eu; + d1 = (const struct usb_audio_extension_unit_1 *) + &d->baSourceId[d->bNrInPins]; DPRINTFN(2,("uaudio_add_extension: bUnitId=%d bNrInPins=%d\n", d->bUnitId, d->bNrInPins)); @@ -1659,7 +1737,7 @@ } } -Static struct terminal_list* +static struct terminal_list* uaudio_merge_terminal_list(const struct io_terminal *iot) { struct terminal_list *tml; @@ -1694,7 +1772,7 @@ return tml; } -Static struct terminal_list * +static struct terminal_list * uaudio_io_terminaltype(int outtype, struct io_terminal *iot, int id) { struct terminal_list *tml; @@ -1845,18 +1923,18 @@ } } -Static usbd_status +static usbd_status uaudio_identify(struct uaudio_softc *sc, const usb_config_descriptor_t *cdesc) { usbd_status err; err = uaudio_identify_ac(sc, cdesc); if (err) - return (err); - return (uaudio_identify_as(sc, cdesc)); + return err; + return uaudio_identify_as(sc, cdesc); } -Static void +static void uaudio_add_alt(struct uaudio_softc *sc, const struct as_info *ai) { size_t len; @@ -1879,7 +1957,7 @@ sc->sc_alts[sc->sc_nalts++] = *ai; } -Static usbd_status +static usbd_status uaudio_process_as(struct uaudio_softc *sc, const char *buf, int *offsp, int size, const usb_interface_descriptor_t *id) #define offs (*offsp) @@ -1895,32 +1973,33 @@ const char *format_str; asid = (const void *)(buf + offs); + if (asid->bDescriptorType != UDESC_CS_INTERFACE || asid->bDescriptorSubtype != AS_GENERAL) - return (USBD_INVAL); + return USBD_INVAL; DPRINTF(("uaudio_process_as: asid: bTerminakLink=%d wFormatTag=%d\n", asid->bTerminalLink, UGETW(asid->wFormatTag))); offs += asid->bLength; if (offs > size) - return (USBD_INVAL); + return USBD_INVAL; asf1d = (const void *)(buf + offs); if (asf1d->bDescriptorType != UDESC_CS_INTERFACE || asf1d->bDescriptorSubtype != FORMAT_TYPE) - return (USBD_INVAL); + return USBD_INVAL; offs += asf1d->bLength; if (offs > size) - return (USBD_INVAL); + return USBD_INVAL; if (asf1d->bFormatType != FORMAT_TYPE_I) { printf("%s: ignored setting with type %d format\n", - USBDEVNAME(sc->sc_dev), UGETW(asid->wFormatTag)); - return (USBD_NORMAL_COMPLETION); + device_get_nameunit(sc->sc_dev), UGETW(asid->wFormatTag)); + return USBD_NORMAL_COMPLETION; } ed = (const void *)(buf + offs); if (ed->bDescriptorType != UDESC_ENDPOINT) - return (USBD_INVAL); + return USBD_INVAL; DPRINTF(("uaudio_process_as: endpoint[0] bLength=%d bDescriptorType=%d " "bEndpointAddress=%d bmAttributes=0x%x wMaxPacketSize=%d " "bInterval=%d bRefresh=%d bSynchAddress=%d\n", @@ -1929,9 +2008,9 @@ ed->bInterval, ed->bRefresh, ed->bSynchAddress)); offs += ed->bLength; if (offs > size) - return (USBD_INVAL); + return USBD_INVAL; if (UE_GET_XFERTYPE(ed->bmAttributes) != UE_ISOCHRONOUS) - return (USBD_INVAL); + return USBD_INVAL; dir = UE_GET_DIR(ed->bEndpointAddress); type = UE_GET_ISO_TYPE(ed->bmAttributes); @@ -1945,36 +2024,38 @@ sync = TRUE; #ifndef UAUDIO_MULTIPLE_ENDPOINTS printf("%s: ignored input endpoint of type adaptive\n", - USBDEVNAME(sc->sc_dev)); - return (USBD_NORMAL_COMPLETION); + device_get_nameunit(sc->sc_dev)); + return USBD_NORMAL_COMPLETION; #endif } if (dir != UE_DIR_IN && type == UE_ISO_ASYNC) { sync = TRUE; #ifndef UAUDIO_MULTIPLE_ENDPOINTS printf("%s: ignored output endpoint of type async\n", - USBDEVNAME(sc->sc_dev)); - return (USBD_NORMAL_COMPLETION); + device_get_nameunit(sc->sc_dev)); + return USBD_NORMAL_COMPLETION; #endif } sed = (const void *)(buf + offs); if (sed->bDescriptorType != UDESC_CS_ENDPOINT || sed->bDescriptorSubtype != AS_GENERAL) - return (USBD_INVAL); + return USBD_INVAL; DPRINTF((" streadming_endpoint: offset=%d bLength=%d\n", offs, sed->bLength)); offs += sed->bLength; if (offs > size) - return (USBD_INVAL); + return USBD_INVAL; +#ifdef UAUDIO_MULTIPLE_ENDPOINTS if (sync && id->bNumEndpoints <= 1) { printf("%s: a sync-pipe endpoint but no other endpoint\n", - USBDEVNAME(sc->sc_dev)); + device_get_nameunit(sc->sc_dev)); return USBD_INVAL; } +#endif if (!sync && id->bNumEndpoints > 1) { printf("%s: non sync-pipe endpoint but multiple endpoints\n", - USBDEVNAME(sc->sc_dev)); + device_get_nameunit(sc->sc_dev)); return USBD_INVAL; } epdesc1 = NULL; @@ -1995,19 +2076,19 @@ return USBD_INVAL; if (epdesc1->bSynchAddress != 0) { printf("%s: invalid endpoint: bSynchAddress=0\n", - USBDEVNAME(sc->sc_dev)); + device_get_nameunit(sc->sc_dev)); return USBD_INVAL; } if (UE_GET_XFERTYPE(epdesc1->bmAttributes) != UE_ISOCHRONOUS) { printf("%s: invalid endpoint: bmAttributes=0x%x\n", - USBDEVNAME(sc->sc_dev), epdesc1->bmAttributes); + device_get_nameunit(sc->sc_dev), epdesc1->bmAttributes); return USBD_INVAL; } if (epdesc1->bEndpointAddress != ed->bSynchAddress) { printf("%s: invalid endpoint addresses: " "ep[0]->bSynchAddress=0x%x " "ep[1]->bEndpointAddress=0x%x\n", - USBDEVNAME(sc->sc_dev), ed->bSynchAddress, + device_get_nameunit(sc->sc_dev), ed->bSynchAddress, epdesc1->bEndpointAddress); return USBD_INVAL; } @@ -2017,10 +2098,10 @@ format = UGETW(asid->wFormatTag); chan = asf1d->bNrChannels; prec = asf1d->bBitResolution; - if (prec != 8 && prec != 16 && prec != 24) { + if (prec != 8 && prec != 16 && prec != 24 && prec != 32) { printf("%s: ignored setting with precision %d\n", - USBDEVNAME(sc->sc_dev), prec); - return (USBD_NORMAL_COMPLETION); + device_get_nameunit(sc->sc_dev), prec); + return USBD_NORMAL_COMPLETION; } switch (format) { case UA_FMT_PCM: @@ -2030,6 +2111,8 @@ sc->sc_altflags |= HAS_16; } else if (prec == 24) { sc->sc_altflags |= HAS_24; + } else if (prec == 32) { + sc->sc_altflags |= HAS_32; } enc = AUDIO_ENCODING_SLINEAR_LE; format_str = "pcm"; @@ -2052,11 +2135,11 @@ case UA_FMT_IEEE_FLOAT: default: printf("%s: ignored setting with format %d\n", - USBDEVNAME(sc->sc_dev), format); - return (USBD_NORMAL_COMPLETION); + device_get_nameunit(sc->sc_dev), format); + return USBD_NORMAL_COMPLETION; } #ifdef USB_DEBUG - printf("%s: %s: %dch, %d/%dbit, %s,", USBDEVNAME(sc->sc_dev), + printf("%s: %s: %dch, %d/%dbit, %s,", device_get_nameunit(sc->sc_dev), dir == UE_DIR_IN ? "recording" : "playback", chan, prec, asf1d->bSubFrameSize * 8, format_str); if (asf1d->bSamFreqType == UA_SAMP_CONTNUOUS) { @@ -2069,6 +2152,28 @@ printf("Hz\n"); } #endif +#if defined(__FreeBSD__) + if (sc->uaudio_sndstat_flag != 0) { + sbuf_printf(&(sc->uaudio_sndstat), "\n\t"); + sbuf_printf(&(sc->uaudio_sndstat), + "mode %d:(%s) %dch, %d/%dbit, %s,", + id->bAlternateSetting, + dir == UE_DIR_IN ? "input" : "output", + chan, prec, asf1d->bSubFrameSize * 8, format_str); + if (asf1d->bSamFreqType == UA_SAMP_CONTNUOUS) { + sbuf_printf(&(sc->uaudio_sndstat), " %d-%dHz", + UA_SAMP_LO(asf1d), UA_SAMP_HI(asf1d)); + } else { + int r; + sbuf_printf(&(sc->uaudio_sndstat), + " %d", UA_GETSAMP(asf1d, 0)); + for (r = 1; r < asf1d->bSamFreqType; r++) + sbuf_printf(&(sc->uaudio_sndstat), + ",%d", UA_GETSAMP(asf1d, r)); + sbuf_printf(&(sc->uaudio_sndstat), "Hz"); + } + } +#endif ai.alt = id->bAlternateSetting; ai.encoding = enc; ai.attributes = sed->bmAttributes; @@ -2077,6 +2182,7 @@ ai.edesc1 = epdesc1; ai.asf1desc = asf1d; ai.sc_busy = 0; + ai.ifaceh = NULL; uaudio_add_alt(sc, &ai); #ifdef USB_DEBUG if (ai.attributes & UA_SED_FREQ_CONTROL) @@ -2086,11 +2192,11 @@ #endif sc->sc_mode |= (dir == UE_DIR_OUT) ? AUMODE_PLAY : AUMODE_RECORD; - return (USBD_NORMAL_COMPLETION); + return USBD_NORMAL_COMPLETION; } #undef offs -Static usbd_status +static usbd_status uaudio_identify_as(struct uaudio_softc *sc, const usb_config_descriptor_t *cdesc) { @@ -2105,8 +2211,13 @@ offs = 0; id = uaudio_find_iface(buf, size, &offs, UISUBCLASS_AUDIOSTREAM); if (id == NULL) - return (USBD_INVAL); + return USBD_INVAL; +#if defined(__FreeBSD__) + sc->uaudio_sndstat_flag = 0; + if (sbuf_new(&(sc->uaudio_sndstat), NULL, 4096, SBUF_AUTOEXTEND) != NULL) + sc->uaudio_sndstat_flag = 1; +#endif /* Loop through all the alternate settings. */ while (offs <= size) { DPRINTFN(2, ("uaudio_identify: interface=%d offset=%d\n", @@ -2126,27 +2237,30 @@ default: printf("%s: ignored audio interface with %d " "endpoints\n", - USBDEVNAME(sc->sc_dev), id->bNumEndpoints); + device_get_nameunit(sc->sc_dev), id->bNumEndpoints); break; } id = uaudio_find_iface(buf, size, &offs,UISUBCLASS_AUDIOSTREAM); if (id == NULL) break; } +#if defined(__FreeBSD__) + sbuf_finish(&(sc->uaudio_sndstat)); +#endif if (offs > size) - return (USBD_INVAL); + return USBD_INVAL; DPRINTF(("uaudio_identify_as: %d alts available\n", sc->sc_nalts)); if (sc->sc_mode == 0) { printf("%s: no usable endpoint found\n", - USBDEVNAME(sc->sc_dev)); - return (USBD_INVAL); + device_get_nameunit(sc->sc_dev)); + return USBD_INVAL; } - return (USBD_NORMAL_COMPLETION); + return USBD_NORMAL_COMPLETION; } -Static usbd_status +static usbd_status uaudio_identify_ac(struct uaudio_softc *sc, const usb_config_descriptor_t *cdesc) { struct io_terminal* iot; @@ -2165,9 +2279,9 @@ offs = 0; id = uaudio_find_iface(buf, size, &offs, UISUBCLASS_AUDIOCONTROL); if (id == NULL) - return (USBD_INVAL); + return USBD_INVAL; if (offs + sizeof *acdp > size) - return (USBD_INVAL); + return USBD_INVAL; sc->sc_ac_iface = id->bInterfaceNumber; DPRINTFN(2,("uaudio_identify_ac: AC interface is %d\n", sc->sc_ac_iface)); @@ -2176,14 +2290,14 @@ acdp = (const struct usb_audio_control_descriptor *)ibuf; if (acdp->bDescriptorType != UDESC_CS_INTERFACE || acdp->bDescriptorSubtype != UDESCSUB_AC_HEADER) - return (USBD_INVAL); + return USBD_INVAL; aclen = UGETW(acdp->wTotalLength); if (offs + aclen > size) - return (USBD_INVAL); + return USBD_INVAL; if (!(usbd_get_quirks(sc->sc_udev)->uq_flags & UQ_BAD_ADC) && UGETW(acdp->bcdADC) != UAUDIO_VERSION) - return (USBD_INVAL); + return USBD_INVAL; sc->sc_audio_rev = UGETW(acdp->bcdADC); DPRINTFN(2,("uaudio_identify_ac: found AC header, vers=%03x, len=%d\n", @@ -2205,8 +2319,10 @@ if (ibuf >= ibufend) break; dp = (const usb_descriptor_t *)ibuf; - if (ibuf + dp->bLength > ibufend) - return (USBD_INVAL); + if (ibuf + dp->bLength > ibufend) { + free(iot, M_TEMP); + return USBD_INVAL; + } if (dp->bDescriptorType != UDESC_CS_INTERFACE) { printf("uaudio_identify_ac: skip desc type=0x%02x\n", dp->bDescriptorType); @@ -2238,79 +2354,79 @@ if (iot[i].d.desc == NULL) continue; - logprintf("id %d:\t", i); + printf("id %d:\t", i); switch (iot[i].d.desc->bDescriptorSubtype) { case UDESCSUB_AC_INPUT: - logprintf("AC_INPUT type=%s\n", uaudio_get_terminal_name + printf("AC_INPUT type=%s\n", uaudio_get_terminal_name (UGETW(iot[i].d.it->wTerminalType))); - logprintf("\t"); + printf("\t"); cluster = uaudio_get_cluster(i, iot); uaudio_dump_cluster(&cluster); - logprintf("\n"); + printf("\n"); break; case UDESCSUB_AC_OUTPUT: - logprintf("AC_OUTPUT type=%s ", uaudio_get_terminal_name + printf("AC_OUTPUT type=%s ", uaudio_get_terminal_name (UGETW(iot[i].d.ot->wTerminalType))); - logprintf("src=%d\n", iot[i].d.ot->bSourceId); + printf("src=%d\n", iot[i].d.ot->bSourceId); break; case UDESCSUB_AC_MIXER: - logprintf("AC_MIXER src="); + printf("AC_MIXER src="); for (j = 0; j < iot[i].d.mu->bNrInPins; j++) - logprintf("%d ", iot[i].d.mu->baSourceId[j]); - logprintf("\n\t"); + printf("%d ", iot[i].d.mu->baSourceId[j]); + printf("\n\t"); cluster = uaudio_get_cluster(i, iot); uaudio_dump_cluster(&cluster); - logprintf("\n"); + printf("\n"); break; case UDESCSUB_AC_SELECTOR: - logprintf("AC_SELECTOR src="); + printf("AC_SELECTOR src="); for (j = 0; j < iot[i].d.su->bNrInPins; j++) - logprintf("%d ", iot[i].d.su->baSourceId[j]); - logprintf("\n"); + printf("%d ", iot[i].d.su->baSourceId[j]); + printf("\n"); break; case UDESCSUB_AC_FEATURE: - logprintf("AC_FEATURE src=%d\n", iot[i].d.fu->bSourceId); + printf("AC_FEATURE src=%d\n", iot[i].d.fu->bSourceId); break; case UDESCSUB_AC_PROCESSING: - logprintf("AC_PROCESSING src="); + printf("AC_PROCESSING src="); for (j = 0; j < iot[i].d.pu->bNrInPins; j++) - logprintf("%d ", iot[i].d.pu->baSourceId[j]); - logprintf("\n\t"); + printf("%d ", iot[i].d.pu->baSourceId[j]); + printf("\n\t"); cluster = uaudio_get_cluster(i, iot); uaudio_dump_cluster(&cluster); - logprintf("\n"); + printf("\n"); break; case UDESCSUB_AC_EXTENSION: - logprintf("AC_EXTENSION src="); + printf("AC_EXTENSION src="); for (j = 0; j < iot[i].d.eu->bNrInPins; j++) - logprintf("%d ", iot[i].d.eu->baSourceId[j]); - logprintf("\n\t"); + printf("%d ", iot[i].d.eu->baSourceId[j]); + printf("\n\t"); cluster = uaudio_get_cluster(i, iot); uaudio_dump_cluster(&cluster); - logprintf("\n"); + printf("\n"); break; default: - logprintf("unknown audio control (subtype=%d)\n", + printf("unknown audio control (subtype=%d)\n", iot[i].d.desc->bDescriptorSubtype); } for (j = 0; j < iot[i].inputs_size; j++) { int k; - logprintf("\tinput%d: ", j); + printf("\tinput%d: ", j); tml = iot[i].inputs[j]; if (tml == NULL) { - logprintf("NULL\n"); + printf("NULL\n"); continue; } for (k = 0; k < tml->size; k++) - logprintf("%s ", uaudio_get_terminal_name + printf("%s ", uaudio_get_terminal_name (tml->terminals[k])); - logprintf("\n"); + printf("\n"); } - logprintf("\toutput: "); + printf("\toutput: "); tml = iot[i].output; for (j = 0; j < tml->size; j++) - logprintf("%s ", uaudio_get_terminal_name(tml->terminals[j])); - logprintf("\n"); + printf("%s ", uaudio_get_terminal_name(tml->terminals[j])); + printf("\n"); } #endif @@ -2369,20 +2485,21 @@ } free(iot, M_TEMP); - return (USBD_NORMAL_COMPLETION); + return USBD_NORMAL_COMPLETION; } #if defined(__NetBSD__) || defined(__OpenBSD__) -Static int +static int uaudio_query_devinfo(void *addr, mixer_devinfo_t *mi) { - struct uaudio_softc *sc = addr; + struct uaudio_softc *sc; struct mixerctl *mc; int n, nctls, i; + sc = addr; DPRINTFN(2,("uaudio_query_devinfo: index=%d\n", mi->index)); if (sc->sc_dying) - return (EIO); + return EIO; n = mi->index; nctls = sc->sc_nctls; @@ -2393,20 +2510,20 @@ mi->mixer_class = UAC_OUTPUT; mi->next = mi->prev = AUDIO_MIXER_LAST; strlcpy(mi->label.name, AudioCoutputs, sizeof(mi->label.name)); - return (0); + return 0; case UAC_INPUT: mi->type = AUDIO_MIXER_CLASS; mi->mixer_class = UAC_INPUT; mi->next = mi->prev = AUDIO_MIXER_LAST; strlcpy(mi->label.name, AudioCinputs, sizeof(mi->label.name)); - return (0); + return 0; case UAC_EQUAL: mi->type = AUDIO_MIXER_CLASS; mi->mixer_class = UAC_EQUAL; mi->next = mi->prev = AUDIO_MIXER_LAST; strlcpy(mi->label.name, AudioCequalization, sizeof(mi->label.name)); - return (0); + return 0; case UAC_RECORD: mi->type = AUDIO_MIXER_CLASS; mi->mixer_class = UAC_RECORD; @@ -2419,7 +2536,7 @@ n -= UAC_NCLASSES; if (n < 0 || n >= nctls) - return (ENXIO); + return ENXIO; mc = &sc->sc_ctls[n]; strlcpy(mi->label.name, mc->ctlname, sizeof(mi->label.name)); @@ -2448,56 +2565,59 @@ break; default: mi->type = AUDIO_MIXER_VALUE; - strncpy(mi->un.v.units.name, mc->ctlunit, MAX_AUDIO_DEV_LEN); + strlcpy(mi->un.v.units.name, mc->ctlunit, MAX_AUDIO_DEV_LEN); mi->un.v.num_channels = mc->nchan; mi->un.v.delta = mc->delta; break; } - return (0); + return 0; } -Static int +static int uaudio_open(void *addr, int flags) { - struct uaudio_softc *sc = addr; + struct uaudio_softc *sc; + sc = addr; DPRINTF(("uaudio_open: sc=%p\n", sc)); if (sc->sc_dying) - return (EIO); + return EIO; if ((flags & FWRITE) && !(sc->sc_mode & AUMODE_PLAY)) - return (EACCES); + return EACCES; if ((flags & FREAD) && !(sc->sc_mode & AUMODE_RECORD)) - return (EACCES); + return EACCES; - return (0); + return 0; } /* * Close function is called at splaudio(). */ -Static void +static void uaudio_close(void *addr) { } -Static int +static int uaudio_drain(void *addr) { - struct uaudio_softc *sc = addr; + struct uaudio_softc *sc; + sc = addr; usbd_delay_ms(sc->sc_udev, UAUDIO_NCHANBUFS * UAUDIO_NFRAMES); - return (0); + return 0; } -Static int +static int uaudio_halt_out_dma(void *addr) { - struct uaudio_softc *sc = addr; + struct uaudio_softc *sc; + sc = addr; if (sc->sc_dying) - return (EIO); + return EIO; DPRINTF(("uaudio_halt_out_dma: enter\n")); if (sc->sc_playchan.pipe != NULL) { @@ -2506,63 +2626,75 @@ uaudio_chan_free_buffers(sc, &sc->sc_playchan); sc->sc_playchan.intr = NULL; } - return (0); + return 0; } -Static int +static int uaudio_halt_in_dma(void *addr) { - struct uaudio_softc *sc = addr; + struct uaudio_softc *sc; DPRINTF(("uaudio_halt_in_dma: enter\n")); + sc = addr; if (sc->sc_recchan.pipe != NULL) { uaudio_chan_close(sc, &sc->sc_recchan); sc->sc_recchan.pipe = NULL; uaudio_chan_free_buffers(sc, &sc->sc_recchan); sc->sc_recchan.intr = NULL; } - return (0); + return 0; } -Static int +static int uaudio_getdev(void *addr, struct audio_device *retp) { - struct uaudio_softc *sc = addr; + struct uaudio_softc *sc; DPRINTF(("uaudio_mixer_getdev:\n")); + sc = addr; if (sc->sc_dying) - return (EIO); + return EIO; *retp = uaudio_device; - return (0); + return 0; } /* * Make sure the block size is large enough to hold all outstanding transfers. */ -Static int +static int uaudio_round_blocksize(void *addr, int blk) { - struct uaudio_softc *sc = addr; - int bpf; + struct uaudio_softc *sc; + int b; - DPRINTF(("uaudio_round_blocksize: p.bpf=%d r.bpf=%d\n", - sc->sc_playchan.bytes_per_frame, - sc->sc_recchan.bytes_per_frame)); - if (sc->sc_playchan.bytes_per_frame > sc->sc_recchan.bytes_per_frame) { - bpf = sc->sc_playchan.bytes_per_frame - + sc->sc_playchan.sample_size; + sc = addr; + DPRINTF(("uaudio_round_blocksize: blk=%d mode=%s\n", blk, + mode == AUMODE_PLAY ? "AUMODE_PLAY" : "AUMODE_RECORD")); + + /* chan.bytes_per_frame can be 0. */ + if (mode == AUMODE_PLAY || sc->sc_recchan.bytes_per_frame <= 0) { + b = param->sample_rate * UAUDIO_NFRAMES * UAUDIO_NCHANBUFS; + + /* + * This does not make accurate value in the case + * of b % USB_FRAMES_PER_SECOND != 0 + */ + b /= USB_FRAMES_PER_SECOND; + + b *= param->precision / 8 * param->channels; } else { - bpf = sc->sc_recchan.bytes_per_frame - + sc->sc_recchan.sample_size; + /* + * use wMaxPacketSize in bytes_per_frame. + * See uaudio_set_params() and uaudio_chan_init() + */ + b = sc->sc_recchan.bytes_per_frame + * UAUDIO_NFRAMES * UAUDIO_NCHANBUFS; } - /* XXX */ - bpf *= UAUDIO_NFRAMES * UAUDIO_NCHANBUFS; - - bpf = (bpf + 15) &~ 15; - if (blk < bpf) - blk = bpf; + if (b <= 0) + b = 1; + blk = blk <= b ? b : blk / b * b; #ifdef DIAGNOSTIC if (blk <= 0) { @@ -2571,34 +2703,34 @@ } #endif - DPRINTFN(1,("uaudio_round_blocksize: blk=%d\n", blk)); - return (blk); + DPRINTF(("uaudio_round_blocksize: resultant blk=%d\n", blk)); + return blk; } -Static int +static int uaudio_get_props(void *addr) { - return (AUDIO_PROP_FULLDUPLEX | AUDIO_PROP_INDEPENDENT); + return AUDIO_PROP_FULLDUPLEX | AUDIO_PROP_INDEPENDENT; } #endif /* NetBSD or OpenBSD */ -Static int +static int uaudio_get(struct uaudio_softc *sc, int which, int type, int wValue, int wIndex, int len) { usb_device_request_t req; - u_int8_t data[4]; + uint8_t data[4]; usbd_status err; int val; #if defined(__FreeBSD__) if (sc->sc_dying) - return (EIO); + return EIO; #endif if (wValue == -1) - return (0); + return 0; req.bmRequestType = type; req.bRequest = which; @@ -2608,10 +2740,15 @@ DPRINTFN(2,("uaudio_get: type=0x%02x req=0x%02x wValue=0x%04x " "wIndex=0x%04x len=%d\n", type, which, wValue, wIndex, len)); - err = usbd_do_request(sc->sc_udev, &req, data); +#if defined(__FreeBSD__) + if (sc->async != 0) + err = usbd_do_request_async(sc->sc_udev, &req, data); + else +#endif + err = usbd_do_request(sc->sc_udev, &req, data); if (err) { DPRINTF(("uaudio_get: err=%s\n", usbd_errstr(err))); - return (-1); + return -1; } switch (len) { case 1: @@ -2622,18 +2759,18 @@ break; default: DPRINTF(("uaudio_get: bad length=%d\n", len)); - return (-1); + return -1; } DPRINTFN(2,("uaudio_get: val=%d\n", val)); - return (val); + return val; } -Static void +static void uaudio_set(struct uaudio_softc *sc, int which, int type, int wValue, int wIndex, int len, int val) { usb_device_request_t req; - u_int8_t data[4]; + uint8_t data[4]; usbd_status err; #if defined(__FreeBSD__) @@ -2663,14 +2800,19 @@ DPRINTFN(2,("uaudio_set: type=0x%02x req=0x%02x wValue=0x%04x " "wIndex=0x%04x len=%d, val=%d\n", type, which, wValue, wIndex, len, val & 0xffff)); - err = usbd_do_request(sc->sc_udev, &req, data); +#if defined(__FreeBSD__) + if (sc->async != 0) + err = usbd_do_request_async(sc->sc_udev, &req, data); + else +#endif + err = usbd_do_request(sc->sc_udev, &req, data); #ifdef USB_DEBUG if (err) DPRINTF(("uaudio_set: err=%d\n", err)); #endif } -Static int +static int uaudio_signext(int type, int val) { if (!MIX_UNSIGNED(type)) { @@ -2679,11 +2821,11 @@ else val = (int8_t)val; } - return (val); + return val; } #if defined(__NetBSD__) || defined(__OpenBSD__) -Static int +static int uaudio_value2bsd(struct mixerctl *mc, int val) { DPRINTFN(5, ("uaudio_value2bsd: type=%03x val=%d min=%d max=%d ", @@ -2697,7 +2839,7 @@ val = ((uaudio_signext(mc->type, val) - mc->minval) * 255 + mc->mul/2) / mc->mul; DPRINTFN(5, ("val'=%d\n", val)); - return (val); + return val; } #endif @@ -2714,11 +2856,11 @@ } else val = (val + mc->delta/2) * mc->mul / 255 + mc->minval; DPRINTFN(5, ("val'=%d\n", val)); - return (val); + return val; } #if defined(__NetBSD__) || defined(__OpenBSD__) -Static int +static int uaudio_ctl_get(struct uaudio_softc *sc, int which, struct mixerctl *mc, int chan) { @@ -2727,11 +2869,11 @@ DPRINTFN(5,("uaudio_ctl_get: which=%d chan=%d\n", which, chan)); val = uaudio_get(sc, which, UT_READ_CLASS_INTERFACE, mc->wValue[chan], mc->wIndex, MIX_SIZE(mc->type)); - return (uaudio_value2bsd(mc, val)); + return uaudio_value2bsd(mc, val); } #endif -Static void +static void uaudio_ctl_set(struct uaudio_softc *sc, int which, struct mixerctl *mc, int chan, int val) { @@ -2741,37 +2883,37 @@ } #if defined(__NetBSD__) || defined(__OpenBSD__) -Static int +static int uaudio_mixer_get_port(void *addr, mixer_ctrl_t *cp) { - struct uaudio_softc *sc = addr; + struct uaudio_softc *sc; struct mixerctl *mc; int i, n, vals[MIX_MAX_CHAN], val; DPRINTFN(2,("uaudio_mixer_get_port: index=%d\n", cp->dev)); - + sc = addr; if (sc->sc_dying) - return (EIO); + return EIO; n = cp->dev - UAC_NCLASSES; if (n < 0 || n >= sc->sc_nctls) - return (ENXIO); + return ENXIO; mc = &sc->sc_ctls[n]; if (mc->type == MIX_ON_OFF) { if (cp->type != AUDIO_MIXER_ENUM) - return (EINVAL); + return EINVAL; cp->un.ord = uaudio_ctl_get(sc, GET_CUR, mc, 0); } else if (mc->type == MIX_SELECTOR) { if (cp->type != AUDIO_MIXER_ENUM) - return (EINVAL); + return EINVAL; cp->un.ord = uaudio_ctl_get(sc, GET_CUR, mc, 0); } else { if (cp->type != AUDIO_MIXER_VALUE) return (EINVAL); if (cp->un.value.num_channels != 1 && cp->un.value.num_channels != mc->nchan) - return (EINVAL); + return EINVAL; for (i = 0; i < mc->nchan; i++) vals[i] = uaudio_ctl_get(sc, GET_CUR, mc, i); if (cp->un.value.num_channels == 1 && mc->nchan != 1) { @@ -2783,36 +2925,37 @@ cp->un.value.level[i] = vals[i]; } - return (0); + return 0; } -Static int +static int uaudio_mixer_set_port(void *addr, mixer_ctrl_t *cp) { - struct uaudio_softc *sc = addr; + struct uaudio_softc *sc; struct mixerctl *mc; int i, n, vals[MIX_MAX_CHAN]; DPRINTFN(2,("uaudio_mixer_set_port: index = %d\n", cp->dev)); + sc = addr; if (sc->sc_dying) - return (EIO); + return EIO; n = cp->dev - UAC_NCLASSES; if (n < 0 || n >= sc->sc_nctls) - return (ENXIO); + return ENXIO; mc = &sc->sc_ctls[n]; if (mc->type == MIX_ON_OFF) { if (cp->type != AUDIO_MIXER_ENUM) - return (EINVAL); + return EINVAL; uaudio_ctl_set(sc, SET_CUR, mc, 0, cp->un.ord); } else if (mc->type == MIX_SELECTOR) { if (cp->type != AUDIO_MIXER_ENUM) - return (EINVAL); + return EINVAL; uaudio_ctl_set(sc, SET_CUR, mc, 0, cp->un.ord); } else { if (cp->type != AUDIO_MIXER_VALUE) - return (EINVAL); + return EINVAL; if (cp->un.value.num_channels == 1) for (i = 0; i < mc->nchan; i++) vals[i] = cp->un.value.level[0]; @@ -2820,29 +2963,30 @@ for (i = 0; i < mc->nchan; i++) vals[i] = cp->un.value.level[i]; else - return (EINVAL); + return EINVAL; for (i = 0; i < mc->nchan; i++) uaudio_ctl_set(sc, SET_CUR, mc, i, vals[i]); } - return (0); + return 0; } -Static int +static int uaudio_trigger_input(void *addr, void *start, void *end, int blksize, void (*intr)(void *), void *arg, struct audio_params *param) { - struct uaudio_softc *sc = addr; - struct chan *ch = &sc->sc_recchan; + struct uaudio_softc *sc; + struct chan *ch; usbd_status err; int i, s; + sc = addr; if (sc->sc_dying) - return (EIO); + return EIO; DPRINTFN(3,("uaudio_trigger_input: sc=%p start=%p end=%p " "blksize=%d\n", sc, start, end, blksize)); - + ch = &sc->sc_recchan; uaudio_chan_set_param(ch, start, end, blksize); DPRINTFN(3,("uaudio_trigger_input: sample_size=%d bytes/frame=%d " "fraction=0.%03d\n", ch->sample_size, ch->bytes_per_frame, @@ -2850,12 +2994,12 @@ err = uaudio_chan_alloc_buffers(sc, ch); if (err) - return (EIO); + return EIO; err = uaudio_chan_open(sc, ch); if (err) { uaudio_chan_free_buffers(sc, ch); - return (EIO); + return EIO; } ch->intr = intr; @@ -2866,25 +3010,26 @@ uaudio_chan_rtransfer(ch); splx(s); - return (0); + return 0; } -Static int +static int uaudio_trigger_output(void *addr, void *start, void *end, int blksize, void (*intr)(void *), void *arg, struct audio_params *param) { - struct uaudio_softc *sc = addr; - struct chan *ch = &sc->sc_playchan; + struct uaudio_softc *sc; + struct chan *ch; usbd_status err; int i, s; + sc = addr; if (sc->sc_dying) - return (EIO); + return EIO; DPRINTFN(3,("uaudio_trigger_output: sc=%p start=%p end=%p " "blksize=%d\n", sc, start, end, blksize)); - + ch = &sc->sc_playchan; uaudio_chan_set_param(ch, start, end, blksize); DPRINTFN(3,("uaudio_trigger_output: sample_size=%d bytes/frame=%d " "fraction=0.%03d\n", ch->sample_size, ch->bytes_per_frame, @@ -2892,12 +3037,12 @@ err = uaudio_chan_alloc_buffers(sc, ch); if (err) - return (EIO); + return EIO; err = uaudio_chan_open(sc, ch); if (err) { uaudio_chan_free_buffers(sc, ch); - return (EIO); + return EIO; } ch->intr = intr; @@ -2908,30 +3053,44 @@ uaudio_chan_ptransfer(ch); splx(s); - return (0); + return 0; } #endif /* NetBSD or OpenBSD */ /* Set up a pipe for a channel. */ -Static usbd_status +static usbd_status uaudio_chan_open(struct uaudio_softc *sc, struct chan *ch) { - struct as_info *as = &sc->sc_alts[ch->altidx]; - int endpt = as->edesc->bEndpointAddress; + struct as_info *as; + int endpt; +#if defined(__FreeBSD__) + int locked; +#endif usbd_status err; #if defined(__FreeBSD__) if (sc->sc_dying) - return (EIO); + return EIO; #endif + as = &sc->sc_alts[ch->altidx]; + endpt = as->edesc->bEndpointAddress; DPRINTF(("uaudio_chan_open: endpt=0x%02x, speed=%d, alt=%d\n", endpt, ch->sample_rate, as->alt)); +#if defined(__FreeBSD__) + locked = (ch->pcm_ch != NULL && mtx_owned(ch->pcm_ch->lock)) ? 1 : 0; + if (locked) + CHN_UNLOCK(ch->pcm_ch); +#endif /* Set alternate interface corresponding to the mode. */ err = usbd_set_interface(as->ifaceh, as->alt); +#if defined(__FreeBSD__) + if (locked) + CHN_LOCK(ch->pcm_ch); +#endif if (err) - return (err); + return err; /* * If just one sampling rate is supported, @@ -2940,9 +3099,10 @@ */ if (as->asf1desc->bSamFreqType != 1) { err = uaudio_set_speed(sc, endpt, ch->sample_rate); - if (err) + if (err) { DPRINTF(("uaudio_chan_open: set_speed failed err=%s\n", usbd_errstr(err))); + } } ch->pipe = 0; @@ -2959,21 +3119,32 @@ return err; } -Static void +static void uaudio_chan_close(struct uaudio_softc *sc, struct chan *ch) { - struct as_info *as = &sc->sc_alts[ch->altidx]; - + struct as_info *as; #if defined(__FreeBSD__) + int locked; + if (sc->sc_dying) return ; #endif + as = &sc->sc_alts[ch->altidx]; as->sc_busy = 0; +#if defined(__FreeBSD__) + locked = (ch->pcm_ch != NULL && mtx_owned(ch->pcm_ch->lock)) ? 1 : 0; + if (locked) + CHN_UNLOCK(ch->pcm_ch); +#endif if (sc->sc_nullalt >= 0) { DPRINTF(("uaudio_chan_close: set null alt=%d\n", sc->sc_nullalt)); - usbd_set_interface(as->ifaceh, sc->sc_nullalt); + /* + * The interface will be initialized later again, so an + * error does not hurt. + */ + (void)usbd_set_interface(as->ifaceh, sc->sc_nullalt); } if (ch->pipe) { usbd_abort_pipe(ch->pipe); @@ -2983,9 +3154,13 @@ usbd_abort_pipe(ch->sync_pipe); usbd_close_pipe(ch->sync_pipe); } +#if defined(__FreeBSD__) + if (locked) + CHN_LOCK(ch->pcm_ch); +#endif } -Static usbd_status +static usbd_status uaudio_chan_alloc_buffers(struct uaudio_softc *sc, struct chan *ch) { usbd_xfer_handle xfer; @@ -3007,16 +3182,16 @@ ch->chanbufs[i].chan = ch; } - return (USBD_NORMAL_COMPLETION); + return USBD_NORMAL_COMPLETION; bad: while (--i >= 0) /* implicit buffer free */ usbd_free_xfer(ch->chanbufs[i].xfer); - return (USBD_NOMEM); + return USBD_NOMEM; } -Static void +static void uaudio_chan_free_buffers(struct uaudio_softc *sc, struct chan *ch) { int i; @@ -3026,7 +3201,7 @@ } /* Called at splusb() */ -Static void +static void uaudio_chan_ptransfer(struct chan *ch) { struct chanbuf *cb; @@ -3091,15 +3266,17 @@ (void)usbd_transfer(cb->xfer); } -Static void +static void uaudio_chan_pintr(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status status) { - struct chanbuf *cb = priv; - struct chan *ch = cb->chan; + struct chanbuf *cb; + struct chan *ch; u_int32_t count; int s; + cb = priv; + ch = cb->chan; /* Return if we are aborting. */ if (status == USBD_CANCELLED) return; @@ -3137,7 +3314,7 @@ } /* Called at splusb() */ -Static void +static void uaudio_chan_rtransfer(struct chan *ch) { struct chanbuf *cb; @@ -3182,7 +3359,7 @@ (void)usbd_transfer(cb->xfer); } -Static void +static void uaudio_chan_rintr(usbd_xfer_handle xfer, usbd_private_handle priv, usbd_status status) { @@ -3247,7 +3424,7 @@ } #if defined(__NetBSD__) || defined(__OpenBSD__) -Static void +static void uaudio_chan_init(struct chan *ch, int altidx, const struct audio_params *param, int maxpktsize) { @@ -3268,7 +3445,7 @@ ch->residue = 0; } -Static void +static void uaudio_chan_set_param(struct chan *ch, u_char *start, u_char *end, int blksize) { ch->start = start; @@ -3276,11 +3453,10 @@ ch->cur = start; ch->blksize = blksize; ch->transferred = 0; - ch->curchanbuf = 0; } -Static void +static void uaudio_get_minmax_rates(int nalts, const struct as_info *alts, const struct audio_params *p, int mode, u_long *min, u_long *max) @@ -3322,7 +3498,7 @@ } } -Static int +static int uaudio_match_alt_sub(int nalts, const struct as_info *alts, const struct audio_params *p, int mode, u_long rate) { @@ -3361,7 +3537,7 @@ return -1; } -Static int +static int uaudio_match_alt_chan(int nalts, const struct as_info *alts, struct audio_params *p, int mode) { @@ -3405,7 +3581,7 @@ return -1; } -Static int +static int uaudio_match_alt(int nalts, const struct as_info *alts, struct audio_params *p, int mode) { @@ -3429,25 +3605,29 @@ return uaudio_match_alt_chan(nalts, alts, p, mode); } -Static int +static int uaudio_set_params(void *addr, int setmode, int usemode, struct audio_params *play, struct audio_params *rec) { - struct uaudio_softc *sc = addr; - int flags = sc->sc_altflags; + struct uaudio_softc *sc; + int flags; int factor; int enc, i; - int paltidx=-1, raltidx=-1; + int paltidx, raltidx; void (*swcode)(void *, u_char *buf, int cnt); struct audio_params *p; int mode; + sc = addr; + flags = sc->sc_altflags; + paltidx = -1; + raltidx = -1; if (sc->sc_dying) - return (EIO); + return EIO; if (((usemode & AUMODE_PLAY) && sc->sc_playchan.pipe != NULL) || ((usemode & AUMODE_RECORD) && sc->sc_recchan.pipe != NULL)) - return (EBUSY); + return EBUSY; if ((usemode & AUMODE_PLAY) && sc->sc_playchan.altidx != -1) sc->sc_alts[sc->sc_playchan.altidx].sc_busy = 0; @@ -3615,15 +3795,15 @@ ? sc->sc_alts[sc->sc_recchan.altidx].idesc->bAlternateSetting : -1)); - return (0); + return 0; } #endif /* NetBSD or OpenBSD */ -Static usbd_status +static usbd_status uaudio_set_speed(struct uaudio_softc *sc, int endpt, u_int speed) { usb_device_request_t req; - u_int8_t data[3]; + uint8_t data[3]; DPRINTFN(5,("uaudio_set_speed: endpt=%d speed=%u\n", endpt, speed)); req.bmRequestType = UT_WRITE_CLASS_ENDPOINT; @@ -3635,7 +3815,11 @@ data[1] = speed >> 8; data[2] = speed >> 16; - return (usbd_do_request(sc->sc_udev, &req, data)); +#if defined(__FreeBSD__) + if (sc->async != 0) + return usbd_do_request_async(sc->sc_udev, &req, data); +#endif + return usbd_do_request(sc->sc_udev, &req, data); } @@ -3650,7 +3834,7 @@ if ((sc->sc_playchan.pipe != NULL) || (sc->sc_recchan.pipe != NULL)) return (-1); - switch(ch->format & 0x0000FFFF) { + switch(ch->format & 0x000FFFFF) { case AFMT_U8: enc = AUDIO_ENCODING_ULINEAR_LE; ch->precision = 8; @@ -3683,6 +3867,38 @@ enc = AUDIO_ENCODING_ULINEAR_BE; ch->precision = 16; break; + case AFMT_S24_LE: + enc = AUDIO_ENCODING_SLINEAR_LE; + ch->precision = 24; + break; + case AFMT_S24_BE: + enc = AUDIO_ENCODING_SLINEAR_BE; + ch->precision = 24; + break; + case AFMT_U24_LE: + enc = AUDIO_ENCODING_ULINEAR_LE; + ch->precision = 24; + break; + case AFMT_U24_BE: + enc = AUDIO_ENCODING_ULINEAR_BE; + ch->precision = 24; + break; + case AFMT_S32_LE: + enc = AUDIO_ENCODING_SLINEAR_LE; + ch->precision = 32; + break; + case AFMT_S32_BE: + enc = AUDIO_ENCODING_SLINEAR_BE; + ch->precision = 32; + break; + case AFMT_U32_LE: + enc = AUDIO_ENCODING_ULINEAR_LE; + ch->precision = 32; + break; + case AFMT_U32_BE: + enc = AUDIO_ENCODING_ULINEAR_BE; + ch->precision = 32; + break; default: enc = 0; ch->precision = 16; @@ -3711,8 +3927,8 @@ if (a1d->bSamFreqType == UA_SAMP_CONTNUOUS) { DPRINTFN(2,("uaudio_set_params: cont %d-%d\n", UA_SAMP_LO(a1d), UA_SAMP_HI(a1d))); - if (UA_SAMP_LO(a1d) < ch->sample_rate && - ch->sample_rate < UA_SAMP_HI(a1d)) { + if (UA_SAMP_LO(a1d) <= ch->sample_rate && + ch->sample_rate <= UA_SAMP_HI(a1d)) { if (mode == AUMODE_PLAY) sc->sc_playchan.altidx = i; else @@ -3765,83 +3981,135 @@ return (0); } -void -uaudio_query_formats(device_t dev, u_int32_t *pfmt, u_int32_t *rfmt) +struct uaudio_conversion { + uint8_t uaudio_fmt; + uint8_t uaudio_prec; + uint32_t freebsd_fmt; +}; + +const struct uaudio_conversion const accepted_conversion[] = { + {AUDIO_ENCODING_ULINEAR_LE, 8, AFMT_U8}, + {AUDIO_ENCODING_ULINEAR_LE, 16, AFMT_U16_LE}, + {AUDIO_ENCODING_ULINEAR_LE, 24, AFMT_U24_LE}, + {AUDIO_ENCODING_ULINEAR_LE, 32, AFMT_U32_LE}, + {AUDIO_ENCODING_ULINEAR_BE, 16, AFMT_U16_BE}, + {AUDIO_ENCODING_ULINEAR_BE, 24, AFMT_U24_BE}, + {AUDIO_ENCODING_ULINEAR_BE, 32, AFMT_U32_BE}, + {AUDIO_ENCODING_SLINEAR_LE, 8, AFMT_S8}, + {AUDIO_ENCODING_SLINEAR_LE, 16, AFMT_S16_LE}, + {AUDIO_ENCODING_SLINEAR_LE, 24, AFMT_S24_LE}, + {AUDIO_ENCODING_SLINEAR_LE, 32, AFMT_S32_LE}, + {AUDIO_ENCODING_SLINEAR_BE, 16, AFMT_S16_BE}, + {AUDIO_ENCODING_SLINEAR_BE, 24, AFMT_S24_BE}, + {AUDIO_ENCODING_SLINEAR_BE, 32, AFMT_S32_BE}, + {AUDIO_ENCODING_ALAW, 8, AFMT_A_LAW}, + {AUDIO_ENCODING_ULAW, 8, AFMT_MU_LAW}, + {0,0,0} +}; + +unsigned +uaudio_query_formats(device_t dev, int reqdir, unsigned maxfmt, struct pcmchan_caps *cap) { - int i, pn=0, rn=0; - int prec, dir; - u_int32_t fmt; struct uaudio_softc *sc; - - const struct usb_audio_streaming_type1_descriptor *a1d; + const struct usb_audio_streaming_type1_descriptor *asf1d; + const struct uaudio_conversion *iterator; + unsigned fmtcount, foundcount; + u_int32_t fmt; + uint8_t format, numchan, subframesize, prec, dir, iscontinuous; + int freq, freq_min, freq_max; + char *numchannel_descr; + char freq_descr[64]; + int i,r; sc = device_get_softc(dev); + if (sc == NULL) + return 0; + + cap->minspeed = cap->maxspeed = 0; + foundcount = fmtcount = 0; for (i = 0; i < sc->sc_nalts; i++) { - fmt = 0; - a1d = sc->sc_alts[i].asf1desc; - prec = a1d->bBitResolution; /* precision */ + dir = UE_GET_DIR(sc->sc_alts[i].edesc->bEndpointAddress); - switch (sc->sc_alts[i].encoding) { - case AUDIO_ENCODING_ULINEAR_LE: - if (prec == 8) { - fmt = AFMT_U8; - } else if (prec == 16) { - fmt = AFMT_U16_LE; - } - break; - case AUDIO_ENCODING_SLINEAR_LE: - if (prec == 8) { - fmt = AFMT_S8; - } else if (prec == 16) { - fmt = AFMT_S16_LE; - } - break; - case AUDIO_ENCODING_ULINEAR_BE: - if (prec == 16) { - fmt = AFMT_U16_BE; - } - break; - case AUDIO_ENCODING_SLINEAR_BE: - if (prec == 16) { - fmt = AFMT_S16_BE; - } - break; - case AUDIO_ENCODING_ALAW: - if (prec == 8) { - fmt = AFMT_A_LAW; - } - break; - case AUDIO_ENCODING_ULAW: - if (prec == 8) { - fmt = AFMT_MU_LAW; - } - break; - } + if ((dir == UE_DIR_OUT) != (reqdir == PCMDIR_PLAY)) + continue; - if (fmt != 0) { - if (a1d->bNrChannels == 2) { /* stereo/mono */ - fmt |= AFMT_STEREO; - } else if (a1d->bNrChannels != 1) { - fmt = 0; - } - } + asf1d = sc->sc_alts[i].asf1desc; + format = sc->sc_alts[i].encoding; + + numchan = asf1d->bNrChannels; + subframesize = asf1d->bSubFrameSize; + prec = asf1d->bBitResolution; /* precision */ + iscontinuous = asf1d->bSamFreqType == UA_SAMP_CONTNUOUS; + + if (iscontinuous) + snprintf(freq_descr, sizeof(freq_descr), "continous min %d max %d", UA_SAMP_LO(asf1d), UA_SAMP_HI(asf1d)); + else + snprintf(freq_descr, sizeof(freq_descr), "fixed frequency (%d listed formats)", asf1d->bSamFreqType); + + if (numchan == 1) + numchannel_descr = " (mono)"; + else if (numchan == 2) + numchannel_descr = " (stereo)"; + else + numchannel_descr = ""; + + if (bootverbose) { + device_printf(dev, "uaudio_query_formats: found a native %s channel%s %s %dbit %dbytes/subframe X %d channels = %d bytes per sample\n", + (dir==UE_DIR_OUT)?"playback":"record", + numchannel_descr, freq_descr, + prec, subframesize, numchan, subframesize*numchan); + } + /* + * Now start rejecting the ones that don't map to FreeBSD + */ + + if (numchan != 1 && numchan != 2) + continue; + + for (iterator = accepted_conversion ; iterator->uaudio_fmt != 0 ; iterator++) + if (iterator->uaudio_fmt == format && iterator->uaudio_prec == prec) + break; + + if (iterator->uaudio_fmt == 0) + continue; + + fmt = iterator->freebsd_fmt; - if (fmt != 0) { - dir= UE_GET_DIR(sc->sc_alts[i].edesc->bEndpointAddress); - if (dir == UE_DIR_OUT) { - pfmt[pn++] = fmt; - } else if (dir == UE_DIR_IN) { - rfmt[rn++] = fmt; + if (numchan == 2) + fmt |= AFMT_STEREO; + + foundcount++; + + if (fmtcount >= maxfmt) + continue; + + cap->fmtlist[fmtcount++] = fmt; + + if (iscontinuous) { + freq_min = UA_SAMP_LO(asf1d); + freq_max = UA_SAMP_HI(asf1d); + + if (cap->minspeed == 0 || freq_min < cap->minspeed) + cap->minspeed = freq_min; + if (cap->maxspeed == 0) + cap->maxspeed = cap->minspeed; + if (freq_max > cap->maxspeed) + cap->maxspeed = freq_max; + } else { + for (r = 0; r < asf1d->bSamFreqType; r++) { + freq = UA_GETSAMP(asf1d, r); + if (cap->minspeed == 0 || freq < cap->minspeed) + cap->minspeed = freq; + if (cap->maxspeed == 0) + cap->maxspeed = cap->minspeed; + if (freq > cap->maxspeed) + cap->maxspeed = freq; } } - - if ((pn > 8*2) || (rn > 8*2)) - break; } - pfmt[pn] = 0; - rfmt[rn] = 0; - return; + cap->fmtlist[fmtcount] = 0; + return foundcount; } void @@ -3890,25 +4158,81 @@ return; } -void -uaudio_chan_set_param_speed(device_t dev, u_int32_t speed, int dir) +int +uaudio_chan_set_param_speed(device_t dev, u_int32_t speed, int reqdir) { + const struct uaudio_conversion *iterator; struct uaudio_softc *sc; struct chan *ch; + int i, r, score, hiscore, bestspeed; sc = device_get_softc(dev); #ifndef NO_RECORDING - if (dir == PCMDIR_PLAY) + if (reqdir == PCMDIR_PLAY) ch = &sc->sc_playchan; else ch = &sc->sc_recchan; #else ch = &sc->sc_playchan; #endif + /* + * We are successful if we find an endpoint that matches our selected format and it + * supports the requested speed. + */ + hiscore = 0; + bestspeed = 1; + for (i = 0; i < sc->sc_nalts; i++) { + int dir = UE_GET_DIR(sc->sc_alts[i].edesc->bEndpointAddress); + int format = sc->sc_alts[i].encoding; + const struct usb_audio_streaming_type1_descriptor *asf1d = sc->sc_alts[i].asf1desc; + int iscontinuous = asf1d->bSamFreqType == UA_SAMP_CONTNUOUS; - ch->sample_rate = speed; + if ((dir == UE_DIR_OUT) != (reqdir == PCMDIR_PLAY)) + continue; - return; + for (iterator = accepted_conversion ; iterator->uaudio_fmt != 0 ; iterator++) + if (iterator->uaudio_fmt != format || iterator->freebsd_fmt != (ch->format&0xfffffff)) + continue; + if (iscontinuous) { + if (speed >= UA_SAMP_LO(asf1d) && speed <= UA_SAMP_HI(asf1d)) { + ch->sample_rate = speed; + return speed; + } else if (speed < UA_SAMP_LO(asf1d)) { + score = 0xfff * speed / UA_SAMP_LO(asf1d); + if (score > hiscore) { + bestspeed = UA_SAMP_LO(asf1d); + hiscore = score; + } + } else if (speed > UA_SAMP_HI(asf1d)) { + score = 0xfff * UA_SAMP_HI(asf1d) / speed; + if (score > hiscore) { + bestspeed = UA_SAMP_HI(asf1d); + hiscore = score; + } + } + continue; + } + for (r = 0; r < asf1d->bSamFreqType; r++) { + if (speed == UA_GETSAMP(asf1d, r)) { + ch->sample_rate = speed; + return speed; + } + if (speed > UA_GETSAMP(asf1d, r)) + score = 0xfff * UA_GETSAMP(asf1d, r) / speed; + else + score = 0xfff * speed / UA_GETSAMP(asf1d, r); + if (score > hiscore) { + bestspeed = UA_GETSAMP(asf1d, r); + hiscore = score; + } + } + } + if (bestspeed != 1) { + ch->sample_rate = bestspeed; + return bestspeed; + } + + return 0; } int @@ -4130,10 +4454,10 @@ if (mc->ctl == type) { if (mc->nchan == 2) { /* set Right */ - uaudio_ctl_set(sc, SET_CUR, mc, 1, (int)(right*256)/100); + uaudio_ctl_set(sc, SET_CUR, mc, 1, (int)(right*255)/100); } /* set Left or Mono */ - uaudio_ctl_set(sc, SET_CUR, mc, 0, (int)(left*256)/100); + uaudio_ctl_set(sc, SET_CUR, mc, 0, (int)(left*255)/100); } } return; @@ -4172,7 +4496,61 @@ return (1 << mc->slctrtype[mc->minval - 1]); } -Static int +static int +uaudio_sndstat_prepare_pcm(SNDSTAT_PREPARE_PCM_ARGS) +{ + device_t pa_dev = device_get_parent(dev); + struct uaudio_softc *sc = device_get_softc(pa_dev); + + SNDSTAT_PREPARE_PCM_BEGIN(); + + if (sc->uaudio_sndstat_flag != 0) + sbuf_cat(s, sbuf_data(&(sc->uaudio_sndstat))); + + SNDSTAT_PREPARE_PCM_END(); +} + +void +uaudio_sndstat_register(device_t dev) +{ + struct snddev_info *d = device_get_softc(dev); + sndstat_register(dev, d->status, uaudio_sndstat_prepare_pcm); +} + +int +uaudio_get_vendor(device_t dev) +{ + struct uaudio_softc *sc = device_get_softc(dev); + + if (sc == NULL) + return 0; + + return sc->sc_vendor; +} + +int +uaudio_get_product(device_t dev) +{ + struct uaudio_softc *sc = device_get_softc(dev); + + if (sc == NULL) + return 0; + + return sc->sc_product; +} + +int +uaudio_get_release(device_t dev) +{ + struct uaudio_softc *sc = device_get_softc(dev); + + if (sc == NULL) + return 0; + + return sc->sc_release; +} + +static int audio_attach_mi(device_t dev) { device_t child; @@ -4180,10 +4558,9 @@ /* Attach the children. */ /* PCM Audio */ - func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT); + func = malloc(sizeof(struct sndcard_func), M_DEVBUF, M_NOWAIT | M_ZERO); if (func == NULL) return (ENOMEM); - bzero(func, sizeof(*func)); func->func = SCF_PCM; child = device_add_child(dev, "pcm", -1); device_set_ivars(child, func); --- sys/dev/sound/usb/uaudio.h.orig Fri Apr 15 12:15:24 2005 +++ sys/dev/sound/usb/uaudio.h Thu Jul 12 12:04:19 2007 @@ -1,4 +1,4 @@ -/* $FreeBSD: src/sys/dev/sound/usb/uaudio.h,v 1.1.10.2 2005/04/15 04:15:24 julian Exp $ */ +/* $FreeBSD: src/sys/dev/sound/usb/uaudio.h,v 1.8 2007/03/16 17:19:03 ariff Exp $ */ /*- * Copyright (c) 2000-2002 Hiroyuki Aizu @@ -41,7 +41,7 @@ #endif void uaudio_chan_set_param(device_t, u_char *, u_char *); void uaudio_chan_set_param_blocksize(device_t dev, u_int32_t blocksize, int dir); -void uaudio_chan_set_param_speed(device_t dev, u_int32_t speed, int dir); +int uaudio_chan_set_param_speed(device_t dev, u_int32_t speed, int reqdir); void uaudio_chan_set_param_format(device_t dev, u_int32_t format,int dir); int uaudio_chan_getptr(device_t dev, int); void uaudio_mixer_set(device_t dev, unsigned type, unsigned left, @@ -49,4 +49,8 @@ u_int32_t uaudio_mixer_setrecsrc(device_t dev, u_int32_t src); u_int32_t uaudio_query_mix_info(device_t dev); u_int32_t uaudio_query_recsrc_info(device_t dev); -void uaudio_query_formats(device_t dev, u_int32_t *pfmt, u_int32_t *rfmt); +unsigned uaudio_query_formats(device_t dev, int dir, unsigned maxfmt, struct pcmchan_caps *fmt); +void uaudio_sndstat_register(device_t dev); +int uaudio_get_vendor(device_t dev); +int uaudio_get_product(device_t dev); +int uaudio_get_release(device_t dev); --- sys/dev/sound/usb/uaudio_pcm.c.orig Wed Apr 20 14:43:41 2005 +++ sys/dev/sound/usb/uaudio_pcm.c Thu Jul 12 12:04:19 2007 @@ -1,4 +1,4 @@ -/* $FreeBSD: src/sys/dev/sound/usb/uaudio_pcm.c,v 1.5.2.3 2005/04/20 06:43:41 julian Exp $ */ +/* $FreeBSD: src/sys/dev/sound/usb/uaudio_pcm.c,v 1.24 2007/06/17 06:10:43 ariff Exp $ */ /*- * Copyright (c) 2000-2002 Hiroyuki Aizu @@ -40,32 +40,39 @@ struct ua_info *parent; struct pcm_channel *channel; struct snd_dbuf *buffer; + u_char *buf; int dir, hwch; u_int32_t fmt, spd, blksz; /* XXXXX */ }; struct ua_info { device_t sc_dev; + u_int32_t bufsz; struct ua_chinfo pch, rch; - bus_dma_tag_t parent_dmat; +#define FORMAT_NUM 32 + u_int32_t ua_playfmt[FORMAT_NUM*2+1]; /* FORMAT_NUM format * (stereo or mono) + endptr */ + u_int32_t ua_recfmt[FORMAT_NUM*2+1]; /* FORMAT_NUM format * (stereo or mono) + endptr */ + struct pcmchan_caps ua_playcaps; + struct pcmchan_caps ua_reccaps; + int vendor, product, release; }; -static u_int32_t ua_playfmt[8*2+1]; /* 8 format * (stereo or mono) + endptr */ +#define UAUDIO_DEFAULT_BUFSZ 16*1024 -static struct pcmchan_caps ua_playcaps = {8000, 48000, ua_playfmt, 0}; - -static u_int32_t ua_recfmt[8*2+1]; /* 8 format * (stereo or mono) + endptr */ - -static struct pcmchan_caps ua_reccaps = {8000, 48000, ua_recfmt, 0}; - -#define UAUDIO_PCM_BUFF_SIZE 16*1024 +static const struct { + int vendor; + int product; + int release; + uint32_t dflags; +} ua_quirks[] = { + { 0x1130, 0xf211, 0x0101, SD_F_PSWAPLR }, +}; /************************************************************/ static void * ua_chan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { device_t pa_dev; - u_char *buf,*end; struct ua_info *sc = devinfo; struct ua_chinfo *ch = (dir == PCMDIR_PLAY)? &sc->pch : &sc->rch; @@ -76,29 +83,18 @@ ch->dir = dir; pa_dev = device_get_parent(sc->sc_dev); - /* Create ua_playfmt[] & ua_recfmt[] */ - uaudio_query_formats(pa_dev, (u_int32_t *)&ua_playfmt, (u_int32_t *)&ua_recfmt); - if (dir == PCMDIR_PLAY) { - if (ua_playfmt[0] == 0) { - printf("play channel supported format list invalid\n"); - return NULL; - } - } else { - if (ua_recfmt[0] == 0) { - printf("record channel supported format list invalid\n"); - return NULL; - } + ch->buf = malloc(sc->bufsz, M_DEVBUF, M_NOWAIT); + if (ch->buf == NULL) + return NULL; + if (sndbuf_setup(b, ch->buf, sc->bufsz) != 0) { + free(ch->buf, M_DEVBUF); + return NULL; } - - /* allocate PCM side DMA buffer */ - if (sndbuf_alloc(ch->buffer, sc->parent_dmat, UAUDIO_PCM_BUFF_SIZE) != 0) { - return NULL; - } - - buf = end = sndbuf_getbuf(b); - end += sndbuf_getsize(b); - uaudio_chan_set_param_pcm_dma_buff(pa_dev, buf, end, ch->channel, dir); + uaudio_chan_set_param_pcm_dma_buff(pa_dev, ch->buf, ch->buf+sc->bufsz, ch->channel, dir); + if (bootverbose) + device_printf(pa_dev, "%s buf %p\n", (dir == PCMDIR_PLAY)? + "play" : "rec", sndbuf_getbuf(ch->buffer)); ch->dir = dir; #ifndef NO_RECORDING @@ -113,6 +109,16 @@ } static int +ua_chan_free(kobj_t obj, void *data) +{ + struct ua_chinfo *ua = data; + + if (ua->buf != NULL) + free(ua->buf, M_DEVBUF); + return 0; +} + +static int ua_chan_setformat(kobj_t obj, void *data, u_int32_t format) { device_t pa_dev; @@ -120,6 +126,9 @@ struct ua_chinfo *ch = data; + /* + * At this point, no need to query as we shouldn't select an unsorted format + */ ua = ch->parent; pa_dev = device_get_parent(ua->sc_dev); uaudio_chan_set_param_format(pa_dev, format, ch->dir); @@ -131,36 +140,61 @@ static int ua_chan_setspeed(kobj_t obj, void *data, u_int32_t speed) { + struct ua_chinfo *ch; device_t pa_dev; - struct ua_info *ua; + int bestspeed; - struct ua_chinfo *ch = data; - ch->spd = speed; + ch = data; + pa_dev = device_get_parent(ch->parent->sc_dev); - ua = ch->parent; - pa_dev = device_get_parent(ua->sc_dev); - uaudio_chan_set_param_speed(pa_dev, speed, ch->dir); + if ((bestspeed = uaudio_chan_set_param_speed(pa_dev, speed, ch->dir))) + ch->spd = bestspeed; return ch->spd; } static int -ua_chan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) +ua_chan_setfragments(kobj_t obj, void *data, u_int32_t blksz, u_int32_t blkcnt) { device_t pa_dev; - struct ua_info *ua; struct ua_chinfo *ch = data; - /* ch->blksz = blocksize; */ - if (blocksize) { - ch->blksz = blocksize; - } else { - ch->blksz = UAUDIO_PCM_BUFF_SIZE/2; + struct ua_info *ua = ch->parent; + + RANGE(blksz, 128, sndbuf_getmaxsize(ch->buffer) / 2); + RANGE(blkcnt, 2, 512); + + while ((blksz * blkcnt) > sndbuf_getmaxsize(ch->buffer)) { + if ((blkcnt >> 1) >= 2) + blkcnt >>= 1; + else if ((blksz >> 1) >= 128) + blksz >>= 1; + else + break; } - /* XXXXX */ - ua = ch->parent; + if ((sndbuf_getblksz(ch->buffer) != blksz || + sndbuf_getblkcnt(ch->buffer) != blkcnt) && + sndbuf_resize(ch->buffer, blkcnt, blksz) != 0) + device_printf(ua->sc_dev, "%s: failed blksz=%u blkcnt=%u\n", + __func__, blksz, blkcnt); + + ch->blksz = sndbuf_getblksz(ch->buffer); + pa_dev = device_get_parent(ua->sc_dev); - uaudio_chan_set_param_blocksize(pa_dev, blocksize, ch->dir); + uaudio_chan_set_param_pcm_dma_buff(pa_dev, ch->buf, + ch->buf + sndbuf_getsize(ch->buffer), ch->channel, ch->dir); + uaudio_chan_set_param_blocksize(pa_dev, ch->blksz, ch->dir); + + return 1; +} + +static int +ua_chan_setblocksize(kobj_t obj, void *data, u_int32_t blksz) +{ + struct ua_chinfo *ch = data; + + ua_chan_setfragments(obj, data, blksz, + sndbuf_getmaxsize(ch->buffer) / blksz); return ch->blksz; } @@ -172,7 +206,7 @@ struct ua_info *ua; struct ua_chinfo *ch = data; - if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD) + if (!PCMTRIG_COMMON(go)) return 0; ua = ch->parent; @@ -213,16 +247,19 @@ static struct pcmchan_caps * ua_chan_getcaps(kobj_t obj, void *data) { - struct ua_chinfo *ch = data; + struct ua_chinfo *ch; - return (ch->dir == PCMDIR_PLAY) ? &ua_playcaps : & ua_reccaps; + ch = data; + return (ch->dir == PCMDIR_PLAY) ? &(ch->parent->ua_playcaps) : &(ch->parent->ua_reccaps); } static kobj_method_t ua_chan_methods[] = { KOBJMETHOD(channel_init, ua_chan_init), + KOBJMETHOD(channel_free, ua_chan_free), KOBJMETHOD(channel_setformat, ua_chan_setformat), KOBJMETHOD(channel_setspeed, ua_chan_setspeed), KOBJMETHOD(channel_setblocksize, ua_chan_setblocksize), + KOBJMETHOD(channel_setfragments, ua_chan_setfragments), KOBJMETHOD(channel_trigger, ua_chan_trigger), KOBJMETHOD(channel_getptr, ua_chan_getptr), KOBJMETHOD(channel_getcaps, ua_chan_getcaps), @@ -242,6 +279,18 @@ pa_dev = device_get_parent(ua->sc_dev); mask = uaudio_query_mix_info(pa_dev); + if (!(mask & SOUND_MASK_PCM)) { + /* + * Emulate missing pcm mixer controller + * through FEEDER_VOLUME + */ + pcm_setflags(ua->sc_dev, pcm_getflags(ua->sc_dev) | + SD_F_SOFTPCMVOL); + } + if (!(mask & SOUND_MASK_VOLUME)) { + mix_setparentchild(m, SOUND_MIXER_VOLUME, SOUND_MASK_PCM); + mix_setrealdev(m, SOUND_MIXER_VOLUME, SOUND_MIXER_NONE); + } mix_setdevs(m, mask); mask = uaudio_query_recsrc_info(pa_dev); @@ -298,80 +347,112 @@ s = "USB Audio"; device_set_desc(dev, s); - return 0; + return BUS_PROBE_DEFAULT; } static int ua_attach(device_t dev) { struct ua_info *ua; + struct sndcard_func *func; char status[SND_STATUSLEN]; - unsigned int bufsz; - - ua = (struct ua_info *)malloc(sizeof *ua, M_DEVBUF, M_NOWAIT); - if (!ua) - return ENXIO; - bzero(ua, sizeof *ua); + device_t pa_dev; + u_int32_t nplay, nrec, flags; + int i; + ua = malloc(sizeof(*ua), M_DEVBUF, M_WAITOK | M_ZERO); ua->sc_dev = dev; - bufsz = pcm_getbuffersize(dev, 4096, UAUDIO_PCM_BUFF_SIZE, 65536); + /* Mark for existence */ + func = device_get_ivars(dev); + if (func != NULL) + func->varinfo = (void *)ua; + + pa_dev = device_get_parent(dev); + ua->vendor = uaudio_get_vendor(pa_dev); + ua->product = uaudio_get_product(pa_dev); + ua->release = uaudio_get_release(pa_dev); + + if (bootverbose) + device_printf(dev, + "USB Audio: " + "vendor=0x%04x, product=0x%04x, release=0x%04x\n", + ua->vendor, ua->product, ua->release); + + ua->bufsz = pcm_getbuffersize(dev, 4096, UAUDIO_DEFAULT_BUFSZ, 65536); + if (bootverbose) + device_printf(dev, "using a default buffer size of %jd\n", (intmax_t)ua->bufsz); - if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/2, /*boundary*/0, - /*lowaddr*/BUS_SPACE_MAXADDR_32BIT, - /*highaddr*/BUS_SPACE_MAXADDR, - /*filter*/NULL, /*filterarg*/NULL, - /*maxsize*/bufsz, /*nsegments*/1, - /*maxsegz*/0x4000, /*flags*/0, - /*lockfunc*/busdma_lock_mutex, - /*lockarg*/&Giant, - &ua->parent_dmat) != 0) { - device_printf(dev, "unable to create dma tag\n"); + if (mixer_init(dev, &ua_mixer_class, ua)) { goto bad; } - if (mixer_init(dev, &ua_mixer_class, ua)) { - return(ENXIO); - } + snprintf(status, SND_STATUSLEN, "at ? %s", PCM_KLDSTRING(snd_uaudio)); - snprintf(status, SND_STATUSLEN, "at addr ?"); + ua->ua_playcaps.fmtlist = ua->ua_playfmt; + ua->ua_reccaps.fmtlist = ua->ua_recfmt; + nplay = uaudio_query_formats(pa_dev, PCMDIR_PLAY, FORMAT_NUM * 2, &ua->ua_playcaps); + nrec = uaudio_query_formats(pa_dev, PCMDIR_REC, FORMAT_NUM * 2, &ua->ua_reccaps); + + if (nplay > 1) + nplay = 1; + if (nrec > 1) + nrec = 1; + + flags = pcm_getflags(dev); + for (i = 0; i < (sizeof(ua_quirks) / sizeof(ua_quirks[0])); i++) { + if (ua->vendor == ua_quirks[i].vendor && + ua->product == ua_quirks[i].product && + ua->release == ua_quirks[i].release) + flags |= ua_quirks[i].dflags; + } + pcm_setflags(dev, flags); #ifndef NO_RECORDING - if (pcm_register(dev, ua, 1, 1)) { + if (pcm_register(dev, ua, nplay, nrec)) { #else - if (pcm_register(dev, ua, 1, 0)) { + if (pcm_register(dev, ua, nplay, 0)) { #endif - return(ENXIO); + goto bad; } - pcm_addchan(dev, PCMDIR_PLAY, &ua_chan_class, ua); + sndstat_unregister(dev); + uaudio_sndstat_register(dev); + + for (i = 0; i < nplay; i++) { + pcm_addchan(dev, PCMDIR_PLAY, &ua_chan_class, ua); + } #ifndef NO_RECORDING - pcm_addchan(dev, PCMDIR_REC, &ua_chan_class, ua); + for (i = 0; i < nrec; i++) { + pcm_addchan(dev, PCMDIR_REC, &ua_chan_class, ua); + } #endif pcm_setstatus(dev, status); return 0; -bad: - if (ua->parent_dmat) - bus_dma_tag_destroy(ua->parent_dmat); - free(ua, M_DEVBUF); +bad: free(ua, M_DEVBUF); return ENXIO; } static int ua_detach(device_t dev) { - int r; struct ua_info *sc; + struct sndcard_func *func; + int r; r = pcm_unregister(dev); if (r) return r; sc = pcm_getdevinfo(dev); - bus_dma_tag_destroy(sc->parent_dmat); free(sc, M_DEVBUF); + + /* Mark for deletion */ + func = device_get_ivars(dev); + if (func != NULL) + func->varinfo = NULL; return 0; } --- sys/dev/sound/usb/uaudioreg.h.orig Fri Apr 15 12:15:24 2005 +++ sys/dev/sound/usb/uaudioreg.h Thu Jan 6 09:43:22 2005 @@ -1,5 +1,5 @@ /* $NetBSD: uaudioreg.h,v 1.12 2004/11/05 19:08:29 kent Exp $ */ -/* $FreeBSD: src/sys/dev/sound/usb/uaudioreg.h,v 1.2.8.2 2005/04/15 04:15:24 julian Exp $ */ +/* $FreeBSD: src/sys/dev/sound/usb/uaudioreg.h,v 1.4 2005/01/06 01:43:22 imp Exp $ */ /*- * Copyright (c) 1999 The NetBSD Foundation, Inc. --- sys/dev/sound/version.h.orig Thu Jan 1 07:30:00 1970 +++ sys/dev/sound/version.h Thu Jul 12 14:46:27 2007 @@ -0,0 +1,42 @@ +/*- + * 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/version.h,v 1.2 2007/06/16 03:37:27 ariff Exp $ + */ + +#ifndef _SND_VERSION_H_ +#define _SND_VERSION_H_ + +/* + * FreeBSD sound driver internal versioning, nothing to do + * with OSS whatsoever. Dear future maintainer, please revisit + * this _before_ Jan 1 2148 + * + * Last 2 decimal places reserved for daily versioning, starting + * with 0. + */ +#define SND_DRV_VERSION 2007071200 + +#endif /* !_SND_VERSION_H_ */ --- sys/gnu/dev/sound/pci/csaimg.h.orig Tue Feb 1 07:26:01 2005 +++ sys/gnu/dev/sound/pci/csaimg.h Fri Jan 7 02:27:30 2005 @@ -19,7 +19,7 @@ * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, * USA. * - * $FreeBSD: src/sys/gnu/dev/sound/pci/csaimg.h,v 1.1.10.1 2005/01/31 23:26:01 imp Exp $ + * $FreeBSD: src/sys/gnu/dev/sound/pci/csaimg.h,v 1.2 2005/01/06 18:27:30 imp Exp $ * ***************************************************************************/ --- sys/gnu/dev/sound/pci/emu10k1-ac97.h.orig Fri Jan 9 13:08:32 2004 +++ sys/gnu/dev/sound/pci/emu10k1-ac97.h Thu Jan 1 07:30:00 1970 @@ -1,293 +0,0 @@ -/* $FreeBSD: src/sys/gnu/dev/sound/pci/emu10k1-ac97.h,v 1.2 2004/01/09 05:08:32 obrien Exp $ */ - -#ifndef _AC97_CODEC_H_ -#define _AC97_CODEC_H_ - -#ifdef __linux__ -#include -#include -#endif - -/* AC97 1.0 */ -#define AC97_RESET 0x0000 // -#define AC97_MASTER_VOL_STEREO 0x0002 // Line Out -#define AC97_HEADPHONE_VOL 0x0004 // -#define AC97_MASTER_VOL_MONO 0x0006 // TAD Output -#define AC97_MASTER_TONE 0x0008 // -#define AC97_PCBEEP_VOL 0x000a // none -#define AC97_PHONE_VOL 0x000c // TAD Input (mono) -#define AC97_MIC_VOL 0x000e // MIC Input (mono) -#define AC97_LINEIN_VOL 0x0010 // Line Input (stereo) -#define AC97_CD_VOL 0x0012 // CD Input (stereo) -#define AC97_VIDEO_VOL 0x0014 // none -#define AC97_AUX_VOL 0x0016 // Aux Input (stereo) -#define AC97_PCMOUT_VOL 0x0018 // Wave Output (stereo) -#define AC97_RECORD_SELECT 0x001a // -#define AC97_RECORD_GAIN 0x001c -#define AC97_RECORD_GAIN_MIC 0x001e -#define AC97_GENERAL_PURPOSE 0x0020 -#define AC97_3D_CONTROL 0x0022 -#define AC97_MODEM_RATE 0x0024 -#define AC97_POWER_CONTROL 0x0026 - -/* AC'97 2.0 */ -#define AC97_EXTENDED_ID 0x0028 /* Extended Audio ID */ -#define AC97_EXTENDED_STATUS 0x002A /* Extended Audio Status */ -#define AC97_PCM_FRONT_DAC_RATE 0x002C /* PCM Front DAC Rate */ -#define AC97_PCM_SURR_DAC_RATE 0x002E /* PCM Surround DAC Rate */ -#define AC97_PCM_LFE_DAC_RATE 0x0030 /* PCM LFE DAC Rate */ -#define AC97_PCM_LR_ADC_RATE 0x0032 /* PCM LR ADC Rate */ -#define AC97_PCM_MIC_ADC_RATE 0x0034 /* PCM MIC ADC Rate */ -#define AC97_CENTER_LFE_MASTER 0x0036 /* Center + LFE Master Volume */ -#define AC97_SURROUND_MASTER 0x0038 /* Surround (Rear) Master Volume */ -#define AC97_RESERVED_3A 0x003A /* Reserved in AC '97 < 2.2 */ - -/* AC'97 2.2 */ -#define AC97_SPDIF_CONTROL 0x003A /* S/PDIF Control */ - -/* range 0x3c-0x58 - MODEM */ -#define AC97_EXTENDED_MODEM_ID 0x003C -#define AC97_EXTEND_MODEM_STAT 0x003E -#define AC97_LINE1_RATE 0x0040 -#define AC97_LINE2_RATE 0x0042 -#define AC97_HANDSET_RATE 0x0044 -#define AC97_LINE1_LEVEL 0x0046 -#define AC97_LINE2_LEVEL 0x0048 -#define AC97_HANDSET_LEVEL 0x004A -#define AC97_GPIO_CONFIG 0x004C -#define AC97_GPIO_POLARITY 0x004E -#define AC97_GPIO_STICKY 0x0050 -#define AC97_GPIO_WAKE_UP 0x0052 -#define AC97_GPIO_STATUS 0x0054 -#define AC97_MISC_MODEM_STAT 0x0056 -#define AC97_RESERVED_58 0x0058 - -/* registers 0x005a - 0x007a are vendor reserved */ - -#define AC97_VENDOR_ID1 0x007c -#define AC97_VENDOR_ID2 0x007e - -/* volume control bit defines */ -#ifndef AC97_MUTE -#define AC97_MUTE 0x8000 -#endif -#define AC97_MICBOOST 0x0040 -#define AC97_LEFTVOL 0x3f00 -#define AC97_RIGHTVOL 0x003f - -/* record mux defines */ -#define AC97_RECMUX_MIC 0x0000 -#define AC97_RECMUX_CD 0x0101 -#define AC97_RECMUX_VIDEO 0x0202 -#define AC97_RECMUX_AUX 0x0303 -#define AC97_RECMUX_LINE 0x0404 -#define AC97_RECMUX_STEREO_MIX 0x0505 -#define AC97_RECMUX_MONO_MIX 0x0606 -#define AC97_RECMUX_PHONE 0x0707 - -/* general purpose register bit defines */ -#define AC97_GP_LPBK 0x0080 /* Loopback mode */ -#define AC97_GP_MS 0x0100 /* Mic Select 0=Mic1, 1=Mic2 */ -#define AC97_GP_MIX 0x0200 /* Mono output select 0=Mix, 1=Mic */ -#define AC97_GP_RLBK 0x0400 /* Remote Loopback - Modem line codec */ -#define AC97_GP_LLBK 0x0800 /* Local Loopback - Modem Line codec */ -#define AC97_GP_LD 0x1000 /* Loudness 1=on */ -#define AC97_GP_3D 0x2000 /* 3D Enhancement 1=on */ -#define AC97_GP_ST 0x4000 /* Stereo Enhancement 1=on */ -#define AC97_GP_POP 0x8000 /* Pcm Out Path, 0=pre 3D, 1=post 3D */ - -/* extended audio status and control bit defines */ -#define AC97_EA_VRA 0x0001 /* Variable bit rate enable bit */ -#define AC97_EA_DRA 0x0002 /* Double-rate audio enable bit */ -#define AC97_EA_SPDIF 0x0004 /* S/PDIF Enable bit */ -#define AC97_EA_VRM 0x0008 /* Variable bit rate for MIC enable bit */ -#define AC97_EA_CDAC 0x0040 /* PCM Center DAC is ready (Read only) */ -#define AC97_EA_SDAC 0x0040 /* PCM Surround DACs are ready (Read only) */ -#define AC97_EA_LDAC 0x0080 /* PCM LFE DAC is ready (Read only) */ -#define AC97_EA_MDAC 0x0100 /* MIC ADC is ready (Read only) */ -#define AC97_EA_SPCV 0x0400 /* S/PDIF configuration valid (Read only) */ -#define AC97_EA_PRI 0x0800 /* Turns the PCM Center DAC off */ -#define AC97_EA_PRJ 0x1000 /* Turns the PCM Surround DACs off */ -#define AC97_EA_PRK 0x2000 /* Turns the PCM LFE DAC off */ -#define AC97_EA_PRL 0x4000 /* Turns the MIC ADC off */ -#define AC97_EA_SLOT_MASK 0xffcf /* Mask for slot assignment bits */ -#define AC97_EA_SPSA_3_4 0x0000 /* Slot assigned to 3 & 4 */ -#define AC97_EA_SPSA_7_8 0x0010 /* Slot assigned to 7 & 8 */ -#define AC97_EA_SPSA_6_9 0x0020 /* Slot assigned to 6 & 9 */ -#define AC97_EA_SPSA_10_11 0x0030 /* Slot assigned to 10 & 11 */ - -/* S/PDIF control bit defines */ -#define AC97_SC_PRO 0x0001 /* Professional status */ -#define AC97_SC_NAUDIO 0x0002 /* Non audio stream */ -#define AC97_SC_COPY 0x0004 /* Copyright status */ -#define AC97_SC_PRE 0x0008 /* Preemphasis status */ -#define AC97_SC_CC_MASK 0x07f0 /* Category Code mask */ -#define AC97_SC_L 0x0800 /* Generation Level status */ -#define AC97_SC_SPSR_MASK 0xcfff /* S/PDIF Sample Rate bits */ -#define AC97_SC_SPSR_44K 0x0000 /* Use 44.1kHz Sample rate */ -#define AC97_SC_SPSR_48K 0x2000 /* Use 48kHz Sample rate */ -#define AC97_SC_SPSR_32K 0x3000 /* Use 32kHz Sample rate */ -#define AC97_SC_DRS 0x4000 /* Double Rate S/PDIF */ -#define AC97_SC_V 0x8000 /* Validity status */ - -/* powerdown control and status bit defines */ - -/* status */ -#define AC97_PWR_MDM 0x0010 /* Modem section ready */ -#define AC97_PWR_REF 0x0008 /* Vref nominal */ -#define AC97_PWR_ANL 0x0004 /* Analog section ready */ -#define AC97_PWR_DAC 0x0002 /* DAC section ready */ -#define AC97_PWR_ADC 0x0001 /* ADC section ready */ - -/* control */ -#define AC97_PWR_PR0 0x0100 /* ADC and Mux powerdown */ -#define AC97_PWR_PR1 0x0200 /* DAC powerdown */ -#define AC97_PWR_PR2 0x0400 /* Output mixer powerdown (Vref on) */ -#define AC97_PWR_PR3 0x0800 /* Output mixer powerdown (Vref off) */ -#define AC97_PWR_PR4 0x1000 /* AC-link powerdown */ -#define AC97_PWR_PR5 0x2000 /* Internal Clk disable */ -#define AC97_PWR_PR6 0x4000 /* HP amp powerdown */ -#define AC97_PWR_PR7 0x8000 /* Modem off - if supported */ - -/* extended audio ID register bit defines */ -#define AC97_EXTID_VRA 0x0001 -#define AC97_EXTID_DRA 0x0002 -#define AC97_EXTID_SPDIF 0x0004 -#define AC97_EXTID_VRM 0x0008 -#define AC97_EXTID_DSA0 0x0010 -#define AC97_EXTID_DSA1 0x0020 -#define AC97_EXTID_CDAC 0x0040 -#define AC97_EXTID_SDAC 0x0080 -#define AC97_EXTID_LDAC 0x0100 -#define AC97_EXTID_AMAP 0x0200 -#define AC97_EXTID_REV0 0x0400 -#define AC97_EXTID_REV1 0x0800 -#define AC97_EXTID_ID0 0x4000 -#define AC97_EXTID_ID1 0x8000 - -/* extended status register bit defines */ -#define AC97_EXTSTAT_VRA 0x0001 -#define AC97_EXTSTAT_DRA 0x0002 -#define AC97_EXTSTAT_SPDIF 0x0004 -#define AC97_EXTSTAT_VRM 0x0008 -#define AC97_EXTSTAT_SPSA0 0x0010 -#define AC97_EXTSTAT_SPSA1 0x0020 -#define AC97_EXTSTAT_CDAC 0x0040 -#define AC97_EXTSTAT_SDAC 0x0080 -#define AC97_EXTSTAT_LDAC 0x0100 -#define AC97_EXTSTAT_MADC 0x0200 -#define AC97_EXTSTAT_SPCV 0x0400 -#define AC97_EXTSTAT_PRI 0x0800 -#define AC97_EXTSTAT_PRJ 0x1000 -#define AC97_EXTSTAT_PRK 0x2000 -#define AC97_EXTSTAT_PRL 0x4000 - -/* useful power states */ -#define AC97_PWR_D0 0x0000 /* everything on */ -#define AC97_PWR_D1 AC97_PWR_PR0|AC97_PWR_PR1|AC97_PWR_PR4 -#define AC97_PWR_D2 AC97_PWR_PR0|AC97_PWR_PR1|AC97_PWR_PR2|AC97_PWR_PR3|AC97_PWR_PR4 -#define AC97_PWR_D3 AC97_PWR_PR0|AC97_PWR_PR1|AC97_PWR_PR2|AC97_PWR_PR3|AC97_PWR_PR4 -#define AC97_PWR_ANLOFF AC97_PWR_PR2|AC97_PWR_PR3 /* analog section off */ - -/* Total number of defined registers. */ -#define AC97_REG_CNT 64 - - -/* OSS interface to the ac97s.. */ -#define AC97_STEREO_MASK (SOUND_MASK_VOLUME|SOUND_MASK_PCM|\ - SOUND_MASK_LINE|SOUND_MASK_CD|\ - SOUND_MASK_ALTPCM|SOUND_MASK_IGAIN|\ - SOUND_MASK_LINE1|SOUND_MASK_VIDEO) - -#define AC97_SUPPORTED_MASK (AC97_STEREO_MASK | \ - SOUND_MASK_BASS|SOUND_MASK_TREBLE|\ - SOUND_MASK_SPEAKER|SOUND_MASK_MIC|\ - SOUND_MASK_PHONEIN|SOUND_MASK_PHONEOUT) - -#define AC97_RECORD_MASK (SOUND_MASK_MIC|\ - SOUND_MASK_CD|SOUND_MASK_IGAIN|SOUND_MASK_VIDEO|\ - SOUND_MASK_LINE1| SOUND_MASK_LINE|\ - SOUND_MASK_PHONEIN) - -/* original check is not good enough in case FOO is greater than - * SOUND_MIXER_NRDEVICES because the supported_mixers has exactly - * SOUND_MIXER_NRDEVICES elements. - * before matching the given mixer against the bitmask in supported_mixers we - * check if mixer number exceeds maximum allowed size which is as mentioned - * above SOUND_MIXER_NRDEVICES */ -#define supported_mixer(CODEC,FOO) ((FOO >= 0) && \ - (FOO < SOUND_MIXER_NRDEVICES) && \ - (CODEC)->supported_mixers & (1<, * Creative Labs, Inc. * Definitions for EMU10K1 (SB Live!) chips @@ -23,6 +23,8 @@ * */ +/* $FreeBSD: src/sys/gnu/dev/sound/pci/emu10k1-alsa.h,v 1.3 2006/07/15 19:19:54 netchild Exp $ */ + #ifdef __KERNEL__ #include @@ -30,16 +32,12 @@ #include #include #include +#include +#include #include +#include #include -#ifndef PCI_VENDOR_ID_CREATIVE -#define PCI_VENDOR_ID_CREATIVE 0x1102 -#endif -#ifndef PCI_DEVICE_ID_CREATIVE_EMU10K1 -#define PCI_DEVICE_ID_CREATIVE_EMU10K1 0x0002 -#endif - /* ------------------- DEFINES -------------------- */ #define EMUPAGESIZE 4096 @@ -49,9 +47,12 @@ #define NUM_MIDI 16 #define NUM_G 64 /* use all channels */ #define NUM_FXSENDS 4 +#define NUM_EFX_PLAYBACK 16 +/* FIXME? - according to the OSS driver the EMU10K1 needs a 29 bit DMA mask */ #define EMU10K1_DMA_MASK 0x7fffffffUL /* 31bit */ -#define AUDIGY_DMA_MASK 0xffffffffUL /* 32bit */ +#define AUDIGY_DMA_MASK 0x7fffffffUL /* 31bit FIXME - 32 should work? */ + /* See ALSA bug #1276 - rlrevell */ #define TMEMSIZE 256*1024 #define TMEMSIZEREG 4 @@ -79,11 +80,18 @@ #define IPR 0x08 /* Global interrupt pending register */ /* Clear pending interrupts by writing a 1 to */ /* the relevant bits and zero to the other bits */ +#define IPR_P16V 0x80000000 /* Bit set when the CA0151 P16V chip wishes + to interrupt */ +#define IPR_GPIOMSG 0x20000000 /* GPIO message interrupt (RE'd, still not sure + which INTE bits enable it) */ /* The next two interrupts are for the midi port on the Audigy Drive (A_MPU1) */ #define IPR_A_MIDITRANSBUFEMPTY2 0x10000000 /* MIDI UART transmit buffer empty */ #define IPR_A_MIDIRECVBUFEMPTY2 0x08000000 /* MIDI UART receive buffer empty */ +#define IPR_SPDIFBUFFULL 0x04000000 /* SPDIF capture related, 10k2 only? (RE) */ +#define IPR_SPDIFBUFHALFFULL 0x02000000 /* SPDIF capture related? (RE) */ + #define IPR_SAMPLERATETRACKER 0x01000000 /* Sample rate tracker lock status change */ #define IPR_FXDSP 0x00800000 /* Enable FX DSP interrupts */ #define IPR_FORCEINT 0x00400000 /* Force Sound Blaster interrupt */ @@ -102,12 +110,12 @@ #define IPR_INTERVALTIMER 0x00000200 /* Interval timer terminal count */ #define IPR_MIDITRANSBUFEMPTY 0x00000100 /* MIDI UART transmit buffer empty */ #define IPR_MIDIRECVBUFEMPTY 0x00000080 /* MIDI UART receive buffer empty */ -#define IPR_CHANNELLOOP 0x00000040 /* One or more channel loop interrupts pending */ +#define IPR_CHANNELLOOP 0x00000040 /* Channel (half) loop interrupt(s) pending */ #define IPR_CHANNELNUMBERMASK 0x0000003f /* When IPR_CHANNELLOOP is set, indicates the */ - /* Highest set channel in CLIPL or CLIPH. When */ - /* IP is written with CL set, the bit in CLIPL */ - /* or CLIPH corresponding to the CIN value */ - /* written will be cleared. */ + /* highest set channel in CLIPL, CLIPH, HLIPL, */ + /* or HLIPH. When IP is written with CL set, */ + /* the bit in H/CLIPL or H/CLIPH corresponding */ + /* to the CIN value written will be cleared. */ #define INTE 0x0c /* Interrupt enable register */ #define INTE_VIRTUALSB_MASK 0xc0000000 /* Virtual Soundblaster I/O port capture */ @@ -234,9 +242,28 @@ #define A_IOCFG 0x18 /* GPIO on Audigy card (16bits) */ #define A_GPINPUT_MASK 0xff00 #define A_GPOUTPUT_MASK 0x00ff -#define A_IOCFG_GPOUT0 0x0044 /* analog/digital? */ -#define A_IOCFG_GPOUT1 0x0002 /* IR */ + +// Audigy output/GPIO stuff taken from the kX drivers +#define A_IOCFG_GPOUT0 0x0044 /* analog/digital */ +#define A_IOCFG_DISABLE_ANALOG 0x0040 /* = 'enable' for Audigy2 (chiprev=4) */ +#define A_IOCFG_ENABLE_DIGITAL 0x0004 +#define A_IOCFG_ENABLE_DIGITAL_AUDIGY4 0x0080 +#define A_IOCFG_UNKNOWN_20 0x0020 +#define A_IOCFG_DISABLE_AC97_FRONT 0x0080 /* turn off ac97 front -> front (10k2.1) */ +#define A_IOCFG_GPOUT1 0x0002 /* IR? drive's internal bypass (?) */ #define A_IOCFG_GPOUT2 0x0001 /* IR */ +#define A_IOCFG_MULTIPURPOSE_JACK 0x2000 /* center+lfe+rear_center (a2/a2ex) */ + /* + digital for generic 10k2 */ +#define A_IOCFG_DIGITAL_JACK 0x1000 /* digital for a2 platinum */ +#define A_IOCFG_FRONT_JACK 0x4000 +#define A_IOCFG_REAR_JACK 0x8000 +#define A_IOCFG_PHONES_JACK 0x0100 /* LiveDrive */ + +/* outputs: + * for audigy2 platinum: 0xa00 + * for a2 platinum ex: 0x1c00 + * for a1 platinum: 0x0 + */ #define TIMER 0x1a /* Timer terminal count register */ /* NOTE: After the rate is changed, a maximum */ @@ -252,6 +279,46 @@ #define AC97ADDRESS_READY 0x80 /* Read-only bit, reflects CODEC READY signal */ #define AC97ADDRESS_ADDRESS 0x7f /* Address of indexed AC97 register */ +/* Available on the Audigy 2 and Audigy 4 only. This is the P16V chip. */ +#define PTR2 0x20 /* Indexed register set pointer register */ +#define DATA2 0x24 /* Indexed register set data register */ +#define IPR2 0x28 /* P16V interrupt pending register */ +#define IPR2_PLAYBACK_CH_0_LOOP 0x00001000 /* Playback Channel 0 loop */ +#define IPR2_PLAYBACK_CH_0_HALF_LOOP 0x00000100 /* Playback Channel 0 half loop */ +#define IPR2_CAPTURE_CH_0_LOOP 0x00100000 /* Capture Channel 0 loop */ +#define IPR2_CAPTURE_CH_0_HALF_LOOP 0x00010000 /* Capture Channel 0 half loop */ + /* 0x00000100 Playback. Only in once per period. + * 0x00110000 Capture. Int on half buffer. + */ +#define INTE2 0x2c /* P16V Interrupt enable register. */ +#define INTE2_PLAYBACK_CH_0_LOOP 0x00001000 /* Playback Channel 0 loop */ +#define INTE2_PLAYBACK_CH_0_HALF_LOOP 0x00000100 /* Playback Channel 0 half loop */ +#define INTE2_PLAYBACK_CH_1_LOOP 0x00002000 /* Playback Channel 1 loop */ +#define INTE2_PLAYBACK_CH_1_HALF_LOOP 0x00000200 /* Playback Channel 1 half loop */ +#define INTE2_PLAYBACK_CH_2_LOOP 0x00004000 /* Playback Channel 2 loop */ +#define INTE2_PLAYBACK_CH_2_HALF_LOOP 0x00000400 /* Playback Channel 2 half loop */ +#define INTE2_PLAYBACK_CH_3_LOOP 0x00008000 /* Playback Channel 3 loop */ +#define INTE2_PLAYBACK_CH_3_HALF_LOOP 0x00000800 /* Playback Channel 3 half loop */ +#define INTE2_CAPTURE_CH_0_LOOP 0x00100000 /* Capture Channel 0 loop */ +#define INTE2_CAPTURE_CH_0_HALF_LOOP 0x00010000 /* Caputre Channel 0 half loop */ +#define HCFG2 0x34 /* Defaults: 0, win2000 sets it to 00004201 */ + /* 0x00000000 2-channel output. */ + /* 0x00000200 8-channel output. */ + /* 0x00000004 pauses stream/irq fail. */ + /* Rest of bits no nothing to sound output */ + /* bit 0: Enable P16V audio. + * bit 1: Lock P16V record memory cache. + * bit 2: Lock P16V playback memory cache. + * bit 3: Dummy record insert zero samples. + * bit 8: Record 8-channel in phase. + * bit 9: Playback 8-channel in phase. + * bit 11-12: Playback mixer attenuation: 0=0dB, 1=-6dB, 2=-12dB, 3=Mute. + * bit 13: Playback mixer enable. + * bit 14: Route SRC48 mixer output to fx engine. + * bit 15: Enable IEEE 1394 chip. + */ +#define IPR3 0x38 /* Cdif interrupt pending register */ +#define INTE3 0x3c /* Cdif interrupt enable register. */ /************************************************************************************************/ /* PCI function 1 registers, address = + PCIBASE1 */ /************************************************************************************************/ @@ -462,6 +529,8 @@ /* NOTE: All channels contain internal variables; do */ /* not write to these locations. */ +/* 1f something */ + #define CD0 0x20 /* Cache data 0 register */ #define CD1 0x21 /* Cache data 1 register */ #define CD2 0x22 /* Cache data 2 register */ @@ -479,6 +548,8 @@ #define CDE 0x2e /* Cache data E register */ #define CDF 0x2f /* Cache data F register */ +/* 0x30-3f seem to be the same as 0x20-2f */ + #define PTB 0x40 /* Page table base register */ #define PTB_MASK 0xfffff000 /* Physical address of the page table in host memory */ @@ -509,7 +580,11 @@ #define FXWC 0x43 /* FX output write channels register */ /* When set, each bit enables the writing of the */ - /* corresponding FX output channel into host memory */ + /* corresponding FX output channel (internal registers */ + /* 0x20-0x3f) to host memory. This mode of recording */ + /* is 16bit, 48KHz only. All 32 channels can be enabled */ + /* simultaneously. */ + #define FXWC_DEFAULTROUTE_C (1<<0) /* left emu out? */ #define FXWC_DEFAULTROUTE_B (1<<1) /* right emu out? */ #define FXWC_DEFAULTROUTE_A (1<<12) @@ -544,12 +619,16 @@ #define FXBA 0x47 /* FX Buffer Address */ #define FXBA_MASK 0xfffff000 /* 20 bit base address */ +/* 0x48 something - word access, defaults to 3f */ + #define MICBS 0x49 /* Microphone buffer size register */ #define ADCBS 0x4a /* ADC buffer size register */ #define FXBS 0x4b /* FX buffer size register */ +/* register: 0x4c..4f: ffff-ffff current amounts, per-channel */ + /* The following mask values define the size of the ADC, MIX and FX buffers in bytes */ #define ADCBS_BUFSIZE_NONE 0x00000000 #define ADCBS_BUFSIZE_384 0x00000001 @@ -600,6 +679,7 @@ #define A_DBG_SATURATION_OCCURED 0x20000000 #define A_DBG_SATURATION_ADDR 0x0ffc0000 +// NOTE: 0x54,55,56: 64-bit #define SPCS0 0x54 /* SPDIF output Channel Status 0 register */ #define SPCS1 0x55 /* SPDIF output Channel Status 1 register */ @@ -644,12 +724,18 @@ #define SOLEH 0x5d /* Stop on loop enable high register */ #define SPBYPASS 0x5e /* SPDIF BYPASS mode register */ -#define SPBYPASS_ENABLE 0x00000001 /* Enable SPDIF bypass mode */ +#define SPBYPASS_SPDIF0_MASK 0x00000003 /* SPDIF 0 bypass mode */ +#define SPBYPASS_SPDIF1_MASK 0x0000000c /* SPDIF 1 bypass mode */ +/* bypass mode: 0 - DSP; 1 - SPDIF A, 2 - SPDIF B, 3 - SPDIF C */ +#define SPBYPASS_FORMAT 0x00000f00 /* If 1, SPDIF XX uses 24 bit, if 0 - 20 bit */ #define AC97SLOT 0x5f /* additional AC97 slots enable bits */ +#define AC97SLOT_REAR_RIGHT 0x01 /* Rear left */ +#define AC97SLOT_REAR_LEFT 0x02 /* Rear right */ #define AC97SLOT_CNTR 0x10 /* Center enable */ #define AC97SLOT_LFE 0x20 /* LFE enable */ +// NOTE: 0x60,61,62: 64-bit #define CDSRCS 0x60 /* CD-ROM Sample Rate Converter status register */ #define GPSRCS 0x61 /* General Purpose SPDIF sample rate cvt status */ @@ -659,6 +745,7 @@ /* Assumes sample lock */ /* These three bitfields apply to CDSRCS, GPSRCS, and (except as noted) ZVSRCS. */ +#define SRCS_SPDIFVALID 0x04000000 /* SPDIF stream valid */ #define SRCS_SPDIFLOCKED 0x02000000 /* SPDIF stream locked */ #define SRCS_RATELOCKED 0x01000000 /* Sample rate locked */ #define SRCS_ESTSAMPLERATE 0x0007ffff /* Do not modify this field. */ @@ -686,6 +773,19 @@ #define FXIDX_MASK 0x0000ffff /* 16-bit value */ #define FXIDX_IDX 0x10000065 +/* The 32-bit HLIx and HLIPx registers all have one bit per channel control/status */ +#define HLIEL 0x66 /* Channel half loop interrupt enable low register */ + +#define HLIEH 0x67 /* Channel half loop interrupt enable high register */ + +#define HLIPL 0x68 /* Channel half loop interrupt pending low register */ + +#define HLIPH 0x69 /* Channel half loop interrupt pending high register */ + +// 0x6a,6b,6c used for some recording +// 0x6d unused +// 0x6e,6f - tanktable base / offset + /* This is the MPU port on the card (via the game port) */ #define A_MUDATA1 0x70 #define A_MUCMD1 0x71 @@ -703,9 +803,29 @@ #define A_FXWC2 0x75 /* Selects 0x9f-0x80 for FX recording */ #define A_SPDIF_SAMPLERATE 0x76 /* Set the sample rate of SPDIF output */ -#define A_SPDIF_48000 0x00000080 -#define A_SPDIF_44100 0x00000000 +#define A_SAMPLE_RATE 0x76 /* Various sample rate settings. */ +#define A_SAMPLE_RATE_NOT_USED 0x0ffc111e /* Bits that are not used and cannot be set. */ +#define A_SAMPLE_RATE_UNKNOWN 0xf0030001 /* Bits that can be set, but have unknown use. */ +#define A_SPDIF_RATE_MASK 0x000000e0 /* Any other values for rates, just use 48000 */ +#define A_SPDIF_48000 0x00000000 +#define A_SPDIF_192000 0x00000020 #define A_SPDIF_96000 0x00000040 +#define A_SPDIF_44100 0x00000080 + +#define A_I2S_CAPTURE_RATE_MASK 0x00000e00 /* This sets the capture PCM rate, but it is */ +#define A_I2S_CAPTURE_48000 0x00000000 /* unclear if this sets the ADC rate as well. */ +#define A_I2S_CAPTURE_192000 0x00000200 +#define A_I2S_CAPTURE_96000 0x00000400 +#define A_I2S_CAPTURE_44100 0x00000800 + +#define A_PCM_RATE_MASK 0x0000e000 /* This sets the playback PCM rate on the P16V */ +#define A_PCM_48000 0x00000000 +#define A_PCM_192000 0x00002000 +#define A_PCM_96000 0x00004000 +#define A_PCM_44100 0x00008000 + +/* 0x77,0x78,0x79 "something i2s-related" - default to 0x01080000 on my audigy 2 ZS --rlrevell */ +/* 0x7a, 0x7b - lookup tables */ #define A_FXRT2 0x7c #define A_FXRT_CHANNELE 0x0000003f /* Effects send bus number for channel's effects send E */ @@ -718,7 +838,8 @@ #define A_FXSENDAMOUNT_F_MASK 0x00FF0000 #define A_FXSENDAMOUNT_G_MASK 0x0000FF00 #define A_FXSENDAMOUNT_H_MASK 0x000000FF - +/* 0x7c, 0x7e "high bit is used for filtering" */ + /* The send amounts for this one are the same as used with the emu10k1 */ #define A_FXRT1 0x7e #define A_FXRT_CHANNELA 0x0000003f @@ -731,6 +852,9 @@ #define FXGPREGBASE 0x100 /* FX general purpose registers base */ #define A_FXGPREGBASE 0x400 /* Audigy GPRs, 0x400 to 0x5ff */ +#define A_TANKMEMCTLREGBASE 0x100 /* Tank memory control registers base - only for Audigy */ +#define A_TANKMEMCTLREG_MASK 0x1f /* only 5 bits used - only for Audigy */ + /* Tank audio data is logarithmically compressed down to 16 bits before writing to TRAM and is */ /* decompressed back to 20 bits on a read. There are a total of 160 locations, the last 32 */ /* locations are for external TRAM. */ @@ -767,44 +891,45 @@ /* ------------------- STRUCTURES -------------------- */ -typedef struct _snd_emu10k1 emu10k1_t; -typedef struct _snd_emu10k1_voice emu10k1_voice_t; -typedef struct _snd_emu10k1_pcm emu10k1_pcm_t; - -typedef enum { +enum { + EMU10K1_EFX, EMU10K1_PCM, EMU10K1_SYNTH, EMU10K1_MIDI -} emu10k1_voice_type_t; +}; + +struct snd_emu10k1; -struct _snd_emu10k1_voice { - emu10k1_t *emu; +struct snd_emu10k1_voice { + struct snd_emu10k1 *emu; int number; - int use: 1, + unsigned int use: 1, pcm: 1, + efx: 1, synth: 1, midi: 1; - void (*interrupt)(emu10k1_t *emu, emu10k1_voice_t *pvoice); + void (*interrupt)(struct snd_emu10k1 *emu, struct snd_emu10k1_voice *pvoice); - emu10k1_pcm_t *epcm; + struct snd_emu10k1_pcm *epcm; }; -typedef enum { +enum { PLAYBACK_EMUVOICE, + PLAYBACK_EFX, CAPTURE_AC97ADC, CAPTURE_AC97MIC, CAPTURE_EFX -} snd_emu10k1_pcm_type_t; +}; -struct _snd_emu10k1_pcm { - emu10k1_t *emu; - snd_emu10k1_pcm_type_t type; - snd_pcm_substream_t *substream; - emu10k1_voice_t *voices[2]; - emu10k1_voice_t *extra; +struct snd_emu10k1_pcm { + struct snd_emu10k1 *emu; + int type; + struct snd_pcm_substream *substream; + struct snd_emu10k1_voice *voices[NUM_EFX_PLAYBACK]; + struct snd_emu10k1_voice *extra; unsigned short running; unsigned short first_ptr; - snd_util_memblk_t *memblk; + struct snd_util_memblk *memblk; unsigned int start_addr; unsigned int ccca_start_addr; unsigned int capture_ipr; /* interrupt acknowledge mask */ @@ -818,106 +943,102 @@ unsigned int capture_bufsize; /* buffer size in bytes */ }; -typedef struct { +struct snd_emu10k1_pcm_mixer { + /* mono, left, right x 8 sends (4 on emu10k1) */ unsigned char send_routing[3][8]; unsigned char send_volume[3][8]; unsigned short attn[3]; - emu10k1_pcm_t *epcm; -} emu10k1_pcm_mixer_t; + struct snd_emu10k1_pcm *epcm; +}; #define snd_emu10k1_compose_send_routing(route) \ ((route[0] | (route[1] << 4) | (route[2] << 8) | (route[3] << 12)) << 16) #define snd_emu10k1_compose_audigy_fxrt1(route) \ -(((unsigned int)route[0] | ((unsigned int)route[1] << 8) | ((unsigned int)route[2] << 16) | ((unsigned int)route[3] << 12)) << 24) +((unsigned int)route[0] | ((unsigned int)route[1] << 8) | ((unsigned int)route[2] << 16) | ((unsigned int)route[3] << 24)) #define snd_emu10k1_compose_audigy_fxrt2(route) \ -(((unsigned int)route[4] | ((unsigned int)route[5] << 8) | ((unsigned int)route[6] << 16) | ((unsigned int)route[7] << 12)) << 24) +((unsigned int)route[4] | ((unsigned int)route[5] << 8) | ((unsigned int)route[6] << 16) | ((unsigned int)route[7] << 24)) -typedef struct snd_emu10k1_memblk { - snd_util_memblk_t mem; +struct snd_emu10k1_memblk { + struct snd_util_memblk mem; /* private part */ - short first_page, last_page, pages, mapped_page; + int first_page, last_page, pages, mapped_page; unsigned int map_locked; struct list_head mapped_link; struct list_head mapped_order_link; -} emu10k1_memblk_t; +}; #define snd_emu10k1_memblk_offset(blk) (((blk)->mapped_page << PAGE_SHIFT) | ((blk)->mem.offset & (PAGE_SIZE - 1))) #define EMU10K1_MAX_TRAM_BLOCKS_PER_CODE 16 -typedef struct { +struct snd_emu10k1_fx8010_ctl { struct list_head list; /* list link container */ unsigned int vcount; unsigned int count; /* count of GPR (1..16) */ - unsigned char gpr[32]; /* GPR number(s) */ + unsigned short gpr[32]; /* GPR number(s) */ unsigned int value[32]; unsigned int min; /* minimum range */ unsigned int max; /* maximum range */ unsigned int translation; /* translation type (EMU10K1_GPR_TRANSLATION*) */ - snd_kcontrol_t *kcontrol; -} snd_emu10k1_fx8010_ctl_t; + struct snd_kcontrol *kcontrol; +}; -typedef void (snd_fx8010_irq_handler_t)(emu10k1_t *emu, void *private_data); +typedef void (snd_fx8010_irq_handler_t)(struct snd_emu10k1 *emu, void *private_data); -typedef struct _snd_emu10k1_fx8010_irq { - struct _snd_emu10k1_fx8010_irq *next; +struct snd_emu10k1_fx8010_irq { + struct snd_emu10k1_fx8010_irq *next; snd_fx8010_irq_handler_t *handler; - unsigned char gpr_running; + unsigned short gpr_running; void *private_data; -} snd_emu10k1_fx8010_irq_t; +}; -typedef struct { +struct snd_emu10k1_fx8010_pcm { unsigned int valid: 1, opened: 1, active: 1; unsigned int channels; /* 16-bit channels count */ unsigned int tram_start; /* initial ring buffer position in TRAM (in samples) */ unsigned int buffer_size; /* count of buffered samples */ - unsigned char gpr_size; /* GPR containing size of ring buffer in samples (host) */ - unsigned char gpr_ptr; /* GPR containing current pointer in the ring buffer (host = reset, FX8010) */ - unsigned char gpr_count; /* GPR containing count of samples between two interrupts (host) */ - unsigned char gpr_tmpcount; /* GPR containing current count of samples to interrupt (host = set, FX8010) */ - unsigned char gpr_trigger; /* GPR containing trigger (activate) information (host) */ - unsigned char gpr_running; /* GPR containing info if PCM is running (FX8010) */ + unsigned short gpr_size; /* GPR containing size of ring buffer in samples (host) */ + unsigned short gpr_ptr; /* GPR containing current pointer in the ring buffer (host = reset, FX8010) */ + unsigned short gpr_count; /* GPR containing count of samples between two interrupts (host) */ + unsigned short gpr_tmpcount; /* GPR containing current count of samples to interrupt (host = set, FX8010) */ + unsigned short gpr_trigger; /* GPR containing trigger (activate) information (host) */ + unsigned short gpr_running; /* GPR containing info if PCM is running (FX8010) */ unsigned char etram[32]; /* external TRAM address & data */ - unsigned int sw_data, hw_data; - unsigned int sw_io, hw_io; - unsigned int sw_ready, hw_ready; - unsigned int appl_ptr; + struct snd_pcm_indirect pcm_rec; unsigned int tram_pos; unsigned int tram_shift; - snd_emu10k1_fx8010_irq_t *irq; -} snd_emu10k1_fx8010_pcm_t; + struct snd_emu10k1_fx8010_irq *irq; +}; -typedef struct { +struct snd_emu10k1_fx8010 { unsigned short fxbus_mask; /* used FX buses (bitmask) */ unsigned short extin_mask; /* used external inputs (bitmask) */ unsigned short extout_mask; /* used external outputs (bitmask) */ unsigned short pad1; unsigned int itram_size; /* internal TRAM size in samples */ - unsigned int etram_size; /* external TRAM size in samples */ - void *etram_pages; /* allocated pages for external TRAM */ - dma_addr_t etram_pages_dmaaddr; + struct snd_dma_buffer etram_pages; /* external TRAM pages and size */ unsigned int dbg; /* FX debugger register */ unsigned char name[128]; int gpr_size; /* size of allocated GPR controls */ int gpr_count; /* count of used kcontrols */ struct list_head gpr_ctl; /* GPR controls */ - struct semaphore lock; - snd_emu10k1_fx8010_pcm_t pcm[8]; + struct mutex lock; + struct snd_emu10k1_fx8010_pcm pcm[8]; spinlock_t irq_lock; - snd_emu10k1_fx8010_irq_t *irq_handlers; -} snd_emu10k1_fx8010_t; + struct snd_emu10k1_fx8010_irq *irq_handlers; +}; -#define emu10k1_gpr_ctl(n) list_entry(n, snd_emu10k1_fx8010_ctl_t, list) +#define emu10k1_gpr_ctl(n) list_entry(n, struct snd_emu10k1_fx8010_ctl, list) -typedef struct { - struct _snd_emu10k1 *emu; - snd_rawmidi_t *rmidi; - snd_rawmidi_substream_t *substream_input; - snd_rawmidi_substream_t *substream_output; +struct snd_emu10k1_midi { + struct snd_emu10k1 *emu; + struct snd_rawmidi *rmidi; + struct snd_rawmidi_substream *substream_input; + struct snd_rawmidi_substream *substream_output; unsigned int midi_mode; spinlock_t input_lock; spinlock_t output_lock; @@ -925,17 +1046,43 @@ int tx_enable, rx_enable; int port; int ipr_tx, ipr_rx; - void (*interrupt)(emu10k1_t *emu, unsigned int status); -} emu10k1_midi_t; + void (*interrupt)(struct snd_emu10k1 *emu, unsigned int status); +}; + +struct snd_emu_chip_details { + u32 vendor; + u32 device; + u32 subsystem; + unsigned char revision; + unsigned char emu10k1_chip; /* Original SB Live. Not SB Live 24bit. */ + unsigned char emu10k2_chip; /* Audigy 1 or Audigy 2. */ + unsigned char ca0102_chip; /* Audigy 1 or Audigy 2. Not SB Audigy 2 Value. */ + unsigned char ca0108_chip; /* Audigy 2 Value */ + unsigned char ca_cardbus_chip; /* Audigy 2 ZS Notebook */ + unsigned char ca0151_chip; /* P16V */ + unsigned char spk71; /* Has 7.1 speakers */ + unsigned char sblive51; /* SBLive! 5.1 - extout 0x11 -> center, 0x12 -> lfe */ + unsigned char spdif_bug; /* Has Spdif phasing bug */ + unsigned char ac97_chip; /* Has an AC97 chip: 1 = mandatory, 2 = optional */ + unsigned char ecard; /* APS EEPROM */ + unsigned char emu1212m; /* EMU 1212m card */ + unsigned char spi_dac; /* SPI interface for DAC */ + unsigned char i2c_adc; /* I2C interface for ADC */ + unsigned char adc_1361t; /* Use Philips 1361T ADC */ + const char *driver; + const char *name; + const char *id; /* for backward compatibility - can be NULL if not needed */ +}; -struct _snd_emu10k1 { +struct snd_emu10k1 { int irq; unsigned long port; /* I/O port number */ - struct resource *res_port; - int APS: 1, /* APS flag */ - no_ac97: 1, /* no AC'97 */ - tos_link: 1; /* tos link detected */ + unsigned int tos_link: 1, /* tos link detected */ + rear_ac97: 1, /* rear channels are on AC'97 */ + enable_ir: 1; + /* Contains profile of card capabilities */ + const struct snd_emu_chip_details *card_capabilities; unsigned int audigy; /* is Audigy? */ unsigned int revision; /* chip revision */ unsigned int serial; /* serial number */ @@ -944,12 +1091,13 @@ unsigned int ecard_ctrl; /* ecard control bits */ unsigned long dma_mask; /* PCI DMA mask */ int max_cache_pages; /* max memory size / PAGE_SIZE */ - void *silent_page; /* silent page */ - dma_addr_t silent_page_dmaaddr; - volatile u32 *ptb_pages; /* page table pages */ - dma_addr_t ptb_pages_dmaaddr; - snd_util_memhdr_t *memhdr; /* page allocation list */ - emu10k1_memblk_t *reserved_page; /* reserved page */ + struct snd_dma_buffer silent_page; /* silent page */ + struct snd_dma_buffer ptb_pages; /* page table pages */ + struct snd_dma_device p16v_dma_dev; + struct snd_dma_buffer p16v_buffer; + + struct snd_util_memhdr *memhdr; /* page allocation list */ + struct snd_emu10k1_memblk *reserved_page; /* reserved page */ struct list_head mapped_link_head; struct list_head mapped_order_link_head; @@ -959,112 +1107,168 @@ unsigned int spdif_bits[3]; /* s/pdif out setup */ - snd_emu10k1_fx8010_t fx8010; /* FX8010 info */ + struct snd_emu10k1_fx8010 fx8010; /* FX8010 info */ int gpr_base; - ac97_t *ac97; + struct snd_ac97 *ac97; struct pci_dev *pci; - snd_card_t *card; - snd_pcm_t *pcm; - snd_pcm_t *pcm_mic; - snd_pcm_t *pcm_efx; - snd_pcm_t *pcm_fx8010; + struct snd_card *card; + struct snd_pcm *pcm; + struct snd_pcm *pcm_mic; + struct snd_pcm *pcm_efx; + struct snd_pcm *pcm_multi; + struct snd_pcm *pcm_p16v; spinlock_t synth_lock; void *synth; - int (*get_synth_voice)(emu10k1_t *emu); + int (*get_synth_voice)(struct snd_emu10k1 *emu); spinlock_t reg_lock; spinlock_t emu_lock; spinlock_t voice_lock; - struct semaphore ptb_lock; - emu10k1_voice_t voices[64]; - emu10k1_pcm_mixer_t pcm_mixer[32]; - snd_kcontrol_t *ctl_send_routing; - snd_kcontrol_t *ctl_send_volume; - snd_kcontrol_t *ctl_attn; - - void (*hwvol_interrupt)(emu10k1_t *emu, unsigned int status); - void (*capture_interrupt)(emu10k1_t *emu, unsigned int status); - void (*capture_mic_interrupt)(emu10k1_t *emu, unsigned int status); - void (*capture_efx_interrupt)(emu10k1_t *emu, unsigned int status); - void (*timer_interrupt)(emu10k1_t *emu); - void (*spdif_interrupt)(emu10k1_t *emu, unsigned int status); - void (*dsp_interrupt)(emu10k1_t *emu); - - snd_pcm_substream_t *pcm_capture_substream; - snd_pcm_substream_t *pcm_capture_mic_substream; - snd_pcm_substream_t *pcm_capture_efx_substream; + struct snd_emu10k1_voice voices[NUM_G]; + struct snd_emu10k1_voice p16v_voices[4]; + struct snd_emu10k1_voice p16v_capture_voice; + int p16v_device_offset; + u32 p16v_capture_source; + u32 p16v_capture_channel; + struct snd_emu10k1_pcm_mixer pcm_mixer[32]; + struct snd_emu10k1_pcm_mixer efx_pcm_mixer[NUM_EFX_PLAYBACK]; + struct snd_kcontrol *ctl_send_routing; + struct snd_kcontrol *ctl_send_volume; + struct snd_kcontrol *ctl_attn; + struct snd_kcontrol *ctl_efx_send_routing; + struct snd_kcontrol *ctl_efx_send_volume; + struct snd_kcontrol *ctl_efx_attn; + + void (*hwvol_interrupt)(struct snd_emu10k1 *emu, unsigned int status); + void (*capture_interrupt)(struct snd_emu10k1 *emu, unsigned int status); + void (*capture_mic_interrupt)(struct snd_emu10k1 *emu, unsigned int status); + void (*capture_efx_interrupt)(struct snd_emu10k1 *emu, unsigned int status); + void (*spdif_interrupt)(struct snd_emu10k1 *emu, unsigned int status); + void (*dsp_interrupt)(struct snd_emu10k1 *emu); + + struct snd_pcm_substream *pcm_capture_substream; + struct snd_pcm_substream *pcm_capture_mic_substream; + struct snd_pcm_substream *pcm_capture_efx_substream; + struct snd_pcm_substream *pcm_playback_efx_substream; + + struct snd_timer *timer; - emu10k1_midi_t midi; - emu10k1_midi_t midi2; /* for audigy */ + struct snd_emu10k1_midi midi; + struct snd_emu10k1_midi midi2; /* for audigy */ unsigned int efx_voices_mask[2]; + unsigned int next_free_voice; + +#ifdef CONFIG_PM + unsigned int *saved_ptr; + unsigned int *saved_gpr; + unsigned int *tram_val_saved; + unsigned int *tram_addr_saved; + unsigned int *saved_icode; + unsigned int *p16v_saved; + unsigned int saved_a_iocfg, saved_hcfg; +#endif + }; -int snd_emu10k1_create(snd_card_t * card, +int snd_emu10k1_create(struct snd_card *card, struct pci_dev *pci, unsigned short extin_mask, unsigned short extout_mask, long max_cache_bytes, int enable_ir, - emu10k1_t ** remu); + uint subsystem, + struct snd_emu10k1 ** remu); -int snd_emu10k1_pcm(emu10k1_t * emu, int device, snd_pcm_t ** rpcm); -int snd_emu10k1_pcm_mic(emu10k1_t * emu, int device, snd_pcm_t ** rpcm); -int snd_emu10k1_pcm_efx(emu10k1_t * emu, int device, snd_pcm_t ** rpcm); -int snd_emu10k1_fx8010_pcm(emu10k1_t * emu, int device, snd_pcm_t ** rpcm); -int snd_emu10k1_mixer(emu10k1_t * emu); -int snd_emu10k1_fx8010_new(emu10k1_t *emu, int device, snd_hwdep_t ** rhwdep); +int snd_emu10k1_pcm(struct snd_emu10k1 * emu, int device, struct snd_pcm ** rpcm); +int snd_emu10k1_pcm_mic(struct snd_emu10k1 * emu, int device, struct snd_pcm ** rpcm); +int snd_emu10k1_pcm_efx(struct snd_emu10k1 * emu, int device, struct snd_pcm ** rpcm); +int snd_p16v_pcm(struct snd_emu10k1 * emu, int device, struct snd_pcm ** rpcm); +int snd_p16v_free(struct snd_emu10k1 * emu); +int snd_p16v_mixer(struct snd_emu10k1 * emu); +int snd_emu10k1_pcm_multi(struct snd_emu10k1 * emu, int device, struct snd_pcm ** rpcm); +int snd_emu10k1_fx8010_pcm(struct snd_emu10k1 * emu, int device, struct snd_pcm ** rpcm); +int snd_emu10k1_mixer(struct snd_emu10k1 * emu, int pcm_device, int multi_device); +int snd_emu10k1_timer(struct snd_emu10k1 * emu, int device); +int snd_emu10k1_fx8010_new(struct snd_emu10k1 *emu, int device, struct snd_hwdep ** rhwdep); irqreturn_t snd_emu10k1_interrupt(int irq, void *dev_id, struct pt_regs *regs); -/* initialization */ -void snd_emu10k1_voice_init(emu10k1_t * emu, int voice); -int snd_emu10k1_init_efx(emu10k1_t *emu); -void snd_emu10k1_free_efx(emu10k1_t *emu); -int snd_emu10k1_fx8010_tram_setup(emu10k1_t *emu, u32 size); +void snd_emu10k1_voice_init(struct snd_emu10k1 * emu, int voice); +int snd_emu10k1_init_efx(struct snd_emu10k1 *emu); +void snd_emu10k1_free_efx(struct snd_emu10k1 *emu); +int snd_emu10k1_fx8010_tram_setup(struct snd_emu10k1 *emu, u32 size); +int snd_emu10k1_done(struct snd_emu10k1 * emu); /* I/O functions */ -unsigned int snd_emu10k1_ptr_read(emu10k1_t * emu, unsigned int reg, unsigned int chn); -void snd_emu10k1_ptr_write(emu10k1_t *emu, unsigned int reg, unsigned int chn, unsigned int data); -void snd_emu10k1_efx_write(emu10k1_t *emu, unsigned int pc, unsigned int data); -unsigned int snd_emu10k1_efx_read(emu10k1_t *emu, unsigned int pc); -void snd_emu10k1_intr_enable(emu10k1_t *emu, unsigned int intrenb); -void snd_emu10k1_intr_disable(emu10k1_t *emu, unsigned int intrenb); -void snd_emu10k1_voice_intr_enable(emu10k1_t *emu, unsigned int voicenum); -void snd_emu10k1_voice_intr_disable(emu10k1_t *emu, unsigned int voicenum); -void snd_emu10k1_voice_intr_ack(emu10k1_t *emu, unsigned int voicenum); -void snd_emu10k1_voice_set_loop_stop(emu10k1_t *emu, unsigned int voicenum); -void snd_emu10k1_voice_clear_loop_stop(emu10k1_t *emu, unsigned int voicenum); -void snd_emu10k1_wait(emu10k1_t *emu, unsigned int wait); -static inline unsigned int snd_emu10k1_wc(emu10k1_t *emu) { return (inl(emu->port + WC) >> 6) & 0xfffff; } -unsigned short snd_emu10k1_ac97_read(ac97_t *ac97, unsigned short reg); -void snd_emu10k1_ac97_write(ac97_t *ac97, unsigned short reg, unsigned short data); +unsigned int snd_emu10k1_ptr_read(struct snd_emu10k1 * emu, unsigned int reg, unsigned int chn); +void snd_emu10k1_ptr_write(struct snd_emu10k1 *emu, unsigned int reg, unsigned int chn, unsigned int data); +unsigned int snd_emu10k1_ptr20_read(struct snd_emu10k1 * emu, unsigned int reg, unsigned int chn); +void snd_emu10k1_ptr20_write(struct snd_emu10k1 *emu, unsigned int reg, unsigned int chn, unsigned int data); +int snd_emu10k1_spi_write(struct snd_emu10k1 * emu, unsigned int data); +unsigned int snd_emu10k1_efx_read(struct snd_emu10k1 *emu, unsigned int pc); +void snd_emu10k1_intr_enable(struct snd_emu10k1 *emu, unsigned int intrenb); +void snd_emu10k1_intr_disable(struct snd_emu10k1 *emu, unsigned int intrenb); +void snd_emu10k1_voice_intr_enable(struct snd_emu10k1 *emu, unsigned int voicenum); +void snd_emu10k1_voice_intr_disable(struct snd_emu10k1 *emu, unsigned int voicenum); +void snd_emu10k1_voice_intr_ack(struct snd_emu10k1 *emu, unsigned int voicenum); +void snd_emu10k1_voice_half_loop_intr_enable(struct snd_emu10k1 *emu, unsigned int voicenum); +void snd_emu10k1_voice_half_loop_intr_disable(struct snd_emu10k1 *emu, unsigned int voicenum); +void snd_emu10k1_voice_half_loop_intr_ack(struct snd_emu10k1 *emu, unsigned int voicenum); +void snd_emu10k1_voice_set_loop_stop(struct snd_emu10k1 *emu, unsigned int voicenum); +void snd_emu10k1_voice_clear_loop_stop(struct snd_emu10k1 *emu, unsigned int voicenum); +void snd_emu10k1_wait(struct snd_emu10k1 *emu, unsigned int wait); +static inline unsigned int snd_emu10k1_wc(struct snd_emu10k1 *emu) { return (inl(emu->port + WC) >> 6) & 0xfffff; } +unsigned short snd_emu10k1_ac97_read(struct snd_ac97 *ac97, unsigned short reg); +void snd_emu10k1_ac97_write(struct snd_ac97 *ac97, unsigned short reg, unsigned short data); unsigned int snd_emu10k1_rate_to_pitch(unsigned int rate); -unsigned char snd_emu10k1_sum_vol_attn(unsigned int value); + +#ifdef CONFIG_PM +void snd_emu10k1_suspend_regs(struct snd_emu10k1 *emu); +void snd_emu10k1_resume_init(struct snd_emu10k1 *emu); +void snd_emu10k1_resume_regs(struct snd_emu10k1 *emu); +int snd_emu10k1_efx_alloc_pm_buffer(struct snd_emu10k1 *emu); +void snd_emu10k1_efx_free_pm_buffer(struct snd_emu10k1 *emu); +void snd_emu10k1_efx_suspend(struct snd_emu10k1 *emu); +void snd_emu10k1_efx_resume(struct snd_emu10k1 *emu); +int snd_p16v_alloc_pm_buffer(struct snd_emu10k1 *emu); +void snd_p16v_free_pm_buffer(struct snd_emu10k1 *emu); +void snd_p16v_suspend(struct snd_emu10k1 *emu); +void snd_p16v_resume(struct snd_emu10k1 *emu); +#endif /* memory allocation */ -snd_util_memblk_t *snd_emu10k1_alloc_pages(emu10k1_t *emu, snd_pcm_substream_t *substream); -int snd_emu10k1_free_pages(emu10k1_t *emu, snd_util_memblk_t *blk); -snd_util_memblk_t *snd_emu10k1_synth_alloc(emu10k1_t *emu, unsigned int size); -int snd_emu10k1_synth_free(emu10k1_t *emu, snd_util_memblk_t *blk); -int snd_emu10k1_synth_bzero(emu10k1_t *emu, snd_util_memblk_t *blk, int offset, int size); -int snd_emu10k1_synth_copy_from_user(emu10k1_t *emu, snd_util_memblk_t *blk, int offset, const char *data, int size); -int snd_emu10k1_memblk_map(emu10k1_t *emu, emu10k1_memblk_t *blk); +struct snd_util_memblk *snd_emu10k1_alloc_pages(struct snd_emu10k1 *emu, struct snd_pcm_substream *substream); +int snd_emu10k1_free_pages(struct snd_emu10k1 *emu, struct snd_util_memblk *blk); +struct snd_util_memblk *snd_emu10k1_synth_alloc(struct snd_emu10k1 *emu, unsigned int size); +int snd_emu10k1_synth_free(struct snd_emu10k1 *emu, struct snd_util_memblk *blk); +int snd_emu10k1_synth_bzero(struct snd_emu10k1 *emu, struct snd_util_memblk *blk, int offset, int size); +int snd_emu10k1_synth_copy_from_user(struct snd_emu10k1 *emu, struct snd_util_memblk *blk, int offset, const char __user *data, int size); +int snd_emu10k1_memblk_map(struct snd_emu10k1 *emu, struct snd_emu10k1_memblk *blk); /* voice allocation */ -int snd_emu10k1_voice_alloc(emu10k1_t *emu, emu10k1_voice_type_t type, int pair, emu10k1_voice_t **rvoice); -int snd_emu10k1_voice_free(emu10k1_t *emu, emu10k1_voice_t *pvoice); +int snd_emu10k1_voice_alloc(struct snd_emu10k1 *emu, int type, int pair, struct snd_emu10k1_voice **rvoice); +int snd_emu10k1_voice_free(struct snd_emu10k1 *emu, struct snd_emu10k1_voice *pvoice); /* MIDI uart */ -int snd_emu10k1_midi(emu10k1_t * emu); -int snd_emu10k1_audigy_midi(emu10k1_t * emu); +int snd_emu10k1_midi(struct snd_emu10k1 * emu); +int snd_emu10k1_audigy_midi(struct snd_emu10k1 * emu); /* proc interface */ -int snd_emu10k1_proc_init(emu10k1_t * emu); +int snd_emu10k1_proc_init(struct snd_emu10k1 * emu); + +/* fx8010 irq handler */ +int snd_emu10k1_fx8010_register_irq_handler(struct snd_emu10k1 *emu, + snd_fx8010_irq_handler_t *handler, + unsigned char gpr_running, + void *private_data, + struct snd_emu10k1_fx8010_irq **r_irq); +int snd_emu10k1_fx8010_unregister_irq_handler(struct snd_emu10k1 *emu, + struct snd_emu10k1_fx8010_irq *irq); #endif /* __KERNEL__ */ @@ -1098,7 +1302,10 @@ /* GPRs */ #define FXBUS(x) (0x00 + (x)) /* x = 0x00 - 0x0f */ #define EXTIN(x) (0x10 + (x)) /* x = 0x00 - 0x0f */ -#define EXTOUT(x) (0x20 + (x)) /* x = 0x00 - 0x0f */ +#define EXTOUT(x) (0x20 + (x)) /* x = 0x00 - 0x0f physical outs -> FXWC low 16 bits */ +#define FXBUS2(x) (0x30 + (x)) /* x = 0x00 - 0x0f copies of fx buses for capture -> FXWC high 16 bits */ + /* NB: 0x31 and 0x32 are shared with Center/LFE on SB live 5.1 */ + #define C_00000000 0x40 #define C_00000001 0x41 #define C_00000002 0x42 @@ -1133,9 +1340,20 @@ #define ITRAM_ADDR(x) (TANKMEMADDRREGBASE + 0x00 + (x)) /* x = 0x00 - 0x7f */ #define ETRAM_ADDR(x) (TANKMEMADDRREGBASE + 0x80 + (x)) /* x = 0x00 - 0x1f */ -#define A_FXBUS(x) (0x00 + (x)) /* x = 0x00 - 0x3f? */ -#define A_EXTIN(x) (0x40 + (x)) /* x = 0x00 - 0x1f? */ -#define A_EXTOUT(x) (0x60 + (x)) /* x = 0x00 - 0x1f? */ +#define A_ITRAM_DATA(x) (TANKMEMDATAREGBASE + 0x00 + (x)) /* x = 0x00 - 0xbf */ +#define A_ETRAM_DATA(x) (TANKMEMDATAREGBASE + 0xc0 + (x)) /* x = 0x00 - 0x3f */ +#define A_ITRAM_ADDR(x) (TANKMEMADDRREGBASE + 0x00 + (x)) /* x = 0x00 - 0xbf */ +#define A_ETRAM_ADDR(x) (TANKMEMADDRREGBASE + 0xc0 + (x)) /* x = 0x00 - 0x3f */ +#define A_ITRAM_CTL(x) (A_TANKMEMCTLREGBASE + 0x00 + (x)) /* x = 0x00 - 0xbf */ +#define A_ETRAM_CTL(x) (A_TANKMEMCTLREGBASE + 0xc0 + (x)) /* x = 0x00 - 0x3f */ + +#define A_FXBUS(x) (0x00 + (x)) /* x = 0x00 - 0x3f FX buses */ +#define A_EXTIN(x) (0x40 + (x)) /* x = 0x00 - 0x0f physical ins */ +#define A_P16VIN(x) (0x50 + (x)) /* x = 0x00 - 0x0f p16v ins (A2 only) "EMU32 inputs" */ +#define A_EXTOUT(x) (0x60 + (x)) /* x = 0x00 - 0x1f physical outs -> A_FXWC1 0x79-7f unknown */ +#define A_FXBUS2(x) (0x80 + (x)) /* x = 0x00 - 0x1f extra outs used for EFX capture -> A_FXWC2 */ +#define A_EMU32OUTH(x) (0xa0 + (x)) /* x = 0x00 - 0x0f "EMU32_OUT_10 - _1F" - ??? */ +#define A_EMU32OUTL(x) (0xb0 + (x)) /* x = 0x00 - 0x0f "EMU32_OUT_1 - _F" - ??? */ #define A_GPR(x) (A_FXGPREGBASE + (x)) /* cc_reg constants */ @@ -1159,6 +1377,8 @@ #define FXBUS_PCM_RIGHT_FRONT 0x09 #define FXBUS_MIDI_REVERB 0x0c #define FXBUS_MIDI_CHORUS 0x0d +#define FXBUS_PCM_LEFT_SIDE 0x0e +#define FXBUS_PCM_RIGHT_SIDE 0x0f #define FXBUS_PT_LEFT 0x14 #define FXBUS_PT_RIGHT 0x15 @@ -1183,8 +1403,8 @@ #define EXTOUT_AC97_R 0x01 /* AC'97 playback channel - right */ #define EXTOUT_TOSLINK_L 0x02 /* LiveDrive - TOSLink Optical - left */ #define EXTOUT_TOSLINK_R 0x03 /* LiveDrive - TOSLink Optical - right */ -#define EXTOUT_CENTER 0x04 /* SB Live 5.1 - center */ -#define EXTOUT_LFE 0x05 /* SB Live 5.1 - LFE */ +#define EXTOUT_AC97_CENTER 0x04 /* SB Live 5.1 - center */ +#define EXTOUT_AC97_LFE 0x05 /* SB Live 5.1 - LFE */ #define EXTOUT_HEADPHONE_L 0x06 /* LiveDrive - Headphone - left */ #define EXTOUT_HEADPHONE_R 0x07 /* LiveDrive - Headphone - right */ #define EXTOUT_REAR_L 0x08 /* Rear channel - left */ @@ -1192,6 +1412,8 @@ #define EXTOUT_ADC_CAP_L 0x0a /* ADC Capture buffer - left */ #define EXTOUT_ADC_CAP_R 0x0b /* ADC Capture buffer - right */ #define EXTOUT_MIC_CAP 0x0c /* MIC Capture buffer */ +#define EXTOUT_AC97_REAR_L 0x0d /* SB Live 5.1 (c) 2003 - Rear Left */ +#define EXTOUT_AC97_REAR_R 0x0e /* SB Live 5.1 (c) 2003 - Rear Right */ #define EXTOUT_ACENTER 0x11 /* Analog Center */ #define EXTOUT_ALFE 0x12 /* Analog LFE */ @@ -1222,8 +1444,8 @@ #define A_EXTOUT_AFRONT_R 0x09 /* right */ #define A_EXTOUT_ACENTER 0x0a /* analog center */ #define A_EXTOUT_ALFE 0x0b /* analog LFE */ -/* 0x0c ?? */ -/* 0x0d ?? */ +#define A_EXTOUT_ASIDE_L 0x0c /* analog side left - Audigy 2 ZS */ +#define A_EXTOUT_ASIDE_R 0x0d /* right - Audigy 2 ZS */ #define A_EXTOUT_AREAR_L 0x0e /* analog rear left */ #define A_EXTOUT_AREAR_R 0x0f /* right */ #define A_EXTOUT_AC97_L 0x10 /* AC97 left (front) */ @@ -1257,8 +1479,11 @@ #define A_C_00100000 0xd5 #define A_GPR_ACCU 0xd6 /* ACCUM, accumulator */ #define A_GPR_COND 0xd7 /* CCR, condition register */ -/* 0xd8 = noise1 */ -/* 0xd9 = noise2 */ +#define A_GPR_NOISE0 0xd8 /* noise source */ +#define A_GPR_NOISE1 0xd9 /* noise source */ +#define A_GPR_IRQ 0xda /* IRQ register */ +#define A_GPR_DBAC 0xdb /* TRAM Delay Base Address Counter - internal */ +#define A_GPR_DBACE 0xde /* TRAM Delay Base Address Counter - external */ /* definitions for debug register */ #define EMU10K1_DBG_ZC 0x80000000 /* zero tram counter */ @@ -1278,15 +1503,14 @@ #define TANKMEMADDRREG_READ 0x00100000 /* Read from tank memory */ #endif -typedef struct { - unsigned int card; /* card type */ +struct snd_emu10k1_fx8010_info { unsigned int internal_tram_size; /* in samples */ unsigned int external_tram_size; /* in samples */ char fxbus_names[16][32]; /* names of FXBUSes */ char extin_names[16][32]; /* names of external inputs */ char extout_names[32][32]; /* names of external outputs */ unsigned int gpr_controls; /* count of GPR controls */ -} emu10k1_fx8010_info_t; +}; #define EMU10K1_GPR_TRANSLATION_NONE 0 #define EMU10K1_GPR_TRANSLATION_TABLE100 1 @@ -1294,77 +1518,84 @@ #define EMU10K1_GPR_TRANSLATION_TREBLE 3 #define EMU10K1_GPR_TRANSLATION_ONOFF 4 -typedef struct { - snd_ctl_elem_id_t id; /* full control ID definition */ +struct snd_emu10k1_fx8010_control_gpr { + struct snd_ctl_elem_id id; /* full control ID definition */ unsigned int vcount; /* visible count */ unsigned int count; /* count of GPR (1..16) */ - unsigned char gpr[32]; /* GPR number(s) */ + unsigned short gpr[32]; /* GPR number(s) */ unsigned int value[32]; /* initial values */ unsigned int min; /* minimum range */ unsigned int max; /* maximum range */ unsigned int translation; /* translation type (EMU10K1_GPR_TRANSLATION*) */ -} emu10k1_fx8010_control_gpr_t; +}; -typedef struct { +struct snd_emu10k1_fx8010_code { char name[128]; - unsigned long gpr_valid[0x100/(sizeof(unsigned long)*8)]; /* bitmask of valid initializers */ - unsigned int gpr_map[0x100]; /* initializers */ + DECLARE_BITMAP(gpr_valid, 0x200); /* bitmask of valid initializers */ + u_int32_t __user *gpr_map; /* initializers */ unsigned int gpr_add_control_count; /* count of GPR controls to add/replace */ - emu10k1_fx8010_control_gpr_t *gpr_add_controls; /* GPR controls to add/replace */ + struct snd_emu10k1_fx8010_control_gpr __user *gpr_add_controls; /* GPR controls to add/replace */ unsigned int gpr_del_control_count; /* count of GPR controls to remove */ - snd_ctl_elem_id_t *gpr_del_controls; /* IDs of GPR controls to remove */ + struct snd_ctl_elem_id __user *gpr_del_controls; /* IDs of GPR controls to remove */ unsigned int gpr_list_control_count; /* count of GPR controls to list */ unsigned int gpr_list_control_total; /* total count of GPR controls */ - emu10k1_fx8010_control_gpr_t *gpr_list_controls; /* listed GPR controls */ + struct snd_emu10k1_fx8010_control_gpr __user *gpr_list_controls; /* listed GPR controls */ - unsigned long tram_valid[0xa0/(sizeof(unsigned long)*8)]; /* bitmask of valid initializers */ - unsigned int tram_data_map[0xa0]; /* data initializers */ - unsigned int tram_addr_map[0xa0]; /* map initializers */ + DECLARE_BITMAP(tram_valid, 0x100); /* bitmask of valid initializers */ + u_int32_t __user *tram_data_map; /* data initializers */ + u_int32_t __user *tram_addr_map; /* map initializers */ - unsigned long code_valid[512/(sizeof(unsigned long)*8)]; /* bitmask of valid instructions */ - unsigned int code[512][2]; /* one instruction - 64 bits */ -} emu10k1_fx8010_code_t; + DECLARE_BITMAP(code_valid, 1024); /* bitmask of valid instructions */ + u_int32_t __user *code; /* one instruction - 64 bits */ +}; -typedef struct { +struct snd_emu10k1_fx8010_tram { unsigned int address; /* 31.bit == 1 -> external TRAM */ unsigned int size; /* size in samples (4 bytes) */ unsigned int *samples; /* pointer to samples (20-bit) */ /* NULL->clear memory */ -} emu10k1_fx8010_tram_t; +}; -typedef struct { +struct snd_emu10k1_fx8010_pcm_rec { unsigned int substream; /* substream number */ unsigned int res1; /* reserved */ unsigned int channels; /* 16-bit channels count, zero = remove this substream */ unsigned int tram_start; /* ring buffer position in TRAM (in samples) */ unsigned int buffer_size; /* count of buffered samples */ - unsigned char gpr_size; /* GPR containing size of ringbuffer in samples (host) */ - unsigned char gpr_ptr; /* GPR containing current pointer in the ring buffer (host = reset, FX8010) */ - unsigned char gpr_count; /* GPR containing count of samples between two interrupts (host) */ - unsigned char gpr_tmpcount; /* GPR containing current count of samples to interrupt (host = set, FX8010) */ - unsigned char gpr_trigger; /* GPR containing trigger (activate) information (host) */ - unsigned char gpr_running; /* GPR containing info if PCM is running (FX8010) */ + unsigned short gpr_size; /* GPR containing size of ringbuffer in samples (host) */ + unsigned short gpr_ptr; /* GPR containing current pointer in the ring buffer (host = reset, FX8010) */ + unsigned short gpr_count; /* GPR containing count of samples between two interrupts (host) */ + unsigned short gpr_tmpcount; /* GPR containing current count of samples to interrupt (host = set, FX8010) */ + unsigned short gpr_trigger; /* GPR containing trigger (activate) information (host) */ + unsigned short gpr_running; /* GPR containing info if PCM is running (FX8010) */ unsigned char pad; /* reserved */ unsigned char etram[32]; /* external TRAM address & data (one per channel) */ unsigned int res2; /* reserved */ -} emu10k1_fx8010_pcm_t; +}; -#define SNDRV_EMU10K1_IOCTL_INFO _IOR ('H', 0x10, emu10k1_fx8010_info_t) -#define SNDRV_EMU10K1_IOCTL_CODE_POKE _IOW ('H', 0x11, emu10k1_fx8010_code_t) -#define SNDRV_EMU10K1_IOCTL_CODE_PEEK _IOWR('H', 0x12, emu10k1_fx8010_code_t) +#define SNDRV_EMU10K1_IOCTL_INFO _IOR ('H', 0x10, struct snd_emu10k1_fx8010_info) +#define SNDRV_EMU10K1_IOCTL_CODE_POKE _IOW ('H', 0x11, struct snd_emu10k1_fx8010_code) +#define SNDRV_EMU10K1_IOCTL_CODE_PEEK _IOWR('H', 0x12, struct snd_emu10k1_fx8010_code) #define SNDRV_EMU10K1_IOCTL_TRAM_SETUP _IOW ('H', 0x20, int) -#define SNDRV_EMU10K1_IOCTL_TRAM_POKE _IOW ('H', 0x21, emu10k1_fx8010_tram_t) -#define SNDRV_EMU10K1_IOCTL_TRAM_PEEK _IOWR('H', 0x22, emu10k1_fx8010_tram_t) -#define SNDRV_EMU10K1_IOCTL_PCM_POKE _IOW ('H', 0x30, emu10k1_fx8010_pcm_t) -#define SNDRV_EMU10K1_IOCTL_PCM_PEEK _IOWR('H', 0x31, emu10k1_fx8010_pcm_t) +#define SNDRV_EMU10K1_IOCTL_TRAM_POKE _IOW ('H', 0x21, struct snd_emu10k1_fx8010_tram) +#define SNDRV_EMU10K1_IOCTL_TRAM_PEEK _IOWR('H', 0x22, struct snd_emu10k1_fx8010_tram) +#define SNDRV_EMU10K1_IOCTL_PCM_POKE _IOW ('H', 0x30, struct snd_emu10k1_fx8010_pcm_rec) +#define SNDRV_EMU10K1_IOCTL_PCM_PEEK _IOWR('H', 0x31, struct snd_emu10k1_fx8010_pcm_rec) #define SNDRV_EMU10K1_IOCTL_STOP _IO ('H', 0x80) #define SNDRV_EMU10K1_IOCTL_CONTINUE _IO ('H', 0x81) #define SNDRV_EMU10K1_IOCTL_ZERO_TRAM_COUNTER _IO ('H', 0x82) #define SNDRV_EMU10K1_IOCTL_SINGLE_STEP _IOW ('H', 0x83, int) #define SNDRV_EMU10K1_IOCTL_DBG_READ _IOR ('H', 0x84, int) + +/* typedefs for compatibility to user-space */ +typedef struct snd_emu10k1_fx8010_info emu10k1_fx8010_info_t; +typedef struct snd_emu10k1_fx8010_control_gpr emu10k1_fx8010_control_gpr_t; +typedef struct snd_emu10k1_fx8010_code emu10k1_fx8010_code_t; +typedef struct snd_emu10k1_fx8010_tram emu10k1_fx8010_tram_t; +typedef struct snd_emu10k1_fx8010_pcm_rec emu10k1_fx8010_pcm_t; #endif /* __SOUND_EMU10K1_H */ --- sys/gnu/dev/sound/pci/emu10k1.h.orig Fri Jan 9 14:38:11 2004 +++ sys/gnu/dev/sound/pci/emu10k1.h Thu Jan 1 07:30:00 1970 @@ -1,740 +0,0 @@ -/* - ********************************************************************** - * emu10k1.h, derived from 8010.h - * Copyright 1999-2001 Creative Labs, Inc. - * - ********************************************************************** - * - * Date Author Summary of changes - * ---- ------ ------------------ - * October 20, 1999 Bertrand Lee base code release - * November 2, 1999 Alan Cox Cleaned of 8bit chars, DOS - * line endings - * December 8, 1999 Jon Taylor Added lots of new register info - * May 16, 2001 Daniel Bertrand Added unofficial DBG register info - * Oct-Nov 2001 D.B. Added unofficial Audigy registers - * - ********************************************************************** - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; either version 2 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program; if not, write to the Free - * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, - * USA. - * - * - ********************************************************************** - * $FreeBSD: src/sys/gnu/dev/sound/pci/emu10k1.h,v 1.7 2004/01/09 06:38:11 obrien Exp $ - */ - - -#ifndef _8010_H -#define _8010_H - - /* -#include - */ - -// Driver version: -#define MAJOR_VER 0 -#define MINOR_VER 20 -#define DRIVER_VERSION "0.20a" - - -// Audigy specify registers are prefixed with 'A_' - -/************************************************************************************************/ -/* PCI function 0 registers, address = + PCIBASE0 */ -/************************************************************************************************/ - -#define PTR 0x00 /* Indexed register set pointer register */ - /* NOTE: The CHANNELNUM and ADDRESS words can */ - /* be modified independently of each other. */ -#define PTR_CHANNELNUM_MASK 0x0000003f /* For each per-channel register, indicates the */ - /* channel number of the register to be */ - /* accessed. For non per-channel registers the */ - /* value should be set to zero. */ -#define PTR_ADDRESS_MASK 0x07ff0000 /* Register index */ - -#define DATA 0x04 /* Indexed register set data register */ - -#define IPR 0x08 /* Global interrupt pending register */ - /* Clear pending interrupts by writing a 1 to */ - /* the relevant bits and zero to the other bits */ - -/* The next two interrupts are for the midi port on the Audigy Drive (A_MPU1) */ -#define A_IPR_MIDITRANSBUFEMPTY2 0x10000000 /* MIDI UART transmit buffer empty */ -#define A_IPR_MIDIRECVBUFEMPTY2 0x08000000 /* MIDI UART receive buffer empty */ - -#define IPR_SAMPLERATETRACKER 0x01000000 /* Sample rate tracker lock status change */ -#define IPR_FXDSP 0x00800000 /* Enable FX DSP interrupts */ -#define IPR_FORCEINT 0x00400000 /* Force Sound Blaster interrupt */ -#define IPR_PCIERROR 0x00200000 /* PCI bus error */ -#define IPR_VOLINCR 0x00100000 /* Volume increment button pressed */ -#define IPR_VOLDECR 0x00080000 /* Volume decrement button pressed */ -#define IPR_MUTE 0x00040000 /* Mute button pressed */ -#define IPR_MICBUFFULL 0x00020000 /* Microphone buffer full */ -#define IPR_MICBUFHALFFULL 0x00010000 /* Microphone buffer half full */ -#define IPR_ADCBUFFULL 0x00008000 /* ADC buffer full */ -#define IPR_ADCBUFHALFFULL 0x00004000 /* ADC buffer half full */ -#define IPR_EFXBUFFULL 0x00002000 /* Effects buffer full */ -#define IPR_EFXBUFHALFFULL 0x00001000 /* Effects buffer half full */ -#define IPR_GPSPDIFSTATUSCHANGE 0x00000800 /* GPSPDIF channel status change */ -#define IPR_CDROMSTATUSCHANGE 0x00000400 /* CD-ROM channel status change */ -#define IPR_INTERVALTIMER 0x00000200 /* Interval timer terminal count */ -#define IPR_MIDITRANSBUFEMPTY 0x00000100 /* MIDI UART transmit buffer empty */ -#define IPR_MIDIRECVBUFEMPTY 0x00000080 /* MIDI UART receive buffer empty */ -#define IPR_CHANNELLOOP 0x00000040 /* One or more channel loop interrupts pending */ -#define IPR_CHANNELNUMBERMASK 0x0000003f /* When IPR_CHANNELLOOP is set, indicates the */ - /* Highest set channel in CLIPL or CLIPH. When */ - /* IP is written with CL set, the bit in CLIPL */ - /* or CLIPH corresponding to the CIN value */ - /* written will be cleared. */ -#define A_IPR_MIDITRANSBUFEMPTY1 IPR_MIDITRANSBUFEMPTY /* MIDI UART transmit buffer empty */ -#define A_IPR_MIDIRECVBUFEMPTY1 IPR_MIDIRECVBUFEMPTY /* MIDI UART receive buffer empty */ - - - -#define INTE 0x0c /* Interrupt enable register */ -#define INTE_VIRTUALSB_MASK 0xc0000000 /* Virtual Soundblaster I/O port capture */ -#define INTE_VIRTUALSB_220 0x00000000 /* Capture at I/O base address 0x220-0x22f */ -#define INTE_VIRTUALSB_240 0x40000000 /* Capture at I/O base address 0x240 */ -#define INTE_VIRTUALSB_260 0x80000000 /* Capture at I/O base address 0x260 */ -#define INTE_VIRTUALSB_280 0xc0000000 /* Capture at I/O base address 0x280 */ -#define INTE_VIRTUALMPU_MASK 0x30000000 /* Virtual MPU I/O port capture */ -#define INTE_VIRTUALMPU_300 0x00000000 /* Capture at I/O base address 0x300-0x301 */ -#define INTE_VIRTUALMPU_310 0x10000000 /* Capture at I/O base address 0x310 */ -#define INTE_VIRTUALMPU_320 0x20000000 /* Capture at I/O base address 0x320 */ -#define INTE_VIRTUALMPU_330 0x30000000 /* Capture at I/O base address 0x330 */ -#define INTE_MASTERDMAENABLE 0x08000000 /* Master DMA emulation at 0x000-0x00f */ -#define INTE_SLAVEDMAENABLE 0x04000000 /* Slave DMA emulation at 0x0c0-0x0df */ -#define INTE_MASTERPICENABLE 0x02000000 /* Master PIC emulation at 0x020-0x021 */ -#define INTE_SLAVEPICENABLE 0x01000000 /* Slave PIC emulation at 0x0a0-0x0a1 */ -#define INTE_VSBENABLE 0x00800000 /* Enable virtual Soundblaster */ -#define INTE_ADLIBENABLE 0x00400000 /* Enable AdLib emulation at 0x388-0x38b */ -#define INTE_MPUENABLE 0x00200000 /* Enable virtual MPU */ -#define INTE_FORCEINT 0x00100000 /* Continuously assert INTAN */ - -#define INTE_MRHANDENABLE 0x00080000 /* Enable the "Mr. Hand" logic */ - /* NOTE: There is no reason to use this under */ - /* Linux, and it will cause odd hardware */ - /* behavior and possibly random segfaults and */ - /* lockups if enabled. */ - -/* The next two interrupts are for the midi port on the Audigy Drive (A_MPU1) */ -#define A_INTE_MIDITXENABLE2 0x00020000 /* Enable MIDI transmit-buffer-empty interrupts */ -#define A_INTE_MIDIRXENABLE2 0x00010000 /* Enable MIDI receive-buffer-empty interrupts */ - - -#define INTE_SAMPLERATETRACKER 0x00002000 /* Enable sample rate tracker interrupts */ - /* NOTE: This bit must always be enabled */ -#define INTE_FXDSPENABLE 0x00001000 /* Enable FX DSP interrupts */ -#define INTE_PCIERRORENABLE 0x00000800 /* Enable PCI bus error interrupts */ -#define INTE_VOLINCRENABLE 0x00000400 /* Enable volume increment button interrupts */ -#define INTE_VOLDECRENABLE 0x00000200 /* Enable volume decrement button interrupts */ -#define INTE_MUTEENABLE 0x00000100 /* Enable mute button interrupts */ -#define INTE_MICBUFENABLE 0x00000080 /* Enable microphone buffer interrupts */ -#define INTE_ADCBUFENABLE 0x00000040 /* Enable ADC buffer interrupts */ -#define INTE_EFXBUFENABLE 0x00000020 /* Enable Effects buffer interrupts */ -#define INTE_GPSPDIFENABLE 0x00000010 /* Enable GPSPDIF status interrupts */ -#define INTE_CDSPDIFENABLE 0x00000008 /* Enable CDSPDIF status interrupts */ -#define INTE_INTERVALTIMERENB 0x00000004 /* Enable interval timer interrupts */ -#define INTE_MIDITXENABLE 0x00000002 /* Enable MIDI transmit-buffer-empty interrupts */ -#define INTE_MIDIRXENABLE 0x00000001 /* Enable MIDI receive-buffer-empty interrupts */ - -/* The next two interrupts are for the midi port on the Audigy (A_MPU2) */ -#define A_INTE_MIDITXENABLE1 INTE_MIDITXENABLE -#define A_INTE_MIDIRXENABLE1 INTE_MIDIRXENABLE - -#define WC 0x10 /* Wall Clock register */ -#define WC_SAMPLECOUNTER_MASK 0x03FFFFC0 /* Sample periods elapsed since reset */ -#define WC_SAMPLECOUNTER 0x14060010 -#define WC_CURRENTCHANNEL 0x0000003F /* Channel [0..63] currently being serviced */ - /* NOTE: Each channel takes 1/64th of a sample */ - /* period to be serviced. */ - -#define HCFG 0x14 /* Hardware config register */ - /* NOTE: There is no reason to use the legacy */ - /* SoundBlaster emulation stuff described below */ - /* under Linux, and all kinds of weird hardware */ - /* behavior can result if you try. Don't. */ -#define HCFG_LEGACYFUNC_MASK 0xe0000000 /* Legacy function number */ -#define HCFG_LEGACYFUNC_MPU 0x00000000 /* Legacy MPU */ -#define HCFG_LEGACYFUNC_SB 0x40000000 /* Legacy SB */ -#define HCFG_LEGACYFUNC_AD 0x60000000 /* Legacy AD */ -#define HCFG_LEGACYFUNC_MPIC 0x80000000 /* Legacy MPIC */ -#define HCFG_LEGACYFUNC_MDMA 0xa0000000 /* Legacy MDMA */ -#define HCFG_LEGACYFUNC_SPCI 0xc0000000 /* Legacy SPCI */ -#define HCFG_LEGACYFUNC_SDMA 0xe0000000 /* Legacy SDMA */ -#define HCFG_IOCAPTUREADDR 0x1f000000 /* The 4 LSBs of the captured I/O address. */ -#define HCFG_LEGACYWRITE 0x00800000 /* 1 = write, 0 = read */ -#define HCFG_LEGACYWORD 0x00400000 /* 1 = word, 0 = byte */ -#define HCFG_LEGACYINT 0x00200000 /* 1 = legacy event captured. Write 1 to clear. */ - /* NOTE: The rest of the bits in this register */ - /* _are_ relevant under Linux. */ -#define HCFG_CODECFORMAT_MASK 0x00070000 /* CODEC format */ -#define HCFG_CODECFORMAT_AC97 0x00000000 /* AC97 CODEC format -- Primary Output */ -#define HCFG_CODECFORMAT_I2S 0x00010000 /* I2S CODEC format -- Secondary (Rear) Output */ -#define HCFG_GPINPUT0 0x00004000 /* External pin112 */ -#define HCFG_GPINPUT1 0x00002000 /* External pin110 */ - -#define HCFG_GPOUTPUT_MASK 0x00001c00 /* External pins which may be controlled */ -#define HCFG_GPOUT0 0x00001000 /* set to enable digital out on 5.1 cards */ - -#define HCFG_JOYENABLE 0x00000200 /* Internal joystick enable */ -#define HCFG_PHASETRACKENABLE 0x00000100 /* Phase tracking enable */ - /* 1 = Force all 3 async digital inputs to use */ - /* the same async sample rate tracker (ZVIDEO) */ -#define HCFG_AC3ENABLE_MASK 0x0x0000e0 /* AC3 async input control - Not implemented */ -#define HCFG_AC3ENABLE_ZVIDEO 0x00000080 /* Channels 0 and 1 replace ZVIDEO */ -#define HCFG_AC3ENABLE_CDSPDIF 0x00000040 /* Channels 0 and 1 replace CDSPDIF */ -#define HCFG_AC3ENABLE_GPSPDIF 0x00000020 /* Channels 0 and 1 replace GPSPDIF */ -#define HCFG_AUTOMUTE 0x00000010 /* When set, the async sample rate convertors */ - /* will automatically mute their output when */ - /* they are not rate-locked to the external */ - /* async audio source */ -#define HCFG_LOCKSOUNDCACHE 0x00000008 /* 1 = Cancel bustmaster accesses to soundcache */ - /* NOTE: This should generally never be used. */ -#define HCFG_LOCKTANKCACHE_MASK 0x00000004 /* 1 = Cancel bustmaster accesses to tankcache */ - /* NOTE: This should generally never be used. */ -#define HCFG_LOCKTANKCACHE 0x01020014 -#define HCFG_MUTEBUTTONENABLE 0x00000002 /* 1 = Master mute button sets AUDIOENABLE = 0. */ - /* NOTE: This is a 'cheap' way to implement a */ - /* master mute function on the mute button, and */ - /* in general should not be used unless a more */ - /* sophisticated master mute function has not */ - /* been written. */ -#define HCFG_AUDIOENABLE 0x00000001 /* 0 = CODECs transmit zero-valued samples */ - /* Should be set to 1 when the EMU10K1 is */ - /* completely initialized. */ - -//For Audigy, MPU port move to 0x70-0x74 ptr register - -#define MUDATA 0x18 /* MPU401 data register (8 bits) */ - -#define MUCMD 0x19 /* MPU401 command register (8 bits) */ -#define MUCMD_RESET 0xff /* RESET command */ -#define MUCMD_ENTERUARTMODE 0x3f /* Enter_UART_mode command */ - /* NOTE: All other commands are ignored */ - -#define MUSTAT MUCMD /* MPU401 status register (8 bits) */ -#define MUSTAT_IRDYN 0x80 /* 0 = MIDI data or command ACK */ -#define MUSTAT_ORDYN 0x40 /* 0 = MUDATA can accept a command or data */ - -#define A_IOCFG 0x18 /* GPIO on Audigy card (16bits) */ -#define A_GPINPUT_MASK 0xff00 -#define A_GPOUTPUT_MASK 0x00ff - -#define TIMER 0x1a /* Timer terminal count register (16-bit) */ - /* NOTE: After the rate is changed, a maximum */ - /* of 1024 sample periods should be allowed */ - /* before the new rate is guaranteed accurate. */ -#define TIMER_RATE_MASK 0x03ff /* Timer interrupt rate in sample periods */ - /* 0 == 1024 periods, [1..4] are not useful */ - -#define AC97DATA 0x1c /* AC97 register set data register (16 bit) */ - -#define AC97ADDRESS 0x1e /* AC97 register set address register (8 bit) */ -#define AC97ADDRESS_READY 0x80 /* Read-only bit, reflects CODEC READY signal */ -#define AC97ADDRESS_ADDRESS 0x7f /* Address of indexed AC97 register */ - -/********************************************************************************************************/ -/* Emu10k1 pointer-offset register set, accessed through the PTR and DATA registers */ -/********************************************************************************************************/ - -#define CPF 0x00 /* Current pitch and fraction register */ -#define CPF_CURRENTPITCH_MASK 0xffff0000 /* Current pitch (linear, 0x4000 == unity pitch shift) */ -#define CPF_CURRENTPITCH 0x10100000 -#define CPF_STEREO_MASK 0x00008000 /* 1 = Even channel interleave, odd channel locked */ -#define CPF_STOP_MASK 0x00004000 /* 1 = Current pitch forced to 0 */ -#define CPF_FRACADDRESS_MASK 0x00003fff /* Linear fractional address of the current channel */ - -#define PTRX 0x01 /* Pitch target and send A/B amounts register */ -#define PTRX_PITCHTARGET_MASK 0xffff0000 /* Pitch target of specified channel */ -#define PTRX_PITCHTARGET 0x10100001 -#define PTRX_FXSENDAMOUNT_A_MASK 0x0000ff00 /* Linear level of channel output sent to FX send bus A */ -#define PTRX_FXSENDAMOUNT_A 0x08080001 -#define PTRX_FXSENDAMOUNT_B_MASK 0x000000ff /* Linear level of channel output sent to FX send bus B */ -#define PTRX_FXSENDAMOUNT_B 0x08000001 - -#define CVCF 0x02 /* Current volume and filter cutoff register */ -#define CVCF_CURRENTVOL_MASK 0xffff0000 /* Current linear volume of specified channel */ -#define CVCF_CURRENTVOL 0x10100002 -#define CVCF_CURRENTFILTER_MASK 0x0000ffff /* Current filter cutoff frequency of specified channel */ -#define CVCF_CURRENTFILTER 0x10000002 - -#define VTFT 0x03 /* Volume target and filter cutoff target register */ -#define VTFT_VOLUMETARGET_MASK 0xffff0000 /* Volume target of specified channel */ -#define VTFT_FILTERTARGET_MASK 0x0000ffff /* Filter cutoff target of specified channel */ - -#define Z1 0x05 /* Filter delay memory 1 register */ - -#define Z2 0x04 /* Filter delay memory 2 register */ - -#define PSST 0x06 /* Send C amount and loop start address register */ -#define PSST_FXSENDAMOUNT_C_MASK 0xff000000 /* Linear level of channel output sent to FX send bus C */ - -#define PSST_FXSENDAMOUNT_C 0x08180006 - -#define PSST_LOOPSTARTADDR_MASK 0x00ffffff /* Loop start address of the specified channel */ -#define PSST_LOOPSTARTADDR 0x18000006 - -#define DSL 0x07 /* Send D amount and loop start address register */ -#define DSL_FXSENDAMOUNT_D_MASK 0xff000000 /* Linear level of channel output sent to FX send bus D */ - -#define DSL_FXSENDAMOUNT_D 0x08180007 - -#define DSL_LOOPENDADDR_MASK 0x00ffffff /* Loop end address of the specified channel */ -#define DSL_LOOPENDADDR 0x18000007 - -#define CCCA 0x08 /* Filter Q, interp. ROM, byte size, cur. addr register */ -#define CCCA_RESONANCE 0xf0000000 /* Lowpass filter resonance (Q) height */ -#define CCCA_INTERPROMMASK 0x0e000000 /* Selects passband of interpolation ROM */ - /* 1 == full band, 7 == lowpass */ - /* ROM 0 is used when pitch shifting downward or less */ - /* then 3 semitones upward. Increasingly higher ROM */ - /* numbers are used, typically in steps of 3 semitones, */ - /* as upward pitch shifting is performed. */ -#define CCCA_INTERPROM_0 0x00000000 /* Select interpolation ROM 0 */ -#define CCCA_INTERPROM_1 0x02000000 /* Select interpolation ROM 1 */ -#define CCCA_INTERPROM_2 0x04000000 /* Select interpolation ROM 2 */ -#define CCCA_INTERPROM_3 0x06000000 /* Select interpolation ROM 3 */ -#define CCCA_INTERPROM_4 0x08000000 /* Select interpolation ROM 4 */ -#define CCCA_INTERPROM_5 0x0a000000 /* Select interpolation ROM 5 */ -#define CCCA_INTERPROM_6 0x0c000000 /* Select interpolation ROM 6 */ -#define CCCA_INTERPROM_7 0x0e000000 /* Select interpolation ROM 7 */ -#define CCCA_8BITSELECT 0x01000000 /* 1 = Sound memory for this channel uses 8-bit samples */ -#define CCCA_CURRADDR_MASK 0x00ffffff /* Current address of the selected channel */ -#define CCCA_CURRADDR 0x18000008 - -#define CCR 0x09 /* Cache control register */ -#define CCR_CACHEINVALIDSIZE 0x07190009 -#define CCR_CACHEINVALIDSIZE_MASK 0xfe000000 /* Number of invalid samples cache for this channel */ -#define CCR_CACHELOOPFLAG 0x01000000 /* 1 = Cache has a loop service pending */ -#define CCR_INTERLEAVEDSAMPLES 0x00800000 /* 1 = A cache service will fetch interleaved samples */ -#define CCR_WORDSIZEDSAMPLES 0x00400000 /* 1 = A cache service will fetch word sized samples */ -#define CCR_READADDRESS 0x06100009 -#define CCR_READADDRESS_MASK 0x003f0000 /* Location of cache just beyond current cache service */ -#define CCR_LOOPINVALSIZE 0x0000fe00 /* Number of invalid samples in cache prior to loop */ - /* NOTE: This is valid only if CACHELOOPFLAG is set */ -#define CCR_LOOPFLAG 0x00000100 /* Set for a single sample period when a loop occurs */ -#define CCR_CACHELOOPADDRHI 0x000000ff /* DSL_LOOPSTARTADDR's hi byte if CACHELOOPFLAG is set */ - -#define CLP 0x0a /* Cache loop register (valid if CCR_CACHELOOPFLAG = 1) */ - /* NOTE: This register is normally not used */ -#define CLP_CACHELOOPADDR 0x0000ffff /* Cache loop address (DSL_LOOPSTARTADDR [0..15]) */ - -#define FXRT 0x0b /* Effects send routing register */ - /* NOTE: It is illegal to assign the same routing to */ - /* two effects sends. */ -#define FXRT_CHANNELA 0x000f0000 /* Effects send bus number for channel's effects send A */ -#define FXRT_CHANNELB 0x00f00000 /* Effects send bus number for channel's effects send B */ -#define FXRT_CHANNELC 0x0f000000 /* Effects send bus number for channel's effects send C */ -#define FXRT_CHANNELD 0xf0000000 /* Effects send bus number for channel's effects send D */ - -#define MAPA 0x0c /* Cache map A */ - -#define MAPB 0x0d /* Cache map B */ - -#define MAP_PTE_MASK 0xffffe000 /* The 19 MSBs of the PTE indexed by the PTI */ -#define MAP_PTI_MASK 0x00001fff /* The 13 bit index to one of the 8192 PTE dwords */ - -#define ENVVOL 0x10 /* Volume envelope register */ -#define ENVVOL_MASK 0x0000ffff /* Current value of volume envelope state variable */ - /* 0x8000-n == 666*n usec delay */ - -#define ATKHLDV 0x11 /* Volume envelope hold and attack register */ -#define ATKHLDV_PHASE0 0x00008000 /* 0 = Begin attack phase */ -#define ATKHLDV_HOLDTIME_MASK 0x00007f00 /* Envelope hold time (127-n == n*88.2msec) */ -#define ATKHLDV_ATTACKTIME_MASK 0x0000007f /* Envelope attack time, log encoded */ - /* 0 = infinite, 1 = 10.9msec, ... 0x7f = 5.5msec */ - -#define DCYSUSV 0x12 /* Volume envelope sustain and decay register */ -#define DCYSUSV_PHASE1_MASK 0x00008000 /* 0 = Begin attack phase, 1 = begin release phase */ -#define DCYSUSV_SUSTAINLEVEL_MASK 0x00007f00 /* 127 = full, 0 = off, 0.75dB increments */ -#define DCYSUSV_CHANNELENABLE_MASK 0x00000080 /* 1 = Inhibit envelope engine from writing values in */ - /* this channel and from writing to pitch, filter and */ - /* volume targets. */ -#define DCYSUSV_DECAYTIME_MASK 0x0000007f /* Volume envelope decay time, log encoded */ - /* 0 = 43.7msec, 1 = 21.8msec, 0x7f = 22msec */ - -#define LFOVAL1 0x13 /* Modulation LFO value */ -#define LFOVAL_MASK 0x0000ffff /* Current value of modulation LFO state variable */ - /* 0x8000-n == 666*n usec delay */ - -#define ENVVAL 0x14 /* Modulation envelope register */ -#define ENVVAL_MASK 0x0000ffff /* Current value of modulation envelope state variable */ - /* 0x8000-n == 666*n usec delay */ - -#define ATKHLDM 0x15 /* Modulation envelope hold and attack register */ -#define ATKHLDM_PHASE0 0x00008000 /* 0 = Begin attack phase */ -#define ATKHLDM_HOLDTIME 0x00007f00 /* Envelope hold time (127-n == n*42msec) */ -#define ATKHLDM_ATTACKTIME 0x0000007f /* Envelope attack time, log encoded */ - /* 0 = infinite, 1 = 11msec, ... 0x7f = 5.5msec */ - -#define DCYSUSM 0x16 /* Modulation envelope decay and sustain register */ -#define DCYSUSM_PHASE1_MASK 0x00008000 /* 0 = Begin attack phase, 1 = begin release phase */ -#define DCYSUSM_SUSTAINLEVEL_MASK 0x00007f00 /* 127 = full, 0 = off, 0.75dB increments */ -#define DCYSUSM_DECAYTIME_MASK 0x0000007f /* Envelope decay time, log encoded */ - /* 0 = 43.7msec, 1 = 21.8msec, 0x7f = 22msec */ - -#define LFOVAL2 0x17 /* Vibrato LFO register */ -#define LFOVAL2_MASK 0x0000ffff /* Current value of vibrato LFO state variable */ - /* 0x8000-n == 666*n usec delay */ - -#define IP 0x18 /* Initial pitch register */ -#define IP_MASK 0x0000ffff /* Exponential initial pitch shift */ - /* 4 bits of octave, 12 bits of fractional octave */ -#define IP_UNITY 0x0000e000 /* Unity pitch shift */ - -#define IFATN 0x19 /* Initial filter cutoff and attenuation register */ -#define IFATN_FILTERCUTOFF_MASK 0x0000ff00 /* Initial filter cutoff frequency in exponential units */ - /* 6 most significant bits are semitones */ - /* 2 least significant bits are fractions */ -#define IFATN_FILTERCUTOFF 0x08080019 -#define IFATN_ATTENUATION_MASK 0x000000ff /* Initial attenuation in 0.375dB steps */ -#define IFATN_ATTENUATION 0x08000019 - - -#define PEFE 0x1a /* Pitch envelope and filter envelope amount register */ -#define PEFE_PITCHAMOUNT_MASK 0x0000ff00 /* Pitch envlope amount */ - /* Signed 2's complement, +/- one octave peak extremes */ -#define PEFE_PITCHAMOUNT 0x0808001a -#define PEFE_FILTERAMOUNT_MASK 0x000000ff /* Filter envlope amount */ - /* Signed 2's complement, +/- six octaves peak extremes */ -#define PEFE_FILTERAMOUNT 0x0800001a -#define FMMOD 0x1b /* Vibrato/filter modulation from LFO register */ -#define FMMOD_MODVIBRATO 0x0000ff00 /* Vibrato LFO modulation depth */ - /* Signed 2's complement, +/- one octave extremes */ -#define FMMOD_MOFILTER 0x000000ff /* Filter LFO modulation depth */ - /* Signed 2's complement, +/- three octave extremes */ - - -#define TREMFRQ 0x1c /* Tremolo amount and modulation LFO frequency register */ -#define TREMFRQ_DEPTH 0x0000ff00 /* Tremolo depth */ - /* Signed 2's complement, with +/- 12dB extremes */ -#define TREMFRQ_FREQUENCY 0x000000ff /* Tremolo LFO frequency */ - /* ??Hz steps, maximum of ?? Hz. */ - -#define FM2FRQ2 0x1d /* Vibrato amount and vibrato LFO frequency register */ -#define FM2FRQ2_DEPTH 0x0000ff00 /* Vibrato LFO vibrato depth */ - /* Signed 2's complement, +/- one octave extremes */ -#define FM2FRQ2_FREQUENCY 0x000000ff /* Vibrato LFO frequency */ - /* 0.039Hz steps, maximum of 9.85 Hz. */ - -#define TEMPENV 0x1e /* Tempory envelope register */ -#define TEMPENV_MASK 0x0000ffff /* 16-bit value */ - /* NOTE: All channels contain internal variables; do */ - /* not write to these locations. */ - -#define CD0 0x20 /* Cache data 0 register */ -#define CD1 0x21 /* Cache data 1 register */ -#define CD2 0x22 /* Cache data 2 register */ -#define CD3 0x23 /* Cache data 3 register */ -#define CD4 0x24 /* Cache data 4 register */ -#define CD5 0x25 /* Cache data 5 register */ -#define CD6 0x26 /* Cache data 6 register */ -#define CD7 0x27 /* Cache data 7 register */ -#define CD8 0x28 /* Cache data 8 register */ -#define CD9 0x29 /* Cache data 9 register */ -#define CDA 0x2a /* Cache data A register */ -#define CDB 0x2b /* Cache data B register */ -#define CDC 0x2c /* Cache data C register */ -#define CDD 0x2d /* Cache data D register */ -#define CDE 0x2e /* Cache data E register */ -#define CDF 0x2f /* Cache data F register */ - -#define PTB 0x40 /* Page table base register */ -#define PTB_MASK 0xfffff000 /* Physical address of the page table in host memory */ - -#define TCB 0x41 /* Tank cache base register */ -#define TCB_MASK 0xfffff000 /* Physical address of the bottom of host based TRAM */ - -#define ADCCR 0x42 /* ADC sample rate/stereo control register */ -#define ADCCR_RCHANENABLE 0x00000010 /* Enables right channel for writing to the host */ -#define ADCCR_LCHANENABLE 0x00000008 /* Enables left channel for writing to the host */ - /* NOTE: To guarantee phase coherency, both channels */ - /* must be disabled prior to enabling both channels. */ -#define A_ADCCR_RCHANENABLE 0x00000020 -#define A_ADCCR_LCHANENABLE 0x00000010 - -#define A_ADCCR_SAMPLERATE_MASK 0x0000000F /* Audigy sample rate convertor output rate */ -#define ADCCR_SAMPLERATE_MASK 0x00000007 /* Sample rate convertor output rate */ - -#define ADCCR_SAMPLERATE_48 0x00000000 /* 48kHz sample rate */ -#define ADCCR_SAMPLERATE_44 0x00000001 /* 44.1kHz sample rate */ -#define ADCCR_SAMPLERATE_32 0x00000002 /* 32kHz sample rate */ -#define ADCCR_SAMPLERATE_24 0x00000003 /* 24kHz sample rate */ -#define ADCCR_SAMPLERATE_22 0x00000004 /* 22.05kHz sample rate */ -#define ADCCR_SAMPLERATE_16 0x00000005 /* 16kHz sample rate */ -#define ADCCR_SAMPLERATE_11 0x00000006 /* 11.025kHz sample rate */ -#define ADCCR_SAMPLERATE_8 0x00000007 /* 8kHz sample rate */ - -#define A_ADCCR_SAMPLERATE_12 0x00000006 /* 12kHz sample rate */ -#define A_ADCCR_SAMPLERATE_11 0x00000007 /* 11.025kHz sample rate */ -#define A_ADCCR_SAMPLERATE_8 0x00000008 /* 8kHz sample rate */ - -#define FXWC 0x43 /* FX output write channels register */ - /* When set, each bit enables the writing of the */ - /* corresponding FX output channel (internal registers */ - /* 0x20-0x3f) into host memory. This mode of recording */ - /* is 16bit, 48KHz only. All 32 channels can be enabled */ - /* simultaneously. */ -#define TCBS 0x44 /* Tank cache buffer size register */ -#define TCBS_MASK 0x00000007 /* Tank cache buffer size field */ -#define TCBS_BUFFSIZE_16K 0x00000000 -#define TCBS_BUFFSIZE_32K 0x00000001 -#define TCBS_BUFFSIZE_64K 0x00000002 -#define TCBS_BUFFSIZE_128K 0x00000003 -#define TCBS_BUFFSIZE_256K 0x00000004 -#define TCBS_BUFFSIZE_512K 0x00000005 -#define TCBS_BUFFSIZE_1024K 0x00000006 -#define TCBS_BUFFSIZE_2048K 0x00000007 - -#define MICBA 0x45 /* AC97 microphone buffer address register */ -#define MICBA_MASK 0xfffff000 /* 20 bit base address */ - -#define ADCBA 0x46 /* ADC buffer address register */ -#define ADCBA_MASK 0xfffff000 /* 20 bit base address */ - -#define FXBA 0x47 /* FX Buffer Address */ -#define FXBA_MASK 0xfffff000 /* 20 bit base address */ - -#define MICBS 0x49 /* Microphone buffer size register */ - -#define ADCBS 0x4a /* ADC buffer size register */ - -#define FXBS 0x4b /* FX buffer size register */ - -/* The following mask values define the size of the ADC, MIX and FX buffers in bytes */ -#define ADCBS_BUFSIZE_NONE 0x00000000 -#define ADCBS_BUFSIZE_384 0x00000001 -#define ADCBS_BUFSIZE_448 0x00000002 -#define ADCBS_BUFSIZE_512 0x00000003 -#define ADCBS_BUFSIZE_640 0x00000004 -#define ADCBS_BUFSIZE_768 0x00000005 -#define ADCBS_BUFSIZE_896 0x00000006 -#define ADCBS_BUFSIZE_1024 0x00000007 -#define ADCBS_BUFSIZE_1280 0x00000008 -#define ADCBS_BUFSIZE_1536 0x00000009 -#define ADCBS_BUFSIZE_1792 0x0000000a -#define ADCBS_BUFSIZE_2048 0x0000000b -#define ADCBS_BUFSIZE_2560 0x0000000c -#define ADCBS_BUFSIZE_3072 0x0000000d -#define ADCBS_BUFSIZE_3584 0x0000000e -#define ADCBS_BUFSIZE_4096 0x0000000f -#define ADCBS_BUFSIZE_5120 0x00000010 -#define ADCBS_BUFSIZE_6144 0x00000011 -#define ADCBS_BUFSIZE_7168 0x00000012 -#define ADCBS_BUFSIZE_8192 0x00000013 -#define ADCBS_BUFSIZE_10240 0x00000014 -#define ADCBS_BUFSIZE_12288 0x00000015 -#define ADCBS_BUFSIZE_14366 0x00000016 -#define ADCBS_BUFSIZE_16384 0x00000017 -#define ADCBS_BUFSIZE_20480 0x00000018 -#define ADCBS_BUFSIZE_24576 0x00000019 -#define ADCBS_BUFSIZE_28672 0x0000001a -#define ADCBS_BUFSIZE_32768 0x0000001b -#define ADCBS_BUFSIZE_40960 0x0000001c -#define ADCBS_BUFSIZE_49152 0x0000001d -#define ADCBS_BUFSIZE_57344 0x0000001e -#define ADCBS_BUFSIZE_65536 0x0000001f - - -#define CDCS 0x50 /* CD-ROM digital channel status register */ - -#define GPSCS 0x51 /* General Purpose SPDIF channel status register*/ - -#define DBG 0x52 /* DO NOT PROGRAM THIS REGISTER!!! MAY DESTROY CHIP */ - -/* definitions for debug register - taken from the alsa drivers */ -#define DBG_ZC 0x80000000 /* zero tram counter */ -#define DBG_SATURATION_OCCURED 0x02000000 /* saturation control */ -#define DBG_SATURATION_ADDR 0x01ff0000 /* saturation address */ -#define DBG_SINGLE_STEP 0x00008000 /* single step mode */ -#define DBG_STEP 0x00004000 /* start single step */ -#define DBG_CONDITION_CODE 0x00003e00 /* condition code */ -#define DBG_SINGLE_STEP_ADDR 0x000001ff /* single step address */ - - -#define REG53 0x53 /* DO NOT PROGRAM THIS REGISTER!!! MAY DESTROY CHIP */ - -#define A_DBG 0x53 -#define A_DBG_SINGLE_STEP 0x00020000 /* Set to zero to start dsp */ -#define A_DBG_ZC 0x40000000 /* zero tram counter */ -#define A_DBG_STEP_ADDR 0x000003ff -#define A_DBG_SATURATION_OCCURED 0x20000000 -#define A_DBG_SATURATION_ADDR 0x0ffc0000 - -#define SPCS0 0x54 /* SPDIF output Channel Status 0 register */ - -#define SPCS1 0x55 /* SPDIF output Channel Status 1 register */ - -#define SPCS2 0x56 /* SPDIF output Channel Status 2 register */ - -#define SPCS_CLKACCYMASK 0x30000000 /* Clock accuracy */ -#define SPCS_CLKACCY_1000PPM 0x00000000 /* 1000 parts per million */ -#define SPCS_CLKACCY_50PPM 0x10000000 /* 50 parts per million */ -#define SPCS_CLKACCY_VARIABLE 0x20000000 /* Variable accuracy */ -#define SPCS_SAMPLERATEMASK 0x0f000000 /* Sample rate */ -#define SPCS_SAMPLERATE_44 0x00000000 /* 44.1kHz sample rate */ -#define SPCS_SAMPLERATE_48 0x02000000 /* 48kHz sample rate */ -#define SPCS_SAMPLERATE_32 0x03000000 /* 32kHz sample rate */ -#define SPCS_CHANNELNUMMASK 0x00f00000 /* Channel number */ -#define SPCS_CHANNELNUM_UNSPEC 0x00000000 /* Unspecified channel number */ -#define SPCS_CHANNELNUM_LEFT 0x00100000 /* Left channel */ -#define SPCS_CHANNELNUM_RIGHT 0x00200000 /* Right channel */ -#define SPCS_SOURCENUMMASK 0x000f0000 /* Source number */ -#define SPCS_SOURCENUM_UNSPEC 0x00000000 /* Unspecified source number */ -#define SPCS_GENERATIONSTATUS 0x00008000 /* Originality flag (see IEC-958 spec) */ -#define SPCS_CATEGORYCODEMASK 0x00007f00 /* Category code (see IEC-958 spec) */ -#define SPCS_MODEMASK 0x000000c0 /* Mode (see IEC-958 spec) */ -#define SPCS_EMPHASISMASK 0x00000038 /* Emphasis */ -#define SPCS_EMPHASIS_NONE 0x00000000 /* No emphasis */ -#define SPCS_EMPHASIS_50_15 0x00000008 /* 50/15 usec 2 channel */ -#define SPCS_COPYRIGHT 0x00000004 /* Copyright asserted flag -- do not modify */ -#define SPCS_NOTAUDIODATA 0x00000002 /* 0 = Digital audio, 1 = not audio */ -#define SPCS_PROFESSIONAL 0x00000001 /* 0 = Consumer (IEC-958), 1 = pro (AES3-1992) */ - -/* The 32-bit CLIx and SOLx registers all have one bit per channel control/status */ -#define CLIEL 0x58 /* Channel loop interrupt enable low register */ - -#define CLIEH 0x59 /* Channel loop interrupt enable high register */ - -#define CLIPL 0x5a /* Channel loop interrupt pending low register */ - -#define CLIPH 0x5b /* Channel loop interrupt pending high register */ - -#define SOLEL 0x5c /* Stop on loop enable low register */ - -#define SOLEH 0x5d /* Stop on loop enable high register */ - -#define SPBYPASS 0x5e /* SPDIF BYPASS mode register */ -#define SPBYPASS_ENABLE 0x00000001 /* Enable SPDIF bypass mode */ - -#define AC97SLOT 0x5f /* additional AC97 slots enable bits */ -#define AC97SLOT_CNTR 0x10 /* Center enable */ -#define AC97SLOT_LFE 0x20 /* LFE enable */ - -#define CDSRCS 0x60 /* CD-ROM Sample Rate Converter status register */ - -#define GPSRCS 0x61 /* General Purpose SPDIF sample rate cvt status */ - -#define ZVSRCS 0x62 /* ZVideo sample rate converter status */ - /* NOTE: This one has no SPDIFLOCKED field */ - /* Assumes sample lock */ - -/* These three bitfields apply to CDSRCS, GPSRCS, and (except as noted) ZVSRCS. */ -#define SRCS_SPDIFLOCKED 0x02000000 /* SPDIF stream locked */ -#define SRCS_RATELOCKED 0x01000000 /* Sample rate locked */ -#define SRCS_ESTSAMPLERATE 0x0007ffff /* Do not modify this field. */ - - -/* Note that these values can vary +/- by a small amount */ -#define SRCS_SPDIFRATE_44 0x0003acd9 -#define SRCS_SPDIFRATE_48 0x00040000 -#define SRCS_SPDIFRATE_96 0x00080000 - -#define MICIDX 0x63 /* Microphone recording buffer index register */ -#define MICIDX_MASK 0x0000ffff /* 16-bit value */ -#define MICIDX_IDX 0x10000063 - -#define A_ADCIDX 0x63 -#define A_ADCIDX_IDX 0x10000063 - -#define ADCIDX 0x64 /* ADC recording buffer index register */ -#define ADCIDX_MASK 0x0000ffff /* 16 bit index field */ -#define ADCIDX_IDX 0x10000064 - -#define FXIDX 0x65 /* FX recording buffer index register */ -#define FXIDX_MASK 0x0000ffff /* 16-bit value */ -#define FXIDX_IDX 0x10000065 - -/* This is the MPU port on the card (via the game port) */ -#define A_MUDATA1 0x70 -#define A_MUCMD1 0x71 -#define A_MUSTAT1 A_MUCMD1 - -/* This is the MPU port on the Audigy Drive */ -#define A_MUDATA2 0x72 -#define A_MUCMD2 0x73 -#define A_MUSTAT2 A_MUCMD2 - -/* The next two are the Audigy equivalent of FXWC */ -/* the Audigy can record any output (16bit, 48kHz, up to 64 channel simultaneously) */ -/* Each bit selects a channel for recording */ -#define A_FXWC1 0x74 /* Selects 0x7f-0x60 for FX recording */ -#define A_FXWC2 0x75 /* Selects 0x9f-0x80 for FX recording */ - -#define A_SPDIF_SAMPLERATE 0x76 /* Set the sample rate of SPDIF output */ -#define A_SPDIF_48000 0x00000080 -#define A_SPDIF_44100 0x00000000 -#define A_SPDIF_96000 0x00000040 - -#define A_FXRT2 0x7c -#define A_FXRT_CHANNELE 0x0000003f /* Effects send bus number for channel's effects send E */ -#define A_FXRT_CHANNELF 0x00003f00 /* Effects send bus number for channel's effects send F */ -#define A_FXRT_CHANNELG 0x003f0000 /* Effects send bus number for channel's effects send G */ -#define A_FXRT_CHANNELH 0x3f000000 /* Effects send bus number for channel's effects send H */ - -#define A_SENDAMOUNTS 0x7d -#define A_FXSENDAMOUNT_E_MASK 0xff000000 -#define A_FXSENDAMOUNT_F_MASK 0x00ff0000 -#define A_FXSENDAMOUNT_G_MASK 0x0000ff00 -#define A_FXSENDAMOUNT_H_MASK 0x000000ff - -/* The send amounts for this one are the same as used with the emu10k1 */ -#define A_FXRT1 0x7e -#define A_FXRT_CHANNELA 0x0000003f -#define A_FXRT_CHANNELB 0x00003f00 -#define A_FXRT_CHANNELC 0x003f0000 -#define A_FXRT_CHANNELD 0x3f000000 - - -/* Each FX general purpose register is 32 bits in length, all bits are used */ -#define FXGPREGBASE 0x100 /* FX general purpose registers base */ -#define A_FXGPREGBASE 0x400 /* Audigy GPRs, 0x400 to 0x5ff */ -/* Tank audio data is logarithmically compressed down to 16 bits before writing to TRAM and is */ -/* decompressed back to 20 bits on a read. There are a total of 160 locations, the last 32 */ -/* locations are for external TRAM. */ -#define TANKMEMDATAREGBASE 0x200 /* Tank memory data registers base */ -#define TANKMEMDATAREG_MASK 0x000fffff /* 20 bit tank audio data field */ - -/* Combined address field and memory opcode or flag field. 160 locations, last 32 are external */ -#define TANKMEMADDRREGBASE 0x300 /* Tank memory address registers base */ -#define TANKMEMADDRREG_ADDR_MASK 0x000fffff /* 20 bit tank address field */ -#define TANKMEMADDRREG_CLEAR 0x00800000 /* Clear tank memory */ -#define TANKMEMADDRREG_ALIGN 0x00400000 /* Align read or write relative to tank access */ -#define TANKMEMADDRREG_WRITE 0x00200000 /* Write to tank memory */ -#define TANKMEMADDRREG_READ 0x00100000 /* Read from tank memory */ - -#define MICROCODEBASE 0x400 /* Microcode data base address */ - -/* Each DSP microcode instruction is mapped into 2 doublewords */ -/* NOTE: When writing, always write the LO doubleword first. Reads can be in either order. */ -#define LOWORD_OPX_MASK 0x000ffc00 /* Instruction operand X */ -#define LOWORD_OPY_MASK 0x000003ff /* Instruction operand Y */ -#define HIWORD_OPCODE_MASK 0x00f00000 /* Instruction opcode */ -#define HIWORD_RESULT_MASK 0x000ffc00 /* Instruction result */ -#define HIWORD_OPA_MASK 0x000003ff /* Instruction operand A */ - - -/* Audigy Soundcard have a different instruction format */ -#define AUDIGY_CODEBASE 0x600 -#define A_LOWORD_OPY_MASK 0x000007ff -#define A_LOWORD_OPX_MASK 0x007ff000 -#define A_HIWORD_OPCODE_MASK 0x0f000000 -#define A_HIWORD_RESULT_MASK 0x007ff000 -#define A_HIWORD_OPA_MASK 0x000007ff - - -#endif /* _8010_H */ --- sys/gnu/dev/sound/pci/maestro3_dsp.h.orig Mon May 28 05:07:39 2001 +++ sys/gnu/dev/sound/pci/maestro3_dsp.h Fri Jan 7 02:27:30 2005 @@ -1,5 +1,5 @@ -/* $FreeBSD: src/sys/gnu/dev/sound/pci/maestro3_dsp.h,v 1.4 2001/05/27 21:07:39 scottl Exp $ */ -/* +/* $FreeBSD: src/sys/gnu/dev/sound/pci/maestro3_dsp.h,v 1.5 2005/01/06 18:27:30 imp Exp $ */ +/*- * ESS Technology allegro audio driver. * * Copyright (C) 1992-2000 Don Kim (don.kim@esstech.com) --- sys/gnu/dev/sound/pci/maestro3_reg.h.orig Mon May 28 05:07:39 2001 +++ sys/gnu/dev/sound/pci/maestro3_reg.h Fri Jan 7 02:27:30 2005 @@ -1,5 +1,5 @@ -/* $FreeBSD: src/sys/gnu/dev/sound/pci/maestro3_reg.h,v 1.4 2001/05/27 21:07:39 scottl Exp $ */ -/* +/* $FreeBSD: src/sys/gnu/dev/sound/pci/maestro3_reg.h,v 1.5 2005/01/06 18:27:30 imp Exp $ */ +/*- * ESS Technology allegro audio driver. * * Copyright (C) 1992-2000 Don Kim (don.kim@esstech.com) --- sys/gnu/dev/sound/pci/p16v-alsa.h.orig Thu Jan 1 07:30:00 1970 +++ sys/gnu/dev/sound/pci/p16v-alsa.h Thu Jul 12 12:04:19 2007 @@ -0,0 +1,301 @@ +/*- + * Copyright (c) by James Courtier-Dutton + * Driver p16v chips + * Version: 0.21 + * + * FEATURES currently supported: + * Output fixed at S32_LE, 2 channel to hw:0,0 + * Rates: 44.1, 48, 96, 192. + * + * Changelog: + * 0.8 + * Use separate card based buffer for periods table. + * 0.9 + * Use 2 channel output streams instead of 8 channel. + * (8 channel output streams might be good for ASIO type output) + * Corrected speaker output, so Front -> Front etc. + * 0.10 + * Fixed missed interrupts. + * 0.11 + * Add Sound card model number and names. + * Add Analog volume controls. + * 0.12 + * Corrected playback interrupts. Now interrupt per period, instead of half period. + * 0.13 + * Use single trigger for multichannel. + * 0.14 + * Mic capture now works at fixed: S32_LE, 96000Hz, Stereo. + * 0.15 + * Force buffer_size / period_size == INTEGER. + * 0.16 + * Update p16v.c to work with changed alsa api. + * 0.17 + * Update p16v.c to work with changed alsa api. Removed boot_devs. + * 0.18 + * Merging with snd-emu10k1 driver. + * 0.19 + * One stereo channel at 24bit now works. + * 0.20 + * Added better register defines. + * 0.21 + * Split from p16v.c + * + * + * BUGS: + * Some stability problems when unloading the snd-p16v kernel module. + * -- + * + * TODO: + * SPDIF out. + * Find out how to change capture sample rates. E.g. To record SPDIF at 48000Hz. + * Currently capture fixed at 48000Hz. + * + * -- + * GENERAL INFO: + * Model: SB0240 + * P16V Chip: CA0151-DBS + * Audigy 2 Chip: CA0102-IAT + * AC97 Codec: STAC 9721 + * ADC: Philips 1361T (Stereo 24bit) + * DAC: CS4382-K (8-channel, 24bit, 192Khz) + * + * This code was initally based on code from ALSA's emu10k1x.c which is: + * Copyright (c) by Francisco Moraes + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* $FreeBSD: src/sys/gnu/dev/sound/pci/p16v-alsa.h,v 1.1 2006/07/15 19:36:28 netchild Exp $ */ + +/********************************************************************************************************/ +/* Audigy2 P16V pointer-offset register set, accessed through the PTR2 and DATA2 registers */ +/********************************************************************************************************/ + +/* The sample rate of the SPDIF outputs is set by modifying a register in the EMU10K2 PTR register A_SPDIF_SAMPLERATE. + * The sample rate is also controlled by the same registers that control the rate of the EMU10K2 sample rate converters. + */ + +/* Initally all registers from 0x00 to 0x3f have zero contents. */ +#define PLAYBACK_LIST_ADDR 0x00 /* Base DMA address of a list of pointers to each period/size */ + /* One list entry: 4 bytes for DMA address, + * 4 bytes for period_size << 16. + * One list entry is 8 bytes long. + * One list entry for each period in the buffer. + */ +#define PLAYBACK_LIST_SIZE 0x01 /* Size of list in bytes << 16. E.g. 8 periods -> 0x00380000 */ +#define PLAYBACK_LIST_PTR 0x02 /* Pointer to the current period being played */ +#define PLAYBACK_UNKNOWN3 0x03 /* Not used */ +#define PLAYBACK_DMA_ADDR 0x04 /* Playback DMA addresss */ +#define PLAYBACK_PERIOD_SIZE 0x05 /* Playback period size. win2000 uses 0x04000000 */ +#define PLAYBACK_POINTER 0x06 /* Playback period pointer. Used with PLAYBACK_LIST_PTR to determine buffer position currently in DAC */ +#define PLAYBACK_FIFO_END_ADDRESS 0x07 /* Playback FIFO end address */ +#define PLAYBACK_FIFO_POINTER 0x08 /* Playback FIFO pointer and number of valid sound samples in cache */ +#define PLAYBACK_UNKNOWN9 0x09 /* Not used */ +#define CAPTURE_DMA_ADDR 0x10 /* Capture DMA address */ +#define CAPTURE_BUFFER_SIZE 0x11 /* Capture buffer size */ +#define CAPTURE_POINTER 0x12 /* Capture buffer pointer. Sample currently in ADC */ +#define CAPTURE_FIFO_POINTER 0x13 /* Capture FIFO pointer and number of valid sound samples in cache */ +#define CAPTURE_P16V_VOLUME1 0x14 /* Low: Capture volume 0xXXXX3030 */ +#define CAPTURE_P16V_VOLUME2 0x15 /* High:Has no effect on capture volume */ +#define CAPTURE_P16V_SOURCE 0x16 /* P16V source select. Set to 0x0700E4E5 for AC97 CAPTURE */ + /* [0:1] Capture input 0 channel select. 0 = Capture output 0. + * 1 = Capture output 1. + * 2 = Capture output 2. + * 3 = Capture output 3. + * [3:2] Capture input 1 channel select. 0 = Capture output 0. + * 1 = Capture output 1. + * 2 = Capture output 2. + * 3 = Capture output 3. + * [5:4] Capture input 2 channel select. 0 = Capture output 0. + * 1 = Capture output 1. + * 2 = Capture output 2. + * 3 = Capture output 3. + * [7:6] Capture input 3 channel select. 0 = Capture output 0. + * 1 = Capture output 1. + * 2 = Capture output 2. + * 3 = Capture output 3. + * [9:8] Playback input 0 channel select. 0 = Play output 0. + * 1 = Play output 1. + * 2 = Play output 2. + * 3 = Play output 3. + * [11:10] Playback input 1 channel select. 0 = Play output 0. + * 1 = Play output 1. + * 2 = Play output 2. + * 3 = Play output 3. + * [13:12] Playback input 2 channel select. 0 = Play output 0. + * 1 = Play output 1. + * 2 = Play output 2. + * 3 = Play output 3. + * [15:14] Playback input 3 channel select. 0 = Play output 0. + * 1 = Play output 1. + * 2 = Play output 2. + * 3 = Play output 3. + * [19:16] Playback mixer output enable. 1 bit per channel. + * [23:20] Capture mixer output enable. 1 bit per channel. + * [26:24] FX engine channel capture 0 = 0x60-0x67. + * 1 = 0x68-0x6f. + * 2 = 0x70-0x77. + * 3 = 0x78-0x7f. + * 4 = 0x80-0x87. + * 5 = 0x88-0x8f. + * 6 = 0x90-0x97. + * 7 = 0x98-0x9f. + * [31:27] Not used. + */ + + /* 0x1 = capture on. + * 0x100 = capture off. + * 0x200 = capture off. + * 0x1000 = capture off. + */ +#define CAPTURE_RATE_STATUS 0x17 /* Capture sample rate. Read only */ + /* [15:0] Not used. + * [18:16] Channel 0 Detected sample rate. 0 - 44.1khz + * 1 - 48 khz + * 2 - 96 khz + * 3 - 192 khz + * 7 - undefined rate. + * [19] Channel 0. 1 - Valid, 0 - Not Valid. + * [22:20] Channel 1 Detected sample rate. + * [23] Channel 1. 1 - Valid, 0 - Not Valid. + * [26:24] Channel 2 Detected sample rate. + * [27] Channel 2. 1 - Valid, 0 - Not Valid. + * [30:28] Channel 3 Detected sample rate. + * [31] Channel 3. 1 - Valid, 0 - Not Valid. + */ +/* 0x18 - 0x1f unused */ +#define PLAYBACK_LAST_SAMPLE 0x20 /* The sample currently being played. Read only */ +/* 0x21 - 0x3f unused */ +#define BASIC_INTERRUPT 0x40 /* Used by both playback and capture interrupt handler */ + /* Playback (0x1< 77770000 so it must be some sort of route. + * bit 0x1 starts DMA playback on channel_id 0 + */ +/* 0x41,42 take values from 0 - 0xffffffff, but have no effect on playback */ +/* 0x43,0x48 do not remember settings */ +/* 0x41-45 unused */ +#define WATERMARK 0x46 /* Test bit to indicate cache level usage */ + /* Values it can have while playing on channel 0. + * 0000f000, 0000f004, 0000f008, 0000f00c. + * Readonly. + */ +/* 0x47-0x4f unused */ +/* 0x50-0x5f Capture cache data */ +#define SRCSel 0x60 /* SRCSel. Default 0x4. Bypass P16V 0x14 */ + /* [0] 0 = 10K2 audio, 1 = SRC48 mixer output. + * [2] 0 = 10K2 audio, 1 = SRCMulti SPDIF mixer output. + * [4] 0 = 10K2 audio, 1 = SRCMulti I2S mixer output. + */ + /* SRC48 converts samples rates 44.1, 48, 96, 192 to 48 khz. */ + /* SRCMulti converts 48khz samples rates to 44.1, 48, 96, 192 to 48. */ + /* SRC48 and SRCMULTI sample rate select and output select. */ + /* 0xffffffff -> 0xC0000015 + * 0xXXXXXXX4 = Enable Front Left/Right + * Enable PCMs + */ + +/* 0x61 -> 0x6c are Volume controls */ +#define PLAYBACK_VOLUME_MIXER1 0x61 /* SRC48 Low to mixer input volume control. */ +#define PLAYBACK_VOLUME_MIXER2 0x62 /* SRC48 High to mixer input volume control. */ +#define PLAYBACK_VOLUME_MIXER3 0x63 /* SRCMULTI SPDIF Low to mixer input volume control. */ +#define PLAYBACK_VOLUME_MIXER4 0x64 /* SRCMULTI SPDIF High to mixer input volume control. */ +#define PLAYBACK_VOLUME_MIXER5 0x65 /* SRCMULTI I2S Low to mixer input volume control. */ +#define PLAYBACK_VOLUME_MIXER6 0x66 /* SRCMULTI I2S High to mixer input volume control. */ +#define PLAYBACK_VOLUME_MIXER7 0x67 /* P16V Low to SRCMULTI SPDIF mixer input volume control. */ +#define PLAYBACK_VOLUME_MIXER8 0x68 /* P16V High to SRCMULTI SPDIF mixer input volume control. */ +#define PLAYBACK_VOLUME_MIXER9 0x69 /* P16V Low to SRCMULTI I2S mixer input volume control. */ + /* 0xXXXX3030 = PCM0 Volume (Front). + * 0x3030XXXX = PCM1 Volume (Center) + */ +#define PLAYBACK_VOLUME_MIXER10 0x6a /* P16V High to SRCMULTI I2S mixer input volume control. */ + /* 0x3030XXXX = PCM3 Volume (Rear). */ +#define PLAYBACK_VOLUME_MIXER11 0x6b /* E10K2 Low to SRC48 mixer input volume control. */ +#define PLAYBACK_VOLUME_MIXER12 0x6c /* E10K2 High to SRC48 mixer input volume control. */ + +#define SRC48_ENABLE 0x6d /* SRC48 input audio enable */ + /* SRC48 converts samples rates 44.1, 48, 96, 192 to 48 khz. */ + /* [23:16] The corresponding P16V channel to SRC48 enabled if == 1. + * [31:24] The corresponding E10K2 channel to SRC48 enabled. + */ +#define SRCMULTI_ENABLE 0x6e /* SRCMulti input audio enable. Default 0xffffffff */ + /* SRCMulti converts 48khz samples rates to 44.1, 48, 96, 192 to 48. */ + /* [7:0] The corresponding P16V channel to SRCMulti_I2S enabled if == 1. + * [15:8] The corresponding E10K2 channel to SRCMulti I2S enabled. + * [23:16] The corresponding P16V channel to SRCMulti SPDIF enabled. + * [31:24] The corresponding E10K2 channel to SRCMulti SPDIF enabled. + */ + /* Bypass P16V 0xff00ff00 + * Bitmap. 0 = Off, 1 = On. + * P16V playback outputs: + * 0xXXXXXXX1 = PCM0 Left. (Front) + * 0xXXXXXXX2 = PCM0 Right. + * 0xXXXXXXX4 = PCM1 Left. (Center/LFE) + * 0xXXXXXXX8 = PCM1 Right. + * 0xXXXXXX1X = PCM2 Left. (Unknown) + * 0xXXXXXX2X = PCM2 Right. + * 0xXXXXXX4X = PCM3 Left. (Rear) + * 0xXXXXXX8X = PCM3 Right. + */ +#define AUDIO_OUT_ENABLE 0x6f /* Default: 000100FF */ + /* [3:0] Does something, but not documented. Probably capture enable. + * [7:4] Playback channels enable. not documented. + * [16] AC97 output enable if == 1 + * [30] 0 = SRCMulti_I2S input from fxengine 0x68-0x6f. + * 1 = SRCMulti_I2S input from SRC48 output. + * [31] 0 = SRCMulti_SPDIF input from fxengine 0x60-0x67. + * 1 = SRCMulti_SPDIF input from SRC48 output. + */ + /* 0xffffffff -> C00100FF */ + /* 0 -> Not playback sound, irq still running */ + /* 0xXXXXXX10 = PCM0 Left/Right On. (Front) + * 0xXXXXXX20 = PCM1 Left/Right On. (Center/LFE) + * 0xXXXXXX40 = PCM2 Left/Right On. (Unknown) + * 0xXXXXXX80 = PCM3 Left/Right On. (Rear) + */ +#define PLAYBACK_SPDIF_SELECT 0x70 /* Default: 12030F00 */ + /* 0xffffffff -> 3FF30FFF */ + /* 0x00000001 pauses stream/irq fail. */ + /* All other bits do not effect playback */ +#define PLAYBACK_SPDIF_SRC_SELECT 0x71 /* Default: 0000E4E4 */ + /* 0xffffffff -> F33FFFFF */ + /* All bits do not effect playback */ +#define PLAYBACK_SPDIF_USER_DATA0 0x72 /* SPDIF out user data 0 */ +#define PLAYBACK_SPDIF_USER_DATA1 0x73 /* SPDIF out user data 1 */ +/* 0x74-0x75 unknown */ +#define CAPTURE_SPDIF_CONTROL 0x76 /* SPDIF in control setting */ +#define CAPTURE_SPDIF_STATUS 0x77 /* SPDIF in status */ +#define CAPURE_SPDIF_USER_DATA0 0x78 /* SPDIF in user data 0 */ +#define CAPURE_SPDIF_USER_DATA1 0x79 /* SPDIF in user data 1 */ +#define CAPURE_SPDIF_USER_DATA2 0x7a /* SPDIF in user data 2 */ + --- sys/gnu/dev/sound/pci/p17v-alsa.h.orig Thu Jan 1 07:30:00 1970 +++ sys/gnu/dev/sound/pci/p17v-alsa.h Thu Jul 12 12:04:19 2007 @@ -0,0 +1,113 @@ +/*- + * Copyright (c) by James Courtier-Dutton + * Driver p17v chips + * Version: 0.01 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* $FreeBSD: src/sys/gnu/dev/sound/pci/p17v-alsa.h,v 1.1 2006/07/15 19:36:28 netchild Exp $ */ + +/******************************************************************************/ +/* Audigy2Value Tina (P17V) pointer-offset register set, + * accessed through the PTR20 and DATA24 registers */ +/******************************************************************************/ + +/* 00 - 07: Not used */ +#define P17V_PLAYBACK_FIFO_PTR 0x08 /* Current playback fifo pointer + * and number of sound samples in cache. + */ +/* 09 - 12: Not used */ +#define P17V_CAPTURE_FIFO_PTR 0x13 /* Current capture fifo pointer + * and number of sound samples in cache. + */ +/* 14 - 17: Not used */ +#define P17V_PB_CHN_SEL 0x18 /* P17v playback channel select */ +#define P17V_SE_SLOT_SEL_L 0x19 /* Sound Engine slot select low */ +#define P17V_SE_SLOT_SEL_H 0x1a /* Sound Engine slot select high */ +/* 1b - 1f: Not used */ +/* 20 - 2f: Not used */ +/* 30 - 3b: Not used */ +#define P17V_SPI 0x3c /* SPI interface register */ +#define P17V_I2C_ADDR 0x3d /* I2C Address */ +#define P17V_I2C_0 0x3e /* I2C Data */ +#define P17V_I2C_1 0x3f /* I2C Data */ + +#define P17V_START_AUDIO 0x40 /* Start Audio bit */ +/* 41 - 47: Reserved */ +#define P17V_START_CAPTURE 0x48 /* Start Capture bit */ +#define P17V_CAPTURE_FIFO_BASE 0x49 /* Record FIFO base address */ +#define P17V_CAPTURE_FIFO_SIZE 0x4a /* Record FIFO buffer size */ +#define P17V_CAPTURE_FIFO_INDEX 0x4b /* Record FIFO capture index */ +#define P17V_CAPTURE_VOL_H 0x4c /* P17v capture volume control */ +#define P17V_CAPTURE_VOL_L 0x4d /* P17v capture volume control */ +/* 4e - 4f: Not used */ +/* 50 - 5f: Not used */ +#define P17V_SRCSel 0x60 /* SRC48 and SRCMulti sample rate select + * and output select + */ +#define P17V_MIXER_AC97_10K1_VOL_L 0x61 /* 10K to Mixer_AC97 input volume control */ +#define P17V_MIXER_AC97_10K1_VOL_H 0x62 /* 10K to Mixer_AC97 input volume control */ +#define P17V_MIXER_AC97_P17V_VOL_L 0x63 /* P17V to Mixer_AC97 input volume control */ +#define P17V_MIXER_AC97_P17V_VOL_H 0x64 /* P17V to Mixer_AC97 input volume control */ +#define P17V_MIXER_AC97_SRP_REC_VOL_L 0x65 /* SRP Record to Mixer_AC97 input volume control */ +#define P17V_MIXER_AC97_SRP_REC_VOL_H 0x66 /* SRP Record to Mixer_AC97 input volume control */ +/* 67 - 68: Reserved */ +#define P17V_MIXER_Spdif_10K1_VOL_L 0x69 /* 10K to Mixer_Spdif input volume control */ +#define P17V_MIXER_Spdif_10K1_VOL_H 0x6A /* 10K to Mixer_Spdif input volume control */ +#define P17V_MIXER_Spdif_P17V_VOL_L 0x6B /* P17V to Mixer_Spdif input volume control */ +#define P17V_MIXER_Spdif_P17V_VOL_H 0x6C /* P17V to Mixer_Spdif input volume control */ +#define P17V_MIXER_Spdif_SRP_REC_VOL_L 0x6D /* SRP Record to Mixer_Spdif input volume control */ +#define P17V_MIXER_Spdif_SRP_REC_VOL_H 0x6E /* SRP Record to Mixer_Spdif input volume control */ +/* 6f - 70: Reserved */ +#define P17V_MIXER_I2S_10K1_VOL_L 0x71 /* 10K to Mixer_I2S input volume control */ +#define P17V_MIXER_I2S_10K1_VOL_H 0x72 /* 10K to Mixer_I2S input volume control */ +#define P17V_MIXER_I2S_P17V_VOL_L 0x73 /* P17V to Mixer_I2S input volume control */ +#define P17V_MIXER_I2S_P17V_VOL_H 0x74 /* P17V to Mixer_I2S input volume control */ +#define P17V_MIXER_I2S_SRP_REC_VOL_L 0x75 /* SRP Record to Mixer_I2S input volume control */ +#define P17V_MIXER_I2S_SRP_REC_VOL_H 0x76 /* SRP Record to Mixer_I2S input volume control */ +/* 77 - 78: Reserved */ +#define P17V_MIXER_AC97_ENABLE 0x79 /* Mixer AC97 input audio enable */ +#define P17V_MIXER_SPDIF_ENABLE 0x7A /* Mixer SPDIF input audio enable */ +#define P17V_MIXER_I2S_ENABLE 0x7B /* Mixer I2S input audio enable */ +#define P17V_AUDIO_OUT_ENABLE 0x7C /* Audio out enable */ +#define P17V_MIXER_ATT 0x7D /* SRP Mixer Attenuation Select */ +#define P17V_SRP_RECORD_SRR 0x7E /* SRP Record channel source Select */ +#define P17V_SOFT_RESET_SRP_MIXER 0x7F /* SRP and mixer soft reset */ + +#define P17V_AC97_OUT_MASTER_VOL_L 0x80 /* AC97 Output master volume control */ +#define P17V_AC97_OUT_MASTER_VOL_H 0x81 /* AC97 Output master volume control */ +#define P17V_SPDIF_OUT_MASTER_VOL_L 0x82 /* SPDIF Output master volume control */ +#define P17V_SPDIF_OUT_MASTER_VOL_H 0x83 /* SPDIF Output master volume control */ +#define P17V_I2S_OUT_MASTER_VOL_L 0x84 /* I2S Output master volume control */ +#define P17V_I2S_OUT_MASTER_VOL_H 0x85 /* I2S Output master volume control */ +/* 86 - 87: Not used */ +#define P17V_I2S_CHANNEL_SWAP_PHASE_INVERSE 0x88 /* I2S out mono channel swap + * and phase inverse */ +#define P17V_SPDIF_CHANNEL_SWAP_PHASE_INVERSE 0x89 /* SPDIF out mono channel swap + * and phase inverse */ +/* 8A: Not used */ +#define P17V_SRP_P17V_ESR 0x8B /* SRP_P17V estimated sample rate and rate lock */ +#define P17V_SRP_REC_ESR 0x8C /* SRP_REC estimated sample rate and rate lock */ +#define P17V_SRP_BYPASS 0x8D /* srps channel bypass and srps bypass */ +/* 8E - 92: Not used */ +#define P17V_I2S_SRC_SEL 0x93 /* I2SIN mode sel */ + + + + + + --- sys/modules/sound/driver/Makefile.orig Mon Dec 13 19:12:49 2004 +++ sys/modules/sound/driver/Makefile Thu Jul 12 12:04:19 2007 @@ -1,12 +1,14 @@ -# $FreeBSD: src/sys/modules/sound/driver/Makefile,v 1.15.8.1 2004/12/13 11:12:49 yongari Exp $ +# $FreeBSD: src/sys/modules/sound/driver/Makefile,v 1.24 2006/10/01 11:18:56 ariff Exp $ .if ${MACHINE_ARCH} == "sparc64" -SUBDIR = audiocs +SUBDIR = audiocs es137x .else -SUBDIR = als4000 ad1816 cmi cs4281 csa ds1 emu10k1 es137x ess -SUBDIR += fm801 ich maestro maestro3 mss neomagic sb16 sb8 sbc solo -SUBDIR += t4dwave via8233 via82c686 vibes +SUBDIR = ad1816 als4000 atiixp cmi cs4281 csa ds1 emu10k1 emu10kx envy24 +SUBDIR += envy24ht es137x ess fm801 hda ich maestro maestro3 mss neomagic +SUBDIR += sb16 sb8 sbc solo spicds t4dwave via8233 via82c686 vibes SUBDIR += driver uaudio .endif + +SUBDIR += null .include --- sys/modules/sound/driver/atiixp/Makefile.orig Thu Jan 1 07:30:00 1970 +++ sys/modules/sound/driver/atiixp/Makefile Thu Jul 12 12:04:19 2007 @@ -0,0 +1,9 @@ +# $FreeBSD: src/sys/modules/sound/driver/atiixp/Makefile,v 1.1 2005/11/27 03:29:59 ariff Exp $ + +.PATH: ${.CURDIR}/../../../../dev/sound/pci + +KMOD= snd_atiixp +SRCS= device_if.h bus_if.h pci_if.h +SRCS+= atiixp.c + +.include --- sys/modules/sound/driver/audiocs/Makefile.orig Mon Dec 13 19:12:49 2004 +++ sys/modules/sound/driver/audiocs/Makefile Mon Oct 25 18:29:57 2004 @@ -1,4 +1,4 @@ -# $FreeBSD: src/sys/modules/sound/driver/audiocs/Makefile,v 1.1.2.1 2004/12/13 11:12:49 yongari Exp $ +# $FreeBSD: src/sys/modules/sound/driver/audiocs/Makefile,v 1.1 2004/10/25 10:29:57 yongari Exp $ .PATH: ${.CURDIR}/../../../../dev/sound/sbus --- sys/modules/sound/driver/cmi/Makefile.orig Fri Feb 7 21:56:31 2003 +++ sys/modules/sound/driver/cmi/Makefile Thu Jul 12 12:04:19 2007 @@ -1,9 +1,10 @@ -# $FreeBSD: src/sys/modules/sound/driver/cmi/Makefile,v 1.3 2003/02/07 13:56:31 nyan Exp $ +# $FreeBSD: src/sys/modules/sound/driver/cmi/Makefile,v 1.4 2006/05/27 16:32:04 netchild Exp $ .PATH: ${.CURDIR}/../../../../dev/sound/pci KMOD= snd_cmi SRCS= device_if.h bus_if.h pci_if.h +SRCS+= mpufoi_if.h SRCS+= cmi.c .include --- sys/modules/sound/driver/emu10k1/Makefile.orig Sun Jan 11 18:30:56 2004 +++ sys/modules/sound/driver/emu10k1/Makefile Thu Jul 12 12:04:19 2007 @@ -1,10 +1,11 @@ -# $FreeBSD: src/sys/modules/sound/driver/emu10k1/Makefile,v 1.4 2004/01/11 10:30:56 obrien Exp $ +# $FreeBSD: src/sys/modules/sound/driver/emu10k1/Makefile,v 1.5 2006/05/27 16:32:04 netchild Exp $ .PATH: ${.CURDIR}/../../../../dev/sound/pci \ ${.CURDIR}/../../../../gnu/dev/sound/pci KMOD= snd_emu10k1 SRCS= device_if.h bus_if.h pci_if.h emu10k1-alsa%diked.h +SRCS+= mpufoi_if.h SRCS+= emu10k1.c CLEANFILES+= emu10k1-alsa%diked.h --- sys/modules/sound/driver/emu10kx/Makefile.orig Thu Jan 1 07:30:00 1970 +++ sys/modules/sound/driver/emu10kx/Makefile Thu Jul 12 12:04:19 2007 @@ -0,0 +1,51 @@ +# $FreeBSD: src/sys/modules/sound/driver/emu10kx/Makefile,v 1.3 2007/01/07 19:43:59 netchild Exp $ +.PATH: ${.CURDIR}/../../../../dev/sound/pci \ + ${.CURDIR}/../../../../gnu/dev/sound/pci + +WARNS?= 2 ## because sound is WARNS=2 only +## WARNS=3 fails on _class.refs in -pcm.c +## WARNS=4 fails on min/max in sound headers +## otherwise it should be WARNS=6 clean +KMOD= snd_emu10kx + +SRCS= device_if.h bus_if.h pci_if.h +SRCS+= isa_if.h channel_if.h ac97_if.h mixer_if.h mpufoi_if.h +SRCS+= vnode_if.h opt_emu10kx.h +# Master, PCM and MIDI devices +SRCS+= emu10kx.c +SRCS+= emu10kx-pcm.c +SRCS+= emu10kx-midi.c +# de-GPLed Makefiles +SRCS+= emu10k1-alsa%diked.h +SRCS+= p16v-alsa%diked.h +SRCS+= p17v-alsa%diked.h + +emu10k1-alsa%diked.h: emu10k1-alsa.h + grep -v '#include' ${.OODATE} | $(CC) -E -D__KERNEL__ -dM - \ + | awk -F"[ (]" '/define/ \ + { print "#ifndef " $$2 ; print ; print "#endif" }' \ + >${.TARGET} +p16v-alsa%diked.h: p16v-alsa.h + grep -v '#include' ${.OODATE} | $(CC) -E -D__KERNEL__ -dM - \ + | awk -F"[ (]" '/define/ \ + { print "#ifndef " $$2 ; print ; print "#endif" }' \ + >${.TARGET} +p17v-alsa%diked.h: p17v-alsa.h + grep -v '#include' ${.OODATE} | $(CC) -E -D__KERNEL__ -dM - \ + | awk -F"[ (]" '/define/ \ + { print "#ifndef " $$2 ; print ; print "#endif" }' \ + >${.TARGET} + +CLEANFILES+= emu10k1-alsa%diked.h +CLEANFILES+= p16v-alsa%diked.h +CLEANFILES+= p17v-alsa%diked.h + +.if !defined(KERNBUILDDIR) +opt_emu10kx.h: + echo "#define SND_EMU10KX_MULTICHANNEL" > opt_emu10kx.h + echo "#undef SND_EMU10KX_MCH_RECORDING" >> opt_emu10kx.h + echo "#undef SND_EMU10KX_DEBUG_OUTPUTS" >> opt_emu10kx.h + echo "#undef SND_EMU10KX_DEBUG" >> opt_emu10kx.h +.endif + +.include --- sys/modules/sound/driver/envy24/Makefile.orig Thu Jan 1 07:30:00 1970 +++ sys/modules/sound/driver/envy24/Makefile Thu Jul 12 12:04:19 2007 @@ -0,0 +1,9 @@ +# $FreeBSD: src/sys/modules/sound/driver/envy24/Makefile,v 1.3 2006/09/30 18:12:32 netchild Exp $ + +.PATH: ${.CURDIR}/../../../../dev/sound/pci + +KMOD= snd_envy24 +SRCS= device_if.h bus_if.h pci_if.h +SRCS+= envy24.c + +.include --- sys/modules/sound/driver/envy24ht/Makefile.orig Thu Jan 1 07:30:00 1970 +++ sys/modules/sound/driver/envy24ht/Makefile Thu Jul 12 12:04:19 2007 @@ -0,0 +1,9 @@ +# $FreeBSD: src/sys/modules/sound/driver/envy24ht/Makefile,v 1.2 2006/09/30 18:12:32 netchild Exp $ + +.PATH: ${.CURDIR}/../../../../dev/sound/pci + +KMOD= snd_envy24ht +SRCS= device_if.h bus_if.h pci_if.h +SRCS+= envy24ht.c + +.include --- sys/modules/sound/driver/ess/Makefile.orig Wed Jan 23 11:32:36 2002 +++ sys/modules/sound/driver/ess/Makefile Thu Jul 12 12:04:19 2007 @@ -1,9 +1,9 @@ -# $FreeBSD: src/sys/modules/sound/driver/ess/Makefile,v 1.3 2002/01/23 03:32:36 cg Exp $ +# $FreeBSD: src/sys/modules/sound/driver/ess/Makefile,v 1.4 2006/05/12 18:05:35 ariff Exp $ .PATH: ${.CURDIR}/../../../../dev/sound/isa KMOD= snd_ess SRCS= device_if.h bus_if.h isa_if.h pci_if.h -SRCS+= ess.c es1888.c +SRCS+= ess.c .include --- sys/modules/sound/driver/hda/Makefile.orig Thu Jan 1 07:30:00 1970 +++ sys/modules/sound/driver/hda/Makefile Thu Jul 12 12:04:19 2007 @@ -0,0 +1,9 @@ +# $FreeBSD: src/sys/modules/sound/driver/hda/Makefile,v 1.1 2006/10/01 11:13:00 ariff Exp $ + +.PATH: ${.CURDIR}/../../../../dev/sound/pci/hda + +KMOD= snd_hda +SRCS= device_if.h bus_if.h pci_if.h channel_if.h mixer_if.h +SRCS+= hdac.c hdac_private.h hdac_reg.h hda_reg.h hdac.h + +.include --- sys/modules/sound/driver/maestro/Makefile.orig Fri Feb 7 21:56:32 2003 +++ sys/modules/sound/driver/maestro/Makefile Thu Jan 27 00:29:07 2005 @@ -1,9 +1,10 @@ -# $FreeBSD: src/sys/modules/sound/driver/maestro/Makefile,v 1.3 2003/02/07 13:56:32 nyan Exp $ +# $FreeBSD: src/sys/modules/sound/driver/maestro/Makefile,v 1.4 2005/01/26 16:29:07 imp Exp $ .PATH: ${.CURDIR}/../../../../dev/sound/pci KMOD= snd_maestro SRCS= device_if.h bus_if.h pci_if.h SRCS+= maestro.c +WERROR= .include --- sys/modules/sound/driver/null/Makefile.orig Thu Jan 1 07:30:00 1970 +++ sys/modules/sound/driver/null/Makefile Thu Jul 12 12:04:19 2007 @@ -0,0 +1,9 @@ +# $FreeBSD$ + +.PATH: ${.CURDIR}/../../../../dev/sound + +KMOD= snd_null +SRCS= device_if.h bus_if.h +SRCS+= null.c + +.include --- sys/modules/sound/driver/spicds/Makefile.orig Thu Jan 1 07:30:00 1970 +++ sys/modules/sound/driver/spicds/Makefile Thu Jul 12 12:04:19 2007 @@ -0,0 +1,9 @@ +# $FreeBSD: src/sys/modules/sound/driver/spicds/Makefile,v 1.2 2006/09/30 18:12:33 netchild Exp $ + +.PATH: ${.CURDIR}/../../../../dev/sound/pci + +KMOD= snd_spicds +SRCS= device_if.h bus_if.h isa_if.h pci_if.h +SRCS+= spicds.c + +.include --- sys/modules/sound/driver/uaudio/Makefile.orig Fri Feb 7 21:56:33 2003 +++ sys/modules/sound/driver/uaudio/Makefile Wed Dec 29 16:50:35 2004 @@ -1,9 +1,9 @@ -# $FreeBSD: src/sys/modules/sound/driver/uaudio/Makefile,v 1.2 2003/02/07 13:56:33 nyan Exp $ +# $FreeBSD: src/sys/modules/sound/driver/uaudio/Makefile,v 1.3 2004/12/29 08:50:35 imp Exp $ .PATH: ${.CURDIR}/../../../../dev/sound/usb KMOD= snd_uaudio -SRCS= device_if.h bus_if.h opt_usb.h vnode_if.h +SRCS= device_if.h bus_if.h opt_usb.h SRCS+= uaudio.c uaudio_pcm.c .include --- sys/modules/sound/sound/Makefile.orig Fri Jul 16 11:58:25 2004 +++ sys/modules/sound/sound/Makefile Thu Jul 12 12:04:19 2007 @@ -1,17 +1,41 @@ -# $FreeBSD: src/sys/modules/sound/sound/Makefile,v 1.15 2004/07/16 03:58:25 tanimura Exp $ +# $FreeBSD: src/sys/modules/sound/sound/Makefile,v 1.21 2007/05/31 18:43:33 ariff Exp $ +.PATH: ${.CURDIR}/../../../dev/sound .PATH: ${.CURDIR}/../../../dev/sound/pcm +.PATH: ${.CURDIR}/../../../dev/sound/midi .PATH: ${.CURDIR}/../../../dev/sound/isa KMOD= sound -SRCS= device_if.h bus_if.h isa_if.h pci_if.h +SRCS= device_if.h bus_if.h isa_if.h pci_if.h opt_isa.h SRCS+= ac97_if.h channel_if.h feeder_if.h mixer_if.h SRCS+= ac97_if.c channel_if.c feeder_if.c mixer_if.c -SRCS+= ac97.c ac97_patch.c buffer.c channel.c dsp.c -SRCS+= fake.c feeder.c feeder_fmt.c feeder_rate.c -SRCS+= mixer.c sndstat.c sound.c vchan.c -SRCS+= sndbuf_dma.c +SRCS+= mpu_if.h mpufoi_if.h synth_if.h +SRCS+= mpu_if.c mpufoi_if.c synth_if.c +SRCS+= ac97.c ac97_patch.c buffer.c channel.c clone.c dsp.c +SRCS+= fake.c feeder.c feeder_fmt.c feeder_rate.c feeder_volume.c +SRCS+= mixer.c sndstat.c sound.c unit.c vchan.c +SRCS+= midi.c mpu401.c sequencer.c EXPORT_SYMS= YES # XXX evaluate + +.if ${MACHINE_ARCH} == "sparc64" +# Create an empty opt_isa.h in order to keep kmod.mk from linking in an +# existing one from KERNBUILDDIR which possibly has DEV_ISA defined so +# sound.ko is always built without isadma support. +opt_isa.h: + :> ${.TARGET} +.else +.if !defined(KERNBUILDDIR) +SRCS+= sndbuf_dma.c + +opt_isa.h: + echo "#define DEV_ISA 1" > ${.TARGET} +.else +DEV_ISA!= sed -n '/DEV_ISA/p' ${KERNBUILDDIR}/opt_isa.h +.if !empty(DEV_ISA) +SRCS+= sndbuf_dma.c +.endif +.endif +.endif .include --- share/man/man4/Makefile.orig Tue Oct 4 05:55:14 2005 +++ share/man/man4/Makefile Tue Apr 17 21:52:37 2007 @@ -267,21 +267,27 @@ sn.4 \ snd_ad1816.4 \ snd_als4000.4 \ + snd_atiixp.4 \ snd_cmi.4 \ snd_cs4281.4 \ snd_csa.4 \ snd_ds1.4 \ snd_emu10k1.4 \ + snd_emu10kx.4 \ + snd_envy24.4 \ + snd_envy24ht.4 \ snd_es137x.4 \ snd_ess.4 \ snd_fm801.4 \ snd_gusc.4 \ + snd_hda.4 \ snd_ich.4 \ snd_maestro.4 \ snd_maestro3.4 \ snd_neomagic.4 \ snd_sbc.4 \ snd_solo.4 \ + snd_spicds.4 \ snd_uaudio.4 \ snd_via8233.4 \ snd_via82c686.4 \ @@ -437,6 +443,9 @@ MLINKS+=sl.4 if_sl.4 MLINKS+=smp.4 SMP.4 MLINKS+=sn.4 if_sn.4 +MLINKS+=snd_envy24.4 snd_ak452x.4 +MLINKS+=snd_sbc.4 snd_sb16.4 \ + snd_sbc.4 snd_sb8.4 MLINKS+=splash.4 screensaver.4 MLINKS+=ste.4 if_ste.4 MLINKS+=stf.4 if_stf.4 --- share/man/man4/pcm.4.orig Thu Dec 8 09:53:21 2005 +++ share/man/man4/pcm.4 Tue Apr 17 21:46:21 2007 @@ -23,9 +23,9 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD: src/share/man/man4/pcm.4,v 1.36.2.3 2005/12/08 01:53:21 jcamou Exp $ +.\" $FreeBSD: src/share/man/man4/pcm.4,v 1.52 2007/06/23 14:34:30 joel Exp $ .\" -.Dd May 22, 2005 +.Dd June 23, 2007 .Dt SOUND 4 .Os .Sh NAME @@ -36,16 +36,20 @@ .Fx PCM audio device infrastructure .Sh SYNOPSIS -For a card with bridge driver support, and a PnP card: +To compile this driver into the kernel, place the following line in your +kernel configuration file: +.Bd -ragged -offset indent .Cd "device sound" +.Ed .Pp -For a card without bridge driver support, and a non-PnP card, -the following lines may be required in -.Pa /boot/device.hints : -.Cd hint.pcm.0.at="isa" -.Cd hint.pcm.0.irq="5" -.Cd hint.pcm.0.drq="1" -.Cd hint.pcm.0.flags="0x0" +Non-PnP sound cards require the following lines in +.Xr device.hints 5 : +.Bd -literal -offset indent +hint.pcm.0.at="isa" +hint.pcm.0.irq="5" +hint.pcm.0.drq="1" +hint.pcm.0.flags="0x0" +.Ed .Sh DESCRIPTION .Bf -emphasis Note: There exists some ambiguity in the naming at the moment @@ -64,10 +68,10 @@ audio play and capture. This driver also supports various .Tn PCI , +.Tn ISA , .Tn WSS/MSS -compatible, -.Tn ISA -sound cards, and AC97 mixer. +compatible +sound cards, AC97 mixer and High Definition Audio. Once the .Nm driver attaches, supported devices provide audio record and @@ -78,7 +82,7 @@ .Dq VCHAN and rate conversion .Dq soft formats . -True full duplex operation is available on most cards. +True full duplex operation is available on most sound cards. .Pp If the sound card is supported by a bridge driver, the .Nm @@ -92,13 +96,6 @@ .Tn DMA channel, or to 0x10 + C to specify channel C. .Pp -The driver works best with -.Tn WSS/MSS -cards, which have a very clean -architecture and an orthogonal set of features. -They also happen to be -among the cheapest audio cards on the market. -.Pp The driver does its best to recognize the installed hardware and drive it correctly so the user is not required to add several lines in .Pa /boot/device.hints . @@ -155,7 +152,7 @@ .Va hint.pcm.0.line Ns = Ns Qq Li 0 . This will mute the input channel per default. .Ss VCHANs -Each device can optionally support more playback channels +Each device can optionally support more playback and recording channels than physical hardware provides by using .Dq virtual channels or @@ -165,42 +162,56 @@ .Xr sysctl 8 interface but can only be manipulated while the device is inactive. .Ss Runtime Configuration -The following +There are a number of .Xr sysctl 8 -variables are available: +variables available. +.Va hw.snd.* +tunables are global settings and +.Va dev.pcm.* +are device specific. .Bl -tag -width ".Va hw.snd.report_soft_formats" -offset indent -.It Va hw.snd.pcm%d.buffersize -Configure the amount of -.Tn DMA -bufferspace available for a device. -.It Va hw.snd.targetirqrate -Set the default block size such that continuous -playback will achieve this -.Tn IRQ -rate. -This value can be tuned to improve application performance. -Increase this value when the sound lags and decrease -it if sound stutters or breaks up. -.It Va hw.snd.unit -When using -.Xr devfs 5 , -the default device for -.Pa /dev/dsp . -Equivalent to a symlink from -.Pa /dev/dsp -to -.Pa /dev/dsp Ns Va ${hw.snd.unit} . +.It Va hw.snd.latency_profile +Define sets of buffering latency conversion tables for the +.Va hw.snd.latency +tunable. +A value of 0 will use a low and aggressive latency profile which can result +in possible underruns if the application cannot keep up with a rapid irq +rate, especially during high workload. +The default value is 1, which is considered a moderate/safe latency profile. +.It Va hw.snd.latency +Configure the buffering latency. +Only affects applications that do not explicitly request +blocksize / fragments. +This tunable provides finer granularity than the +.Va hw.snd.latency_profile +tunable. +Possible values range between 0 (lowest latency) and 10 (highest latency). .It Va hw.snd.report_soft_formats Controls the internal format conversion if it is available transparently to the application software. When disabled or not available, the application will only be able to select formats the device natively supports. +.It Va hw.snd.compat_linux_mmap +Enable to allow PROT_EXEC page mappings. +All Linux applications using sound and +.Xr mmap 2 +require this. +.It Va hw.snd.feeder_rate_round +Sample rate rounding threshold, to avoid large prime division at the +cost of accuracy. +All requested sample rates will be rounded to the nearest threshold value. +Possible values range between 0 (disabled) and 500. +Default is 25. +.It Va hw.snd.feeder_rate_max +Maximum allowable sample rate. +.It Va hw.snd.feeder_rate_min +Minimum allowable sample rate. .It Va hw.snd.verbose Level of verbosity for the .Pa /dev/sndstat device. Higher values include more output and the highest level, -three, should be used when reporting problems. +four, should be used when reporting problems. Other options include: .Bl -tag -width 2n .It 0 @@ -213,12 +224,14 @@ current format, speed, and pseudo device statistics such as buffer overruns and buffer underruns. .It 3 -File names and versions of the currently sound loaded modules. +File names and versions of the currently loaded sound modules. +.It 4 +Various messages intended for debugging. .El .It Va hw.snd.maxautovchans Global .Tn VCHAN -setting that only affects devices that have only one playback channel. +setting that only affects devices with at least one playback or recording channel available. The sound system will dynamically create up this many .Tn VCHANs . Set to @@ -226,7 +239,21 @@ if no .Tn VCHANS are desired. -.It Va hw.snd.pcm%d.vchans +Maximum value is 256. +.It Va hw.snd.default_unit +Default sound card for systems with multiple sound cards. +When using +.Xr devfs 5 , +the default device for +.Pa /dev/dsp . +Equivalent to a symlink from +.Pa /dev/dsp +to +.Pa /dev/dsp Ns Va ${hw.snd.default_unit} . +.It Va hw.snd.default_auto +Enable to automatically assign default sound unit to the most recent +attached device. +.It Va dev.pcm.%d.[play|rec].vchans The current number of .Tn VCHANs allocated per device. @@ -237,11 +264,29 @@ will disable .Tn VCHANs for this device. +.It Va dev.pcm.%d.[play|rec].vchanrate +Sample rate speed for +.Tn VCHAN +mixing. +All playback paths will be converted to this sample rate before the mixing +process begins. +.It Va dev.pcm.%d.[play|rec].vchanformat +Format for +.Tn VCHAN +mixing. +All playback paths will be converted to this format before the mixing +process begins. +.It Va dev.pcm.%d.polling +Experimental polling mode support where the driver operates by querying the +device state on each tick using a +.Xr callout 9 +mechanism. +Disabled by default and currently only available for a few device drivers. .El .Ss Recording Channels On devices that have more than one recording source (ie: mic and line), there is a corresponding -.Pa /dev/dspr%d.%d +.Pa /dev/dsp%d.r%d device. .Ss Statistics Channel statistics are only kept while the device is open. @@ -266,33 +311,6 @@ for a complete list of the supported .Fn ioctl functions. -.Sh HARDWARE -The -.Nm -driver supports the following sound cards: -.Pp -.Bl -bullet -compact -.It -CS4231, CS4232, CS4236, CS4237 (ISA) -.It -Creative Labs SoundBlaster PCI -.It -ENSONIQ AudioPCI ES1370/1371 -.It -ESS Solo-1/1E (PCI) -.It -Intel 443MX, 810, 815, and 815E integrated sound devices -.It -MSS/WSS Compatible DSPs -.It -NeoMagic 256AV/ZK (PCI) -.It -OPTi931/82C931 (ISA) -.It -Trident 4DWave DX/NX (PCI) -.It -Yamaha OPL-SAx (ISA) -.El .Sh FILES The .Nm @@ -308,8 +326,14 @@ Like .Pa /dev/dsp , but 16 bits per sample. -.It Pa /dev/dspr%d.%d -Should be connected to a record codec. +.It Pa /dev/dsp%d.p%d +Playback channel. +.It Pa /dev/dsp%d.r%d +Record channel. +.It Pa /dev/dsp%d.vp%d +Virtual playback channel. +.It Pa /dev/dsp%d.vr%d +Virtual recording channel. .It Pa /dev/sndstat Current .Nm @@ -329,56 +353,56 @@ device is probed and attached, these messages can be viewed with the .Xr dmesg 8 utility. +.Pp +The above device nodes are only created on demand through the dynamic +.Xr devfs 5 +clone handler. +Users are strongly discouraged to access them directly. +For specific sound card access, please instead use +.Pa /dev/dsp +or +.Pa /dev/dsp%d . .Sh DIAGNOSTICS .Bl -diag -.It ac97: dac not ready -AC97 codec is not likely to be accompanied with the sound card. +.It pcm%d:play:%d:dsp%d.p%d: play interrupt timeout, channel dead +The hardware does not generate interrupts to serve incoming (play) +or outgoing (record) data. .It unsupported subdevice XX A device node is not created properly. .El -.Sh BUGS -Some features of your cards (e.g., global volume control) might not -be supported on all devices. -.Sh HISTORY -The -.Nm -device driver first appeared in -.Fx 2.2.6 -as -.Nm pcm , -written by -.An Luigi Rizzo . -It was later -rewritten in -.Fx 4.0 -by -.An Cameron Grant . -The API evolved from the VOXWARE -standard which later became OSS standard. .Sh SEE ALSO .Xr snd_ad1816 4 , .Xr snd_als4000 4 , +.Xr snd_atiixp 4 , .Xr snd_audiocs 4 , .Xr snd_cmi 4 , .Xr snd_cs4281 4 , .Xr snd_csa 4 , .Xr snd_ds1 4 , .Xr snd_emu10k1 4 , +.Xr snd_emu10kx 4 , +.Xr snd_envy24 4 , +.Xr snd_envy24ht 4 , .Xr snd_es137x 4 , .Xr snd_ess 4 , .Xr snd_fm801 4 , .Xr snd_gusc 4 , +.Xr snd_hda 4 , .Xr snd_ich 4 , .Xr snd_maestro 4 , .Xr snd_maestro3 4 , +.Xr snd_mss 4 , .Xr snd_neomagic 4 , .Xr snd_sbc 4 , .Xr snd_solo 4 , +.Xr snd_spicds 4 , +.Xr snd_t4dwave 4 , .Xr snd_uaudio 4 , .Xr snd_via8233 4 , .Xr snd_via82c686 4 , .Xr snd_vibes 4 , .Xr devfs 5 , +.Xr device.hints 5 , .Xr loader.conf 5 , .Xr dmesg 8 , .Xr kldload 8 , @@ -387,6 +411,22 @@ .%T "The OSS API" .%O "http://www.opensound.com/pguide/oss.pdf" .Re +.Sh HISTORY +The +.Nm +device driver first appeared in +.Fx 2.2.6 +as +.Nm pcm , +written by +.An Luigi Rizzo . +It was later +rewritten in +.Fx 4.0 +by +.An Cameron Grant . +The API evolved from the VOXWARE +standard which later became OSS standard. .Sh AUTHORS .An -nosplit .An Luigi Rizzo Aq luigi@iet.unipi.it @@ -400,3 +440,6 @@ revised this manual page. It was then rewritten for .Fx 5.2 . +.Sh BUGS +Some features of your sound card (e.g., global volume control) might not +be supported on all devices. --- share/man/man4/snd_ad1816.4.orig Wed May 25 23:41:28 2005 +++ share/man/man4/snd_ad1816.4 Tue Apr 17 21:46:21 2007 @@ -22,22 +22,37 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD: src/share/man/man4/snd_ad1816.4,v 1.2.2.4 2005/05/25 15:41:28 brueffer Exp $ +.\" $FreeBSD: src/share/man/man4/snd_ad1816.4,v 1.6 2005/12/15 20:25:41 joel Exp $ .\" -.Dd August 27, 2004 +.Dd December 15, 2005 .Dt SND_AD1816 4 .Os .Sh NAME .Nm snd_ad1816 .Nd "Analog Devices AD1816 ISA bridge device driver" .Sh SYNOPSIS +To compile this driver into the kernel, place the following lines in your +kernel configuration file: +.Bd -ragged -offset indent .Cd "device sound" -.Cd "device \*[q]snd_ad1816\*[q]" +.Cd "device snd_ad1816" +.Ed .Pp -.Cd hint.pcm.0.at="isa" -.Cd hint.pcm.0.irq="10" -.Cd hint.pcm.0.drq="1" -.Cd hint.pcm.0.flags="0x0" +Alternatively, to load the driver as a module at boot time, place the +following line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +snd_ad1816_load="YES" +.Ed +.Pp +Non-PnP cards require the following lines in +.Xr device.hints 5 : +.Bd -literal -offset indent +hint.pcm.0.at="isa" +hint.pcm.0.irq="10" +hint.pcm.0.drq="1" +hint.pcm.0.flags="0x0" +.Ed .Sh DESCRIPTION The .Nm @@ -58,8 +73,8 @@ .Sh HISTORY The .Nm -manual page first appeared in -.Fx 5.3 . +device driver first appeared in +.Fx 4.0 . .Sh AUTHORS .An "Cameron Grant" Aq cg@FreeBSD.org .An "Luigi Rizzo" Aq luigi@FreeBSD.org --- share/man/man4/snd_als4000.4.orig Wed May 25 23:41:28 2005 +++ share/man/man4/snd_als4000.4 Tue Apr 17 21:46:21 2007 @@ -22,17 +22,28 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD: src/share/man/man4/snd_als4000.4,v 1.2.2.4 2005/05/25 15:41:28 brueffer Exp $ +.\" $FreeBSD: src/share/man/man4/snd_als4000.4,v 1.7 2005/12/15 20:25:41 joel Exp $ .\" -.Dd August 27, 2004 +.Dd December 15, 2005 .Dt SND_ALS4000 4 .Os .Sh NAME .Nm snd_als4000 .Nd "Avance Logic ALS4000 PCI bridge device driver" .Sh SYNOPSIS +To compile this driver into the kernel, place the following lines in your +kernel configuration file: +.Bd -ragged -offset indent .Cd "device sound" -.Cd "device \*[q]snd_als4000\*[q]" +.Cd "device snd_als4000" +.Ed +.Pp +Alternatively, to load the driver as a module at boot time, place the +following line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +snd_als4000_load="YES" +.Ed .Sh DESCRIPTION The .Nm @@ -46,14 +57,14 @@ .Pp .Bl -bullet -compact .It -Advance Logic ALS4000 +Avance Logic ALS4000 .El .Sh SEE ALSO .Xr sound 4 .Sh HISTORY The .Nm -manual page first appeared in -.Fx 5.3 . +device driver first appeared in +.Fx 4.4 . .Sh AUTHORS .An "Orion Hodson" Aq oho@acm.org --- share/man/man4/snd_atiixp.4.orig Thu Jan 1 07:30:00 1970 +++ share/man/man4/snd_atiixp.4 Tue Apr 17 21:46:21 2007 @@ -0,0 +1,100 @@ +.\" Copyright (c) 2005 Joel Dahl +.\" 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/share/man/man4/snd_atiixp.4,v 1.5 2007/05/12 06:33:52 brueffer Exp $ +.\" +.Dd November 29, 2006 +.Dt SND_ATIIXP 4 +.Os +.Sh NAME +.Nm snd_atiixp +.Nd "ATI IXP bridge device driver" +.Sh SYNOPSIS +To compile this driver into the kernel, place the following lines in your +kernel configuration file: +.Bd -ragged -offset indent +.Cd "device sound" +.Cd "device snd_atiixp" +.Ed +.Pp +Alternatively, to load the driver as a module at boot time, place the +following line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +snd_atiixp_load="YES" +.Ed +.Sh DESCRIPTION +The +.Nm +bridge driver allows the generic audio driver, +.Xr sound 4 , +to attach to ATI IXP audio devices. +This driver supports 16bit playback and recording, and 32bit native playback +and recording. +.Ss Runtime Configuration +The following +.Xr sysctl 8 +variables are available in addition to those available to all +.Xr sound 4 +devices: +.Bl -tag -width ".Va dev.pcm.%d.polling" -offset indent +.It Va dev.pcm.%d.polling +Experimental polling mode, where the driver operates by querying the device +state on each tick using +.Xr callout 9 . +Polling is disabled by default. +Do not enable it unless you are facing weird interrupt problems or if the +device cannot generate interrupts at all. +.El +.Sh HARDWARE +The +.Nm +driver supports the following audio chipsets: +.Pp +.Bl -bullet -compact +.It +ATI IXP 200 +.It +ATI IXP 300 +.It +ATI IXP 400 +.El +.Sh SEE ALSO +.Xr sound 4 +.Sh HISTORY +The +.Nm +device driver first appeared in +.Fx 6.1 . +.Sh AUTHORS +This manual page was written by +.An Joel Dahl Aq joel@FreeBSD.org . +.Sh BUGS +The +.Nm +driver +does not support S/PDIF, but implementing it should be fairly easy if the +right hardware is available. +.Pp +32bit native recording is broken on some hardware. --- share/man/man4/snd_cmi.4.orig Wed May 25 23:41:28 2005 +++ share/man/man4/snd_cmi.4 Tue Apr 17 21:46:21 2007 @@ -22,17 +22,28 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD: src/share/man/man4/snd_cmi.4,v 1.2.2.3 2005/05/25 15:41:28 brueffer Exp $ +.\" $FreeBSD: src/share/man/man4/snd_cmi.4,v 1.7 2005/12/15 20:25:41 joel Exp $ .\" -.Dd August 27, 2004 +.Dd December 15, 2005 .Dt SND_CMI 4 .Os .Sh NAME .Nm snd_cmi .Nd "CMedia CMI8338/CMI8738 PCI bridge device driver" .Sh SYNOPSIS +To compile this driver into the kernel, place the following lines in your +kernel configuration file: +.Bd -ragged -offset indent .Cd "device sound" .Cd "device snd_cmi" +.Ed +.Pp +Alternatively, to load the driver as a module at boot time, place the +following line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +snd_cmi_load="YES" +.Ed .Sh DESCRIPTION The .Nm @@ -46,16 +57,20 @@ .Pp .Bl -bullet -compact .It -CMedia CMI8338 +CMedia CMI8338A +.It +CMedia CMI8338B .It CMedia CMI8738 +.It +CMedia CMI8738B .El .Sh SEE ALSO .Xr sound 4 .Sh HISTORY The .Nm -manual page first appeared in -.Fx 5.3 . +device driver first appeared in +.Fx 4.3 . .Sh AUTHORS .An "Orion Hodson" Aq O.Hodson@cs.ucl.ac.uk --- share/man/man4/snd_cs4281.4.orig Wed May 25 23:41:28 2005 +++ share/man/man4/snd_cs4281.4 Tue Apr 17 21:46:21 2007 @@ -22,17 +22,28 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD: src/share/man/man4/snd_cs4281.4,v 1.2.2.4 2005/05/25 15:41:28 brueffer Exp $ +.\" $FreeBSD: src/share/man/man4/snd_cs4281.4,v 1.6 2005/12/15 20:25:41 joel Exp $ .\" -.Dd August 27, 2004 +.Dd December 15, 2005 .Dt SND_CS4281 4 .Os .Sh NAME .Nm snd_cs4281 .Nd "Crystal Semiconductor CS4281 PCI bridge device driver" .Sh SYNOPSIS +To compile this driver into the kernel, place the following lines in your +kernel configuration file: +.Bd -ragged -offset indent .Cd "device sound" -.Cd "device \*[q]snd_cs4281\*[q]" +.Cd "device snd_cs4281" +.Ed +.Pp +Alternatively, to load the driver as a module at boot time, place the +following line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +snd_cs4281_load="YES" +.Ed .Sh DESCRIPTION The .Nm @@ -53,7 +64,7 @@ .Sh HISTORY The .Nm -manual page first appeared in -.Fx 5.3 . +device driver first appeared in +.Fx 4.3 . .Sh AUTHORS .An "Orion Hodson" Aq O.Hodson@cs.ucl.ac.uk --- share/man/man4/snd_csa.4.orig Wed May 25 23:41:28 2005 +++ share/man/man4/snd_csa.4 Tue Apr 17 21:46:21 2007 @@ -23,21 +23,32 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD: src/share/man/man4/snd_csa.4,v 1.11.2.2 2005/05/25 15:41:28 brueffer Exp $ +.\" $FreeBSD: src/share/man/man4/snd_csa.4,v 1.16 2006/06/18 17:53:04 brueffer Exp $ .\" -.Dd August 28, 2004 +.Dd December 15, 2005 .Dt SND_CSA 4 .Os .Sh NAME .Nm snd_csa .Nd Crystal Semiconductor CS461x/462x/4280 PCI bridge device driver .Sh SYNOPSIS +To compile this driver into the kernel, place the following lines in your +kernel configuration file: +.Bd -ragged -offset indent .Cd "device sound" .Cd "device snd_csa" +.Ed +.Pp +Alternatively, to load the driver as a module at boot time, place the +following line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +snd_csa_load="YES" +.Ed .Sh DESCRIPTION The .Nm -bridge driver allows the generic audio drivers including +bridge driver allows the generic audio driver .Xr sound 4 to attach to Crystal Semiconductor CS461x/462x/4280 based sound cards. .Sh HARDWARE @@ -47,9 +58,27 @@ .Pp .Bl -bullet -compact .It -Crystal Semiconductor CS461x/462x Audio Accelerator +Crystal Semiconductor CS4280 +.It +Crystal Semiconductor CS4610 +.It +Crystal Semiconductor CS4611 +.It +Crystal Semiconductor CS4614 +.It +Crystal Semiconductor CS4615 +.It +Crystal Semiconductor CS4622 +.It +Crystal Semiconductor CS4624 .It -Crystal Semiconductor CS4280 Audio Controller +Crystal Semiconductor CS4630 +.It +Genius Soundmaker 128 Value +.It +Hercules Game Theatre XP +.It +Turtle Beach Santa Cruz .El .Pp Some onboard CS4610 chips are accompanied by the CS423x ISA codec @@ -58,12 +87,12 @@ supported by the .Nm driver yet. +.Sh SEE ALSO +.Xr sound 4 .Sh HISTORY The .Nm device driver first appeared in .Fx 4.0 . -.Sh SEE ALSO -.Xr sound 4 .Sh AUTHORS .An Seigo Tanimura Aq tanimura@r.dl.itc.u-tokyo.ac.jp --- share/man/man4/snd_ds1.4.orig Wed May 25 23:41:28 2005 +++ share/man/man4/snd_ds1.4 Tue Apr 17 21:46:21 2007 @@ -22,17 +22,28 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD: src/share/man/man4/snd_ds1.4,v 1.2.2.4 2005/05/25 15:41:28 brueffer Exp $ +.\" $FreeBSD: src/share/man/man4/snd_ds1.4,v 1.6 2005/12/15 20:25:41 joel Exp $ .\" -.Dd August 27, 2004 +.Dd December 15, 2005 .Dt SND_DS1 4 .Os .Sh NAME .Nm snd_ds1 .Nd "Yamaha DS-1 PCI bridge device driver" .Sh SYNOPSIS +To compile this driver into the kernel, place the following lines in your +kernel configuration file: +.Bd -ragged -offset indent .Cd "device sound" -.Cd "device \*[q]snd_ds1\*[q]" +.Cd "device snd_ds1" +.Ed +.Pp +Alternatively, to load the driver as a module at boot time, place the +following line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +snd_ds1_load="YES" +.Ed .Sh DESCRIPTION The .Nm @@ -55,7 +66,7 @@ .Sh HISTORY The .Nm -manual page first appeared in -.Fx 5.3 . +device driver first appeared in +.Fx 4.1 . .Sh AUTHORS .An "Cameron Grant" Aq cg@FreeBSD.org --- share/man/man4/snd_emu10k1.4.orig Wed May 25 23:41:28 2005 +++ share/man/man4/snd_emu10k1.4 Tue Apr 17 21:46:21 2007 @@ -22,23 +22,34 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD: src/share/man/man4/snd_emu10k1.4,v 1.2.2.4 2005/05/25 15:41:28 brueffer Exp $ +.\" $FreeBSD: src/share/man/man4/snd_emu10k1.4,v 1.8 2005/12/15 20:25:41 joel Exp $ .\" -.Dd August 27, 2004 +.Dd December 15, 2005 .Dt SND_EMU10K1 4 .Os .Sh NAME .Nm snd_emu10k1 .Nd "SoundBlaster Live! and Audigy PCI bridge device driver" .Sh SYNOPSIS +To compile this driver into the kernel, place the following lines in your +kernel configuration file: +.Bd -ragged -offset indent .Cd "device sound" -.Cd "device \*[q]snd_emu10k1\*[q]" +.Cd "device snd_emu10k1" +.Ed +.Pp +Alternatively, to load the driver as a module at boot time, place the +following line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +snd_emu10k1_load="YES" +.Ed .Sh DESCRIPTION The .Nm bridge driver allows the generic audio driver .Xr sound 4 -to attach to the SoundBlaster Live! and Audigy audio cards. +to attach to the SoundBlaster Live!\& and Audigy audio cards. .Pp Digital output is supported by default. .Sh HARDWARE @@ -48,17 +59,21 @@ .Pp .Bl -bullet -compact .It -Creative SoundBlaster Live! (EMU10K1 Chipset) +Creative SoundBlaster Live!\& (EMU10K1 Chipset) .It Creative SoundBlaster Audigy (EMU10K2 Chipset) +.It +Creative SoundBlaster Audigy 2 (EMU10K2 Chipset) +.It +Creative SoundBlaster Audigy 2 (EMU10K3 Chipset) .El .Sh SEE ALSO .Xr sound 4 .Sh HISTORY The .Nm -manual page first appeared in -.Fx 5.3 . +device driver first appeared in +.Fx 4.1 . .Sh AUTHORS .An "David O'Brien" Aq obrien@FreeBSD.org .An "Orlando Bassotto" Aq orlando.bassotto@ieo-research.it --- share/man/man4/snd_emu10kx.4.orig Thu Jan 1 07:30:00 1970 +++ share/man/man4/snd_emu10kx.4 Tue Apr 17 21:46:21 2007 @@ -0,0 +1,217 @@ +.\" +.\" 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. +.\" +.\" $Id: snd_emu10kx.4,v 1.19 2006/06/07 11:18:57 chibis Exp $ +.\" $FreeBSD: src/share/man/man4/snd_emu10kx.4,v 1.4 2006/12/14 16:40:57 mpp Exp $ +.\" +.Dd July 15, 2006 +.Dt SND_EMU10KX 4 +.Os +.Sh NAME +.Nm snd_emu10kx +.Nd Creative SoundBlaster Live! and Audigy sound cards device driver +.Sh SYNOPSIS +To compile this driver into the kernel, place the following lines in your +kernel configuration file: +.Bd -ragged -offset indent +.Cd "device sound" +.Cd "device snd_emu10kx" +.Pp +For additional options: +.Cd "options EMU10KX_MULTICHANNEL" +.Cd "options EMU10KX_DEBUG" +.Ed +.Pp +Alternatively, to load the driver as a module at boot time, place the +following line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +snd_emu10kx_load="YES" +.Ed +.Sh DESCRIPTION +The +.Nm +bridge driver allows the generic audio driver +.Xr sound 4 +to attach to the Creative sound cards based on EMU10K1, CA0100, CA0101, CA0102 +and CA0108 DSPs. +.Pp +The +.Nm +sound cards have a PCM part, which is accessible through one to five +.Xr pcm 4 +devices (see +.Sx MULTICHANNEL PLAYBACK +for details), and MPU401-compatible MIDI I/O controller, which is accessible +through the midi device. +Wave table synthesizer is not supported. +.Sh HARDWARE +The +.Nm +driver supports the following sound cards: +.Pp +.Bl -bullet -compact +.It +Creative Sound Blaster Live!\& (EMU10K1 Chipset). +Both PCM and MIDI interfaces are available. +.It +Creative Sound Blaster Audigy (CA0100 and CA0101 Chipset). +PCM and two MIDI interfaces available. +.It +Creative Sound Blaster Audigy 2 and Creative Sound Blaster Audigy 4 (CA0102 +Chipset). +PCM support is limited to 48kHz/16 bit stereo (192kHz/24 bit part +of this chipset is not supported). +.It +Creative Sound Blaster Audigy 2 Value (CA0108 Chipset). +PCM support is limited +to 48kHz/16 bit stereo (192kHz/24 bit part of this chipset is not supported). +There is no MIDI support for this card. +.El +.Pp +The +.Nm +driver does +.Em not +support the following sound cards (although they are named +similar to some supported ones): +.Pp +.Bl -bullet -compact +.It +Creative Sound Blaster Live!\& 24-Bit, identified by +.Fx +as +.Qq Li "emu10k1x Soundblaster Live! 5.1" . +.It +Creative Sound Blaster Audigy LS / ES, identified by +.Fx +as +.Qq Li "CA0106-DAT Audigy LS" . +.It +All other cards with -DAT chipsets. +.El +.Sh MULTICHANNEL PLAYBACK +It is possible to build this driver with multichannel playback capabilities. +If you enable the +.Dv EMU10KX_MULTICHANNEL +option in your kernel configuration (or +build it as a module) you will get up to five DSP devices, one for each sound +card output. +Only +.Dq FRONT +output can play and record sound from external +sources (like line or S/PDIF inputs). +.Sh OSS MIXER CONTROLS +These are controls available through the standard OSS programming interface. +You can use +.Xr mixer 8 +to change them. +.Pp +On EMU10K1-based cards the OSS mixer directly controls the AC97 codec on card. +On newer cards the OSS mixer controls some parameters of the AC97 codec and +some DSP-based mixer controls. +.Bl -inset +.It Qq vol +mixer control is overall sound volume. +.It Qq pcm +mixer control is PCM playback volume. +It controls only front output +volume in multichannel mode and all output volume in single channel mode. +.It Qq rec +mixer control acts very different on EMU10K1 and other cards. +On EMU10K1 cards it controls the AC97 codec recording level. +On non-EMU10K1 cards +it controls the amount of AC97 "stereo mix" entering the DSP. +AC97 recording level and AC97 recording source are fixed +on CA0100, CA0101, CA0102 and CA0108 cards. +AC97 recording level are always set to +maximum and recording source is always +.Dq Li "stereo mix" . +.El +.Pp +Other OSS mixer controls do not work. +.Sh PRIVATE DEVICE CONTROLS +You can control most of EMU10Kx operation and configuration parameters through +.Va dev.emu10kx. Ns Aq Ar X +sysctls. +These +.Xr sysctl 8 +values are temporary and should not be relied +upon. +.Sh DRIVER CONFIGURATION +.Ss Kernel Configuration Options +The following kernel configuration options control the +.Nm +driver. +.Bl -tag -width ".Dv EMU10KX_MULTICHANNEL" +.It Dv EMU10KX_MULTICHANNEL +This option enables +.Sx MULTICHANNEL PLAYBACK +for all instances of the +.Nm +driver. +.It Dv EMU10KX_DEBUG +This option enables additional debug messages. +.El +.Sh FILES +.Bl -tag -width ".Pa /dev/emu10kx?" -compact +.It Pa /dev/emu10kx? +.Nm +management interface +.El +.Sh SEE ALSO +.Xr sound 4 +.Sh HISTORY +The +.Nm +device driver first appeared in +.Fx 7.0 . +.Sh AUTHORS +.An -nosplit +The PCM part of the driver is based on the +.Xr snd_emu10k1 4 +SB Live!\& driver by +.An "Cameron Grant" . +The MIDI interface is based on the +.Xr snd_emu10k1 4 +MIDI interface code by +.An "Mathew Kanner" . +The +.Nm +device driver and this manual page were written by +.An Yuriy Tsibizov . +.Sh BUGS +8kHz/8bit/mono recording does not work. +8kHz recording was removed from the driver capabilities. +.Pp +The driver does not detect lost S/PDIF signal and produces noise when S/PDIF +is not connected and S/PDIF volume is not zero. +.Pp +The PCM driver cannot detect the presence of Live!Drive or AudigyDrive +breakout boxes +and tries to use them (and list their connectors in the mixer). +.Pp +The MIDI driver cannot detect the presence of Live!Drive or AudigyDrive +breakout boxes and tries to enable the IR receiver on them anyway. --- share/man/man4/snd_envy24.4.orig Thu Jan 1 07:30:00 1970 +++ share/man/man4/snd_envy24.4 Tue Apr 17 21:46:21 2007 @@ -0,0 +1,82 @@ +.\" Copyright (c) 2006 Alexander Leidinger +.\" 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/share/man/man4/snd_envy24.4,v 1.3 2006/09/30 17:19:22 netchild Exp $ +.\" +.Dd September 30, 2006 +.Dt SND_ENVY24 4 +.Os +.Sh NAME +.Nm snd_envy24 +.Nd "VIA Envy24 and compatible bridge device driver" +.Sh SYNOPSIS +To compile this driver into the kernel, place the following lines in your +kernel configuration file: +.Bd -ragged -offset indent +.Cd "device sound" +.Cd "device snd_envy24" +.Cd "device snd_spicds" +.Ed +.Pp +Alternatively, to load the driver as a module at boot time, place the +following line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +snd_envy24_load="YES" +.Ed +.Sh DESCRIPTION +The +.Nm +bridge driver allows the generic audio driver +.Xr sound 4 +to attach to VIA Envy24 (ICE1724 or VT1724 chipset) and compatible audio +devices. +.Sh HARDWARE +The +.Nm +driver supports the following audio devices: +.Pp +.Bl -bullet -compact +.It +M-Audio Audiophile 2496 +.It +M-Audio Delta Dio 2496 +.It +Terratec DMX 6fire +.El +.Sh SEE ALSO +.Xr sound 4 +.Sh HISTORY +The +.Nm +device driver first appeared in +.Fx 7.0 . +.Sh AUTHORS +.An -nosplit +The +.Nm +driver was written by +.An Katsurajima Naoto . +This manual page was written by +.An Alexander Leidinger Aq netchild@FreeBSD.org . --- share/man/man4/snd_envy24ht.4.orig Thu Jan 1 07:30:00 1970 +++ share/man/man4/snd_envy24ht.4 Tue Apr 17 21:46:21 2007 @@ -0,0 +1,105 @@ +.\" Copyright (c) 2006 Alexander Leidinger +.\" 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/share/man/man4/snd_envy24ht.4,v 1.2 2007/05/28 15:57:22 joel Exp $ +.\" +.Dd May 28, 2007 +.Dt SND_ENVY24HT 4 +.Os +.Sh NAME +.Nm snd_envy24ht +.Nd "VIA Envy24HT and compatible bridge device driver" +.Sh SYNOPSIS +To compile this driver into the kernel, place the following lines in your +kernel configuration file: +.Bd -ragged -offset indent +.Cd "device sound" +.Cd "device snd_envy24ht" +.Cd "device snd_spicds" +.Ed +.Pp +Alternatively, to load the driver as a module at boot time, place the +following line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +snd_envy24ht_load="YES" +.Ed +.Sh DESCRIPTION +The +.Nm +bridge driver allows the generic audio driver +.Xr sound 4 +to attach to VIA Envy24HT (ICE1724 or VT1724 chipset) and compatible audio +devices. +.Sh HARDWARE +The +.Nm +driver supports the following audio devices: +.Pp +.Bl -bullet -compact +.It +Audiotrak Prodigy 7.1 +.It +Audiotrak Prodigy 7.1 LT +.It +Audiotrak Prodigy 7.1 XT +.It +Audiotrak Prodigy HD2 +.It +ESI Juli@ +.It +M-Audio Audiophile 192 +.It +M-Audio Revolution 5.1 +.It +M-Audio Revolution 7.1 +.It +Terratec Aureon 5.1 Sky +.It +Terratec Aureon 7.1 Space +.It +Terratec Aureon 7.1 Universe +.It +Terratec PHASE 22 +.It +Terratec PHASE 28 +.El +.Sh SEE ALSO +.Xr sound 4 +.Sh HISTORY +The +.Nm +device driver first appeared in +.Fx 7.0 . +.Sh AUTHORS +.An -nosplit +The +.Nm +driver was written by +.An Konstantin Dimitrov +based upon the +.Xr snd_envy24 4 +driver. +This manual page was written by +.An Alexander Leidinger Aq netchild@FreeBSD.org . --- share/man/man4/snd_es137x.4.orig Wed May 25 23:41:28 2005 +++ share/man/man4/snd_es137x.4 Tue Apr 17 21:46:21 2007 @@ -22,23 +22,58 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD: src/share/man/man4/snd_es137x.4,v 1.2.2.4 2005/05/25 15:41:28 brueffer Exp $ +.\" $FreeBSD: src/share/man/man4/snd_es137x.4,v 1.9 2006/11/29 17:07:02 joel Exp $ .\" -.Dd August 27, 2004 +.Dd November 29, 2006 .Dt SND_ES137X 4 .Os .Sh NAME .Nm snd_es137x .Nd "Ensoniq AudioPCI ES137x bridge device driver" .Sh SYNOPSIS +To compile this driver into the kernel, place the following lines in your +kernel configuration file: +.Bd -ragged -offset indent .Cd "device sound" -.Cd "device \*[q]snd_es137x\*[q]" +.Cd "device snd_es137x" +.Ed +.Pp +Alternatively, to load the driver as a module at boot time, place the +following line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +snd_es137x_load="YES" +.Ed .Sh DESCRIPTION The .Nm bridge driver allows the generic audio driver .Xr sound 4 to attach to the Ensoniq 137x audio cards. +.Ss Runtime Configuration +The following +.Xr sysctl 8 +variables are available in addition to those available to all +.Xr sound 4 +devices: +.Bl -tag -width ".Va hw.snd.pcm%d.latency_timer" -offset indent +.It Va hw.snd.pcm%d.latency_timer +Controls the PCI latency timer setting. +Increasing this value will solve most popping and crackling issues +(especially on VIA motherboards). +.It Va hw.snd.pcm%d.spdif_enabled +Enables S/PDIF output on the primary playback channel. +This +.Xr sysctl 8 +variable is available only if the device is known to support S/PDIF output. +.It Va dev.pcm.%d.polling +Experimental polling mode, where the driver operates by querying the device +state on each tick using +.Xr callout 9 . +Polling is disabled by default. +Do not enable it unless you are facing weird interrupt problems or if the +device cannot generate interrupts at all. +.El .Sh HARDWARE The .Nm @@ -46,16 +81,37 @@ .Pp .Bl -bullet -compact .It -All cards with ES1370/1371 chipset (including SoundBlaster PCI128) +Creative CT5880-A +.It +Creative CT5880-C +.It +Creative CT5880-D +.It +Creative CT5880-E +.It +Creative SB AudioPCI CT4730 +.It +Ensoniq AudioPCI ES1370 +.It +Ensoniq AudioPCI ES1371-A +.It +Ensoniq AudioPCI ES1371-B +.It +Ensoniq AudioPCI ES1373-A +.It +Ensoniq AudioPCI ES1373-B +.It +Ensoniq AudioPCI ES1373-8 .El .Sh SEE ALSO .Xr sound 4 .Sh HISTORY The .Nm -manual page first appeared in -.Fx 5.3 . +device driver first appeared in +.Fx 4.0 . .Sh AUTHORS .An "Russell Cattelan" Aq cattelan@thebarn.com .An "Cameron Grant" Aq cg@FreeBSD.org .An "Joachim Kuebart" +.An "Jonathan Noack" Aq noackjr@alumni.rice.edu --- share/man/man4/snd_ess.4.orig Wed May 25 23:41:28 2005 +++ share/man/man4/snd_ess.4 Tue Apr 17 21:46:21 2007 @@ -22,17 +22,29 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD: src/share/man/man4/snd_ess.4,v 1.2.2.3 2005/05/25 15:41:28 brueffer Exp $ +.\" $FreeBSD: src/share/man/man4/snd_ess.4,v 1.7 2005/12/15 20:25:41 joel Exp $ .\" -.Dd September 13, 2004 +.Dd December 15, 2005 .Dt SND_ESS 4 .Os .Sh NAME .Nm snd_ess .Nd "Ensoniq ESS ISA PnP/non-PnP bridge device driver" .Sh SYNOPSIS +To compile this driver into the kernel, place the following lines in your +kernel configuration file: +.Bd -ragged -offset indent .Cd "device sound" .Cd "device snd_ess" +.Cd "device snd_sbc" +.Ed +.Pp +Alternatively, to load the driver as a module at boot time, place the +following line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +snd_ess_load="YES" +.Ed .Sh DESCRIPTION The .Nm @@ -53,8 +65,8 @@ .Sh HISTORY The .Nm -manual page first appeared in -.Fx 5.3 . +device driver first appeared in +.Fx 4.1 . .Sh AUTHORS .An "Cameron Grant" Aq cg@FreeBSD.org .An "Luigi Rizzo" Aq luigi@FreeBSD.org --- share/man/man4/snd_fm801.4.orig Tue Apr 12 22:37:36 2005 +++ share/man/man4/snd_fm801.4 Tue Apr 17 21:46:21 2007 @@ -22,17 +22,28 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD: src/share/man/man4/snd_fm801.4,v 1.1.2.2 2005/04/12 14:37:36 brueffer Exp $ +.\" $FreeBSD: src/share/man/man4/snd_fm801.4,v 1.3 2005/12/01 12:58:50 joel Exp $ .\" -.Dd March 1, 2005 +.Dd December 1, 2005 .Dt SND_FM801 4 .Os .Sh NAME .Nm snd_fm801 .Nd "Forte Media FM801 bridge device driver" .Sh SYNOPSIS +To compile this driver into the kernel, place the following lines in your +kernel configuration file: +.Bd -ragged -offset indent .Cd "device sound" -.Cd "device \*[q]snd_fm801\*[q]" +.Cd "device snd_fm801" +.Ed +.Pp +Alternatively, to load the driver as a module at boot time, place the +following line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +snd_fm801_load="YES" +.Ed .Sh DESCRIPTION The .Nm @@ -56,6 +67,9 @@ .Nm device driver first appeared in .Fx 4.2 . +.Sh AUTHORS +This manual page was written by +.An Joel Dahl Aq joel@FreeBSD.org . .Sh BUGS The Forte Media FM801 chipset is a sort of PCI bridge, not an actual sound controller, making it possible to have soundless support. --- share/man/man4/snd_gusc.4.orig Wed May 25 23:41:28 2005 +++ share/man/man4/snd_gusc.4 Tue Apr 17 21:46:21 2007 @@ -23,29 +23,42 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD: src/share/man/man4/snd_gusc.4,v 1.10.2.2 2005/05/25 15:41:28 brueffer Exp $ +.\" $FreeBSD: src/share/man/man4/snd_gusc.4,v 1.14 2006/06/18 17:53:04 brueffer Exp $ .\" -.Dd August 28, 2004 +.Dd December 15, 2005 .Dt SND_GUSC 4 .Os .Sh NAME .Nm snd_gusc .Nd Gravis UltraSound ISA bridge device driver .Sh SYNOPSIS +To compile this driver into the kernel, place the following lines in your +kernel configuration file: +.Bd -ragged -offset indent .Cd "device sound" .Cd "device snd_gusc" +.Ed .Pp -For non-PnP cards, add the following lines in -.Pa /boot/device.hints . -.Cd hint.gusc.0.at="isa" -.Cd hint.gusc.0.port="0x220" -.Cd hint.gusc.0.irq="5" -.Cd hint.gusc.0.drq="1" -.Cd hint.gusc.0.flags="0x13" +Alternatively, to load the driver as a module at boot time, place the +following line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +snd_gusc_load="YES" +.Ed +.Pp +Non-PnP cards require the following lines in +.Xr device.hints 5 : +.Bd -literal -offset indent +hint.gusc.0.at="isa" +hint.gusc.0.port="0x220" +hint.gusc.0.irq="5" +hint.gusc.0.drq="1" +hint.gusc.0.flags="0x13" +.Ed .Sh DESCRIPTION The .Nm -bridge driver allows the generic audio drivers including +bridge driver allows the generic audio driver .Xr sound 4 to attach to Gravis UltraSound sound cards. .Pp @@ -70,13 +83,13 @@ .It xxx: gus pcm not attached, out of memory There are not enough memory to drive the device. .El +.Sh SEE ALSO +.Xr sound 4 .Sh HISTORY The .Nm device driver first appeared in .Fx 4.0 . -.Sh SEE ALSO -.Xr sound 4 .Sh AUTHORS .An Ville-Pertti Keinonen Aq will@iki.fi .An Seigo Tanimura Aq tanimura@r.dl.itc.u-tokyo.ac.jp --- share/man/man4/snd_hda.4.orig Thu Jan 1 07:30:00 1970 +++ share/man/man4/snd_hda.4 Tue Apr 17 21:46:21 2007 @@ -0,0 +1,253 @@ +.\" Copyright (c) 2006 Joel Dahl +.\" 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/share/man/man4/snd_hda.4,v 1.11 2007/06/12 15:26:41 joel Exp $ +.\" +.Dd June 12, 2007 +.Dt SND_HDA 4 +.Os +.Sh NAME +.Nm snd_hda +.Nd "Intel High Definition Audio bridge device driver" +.Sh SYNOPSIS +To compile this driver into the kernel, place the following lines in your +kernel configuration file: +.Bd -ragged -offset indent +.Cd "device sound" +.Cd "device snd_hda" +.Ed +.Pp +Alternatively, to load the driver as a module at boot time, place the +following line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +snd_hda_load="YES" +.Ed +.Sh DESCRIPTION +The +.Nm +bridge device driver allows the generic audio driver, +.Xr sound 4 , +to attach to Intel High Definition Audio devices. +The +.Nm +driver supports hardware that conforms with revision 1.0 of the Intel High +Definition Audio specification and tries to behave much like the Microsoft +Universal Audio Architecture (UAA) draft (revision 0.7b) for handling audio +devices. +HDA acts like a primary bus, similar to +.Xr miibus 4 , +for handling various child buses such as audio, modem and HDMI (High Definition +Multimedia Interface). +Only audio is implemented in the +.Nm +driver. +.Pp +The High Definition (HD) Audio specification was developed by Intel as the +logical successor of the old AC'97 specification and has several advantages, +such as higher bandwidth which allows more channels and more detailed formats, +support for several logical audio devices, and general purpose DMA channels. +.Pp +The HDA specification defines the register-level interface, physical link +characteristics, codec programming models, and codec architectural components. +This specification is intended for both device driver developers and hardware +component designers. +.Ss Boot-time Configuration +The following variables are available at boot-time through the +.Xr device.hints 5 +file: +.Bl -tag -width ".Va hint.pcm.%d.config" -offset indent +.It Va hint.pcm.%d.config +Configures a range of possible options. +Possible values are: +.Dq Li dmapos , +.Dq Li eapdinv , +.Dq Li gpio0 , +.Dq Li gpio1 , +.Dq Li gpio2 , +.Dq Li gpio3 , +.Dq Li gpio4 , +.Dq Li gpio5 , +.Dq Li gpio6 , +.Dq Li gpio7 , +.Dq Li gpioflush , +.Dq Li ivref , +.Dq Li ivref50 , +.Dq Li ivref80 , +.Dq Li ivref100 , +.Dq Li fixedrate , +.Dq Li forcestereo , +.Dq Li ovref , +.Dq Li ovref50 , +.Dq Li ovref80 , +.Dq Li ovref100 , +.Dq Li softpcmvol , +and +.Dq Li vref . +An option prefixed with +.Dq Li no , +such as +.Dq Li nofixedrate , +will do the opposite and takes precedence. +Options can be separated by whitespace and commas. +.El +.Ss Runtime Configuration +The following +.Xr sysctl 8 +variables are available in addition to those available to all +.Xr sound 4 +devices: +.Bl -tag -width ".Va dev.pcm.%d.polling" -offset indent +.It Va dev.pcm.%d.polling +Experimental polling mode, where the driver operates by querying the device +state on each tick using +.Xr callout 9 . +Polling is disabled by default. +Do not enable it unless you are facing weird interrupt problems or if the +device cannot generate interrupts at all. +.El +.Sh HARDWARE +The +.Nm +driver supports the following audio chipsets: +.Pp +.Bl -bullet -compact +.It +ATI SB450 +.It +ATI SB600 +.It +Intel 631x/632xESB +.It +Intel 82801F +.It +Intel 82801G +.It +Intel 82801H +.It +nVidia MCP51 +.It +nVidia MCP55 +.It +nVidia MCP61A +.It +nVidia MCP61B +.It +nVidia MCP65A +.It +nVidia MCP65B +.It +SiS 966 +.It +VIA VT8251/8237A +.El +.Pp +Generic audio chipsets compatible with the Intel HDA specification should work, +but have not been verified yet. +The following codecs have been verified to work: +.Pp +.Bl -bullet -compact +.It +Analog Devices AD1981HD +.It +Analog Devices AD1983 +.It +Analog Devices AD1986A +.It +Analog Devices AD1988 +.It +Analog Devices AD1988B +.It +CMedia CMI9880 +.It +Conexant Venice +.It +Conexant Waikiki +.It +Realtek ALC260 +.It +Realtek ALC262 +.It +Realtek ALC660 +.It +Realtek ALC861 +.It +Realtek ALC861VD +.It +Realtek ALC880 +.It +Realtek ALC882 +.It +Realtek ALC883 +.It +Realtek ALC885 +.It +Realtek ALC888 +.It +Sigmatel STAC9220 +.It +Sigmatel STAC9220D/9223D +.It +Sigmatel STAC9221 +.It +Sigmatel STAC9221D +.It +Sigmatel STAC9227 +.It +Sigmatel STAC9271D +.It +VIA VT1708 +.It +VIA VT1709 +.El +.Sh SEE ALSO +.Xr sound 4 , +.Xr device.hints 5 , +.Xr loader.conf 5 , +.Xr sysctl 8 +.Sh HISTORY +The +.Nm +device driver first appeared in +.Fx 7.0 . +.Sh AUTHORS +.An -nosplit +The +.Nm +driver was written by +.An Stephane E. Potvin Aq sepotvin@videotron.ca +and +.An Ariff Abdullah Aq ariff@FreeBSD.org . +This manual page was written by +.An Joel Dahl Aq joel@FreeBSD.org . +.Sh BUGS +There are a couple of missing features, such as support for Digital +S/PDIF and multichannel output. +.Pp +A few Hardware/OEM vendors tend to screw up BIOS settings, thus +rendering the +.Nm +driver useless, which usually results in a state where the +.Nm +driver seems to attach and work, but without any sound. --- share/man/man4/snd_ich.4.orig Wed Mar 30 15:43:45 2005 +++ share/man/man4/snd_ich.4 Tue Apr 17 21:46:21 2007 @@ -22,21 +22,32 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD: src/share/man/man4/snd_ich.4,v 1.2.2.3 2005/03/30 07:43:45 simon Exp $ +.\" $FreeBSD: src/share/man/man4/snd_ich.4,v 1.9 2006/06/18 17:53:04 brueffer Exp $ .\" -.Dd March 19, 2005 +.Dd December 15, 2005 .Dt SND_ICH 4 .Os .Sh NAME .Nm snd_ich .Nd "Intel ICH PCI and compatible bridge device driver" .Sh SYNOPSIS +To compile this driver into the kernel, place the following lines in your +kernel configuration file: +.Bd -ragged -offset indent .Cd "device sound" .Cd "device snd_ich" +.Ed +.Pp +Alternatively, to load the driver as a module at boot time, place the +following line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +snd_ich_load="YES" +.Ed .Sh DESCRIPTION The .Nm -bridge driver allows the generic audio drivers including +bridge driver allows the generic audio driver .Xr sound 4 to attach to Intel ICH and compatible audio devices. .Sh HARDWARE @@ -66,6 +77,8 @@ .It Intel ICH6 .It +Intel ICH7 +.It NVIDIA nForce .It NVIDIA nForce2 @@ -80,13 +93,13 @@ .It SiS 7012 .El +.Sh SEE ALSO +.Xr sound 4 .Sh HISTORY The .Nm device driver first appeared in .Fx 4.2 . -.Sh SEE ALSO -.Xr sound 4 .Sh AUTHORS This manual page was written by .An Jorge Mario G. Mazo Aq jgutie11@eafit.edu.co . --- share/man/man4/snd_maestro.4.orig Tue Dec 21 23:06:33 2004 +++ share/man/man4/snd_maestro.4 Tue Apr 17 21:46:21 2007 @@ -22,17 +22,28 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD: src/share/man/man4/snd_maestro.4,v 1.1.2.1 2004/12/21 15:06:33 simon Exp $ +.\" $FreeBSD: src/share/man/man4/snd_maestro.4,v 1.3 2005/12/15 20:25:41 joel Exp $ .\" -.Dd December 14, 2004 +.Dd December 15, 2005 .Dt SND_MAESTRO 4 .Os .Sh NAME .Nm snd_maestro .Nd "ESS Maestro bridge device driver" .Sh SYNOPSIS +To compile this driver into the kernel, place the following lines in your +kernel configuration file: +.Bd -ragged -offset indent .Cd "device sound" .Cd "device snd_maestro" +.Ed +.Pp +Alternatively, to load the driver as a module at boot time, place the +following line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +snd_maestro_load="YES" +.Ed .Sh DESCRIPTION The .Nm @@ -54,14 +65,14 @@ .It ESS Technology Maestro-2E .El +.Sh SEE ALSO +.Xr snd_maestro3 4 , +.Xr sound 4 .Sh HISTORY The .Nm device driver first appeared in .Fx 4.2 . -.Sh SEE ALSO -.Xr snd_maestro3 4 , -.Xr sound 4 .Sh AUTHORS This manual page was written by .An Jorge Mario G. Mazo Aq jgutie11@eafit.edu.co . --- share/man/man4/snd_maestro3.4.orig Fri Jun 10 18:16:32 2005 +++ share/man/man4/snd_maestro3.4 Tue Apr 17 21:46:21 2007 @@ -22,17 +22,28 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD: src/share/man/man4/snd_maestro3.4,v 1.4.2.4 2005/06/10 10:16:32 yongari Exp $ +.\" $FreeBSD: src/share/man/man4/snd_maestro3.4,v 1.9 2006/01/09 12:51:45 joel Exp $ .\" -.Dd December 14, 2004 +.Dd December 15, 2005 .Dt SND_MAESTRO3 4 .Os .Sh NAME .Nm snd_maestro3 .Nd "ESS Maestro3/Allegro-1 bridge device driver" .Sh SYNOPSIS +To compile this driver into the kernel, place the following lines in your +kernel configuration file: +.Bd -ragged -offset indent .Cd "device sound" -.Cd "device \*[q]snd_maestro3\*[q]" +.Cd "device snd_maestro3" +.Ed +.Pp +Alternatively, to load the driver as a module at boot time, place the +following line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +snd_maestro3_load="YES" +.Ed .Sh DESCRIPTION The .Nm @@ -46,12 +57,6 @@ License, and thus this driver is not included in the default .Pa GENERIC kernel. -A convenient way to automatically load the driver is to add the line -.Pp -.Dl snd_maestro3_load="YES" -.Pp -to the file -.Pa /boot/loader.conf . .Sh HARDWARE The .Nm @@ -65,7 +70,7 @@ .El .Sh DIAGNOSTICS The hardware volume control buttons can be connected to two different pin -sets(GPIO or GD) on the chip, depending on the manufacturer. +sets (GPIO or GD) on the chip, depending on the manufacturer. The driver has no way of determining this configuration, so a hint may be used to override the default guess. The default setting for hardware volume control assumes that GD pins --- share/man/man4/snd_mss.4.orig Thu Jan 1 07:30:00 1970 +++ share/man/man4/snd_mss.4 Tue Apr 17 21:46:21 2007 @@ -0,0 +1,116 @@ +.\" Copyright (c) 2005 Joel Dahl +.\" 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/share/man/man4/snd_mss.4,v 1.3 2005/12/01 12:58:50 joel Exp $ +.\" +.Dd December 1, 2005 +.Dt SND_MSS 4 +.Os +.Sh NAME +.Nm snd_mss +.Nd "Microsoft Sound System ISA PnP/non-PnP bridge device driver" +.Sh SYNOPSIS +To compile this driver into the kernel, place the following lines in your +kernel configuration file: +.Bd -ragged -offset indent +.Cd "device sound" +.Cd "device snd_mss" +.Ed +.Pp +Alternatively, to load the driver as a module at boot time, place the +following line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +snd_mss_load="YES" +.Ed +.Pp +Non-PnP cards require the following lines in +.Xr device.hints 5 : +.Bd -literal -offset indent +hint.pcm.0.at="isa" +hint.pcm.0.irq="10" +hint.pcm.0.drq="1" +hint.pcm.0.flags="0x0" +.Ed +.Sh DESCRIPTION +The +.Nm +bridge driver allows the generic audio driver, +.Xr sound 4 , +to attach to the supported audio devices. +.Sh HARDWARE +The +.Nm +driver supports the following audio devices: +.Pp +.Bl -bullet -compact +.It +AD1845 +.It +AD1848 +.It +Aztech 2320 +.It +CMedia CMI8330 +.It +Crystal Semiconductor CS4231 +.It +Crystal Semiconductor CS4232 +.It +Crystal Semiconductor CS4234 +.It +Crystal Semiconductor CS4235 +.It +Crystal Semiconductor CS4236 +.It +Crystal Semiconductor CS4237 +.It +ENSONIQ SoundscapeVIVO ENS4081 +.It +NeoMagic 256AV (non-AC97) +.It +OPTi 924 +.It +OPTi 925 +.It +OPTi 930 +.It +OPTi 931 +.It +OPTi 933 +.It +Yamaha OPL-SA2 +.It +Yamaha OPL-SA3 +.El +.Sh SEE ALSO +.Xr sound 4 +.Sh HISTORY +The +.Nm +device driver first appeared in +.Fx 2.2.6 . +.Sh AUTHORS +This manual page was written by +.An Joel Dahl Aq joel@FreeBSD.org . --- share/man/man4/snd_neomagic.4.orig Thu Mar 10 16:44:56 2005 +++ share/man/man4/snd_neomagic.4 Tue Apr 17 21:46:21 2007 @@ -22,17 +22,28 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD: src/share/man/man4/snd_neomagic.4,v 1.3.2.1 2005/03/10 08:44:56 simon Exp $ +.\" $FreeBSD: src/share/man/man4/snd_neomagic.4,v 1.5 2005/12/01 12:58:50 joel Exp $ .\" -.Dd February 26, 2005 +.Dd December 1, 2005 .Dt SND_NEOMAGIC 4 .Os .Sh NAME .Nm snd_neomagic .Nd "NeoMagic 256AV/ZX bridge device driver" .Sh SYNOPSIS +To compile this driver into the kernel, place the following lines in your +kernel configuration file: +.Bd -ragged -offset indent .Cd "device sound" .Cd "device snd_neomagic" +.Ed +.Pp +Alternatively, to load the driver as a module at boot time, place the +following line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +snd_neomagic_load="YES" +.Ed .Sh DESCRIPTION The .Nm @@ -58,3 +69,6 @@ .Nm device driver first appeared in .Fx 4.0 . +.Sh AUTHORS +This manual page was written by +.An Joel Dahl Aq joel@FreeBSD.org . --- share/man/man4/snd_sbc.4.orig Wed May 25 23:41:28 2005 +++ share/man/man4/snd_sbc.4 Tue Apr 17 21:46:21 2007 @@ -23,31 +23,51 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD: src/share/man/man4/snd_sbc.4,v 1.11.2.3 2005/05/25 15:41:28 brueffer Exp $ +.\" $FreeBSD: src/share/man/man4/snd_sbc.4,v 1.19 2007/02/17 11:31:58 joel Exp $ .\" -.Dd February 24, 2005 +.Dd February 17, 2007 .Dt SND_SBC 4 .Os .Sh NAME -.Nm snd_sbc +.Nm snd_sbc , +.Nm snd_sb16 , +.Nm snd_sb8 .Nd Creative Sound Blaster ISA and compatible bridge device driver .Sh SYNOPSIS +To compile this driver into the kernel, place the following lines in your +kernel configuration file: +.Bd -ragged -offset indent .Cd "device sound" .Cd "device snd_sbc" +.Cd "device snd_sb16" +.Cd "device snd_sb8" +.Ed +.Pp +Alternatively, to load the driver as a module at boot time, place the +following lines in +.Xr loader.conf 5 : +.Bd -literal -offset indent +snd_sbc_load="YES" +snd_sb16_load="YES" +snd_sb8_load="YES" +.Ed .Pp Non-PnP cards require the following lines in -.Pa /boot/device.hints : -.Cd hint.sbc.0.at="isa" -.Cd hint.sbc.0.port="0x220" -.Cd hint.sbc.0.irq="5" -.Cd hint.sbc.0.drq="1" -.Cd hint.sbc.0.flags="0x15" +.Xr device.hints 5 : +.Bd -literal -offset indent +hint.sbc.0.at="isa" +hint.sbc.0.port="0x220" +hint.sbc.0.irq="5" +hint.sbc.0.drq="1" +hint.sbc.0.flags="0x15" +.Ed .Sh DESCRIPTION The .Nm -bridge driver allows the generic audio drivers including +bridge driver allows the generic audio driver .Xr sound 4 -to attach to Creative Sound Blaster ISA compatible audio cards. +to attach to Creative Sound Blaster ISA (mostly SB16 or SB8, known as +SoundBlaster Pro) compatible audio cards. .Pp The value of flags specifies the secondary DMA channel. If the secondary @@ -61,13 +81,37 @@ .Pp .Bl -bullet -compact .It -Advance Asound 100 and 110 +Avance Asound 110 +.It +Avance Logic ALS100+ +.It +Avance Logic ALS120 +.It +Creative SB16 +.It +Creative SB32 +.It +Creative AWE64 +.It +Creative AWE64 Gold +.It +Creative ViBRA16C +.It +Creative ViBRA16X .It -Creative SB16, SB32, SB AWE64 (including Gold) and ViBRA16 +ESS ES1681 .It -ESS ES1868, ES1869, ES1879 and ES1888 +ESS ES1688 .It -Logic ALS120 +ESS ES1868 +.It +ESS ES1869 +.It +ESS ES1878 +.It +ESS ES1879 +.It +ESS ES1888 .El .Sh DIAGNOSTICS .Bl -diag @@ -77,12 +121,12 @@ .It bad irq XX (5/7/9/10 valid) The IRQ given to the driver is not valid. .El +.Sh SEE ALSO +.Xr sound 4 .Sh HISTORY The .Nm device driver first appeared in .Fx 4.0 . -.Sh SEE ALSO -.Xr sound 4 .Sh AUTHORS .An Seigo Tanimura Aq tanimura@r.dl.itc.u-tokyo.ac.jp --- share/man/man4/snd_solo.4.orig Wed May 25 23:41:28 2005 +++ share/man/man4/snd_solo.4 Tue Apr 17 21:46:21 2007 @@ -22,9 +22,9 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD: src/share/man/man4/snd_solo.4,v 1.2.2.3 2005/05/25 15:41:28 brueffer Exp $ +.\" $FreeBSD: src/share/man/man4/snd_solo.4,v 1.5 2005/11/28 18:47:00 joel Exp $ .\" -.Dd August 27, 2004 +.Dd November 28, 2005 .Dt SND_SOLO 4 .Os .Sh NAME @@ -60,7 +60,7 @@ .Sh HISTORY The .Nm -manual page first appeared in -.Fx 5.3 . +device driver first appeared in +.Fx 4.1 . .Sh AUTHORS .An "Cameron Grant" Aq cg@FreeBSD.org --- share/man/man4/snd_spicds.4.orig Thu Jan 1 07:30:00 1970 +++ share/man/man4/snd_spicds.4 Tue Apr 17 21:46:21 2007 @@ -0,0 +1,89 @@ +.\" Copyright (c) 2006 Alexander Leidinger +.\" 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/share/man/man4/snd_spicds.4,v 1.3 2007/05/28 16:00:08 joel Exp $ +.\" +.Dd May 28, 2007 +.Dt SND_SPICDS 4 +.Os +.Sh NAME +.Nm snd_spicds +.Nd "I2S SPI audio codec driver" +.Sh SYNOPSIS +To compile this driver into the kernel, place the following lines in your +kernel configuration file: +.Bd -ragged -offset indent +.Cd "device snd_spicds" +.Ed +.Pp +Alternatively, to load the driver as a module at boot time, place the +following line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +snd_spicds_load="YES" +.Ed +.Sh DESCRIPTION +The +.Nm +I2S codec driver is used by several sound drivers for soundcards which use +the supported codec chips. +.Sh HARDWARE +The +.Nm +driver supports the following codecs: +.Pp +.Bl -bullet -compact +.It +AK4358 +.It +AK4381 +.It +AK4396 +.It +AK4524 +.It +AK4528 +.It +WM8770 +.El +.Sh SEE ALSO +.Xr snd_envy24 4 , +.Xr snd_envy24ht 4 , +.Xr sound 4 +.Sh HISTORY +The +.Nm +device driver first appeared in +.Fx 7.0 . +.Sh AUTHORS +.An -nosplit +The +.Nm +driver was written by +.An Konstantin Dimitrov +based upon the +.Xr snd_envy24 4 +driver. +This manual page was written by +.An Alexander Leidinger Aq netchild@FreeBSD.org . --- share/man/man4/snd_t4dwave.4.orig Thu Jan 1 07:30:00 1970 +++ share/man/man4/snd_t4dwave.4 Tue Apr 17 21:46:21 2007 @@ -0,0 +1,77 @@ +.\" Copyright (c) 2005 Joel Dahl +.\" 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/share/man/man4/snd_t4dwave.4,v 1.3 2005/12/01 12:58:50 joel Exp $ +.\" +.Dd December 1, 2005 +.Dt SND_T4DWAVE 4 +.Os +.Sh NAME +.Nm snd_t4dwave +.Nd "Trident 4DWave bridge device driver" +.Sh SYNOPSIS +To compile this driver into the kernel, place the following lines in your +kernel configuration file: +.Bd -ragged -offset indent +.Cd "device sound" +.Cd "device snd_t4dwave" +.Ed +.Pp +Alternatively, to load the driver as a module at boot time, place the +following line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +snd_t4dwave_load="YES" +.Ed +.Sh DESCRIPTION +The +.Nm +bridge driver allows the generic audio driver, +.Xr sound 4 , +to attach to Trident 4DWave audio devices. +.Sh HARDWARE +The +.Nm +driver supports the following audio devices: +.Pp +.Bl -bullet -compact +.It +Acer Labs M5451 +.It +SIS 7018 +.It +Trident 4DWave DX +.It +Trident 4DWave NX +.El +.Sh SEE ALSO +.Xr sound 4 +.Sh HISTORY +The +.Nm +device driver first appeared in +.Fx 4.0 . +.Sh AUTHORS +This manual page was written by +.An Joel Dahl Aq joel@FreeBSD.org . --- share/man/man4/snd_uaudio.4.orig Fri Sep 17 04:18:15 2004 +++ share/man/man4/snd_uaudio.4 Tue Apr 17 21:46:21 2007 @@ -34,18 +34,29 @@ .\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE .\" POSSIBILITY OF SUCH DAMAGE. .\" -.\" $FreeBSD: src/share/man/man4/snd_uaudio.4,v 1.5.2.1 2004/09/16 20:18:15 ru Exp $ +.\" $FreeBSD: src/share/man/man4/snd_uaudio.4,v 1.6 2005/12/15 20:25:41 joel Exp $ .\" -.Dd August 28, 2004 +.Dd December 15, 2005 .Dt SND_UAUDIO 4 .Os .Sh NAME .Nm snd_uaudio .Nd USB audio device driver .Sh SYNOPSIS +To compile this driver into the kernel, place the following lines in your +kernel configuration file: +.Bd -ragged -offset indent .Cd "device sound" .Cd "device usb" .Cd "device snd_uaudio" +.Ed +.Pp +Alternatively, to load the driver as a module at boot time, place the +following line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +snd_uaudio_load="YES" +.Ed .Sh DESCRIPTION The .Nm --- share/man/man4/snd_via8233.4.orig Tue Apr 12 22:37:36 2005 +++ share/man/man4/snd_via8233.4 Tue Apr 17 21:46:21 2007 @@ -22,17 +22,28 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD: src/share/man/man4/snd_via8233.4,v 1.1.2.2 2005/04/12 14:37:36 brueffer Exp $ +.\" $FreeBSD: src/share/man/man4/snd_via8233.4,v 1.7 2007/05/12 06:41:41 brueffer Exp $ .\" -.Dd March 2, 2005 +.Dd November 29, 2006 .Dt SND_VIA8233 4 .Os .Sh NAME .Nm snd_via8233 .Nd "VIA Technologies VT8233 bridge device driver" .Sh SYNOPSIS +To compile this driver into the kernel, place the following lines in your +kernel configuration file: +.Bd -ragged -offset indent .Cd "device sound" -.Cd "device \*[q]snd_via8233\*[q]" +.Cd "device snd_via8233" +.Ed +.Pp +Alternatively, to load the driver as a module at boot time, place the +following line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +snd_via8233_load="YES" +.Ed .Sh DESCRIPTION The .Nm @@ -41,6 +52,21 @@ to attach to the VIA VT8233 audio devices. These audio chipsets are integrated in the southbridge on many VIA based motherboards. +.Ss Runtime Configuration +The following +.Xr sysctl 8 +variables are available in addition to those available to all +.Xr sound 4 +devices: +.Bl -tag -width ".Va dev.pcm.%d.polling" -offset indent +.It Va dev.pcm.%d.polling +Experimental polling mode, where the driver operates by querying the device +state on each tick using +.Xr callout 9 . +Polling is disabled by default. +Do not enable it unless you are facing weird interrupt problems or if the +device cannot generate interrupts at all. +.El .Sh HARDWARE The .Nm @@ -57,6 +83,8 @@ VIA VT8235 .It VIA VT8237 +.It +VIA VT8251 .El .Sh SEE ALSO .Xr sound 4 @@ -65,8 +93,13 @@ .Nm device driver first appeared in .Fx 4.7 . +.Sh AUTHORS +This manual page was written by +.An Joel Dahl Aq joel@FreeBSD.org . .Sh BUGS +The .Nm +driver does not support S/PDIF. There is partial support in the code, so implementing it should be fairly easy if the right hardware is available. --- share/man/man4/snd_via82c686.4.orig Tue Apr 12 22:37:36 2005 +++ share/man/man4/snd_via82c686.4 Tue Apr 17 21:46:21 2007 @@ -22,17 +22,28 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD: src/share/man/man4/snd_via82c686.4,v 1.1.2.2 2005/04/12 14:37:36 brueffer Exp $ +.\" $FreeBSD: src/share/man/man4/snd_via82c686.4,v 1.3 2005/12/01 12:58:50 joel Exp $ .\" -.Dd March 9, 2005 +.Dd December 1, 2005 .Dt SND_VIA82C686 4 .Os .Sh NAME .Nm snd_via82c686 .Nd "VIA Technologies 82C686A bridge device driver" .Sh SYNOPSIS +To compile this driver into the kernel, place the following lines in your +kernel configuration file: +.Bd -ragged -offset indent .Cd "device sound" -.Cd "device \*[q]snd_via82c686\*[q]" +.Cd "device snd_via82c686" +.Ed +.Pp +Alternatively, to load the driver as a module at boot time, place the +following line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +snd_via82c686_load="YES" +.Ed .Sh DESCRIPTION The .Nm @@ -55,3 +66,6 @@ .Nm device driver first appeared in .Fx 4.2 . +.Sh AUTHORS +This manual page was written by +.An Joel Dahl Aq joel@FreeBSD.org . --- share/man/man4/snd_vibes.4.orig Sat Mar 12 18:35:13 2005 +++ share/man/man4/snd_vibes.4 Tue Apr 17 21:46:21 2007 @@ -22,17 +22,28 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.\" $FreeBSD: src/share/man/man4/snd_vibes.4,v 1.1.2.1 2005/03/12 10:35:13 simon Exp $ +.\" $FreeBSD: src/share/man/man4/snd_vibes.4,v 1.3 2005/12/01 12:58:51 joel Exp $ .\" -.Dd March 9, 2005 +.Dd December 1, 2005 .Dt SND_VIBES 4 .Os .Sh NAME .Nm snd_vibes .Nd "S3 SonicVibes bridge device driver" .Sh SYNOPSIS +To compile this driver into the kernel, place the following lines in your +kernel configuration file: +.Bd -ragged -offset indent .Cd "device sound" .Cd "device snd_vibes" +.Ed +.Pp +Alternatively, to load the driver as a module at boot time, place the +following line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +snd_vibes_load="YES" +.Ed .Sh DESCRIPTION The .Nm @@ -55,3 +66,6 @@ .Nm device driver first appeared in .Fx 4.4 . +.Sh AUTHORS +This manual page was written by +.An Joel Dahl Aq joel@FreeBSD.org . --- sys/dev/ata/atapi-cd.c.orig Sat Oct 29 19:40:44 2005 +++ sys/dev/ata/atapi-cd.c Sat Oct 29 19:45:30 2005 @@ -53,6 +53,11 @@ #include #include +extern int atapicd_aggresive_toc; +extern int atapicd_multiblock; + +#define ACD_F_MULTIBLOCK (G_PF_IGNOREOFFSET|G_PF_IGNOREBOUNDARY) + /* prototypes */ static void acd_detach(struct ata_device *); static void acd_start(struct ata_device *); @@ -92,6 +97,7 @@ static int acd_read_format_caps(struct acd_softc *, struct cdr_format_capacities *); static int acd_format(struct acd_softc *, struct cdr_format_params *); static int acd_test_ready(struct ata_device *); +static int acd_get_blocksize(off_t, u_int32_t); /* internal vars */ static u_int32_t acd_lun_map = 0; @@ -117,6 +123,7 @@ } ata_set_name(atadev, "acd", cdp->lun); + ata_controlcmd(atadev, ATA_ATAPI_RESET, 0, 0, 0); acd_get_cap(cdp); /* if this is a changer device, allocate the neeeded lun's */ @@ -539,12 +546,15 @@ pp->sectorsize = (cdp->toc.tab[track - 1].control & 4) ? 2048 : 2352; pp->mediasize = ntohl(cdp->toc.tab[track].addr.lba) - ntohl(cdp->toc.tab[track - 1].addr.lba); + pp->mediasize *= pp->sectorsize; } else { pp->sectorsize = cdp->block_size; - pp->mediasize = cdp->disk_size; + if ((pp->flags & ACD_F_MULTIBLOCK) == ACD_F_MULTIBLOCK) + pp->mediasize = (off_t)cdp->disk_size * 2352; + else + pp->mediasize = (off_t)cdp->disk_size * cdp->block_size; } - pp->mediasize *= pp->sectorsize; return 0; } @@ -680,9 +690,11 @@ bcopy(&cdp->toc, toc, sizeof(struct toc)); entry = toc->tab + (toc->hdr.ending_track + 1 - toc->hdr.starting_track) + 1; - while (--entry >= toc->tab) + while (--entry >= toc->tab) { lba2msf(ntohl(entry->addr.lba), &entry->addr.msf.minute, &entry->addr.msf.second, &entry->addr.msf.frame); + entry->addr_type = CD_MSF_FORMAT; + } } error = copyout(toc->tab + starting_track - toc->hdr.starting_track, te->data, len); @@ -1017,6 +1029,7 @@ acd_geom_start(struct bio *bp) { struct acd_softc *cdp = bp->bio_to->geom->softc; + int blocksize; if (cdp->device->flags & ATA_D_DETACHING) { g_io_deliver(bp, ENXIO); @@ -1033,6 +1046,16 @@ return; } + /* Concurrent access with different blocksize */ + if (bp->bio_to->index == 0 && + (cdp->pp[0]->flags & ACD_F_MULTIBLOCK) == ACD_F_MULTIBLOCK) { + blocksize = bp->bio_to->sectorsize; + if (blocksize < 2048 || blocksize > 2352 || bp->bio_length % blocksize) + blocksize = acd_get_blocksize(bp->bio_length, cdp->block_size); + bp->bio_to->sectorsize = blocksize; + cdp->pp[0]->sectorsize = blocksize; + } + /* GEOM classes must do their own request limiting */ if (bp->bio_length <= cdp->iomax) { mtx_lock(&cdp->queue_mtx); @@ -1071,7 +1094,7 @@ struct ata_request *request; u_int32_t lba, lastlba, count; int8_t ccb[16]; - int track, blocksize; + int track, ntracks, blocksize; if (cdp->changer_info) { int i; @@ -1114,22 +1137,56 @@ bzero(ccb, sizeof(ccb)); track = bp->bio_to->index; + ntracks = cdp->toc.hdr.ending_track - cdp->toc.hdr.starting_track + 1; + blocksize = cdp->pp[track]->sectorsize; + lba = 0; if (track) { - blocksize = (cdp->toc.tab[track - 1].control & 4) ? 2048 : 2352; + /* Don't let other than BIO_READ on individual track */ + if (bp->bio_cmd != BIO_READ) { + g_io_deliver(bp, EINVAL); + return; + } lastlba = ntohl(cdp->toc.tab[track].addr.lba); - lba = bp->bio_offset / blocksize; lba += ntohl(cdp->toc.tab[track - 1].addr.lba); } else { - blocksize = cdp->block_size; - lastlba = cdp->disk_size; - lba = bp->bio_offset / blocksize; + if ((cdp->pp[0]->flags & ACD_F_MULTIBLOCK) == ACD_F_MULTIBLOCK) { + if (blocksize < 2048 || blocksize > 2352 || bp->bio_length % blocksize) + blocksize = acd_get_blocksize(bp->bio_length, cdp->block_size); + cdp->pp[0]->sectorsize = blocksize; + bp->bio_to->sectorsize = blocksize; + } else + blocksize = cdp->block_size; + /* Limit BIO_READ until last readable track */ + if (bp->bio_cmd == BIO_READ) + lastlba = ntohl(cdp->toc.tab[ntracks].addr.lba); + else + lastlba = cdp->disk_size; } + lba += bp->bio_offset / blocksize; count = bp->bio_length / blocksize; + /* Reject zero count / non-integral operation */ + if (count < 1 || bp->bio_length % blocksize || + bp->bio_offset % blocksize) { + g_io_deliver(bp, EINVAL); + return; + } + + /* Reject request past the end of media */ + if (bp->bio_offset > cdp->pp[track]->mediasize) { + g_io_deliver(bp, EIO); + return; + } + if (bp->bio_cmd == BIO_READ) { + /* Reject invalid offset */ + if (bp->bio_offset < 0) { + g_io_deliver(bp, EIO); + return; + } /* if transfer goes beyond range adjust it to be within limits */ if (lba + count > lastlba) { /* if we are entirely beyond EOM return EOF */ @@ -1217,18 +1274,38 @@ int track, ntracks, len; u_int32_t sizes[2]; int8_t ccb[16]; + u_int8_t dinfo[34]; struct g_provider *pp; - if (acd_test_ready(cdp->device)) + if (cdp->pp[0] != NULL) { + if (atapicd_multiblock) + cdp->pp[0]->flags |= ACD_F_MULTIBLOCK; + else + cdp->pp[0]->flags &= ~ACD_F_MULTIBLOCK; + } + + if (atapicd_aggresive_toc) { + bzero(&cdp->toc, sizeof(cdp->toc)); + bzero(ccb, sizeof(ccb)); + cdp->disk_size = -1; /* hack for GEOM SOS */ + } + track = 1; + + if (acd_test_ready(cdp->device)) { + if (atapicd_aggresive_toc) + goto flush_geoms; return; + } - if (!(cdp->device->flags & ATA_D_MEDIA_CHANGED)) + if (!atapicd_aggresive_toc && !(cdp->device->flags & ATA_D_MEDIA_CHANGED)) return; cdp->device->flags &= ~ATA_D_MEDIA_CHANGED; - bzero(&cdp->toc, sizeof(cdp->toc)); - bzero(ccb, sizeof(ccb)); - cdp->disk_size = -1; /* hack for GEOM SOS */ + if (!atapicd_aggresive_toc) { + bzero(&cdp->toc, sizeof(cdp->toc)); + bzero(ccb, sizeof(ccb)); + cdp->disk_size = -1; /* hack for GEOM SOS */ + } len = sizeof(struct ioc_toc_header) + sizeof(struct cd_toc_entry); ccb[0] = ATAPI_READ_TOC; @@ -1237,11 +1314,15 @@ if (ata_atapicmd(cdp->device, ccb, (caddr_t)&cdp->toc, len, ATA_R_READ | ATA_R_QUIET, 30)) { bzero(&cdp->toc, sizeof(cdp->toc)); + if (atapicd_aggresive_toc) + goto flush_geoms; return; } ntracks = cdp->toc.hdr.ending_track - cdp->toc.hdr.starting_track + 1; if (ntracks <= 0 || ntracks > MAXTRK) { bzero(&cdp->toc, sizeof(cdp->toc)); + if (atapicd_aggresive_toc) + goto flush_geoms; return; } @@ -1253,20 +1334,38 @@ if (ata_atapicmd(cdp->device, ccb, (caddr_t)&cdp->toc, len, ATA_R_READ | ATA_R_QUIET, 30)) { bzero(&cdp->toc, sizeof(cdp->toc)); + if (atapicd_aggresive_toc) + goto flush_geoms; return; } cdp->toc.hdr.len = ntohs(cdp->toc.hdr.len); cdp->block_size = (cdp->toc.tab[0].control & 4) ? 2048 : 2352; acd_set_ioparm(cdp); + /* writable / appendable media capacity */ bzero(ccb, sizeof(ccb)); - ccb[0] = ATAPI_READ_CAPACITY; - if (ata_atapicmd(cdp->device, ccb, (caddr_t)sizes, sizeof(sizes), + bzero(dinfo, sizeof(dinfo)); + ccb[0] = ATAPI_READ_DISK_INFO; + ccb[7] = sizeof(dinfo) >> 8; + ccb[8] = sizeof(dinfo); + if ((cdp->cap.media & (MST_WRITE_CDR | MST_WRITE_CDRW | + MST_WRITE_DVDR | MST_WRITE_DVDRAM)) + && !ata_atapicmd(cdp->device, ccb, (caddr_t)dinfo, sizeof(dinfo), + ATA_R_READ | ATA_R_QUIET, 30) + && (dinfo[21] != 0xff || dinfo[22] != 0xff || dinfo[23] != 0xff)) { + cdp->disk_size = msf2lba(dinfo[21], dinfo[22], dinfo[23]); + } else { + bzero(ccb, sizeof(ccb)); + ccb[0] = ATAPI_READ_CAPACITY; + if (ata_atapicmd(cdp->device, ccb, (caddr_t)sizes, sizeof(sizes), ATA_R_READ | ATA_R_QUIET, 30)) { - bzero(&cdp->toc, sizeof(cdp->toc)); - return; + bzero(&cdp->toc, sizeof(cdp->toc)); + if (atapicd_aggresive_toc) + goto flush_geoms; + return; + } + cdp->disk_size = ntohl(sizes[0]) + 1; } - cdp->disk_size = ntohl(sizes[0]) + 1; for (track = 1; track <= ntracks; track ++) { if (cdp->pp[track] != NULL) @@ -1276,6 +1375,7 @@ cdp->pp[track] = pp; g_error_provider(pp, 0); } +flush_geoms: for (; track < MAXTRK; track ++) { if (cdp->pp[track] == NULL) continue; @@ -2027,4 +2127,27 @@ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; return ata_atapicmd(atadev, ccb, NULL, 0, 0, 30); +} + +static int +acd_get_blocksize(off_t length, u_int32_t blocksize) +{ + /* + * Determine correct blocksize from bio length + * + * 2048 / 2056 / 2324 / 2332 / 2336 / 2352 + */ + if (length >= 2352 && !(length % 2352)) + return 2352; + else if (length >= 2336 && !(length % 2336)) + return 2336; + else if (length >= 2332 && !(length % 2332)) + return 2332; + else if (length >= 2324 && !(length % 2324)) + return 2324; + else if (length >= 2056 && !(length % 2056)) + return 2056; + else if (length >= 2048 && !(length % 2048)) + return 2048; + return blocksize; /* return default blocksize */ } --- sys/dev/ata/ata-all.c.orig Sun Apr 10 16:05:06 2005 +++ sys/dev/ata/ata-all.c Sun Apr 10 16:06:52 2005 @@ -81,6 +81,10 @@ devclass_t ata_devclass; uma_zone_t ata_zone; int ata_wc = 1; +#ifdef DEV_ATAPICD +int atapicd_aggresive_toc = 0; +int atapicd_multiblock = 1; +#endif /* local vars */ static struct intr_config_hook *ata_delayed_attach = NULL; @@ -99,6 +103,16 @@ TUNABLE_INT("hw.ata.atapi_dma", &atapi_dma); SYSCTL_INT(_hw_ata, OID_AUTO, atapi_dma, CTLFLAG_RDTUN, &atapi_dma, 0, "ATAPI device DMA mode control"); +#ifdef DEV_ATAPICD +TUNABLE_INT("hw.ata.atapicd_aggresive_toc", &atapicd_aggresive_toc); +SYSCTL_INT(_hw_ata, OID_AUTO, atapicd_aggresive_toc, CTLFLAG_RW, + &atapicd_aggresive_toc, 0, + "ATAPI CDROM aggresive TOC read"); +TUNABLE_INT("hw.ata.atapicd_multiblock", &atapicd_multiblock); +SYSCTL_INT(_hw_ata, OID_AUTO, atapicd_multiblock, CTLFLAG_RW, + &atapicd_multiblock, 0, + "ATAPI CDROM multiple blocksize access"); +#endif /* * newbus device interface related functions --- sys/geom/geom.h.orig Sun Apr 10 15:17:01 2005 +++ sys/geom/geom.h Sun Apr 10 15:57:18 2005 @@ -189,6 +189,8 @@ #define G_PF_CANDELETE 0x1 #define G_PF_WITHER 0x2 #define G_PF_ORPHAN 0x4 +#define G_PF_IGNOREOFFSET 0x8 +#define G_PF_IGNOREBOUNDARY 0x10 /* Two fields for the implementing class to use */ void *private; --- sys/geom/geom_dev.c.orig Sun Apr 10 15:15:36 2005 +++ sys/geom/geom_dev.c Sun Apr 10 15:56:31 2005 @@ -342,8 +342,9 @@ KASSERT(cp->acr || cp->acw, ("Consumer with zero access count in g_dev_strategy")); - if ((bp->bio_offset % cp->provider->sectorsize) != 0 || - (bp->bio_bcount % cp->provider->sectorsize) != 0) { + if (!(cp->provider->flags & G_PF_IGNOREBOUNDARY) && + ((bp->bio_offset % cp->provider->sectorsize) != 0 || + (bp->bio_bcount % cp->provider->sectorsize) != 0)) { biofinish(bp, NULL, EINVAL); return; } --- sys/geom/geom_io.c.orig Sun Apr 10 15:20:04 2005 +++ sys/geom/geom_io.c Sun Apr 10 16:00:47 2005 @@ -217,17 +217,21 @@ /* Zero sectorsize is a probably lack of media */ if (pp->sectorsize == 0) return (ENXIO); - /* Reject I/O not on sector boundary */ - if (bp->bio_offset % pp->sectorsize) - return (EINVAL); - /* Reject I/O not integral sector long */ - if (bp->bio_length % pp->sectorsize) - return (EINVAL); + if (!(pp->flags & G_PF_IGNOREBOUNDARY)) { + /* Reject I/O not on sector boundary */ + if (bp->bio_offset % pp->sectorsize) + return (EINVAL); + /* Reject I/O not integral sector long */ + if (bp->bio_length % pp->sectorsize) + return (EINVAL); + } /* Reject requests before or past the end of media. */ - if (bp->bio_offset < 0) - return (EIO); - if (bp->bio_offset > pp->mediasize) - return (EIO); + if (!(pp->flags & G_PF_IGNOREOFFSET)) { + if (bp->bio_offset < 0) + return (EIO); + if (bp->bio_offset > pp->mediasize) + return (EIO); + } break; default: break; --- sys/dev/ata/ata-chipset.c.orig Fri Apr 1 02:20:23 2005 +++ sys/dev/ata/ata-chipset.c Fri Mar 11 15:53:43 2005 @@ -90,6 +90,7 @@ static int ata_nvidia_chipinit(device_t); static int ata_via_chipinit(device_t); static void ata_via_family_setmode(struct ata_device *, int); +static int ata_via_check_80pin(struct ata_device *, int); static void ata_via_southbridge_fixup(device_t); static int ata_promise_chipinit(device_t); static int ata_promise_mio_allocate(device_t, struct ata_channel *); @@ -125,6 +126,8 @@ static int ata_serialize(struct ata_channel *, int); static int ata_mode2idx(int); +static u_int32_t via_80pin = 0; + /* generic or unknown ATA chipset init code */ int ata_generic_ident(device_t dev) @@ -2824,6 +2827,8 @@ ata_via_chipinit(device_t dev) { struct ata_pci_controller *ctlr = device_get_softc(dev); + u_int32_t reg50; + int i; if (ata_setup_interrupt(dev)) return ENXIO; @@ -2859,6 +2864,39 @@ pci_write_config(dev, 0x68, DEV_BSIZE, 2); ctlr->setmode = ata_via_family_setmode; + + via_80pin = 0; + + switch (ctlr->chip->max_dma) { + case ATA_UDMA4: + reg50 = pci_read_config(dev, 0x50, 4); + for (i = 24; i >= 0; i -= 8) { + if (((reg50 >> (1 & 16)) & 8) && + ((reg50 >> i) & 0x20) && (((reg50 >> i) & 7) < 2)) { + via_80pin |= (1 << (1 - (i >> 4))); + } + } + break; + case ATA_UDMA5: + reg50 = pci_read_config(dev, 0x50, 4); + for (i = 24; i >= 0; i -= 8) { + if (((reg50 >> i) & 0x10) || + (((reg50 >> i) & 0x20) && (((reg50 >> i) & 7) < 4))) { + via_80pin |= (1 << (1 - (i >> 4))); + } + } + break; + case ATA_UDMA6: + reg50 = pci_read_config(dev, 0x50, 4); + for (i = 24; i >= 0; i -= 8) { + if (((reg50 >> i) & 0x10) || + (((reg50 >> i) & 0x20) && (((reg50 >> i) & 7) < 6))) { + via_80pin |= (1 << (1 - (i >> 4))); + } + } + break; + } + return 0; } @@ -2918,8 +2956,10 @@ mode = ATA_UDMA2; } } - else + else if (ctlr->chip->cfg1 == AMDNVIDIA) mode = ata_check_80pin(atadev, mode); + else + mode = ata_via_check_80pin(atadev, mode); if (ctlr->chip->cfg2 & NVIDIA) reg += 0x10; @@ -2941,6 +2981,19 @@ pci_write_config(parent, reg, 0x8b, 1); atadev->mode = mode; } +} + +static int +ata_via_check_80pin(struct ata_device *atadev, int mode) +{ + if (mode > ATA_UDMA2) { + if (!((via_80pin >> atadev->channel->unit) & 1)) { + ata_prtdev(atadev,"DMA limited to UDMA33, non-ATA66 cable or device\n"); + mode = ATA_UDMA2; + } else if (!atadev->param->hwres) + mode = ATA_UDMA2; + } + return mode; } /* misc functions */ --- usr.sbin/cdcontrol/cdcontrol.c.orig Wed Feb 16 02:29:43 2005 +++ usr.sbin/cdcontrol/cdcontrol.c Wed Feb 16 02:33:13 2005 @@ -73,6 +73,7 @@ #define STATUS_AUDIO 0x1 #define STATUS_MEDIA 0x2 #define STATUS_VOLUME 0x4 +#define SESSION_SECTORS (152*75) struct cmdtab { int command; @@ -1004,6 +1005,12 @@ e[1].addr.msf.frame); else next = ntohl(e[1].addr.lba); + if (e[1].track < 100) { + if (!(e->control & 4) && (e[1].control & 4)) + next -= SESSION_SECTORS; + else if ((e->control & 4) != (e[1].control & 4)) + next -= 150; + } len = next - block; /* Take into account a start offset time. */ lba2msf (len - 150, &m, &s, &f); --- sys/kern/kern_shutdown.c.orig Tue Jul 20 17:52:41 2004 +++ sys/kern/kern_shutdown.c Tue Jul 20 17:55:22 2004 @@ -57,6 +57,7 @@ #include #include #include +#include #include #include #include @@ -429,6 +430,8 @@ printf("\n"); printf("The operating system has halted.\n"); printf("Please press any key to reboot.\n\n"); + if (msgbufp != NULL) + msgbufp->msg_magic = 0; switch (cngetc()) { case -1: /* No console, just die */ cpu_halt(); @@ -483,6 +486,8 @@ printf("Rebooting...\n"); DELAY(1000000); /* wait 1 sec for printf's to complete and be read */ + if (msgbufp != NULL) + msgbufp->msg_magic = 0; /* cpu_boot(howto); */ /* doesn't do anything at the moment */ cpu_reset(); /* NOTREACHED */ /* assuming reset worked */ --- usr.sbin/burncd/burncd.c.orig Tue Mar 29 16:47:00 2005 +++ usr.sbin/burncd/burncd.c Tue Mar 29 16:52:48 2005 @@ -47,6 +47,16 @@ #define BLOCKS 16 +#define WAVE_HEADER_SIZE 44 +#define RIFF_MAGIC "RIFF" +#define WAVE_MAGIC "WAVE" +#define FMT_MAGIC "fmt " +#define DATA_MAGIC "data" +#define WAVE_VALID_EXT ".wav" +#define WAVE_VALID_RATE ((u_int32_t)44100) +#define WAVE_VALID_SAMPLE ((u_int16_t)16) +#define WAVE_VALID_CHANNELS ((u_int16_t)2) + struct track_info { int file; char file_name[MAXPATHLEN + 1]; @@ -59,7 +69,8 @@ static struct track_info tracks[100]; static int global_fd_for_cleanup, quiet, verbose, saved_block_size, notracks; -void add_track(char *, int, int, int); +u_int is_wavefile(char *, int, int); +void add_track(char *, int, int, int, int); void do_DAO(int fd, int, int); void do_TAO(int fd, int, int, int); void do_format(int, int, char *); @@ -76,6 +87,7 @@ int dao = 0, eject = 0, fixate = 0, list = 0, multi = 0, preemp = 0; int nogap = 0, speed = 4 * 177, test_write = 0, force = 0; int block_size = 0, block_type = 0, cdopen = 0, dvdrw = 0; + int wave_check = 0; const char *dev; if ((dev = getenv("CDROM")) == NULL) @@ -166,7 +178,11 @@ if (!strcasecmp(argv[arg], "msinfo")) { struct ioc_read_toc_single_entry entry; struct ioc_toc_header header; + struct cdr_track dummy_track; + bzero(&dummy_track, sizeof(dummy_track)); + if (ioctl(fd, CDRIOCINITTRACK, &dummy_track) < 0) + err(EX_IOERR, "ioctl(CDRIOCINITTRACK)"); if (ioctl(fd, CDIOREADTOCHEADER, &header) < 0) err(EX_IOERR, "ioctl(CDIOREADTOCHEADER)"); bzero(&entry, sizeof(struct ioc_read_toc_single_entry)); @@ -177,7 +193,9 @@ if (ioctl(fd, CDRIOCNEXTWRITEABLEADDR, &addr) < 0) err(EX_IOERR, "ioctl(CDRIOCNEXTWRITEABLEADDR)"); fprintf(stdout, "%d,%d\n", - ntohl(entry.entry.addr.lba), addr); + (entry.entry.control & 4) + ? ntohl(entry.entry.addr.lba) + : 0, addr); break; } @@ -222,29 +240,40 @@ arg++; continue; } - if (!strcasecmp(argv[arg], "audio") || !strcasecmp(argv[arg], "raw")) { + if (!strcasecmp(argv[arg], "raw")) { + block_type = CDR_DB_RAW; + block_size = 2352; + wave_check = 0; + continue; + } + if (!strcasecmp(argv[arg], "audio")) { block_type = CDR_DB_RAW; block_size = 2352; + wave_check = 1; continue; } if (!strcasecmp(argv[arg], "data") || !strcasecmp(argv[arg], "mode1")) { block_type = CDR_DB_ROM_MODE1; block_size = 2048; + wave_check = 0; continue; } if (!strcasecmp(argv[arg], "mode2")) { block_type = CDR_DB_ROM_MODE2; block_size = 2336; + wave_check = 0; continue; } if (!strcasecmp(argv[arg], "xamode1")) { block_type = CDR_DB_XA_MODE1; block_size = 2048; + wave_check = 0; continue; } if (!strcasecmp(argv[arg], "xamode2")) { block_type = CDR_DB_XA_MODE2_F2; block_size = 2324; + wave_check = 0; continue; } if (!strcasecmp(argv[arg], "vcd")) { @@ -252,12 +281,14 @@ block_size = 2352; dao = 1; nogap = 1; + wave_check = 0; continue; } if (!strcasecmp(argv[arg], "dvdrw")) { block_type = CDR_DB_ROM_MODE1; block_size = 2048; dvdrw = 1; + wave_check = 0; continue; } @@ -275,7 +306,8 @@ continue; if ((eol = strchr(file_buf, '\n'))) *eol = '\0'; - add_track(file_buf, block_size, block_type, nogap); + add_track(file_buf, block_size, block_type, nogap, + wave_check); } if (feof(fp)) fclose(fp); @@ -283,7 +315,8 @@ err(EX_IOERR, "fgets(%s)", file_buf); } else - add_track(argv[arg], block_size, block_type, nogap); + add_track(argv[arg], block_size, block_type, nogap, + wave_check); } if (notracks) { if (dvdrw && notracks > 1) @@ -319,8 +352,42 @@ exit(EX_OK); } +#define BYTES2ULONG(b) ((u_int32_t) \ + (((b)[0] & 0xff) | \ + ((b)[1] << 8 & 0xff00) | \ + ((b)[2] << 16 & 0xff0000) | \ + ((b)[3] << 24 & 0xff000000))) + +#define BYTES2USHORT(b) ((u_int16_t) \ + (((b)[0] & 0xff) | \ + ((b)[1] << 8 & 0xff00))) + +u_int +is_wavefile(char *name, int file, int block_type) +{ + char *ext; + u_int8_t hdr[WAVE_HEADER_SIZE]; + + ext = strrchr(name, '.'); + if (block_type == CDR_DB_RAW && ext != NULL + && !strcasecmp(ext, WAVE_VALID_EXT)) { + if (read(file, &hdr, WAVE_HEADER_SIZE) == WAVE_HEADER_SIZE + && !strncmp((char *)hdr, RIFF_MAGIC, strlen(RIFF_MAGIC)) + && !strncmp((char *)hdr + 8, WAVE_MAGIC, strlen(WAVE_MAGIC)) + && !strncmp((char *)hdr + 12, FMT_MAGIC, strlen(FMT_MAGIC)) + && !strncmp((char *)hdr + 36, DATA_MAGIC, strlen(DATA_MAGIC)) + && BYTES2USHORT(hdr + 22) == WAVE_VALID_CHANNELS + && BYTES2ULONG(hdr + 24) == WAVE_VALID_RATE + && BYTES2USHORT(hdr + 34) == WAVE_VALID_SAMPLE) + return WAVE_HEADER_SIZE; + else + lseek(file, 0, SEEK_SET); + } + return 0; +} + void -add_track(char *name, int block_size, int block_type, int nogap) +add_track(char *name, int block_size, int block_type, int nogap, int wave_check) { struct stat sb; int file; @@ -343,18 +410,18 @@ if (file == STDIN_FILENO) tracks[notracks].file_size = -1; else - tracks[notracks].file_size = sb.st_size; + tracks[notracks].file_size = (u_int)sb.st_size + - ((wave_check && (u_int)sb.st_size > WAVE_HEADER_SIZE) + ? is_wavefile(name, file, block_type) + : 0); tracks[notracks].block_size = block_size; tracks[notracks].block_type = block_type; - if (nogap && notracks) + if (notracks && (nogap || + (tracks[notracks - (notracks > 0)].block_type == block_type))) tracks[notracks].pregap = 0; - else { - if (tracks[notracks - (notracks > 0)].block_type == block_type) - tracks[notracks].pregap = 150; - else - tracks[notracks].pregap = 255; - } + else + tracks[notracks].pregap = 150; if (verbose) { int pad = 0; @@ -379,6 +446,7 @@ struct cdr_cue_entry cue[100]; int format = CDR_SESS_CDROM; int addr, i, j = 0; + char buf[2352*BLOCKS]; int bt2ctl[16] = { 0x0, -1, -1, -1, -1, -1, -1, -1, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, -1, -1 }; @@ -391,6 +459,12 @@ if (verbose) fprintf(stderr, "next writeable LBA %d\n", addr); + if (addr != -150) { + addr = -150; + if (verbose) + fprintf(stderr, "resetting next writable LBA!\n"); + } + cue_ent(&cue[j++], bt2ctl[tracks[0].block_type], 0x01, 0x00, 0x0, (bt2df[tracks[0].block_type] & 0xf0) | (tracks[0].block_type < 8 ? 0x01 : 0x04), 0x00, addr); @@ -403,40 +477,20 @@ if (tracks[i].block_type >= CDR_DB_XA_MODE1) format = CDR_SESS_CDROM_XA; - if (i == 0) { - addr += tracks[i].pregap; - tracks[i].addr = addr; - - cue_ent(&cue[j++], bt2ctl[tracks[i].block_type], - 0x01, i+1, 0x1, bt2df[tracks[i].block_type], + if (tracks[i].pregap) { + cue_ent(&cue[j++],bt2ctl[tracks[i].block_type], + 0x01, i+1, 0x0, + bt2df[tracks[i].block_type], 0x00, addr); - - } - else { - if (tracks[i].pregap) { - if (tracks[i].block_type > 0x7) { - cue_ent(&cue[j++],bt2ctl[tracks[i].block_type], - 0x01, i+1, 0x0, - (bt2df[tracks[i].block_type] & 0xf0) | - (tracks[i].block_type < 8 ? 0x01 :0x04), - 0x00, addr); - } - else - cue_ent(&cue[j++],bt2ctl[tracks[i].block_type], - 0x01, i+1, 0x0, - bt2df[tracks[i].block_type], - 0x00, addr); - } - tracks[i].addr = tracks[i - 1].addr + - roundup_blocks(&tracks[i - 1]); - - cue_ent(&cue[j++], bt2ctl[tracks[i].block_type], - 0x01, i+1, 0x1, bt2df[tracks[i].block_type], - 0x00, addr + tracks[i].pregap); - - if (tracks[i].block_type > 0x7) - addr += tracks[i].pregap; + addr += tracks[i].pregap; } + tracks[i].addr = addr; + if (verbose) + fprintf(stderr, "track %d: addr=%d pregap=%d\n", + i+1, tracks[i].addr, tracks[i].pregap); + cue_ent(&cue[j++], bt2ctl[tracks[i].block_type], + 0x01, i+1, 0x1, bt2df[tracks[i].block_type], + 0x00, addr); addr += roundup_blocks(&tracks[i]); } @@ -464,7 +518,45 @@ if (ioctl(fd, CDRIOCSENDCUE, &sheet) < 0) err(EX_IOERR, "ioctl(CDRIOCSENDCUE)"); + bzero(buf, sizeof(buf)); for (i = 0; i < notracks; i++) { + if (tracks[i].pregap > 0) { + int total, write_size, res, retry_pregap; + + if (ioctl(fd, CDRIOCSETBLOCKSIZE, &tracks[i].block_size) < 0) + err(EX_IOERR, "ioctl(CDRIOCSETBLOCKSIZE)"); + if (lseek(fd, (tracks[i].addr - tracks[i].pregap) * + tracks[i].block_size, SEEK_SET) == -1) + err(EX_IOERR, "lseek"); + total = tracks[i].pregap * tracks[i].block_size; + if (i == 0 && (tracks[i].addr - tracks[i].pregap) < 0) + retry_pregap = total; + else + retry_pregap = -1; + if (verbose) + fprintf(stderr, + "writing pregap addr = %d total = %d blocks / %d bytes\n", + tracks[i].addr - tracks[i].pregap, + tracks[i].pregap, total); + while (total > 0) { + write_size = MIN(tracks[i].block_size * BLOCKS, total); + if ((res = write(fd, buf, write_size)) != write_size) { + /* XXX workaround for FreeBSD 5+ GEOM !@#$%^&* */ + if (res == -1 && retry_pregap == total) { + if (lseek(fd, tracks[i].addr * tracks[i].block_size, + SEEK_SET) == -1) + err(EX_IOERR, "lseek"); + retry_pregap = -1; + continue; + } + fprintf(stderr, + "pregap: only wrote %d of %d bytes err=%d\n", + res, write_size, errno); + break; + } + total -= write_size; + } + } if (write_file(fd, &tracks[i])) err(EX_IOERR, "write_file"); } --- sbin/mdconfig/mdconfig.c.orig Wed Jan 5 13:43:47 2005 +++ sbin/mdconfig/mdconfig.c Wed Jan 5 14:06:49 2005 @@ -54,6 +54,7 @@ int ch, fd, i; char *p; int cmdline = 0; + int retry = 1000; for (;;) { ch = getopt(argc, argv, "ab:df:lno:s:S:t:u:x:y:"); @@ -197,7 +198,8 @@ mdio.md_version = MDIOVERSION; mdmaybeload(); - fd = open("/dev/" MDCTL_NAME, O_RDWR, 0); + /* XXX too fast */ + while ((fd = open("/dev/" MDCTL_NAME, O_RDWR, 0)) < 0 && retry--); if (fd < 0) err(1, "open(/dev/%s)", MDCTL_NAME); if (cmdline == 2 --- share/termcap/termcap.src.orig Wed Feb 9 17:58:25 2005 +++ share/termcap/termcap.src Wed Feb 9 17:59:08 2005 @@ -2961,6 +2961,7 @@ # color_xterm and rxvt. xterm|xterm-color|X11 terminal emulator:\ :ti@:te@:tc=xterm-xfree86: +# :te=\E[2J\E[?47l\E8:ti=\E7\E[?47h:tc=xterm-xfree86: # :tc=xterm-r6: # dtterm termcap entry - Obtained from Xinside's CDE with permission # from Thomas Roell --- sys/kern/kern_poll.c.orig Mon Mar 28 23:18:22 2005 +++ sys/kern/kern_poll.c Mon Mar 28 23:18:43 2005 @@ -41,12 +41,6 @@ #include #include -#ifdef SMP -#ifndef COMPILING_LINT -#error DEVICE_POLLING is not compatible with SMP -#endif -#endif - static void netisr_poll(void); /* the two netisr handlers */ static void netisr_pollmore(void); --- sys/kern/vfs_bio.c 30 May 2005 07:01:18 -0000 1.486 +++ sys/kern/vfs_bio.c 8 Jun 2005 15:20:55 -0000 @@ -2095,8 +2095,7 @@ flushbufqueues(1); break; } - waitrunningbufspace(); - numdirtywakeup((lodirtybuffers + hidirtybuffers) / 2); + uio_yield(); } /* @@ -2143,13 +2142,28 @@ flushbufqueues(int flushdeps) { struct thread *td = curthread; + struct buf sentinal; struct vnode *vp; struct mount *mp; struct buf *bp; int hasdeps; + int flushed; + int target; + target = numdirtybuffers - lodirtybuffers; + if (flushdeps && target > 2) + target /= 2; + flushed = 0; + bp = NULL; mtx_lock(&bqlock); - TAILQ_FOREACH(bp, &bufqueues[QUEUE_DIRTY], b_freelist) { + TAILQ_INSERT_TAIL(&bufqueues[QUEUE_DIRTY], &sentinal, b_freelist); + while (flushed != target) { + bp = TAILQ_FIRST(&bufqueues[QUEUE_DIRTY]); + if (bp == &sentinal) + break; + TAILQ_REMOVE(&bufqueues[QUEUE_DIRTY], bp, b_freelist); + TAILQ_INSERT_TAIL(&bufqueues[QUEUE_DIRTY], bp, b_freelist); + if (BUF_LOCK(bp, LK_EXCLUSIVE | LK_NOWAIT, NULL) != 0) continue; KASSERT((bp->b_flags & B_DELWRI), @@ -2165,7 +2179,9 @@ bremfreel(bp); mtx_unlock(&bqlock); brelse(bp); - return (1); + flushed++; + mtx_lock(&bqlock); + continue; } if (LIST_FIRST(&bp->b_dep) != NULL && buf_countdeps(bp, 0)) { @@ -2197,13 +2213,18 @@ vn_finished_write(mp); VOP_UNLOCK(vp, 0, td); flushwithdeps += hasdeps; - return (1); + flushed++; + waitrunningbufspace(); + numdirtywakeup((lodirtybuffers + hidirtybuffers) / 2); + mtx_lock(&bqlock); + continue; } vn_finished_write(mp); BUF_UNLOCK(bp); } + TAILQ_REMOVE(&bufqueues[QUEUE_DIRTY], &sentinal, b_freelist); mtx_unlock(&bqlock); - return (0); + return (flushed); } /* --- sys/cam/scsi/scsi_da.c.orig Sun Jan 29 14:36:15 2006 +++ sys/cam/scsi/scsi_da.c Sun Jan 29 14:37:21 2006 @@ -152,6 +152,13 @@ /* SPI, FC devices */ { /* + * Apacer Audio Steno AV220 USB MP3 Player + */ + {T_DIRECT, SIP_MEDIA_REMOVABLE, "" , "MP3 Flash Drive 1.02", "*"}, + /*quirks*/ DA_Q_NO_SYNC_CACHE|DA_Q_NO_PREVENT + }, + { + /* * Fujitsu M2513A MO drives. * Tested devices: M2513A2 firmware versions 1200 & 1300. * (dip switch selects whether T_DIRECT or T_OPTICAL device) --- sys/dev/usb/ehci.c.orig Sun Jan 29 14:28:09 2006 +++ sys/dev/usb/ehci.c Sun Jan 29 14:30:19 2006 @@ -2711,11 +2711,11 @@ } /* - * Some EHCI chips from VIA seem to trigger interrupts before writing back the - * qTD status, or miss signalling occasionally under heavy load. If the host - * machine is too fast, we we can miss transaction completion - when we scan - * the active list the transaction still seems to be active. This generally - * exhibits itself as a umass stall that never recovers. + * Some EHCI chips from VIA / ATI seem to trigger interrupts before writing + * back the qTD status, or miss signalling occasionally under heavy load. + * If the host machine is too fast, we can miss transaction completion - when + * we scan the active list the transaction still seems to be active. This + * generally exhibits itself as a umass stall that never recovers. * * We work around this behaviour by setting up this callback after any softintr * that completes with transactions still pending, giving us another chance to --- sys/dev/usb/ehci_pci.c.orig Sun Jan 29 14:28:09 2006 +++ sys/dev/usb/ehci_pci.c Sun Jan 29 14:34:13 2006 @@ -79,6 +79,7 @@ #define PCI_EHCI_VENDORID_ACERLABS 0x10b9 #define PCI_EHCI_VENDORID_AMD 0x1022 #define PCI_EHCI_VENDORID_APPLE 0x106b +#define PCI_EHCI_VENDORID_ATI 0x1002 #define PCI_EHCI_VENDORID_CMDTECH 0x1095 #define PCI_EHCI_VENDORID_INTEL 0x8086 #define PCI_EHCI_VENDORID_NEC 0x1033 @@ -88,6 +89,12 @@ #define PCI_EHCI_VENDORID_NVIDIA2 0x10DE #define PCI_EHCI_VENDORID_VIA 0x1106 +#define PCI_EHCI_DEVICEID_SB200 0x43451002 +static const char *ehci_device_sb200 = "ATI SB200 USB 2.0 controller"; + +#define PCI_EHCI_DEVICEID_SB400 0x43731002 +static const char *ehci_device_sb400 = "ATI SB400 USB 2.0 controller"; + #define PCI_EHCI_DEVICEID_NEC 0x00e01033 static const char *ehci_device_nec = "NEC uPD 720100 USB 2.0 controller"; @@ -164,6 +171,10 @@ u_int32_t device_id = pci_get_devid(self); switch (device_id) { + case PCI_EHCI_DEVICEID_SB200: + return (ehci_device_sb200); + case PCI_EHCI_DEVICEID_SB400: + return (ehci_device_sb400); case PCI_EHCI_DEVICEID_NEC: return (ehci_device_nec); case PCI_EHCI_DEVICEID_VIA: @@ -265,6 +276,9 @@ case PCI_EHCI_VENDORID_APPLE: sprintf(sc->sc_vendor, "Apple"); break; + case PCI_EHCI_VENDORID_ATI: + sprintf(sc->sc_vendor, "ATI"); + break; case PCI_EHCI_VENDORID_CMDTECH: sprintf(sc->sc_vendor, "CMDTECH"); break; @@ -304,8 +318,17 @@ } /* Enable workaround for dropped interrupts as required */ - if (pci_get_vendor(self) == PCI_EHCI_VENDORID_VIA) + switch (pci_get_vendor(self)) { + case PCI_EHCI_VENDORID_ATI: + case PCI_EHCI_VENDORID_VIA: sc->sc_flags |= EHCI_SCFLG_LOSTINTRBUG; + if (bootverbose) + device_printf(self, + "Dropped interrupts workaround enabled\n"); + break; + default: + break; + } /* * Find companion controllers. According to the spec they always --- sys/dev/usb/ehcivar.h.orig Sun Jan 29 14:28:09 2006 +++ sys/dev/usb/ehcivar.h Sun Jan 29 14:35:32 2006 @@ -93,7 +93,7 @@ #define EHCI_COMPANION_MAX 8 #define EHCI_SCFLG_DONEINIT 0x0001 /* ehci_init() has been called. */ -#define EHCI_SCFLG_LOSTINTRBUG 0x0002 /* workaround for VIA chipsets */ +#define EHCI_SCFLG_LOSTINTRBUG 0x0002 /* workaround for VIA / ATI chipsets */ typedef struct ehci_softc { struct usbd_bus sc_bus; /* base device */ --- sys/dev/usb/ohci_pci.c.orig Mon Jan 16 12:15:55 2006 +++ sys/dev/usb/ohci_pci.c Mon Jan 16 12:29:07 2006 @@ -75,6 +75,7 @@ #define PCI_OHCI_VENDORID_ACERLABS 0x10b9 #define PCI_OHCI_VENDORID_AMD 0x1022 #define PCI_OHCI_VENDORID_APPLE 0x106b +#define PCI_OHCI_VENDORID_ATI 0x1002 #define PCI_OHCI_VENDORID_CMDTECH 0x1095 #define PCI_OHCI_VENDORID_NEC 0x1033 #define PCI_OHCI_VENDORID_NVIDIA 0x12D2 @@ -91,6 +92,10 @@ #define PCI_OHCI_DEVICEID_AMD766 0x74141022 static const char *ohci_device_amd766 = "AMD-766 USB Controller"; +#define PCI_OHCI_DEVICEID_SB400_1 0x43741002 +#define PCI_OHCI_DEVICEID_SB400_2 0x43751002 +static const char *ohci_device_sb400 = "ATI SB400 USB Controller"; + #define PCI_OHCI_DEVICEID_FIRELINK 0xc8611045 static const char *ohci_device_firelink = "OPTi 82C861 (FireLink) USB controller"; @@ -170,6 +175,9 @@ return (ohci_device_amd756); case PCI_OHCI_DEVICEID_AMD766: return (ohci_device_amd766); + case PCI_OHCI_DEVICEID_SB400_1: + case PCI_OHCI_DEVICEID_SB400_2: + return (ohci_device_sb400); case PCI_OHCI_DEVICEID_USB0670: return (ohci_device_usb0670); case PCI_OHCI_DEVICEID_USB0673: @@ -257,6 +265,9 @@ break; case PCI_OHCI_VENDORID_APPLE: sprintf(sc->sc_vendor, "Apple"); + break; + case PCI_OHCI_VENDORID_ATI: + sprintf(sc->sc_vendor, "ATI"); break; case PCI_OHCI_VENDORID_CMDTECH: sprintf(sc->sc_vendor, "CMDTECH"); --- libexec/rtld-elf/rtld.c.orig Sat Jun 17 08:04:39 2006 +++ libexec/rtld-elf/rtld.c Sat Jun 17 08:05:45 2006 @@ -211,6 +211,8 @@ int tls_dtv_generation = 1; /* Used to detect when dtv size changes */ int tls_max_index = 1; /* Largest module index allocated */ +__weak_reference(dlsym, _dlsym); + /* * Fill in a DoneList with an allocation large enough to hold all of * the currently-loaded objects. Keep this as a macro since it calls