--- 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); -