|  | /* | 
|  | *  linux/sound/oss/dmasound/dmasound_awacs.c | 
|  | * | 
|  | *  PowerMac `AWACS' and `Burgundy' DMA Sound Driver | 
|  | *  with some limited support for DACA & Tumbler | 
|  | * | 
|  | *  See linux/sound/oss/dmasound/dmasound_core.c for copyright and | 
|  | *  history prior to 2001/01/26. | 
|  | * | 
|  | *	26/01/2001 ed 0.1 Iain Sandoe | 
|  | *		- added version info. | 
|  | *		- moved dbdma command buffer allocation to PMacXXXSqSetup() | 
|  | *		- fixed up beep dbdma cmd buffers | 
|  | * | 
|  | *	08/02/2001 [0.2] | 
|  | *		- make SNDCTL_DSP_GETFMTS return the correct info for the h/w | 
|  | *		- move soft format translations to a separate file | 
|  | *		- [0.3] make SNDCTL_DSP_GETCAPS return correct info. | 
|  | *		- [0.4] more informative machine name strings. | 
|  | *		- [0.5] | 
|  | *		- record changes. | 
|  | *		- made the default_hard/soft entries. | 
|  | *	04/04/2001 [0.6] | 
|  | *		- minor correction to bit assignments in awacs_defs.h | 
|  | *		- incorporate mixer changes from 2.2.x back-port. | 
|  | *		- take out passthru as a rec input (it isn't). | 
|  | *              - make Input Gain slider work the 'right way up'. | 
|  | *              - try to make the mixer sliders more logical - so now the | 
|  | *                input selectors are just two-state (>50% == ON) and the | 
|  | *                Input Gain slider handles the rest of the gain issues. | 
|  | *              - try to pick slider representations that most closely match | 
|  | *                the actual use - e.g. IGain for input gain... | 
|  | *              - first stab at over/under-run detection. | 
|  | *		- minor cosmetic changes to IRQ identification. | 
|  | *		- fix bug where rates > max would be reported as supported. | 
|  | *              - first stab at over/under-run detection. | 
|  | *              - make use of i2c for mixer settings conditional on perch | 
|  | *                rather than cuda (some machines without perch have cuda). | 
|  | *              - fix bug where TX stops when dbdma status comes up "DEAD" | 
|  | *		  so far only reported on PowerComputing clones ... but. | 
|  | *		- put in AWACS/Screamer register write timeouts. | 
|  | *		- part way to partitioning the init() stuff | 
|  | *		- first pass at 'tumbler' stuff (not support - just an attempt | 
|  | *		  to allow the driver to load on new G4s). | 
|  | *      01/02/2002 [0.7] - BenH | 
|  | *	        - all sort of minor bits went in since the latest update, I | 
|  | *	          bumped the version number for that reason | 
|  | * | 
|  | *      07/26/2002 [0.8] - BenH | 
|  | *	        - More minor bits since last changelog (I should be more careful | 
|  | *	          with those) | 
|  | *	        - Support for snapper & better tumbler integration by Toby Sargeant | 
|  | *	        - Headphone detect for scremer by Julien Blache | 
|  | *	        - More tumbler fixed by Andreas Schwab | 
|  | *	11/29/2003 [0.8.1] - Renzo Davoli (King Enzo) | 
|  | *		- Support for Snapper line in | 
|  | *		- snapper input resampling (for rates < 44100) | 
|  | *		- software line gain control | 
|  | */ | 
|  |  | 
|  | /* GENERAL FIXME/TODO: check that the assumptions about what is written to | 
|  | mac-io is valid for DACA & Tumbler. | 
|  |  | 
|  | This driver is in bad need of a rewrite. The dbdma code has to be split, | 
|  | some proper device-tree parsing code has to be written, etc... | 
|  | */ | 
|  |  | 
|  | #include <linux/types.h> | 
|  | #include <linux/module.h> | 
|  | #include <linux/config.h> | 
|  | #include <linux/slab.h> | 
|  | #include <linux/init.h> | 
|  | #include <linux/delay.h> | 
|  | #include <linux/soundcard.h> | 
|  | #include <linux/adb.h> | 
|  | #include <linux/nvram.h> | 
|  | #include <linux/tty.h> | 
|  | #include <linux/vt_kern.h> | 
|  | #include <linux/spinlock.h> | 
|  | #include <linux/kmod.h> | 
|  | #include <linux/interrupt.h> | 
|  | #include <linux/input.h> | 
|  | #include <asm/semaphore.h> | 
|  | #ifdef CONFIG_ADB_CUDA | 
|  | #include <linux/cuda.h> | 
|  | #endif | 
|  | #ifdef CONFIG_ADB_PMU | 
|  | #include <linux/pmu.h> | 
|  | #endif | 
|  |  | 
|  | #include <linux/i2c-dev.h> | 
|  |  | 
|  | #include <asm/uaccess.h> | 
|  | #include <asm/prom.h> | 
|  | #include <asm/machdep.h> | 
|  | #include <asm/io.h> | 
|  | #include <asm/dbdma.h> | 
|  | #include <asm/pmac_feature.h> | 
|  | #include <asm/irq.h> | 
|  | #include <asm/nvram.h> | 
|  |  | 
|  | #include "awacs_defs.h" | 
|  | #include "dmasound.h" | 
|  | #include "tas3001c.h" | 
|  | #include "tas3004.h" | 
|  | #include "tas_common.h" | 
|  |  | 
|  | #define DMASOUND_AWACS_REVISION	0 | 
|  | #define DMASOUND_AWACS_EDITION	7 | 
|  |  | 
|  | #define AWACS_SNAPPER   110	/* fake revision # for snapper */ | 
|  | #define AWACS_BURGUNDY	100	/* fake revision # for burgundy */ | 
|  | #define AWACS_TUMBLER    90	/* fake revision # for tumbler */ | 
|  | #define AWACS_DACA	 80	/* fake revision # for daca (ibook) */ | 
|  | #define AWACS_AWACS       2     /* holding revision for AWACS */ | 
|  | #define AWACS_SCREAMER    3     /* holding revision for Screamer */ | 
|  | /* | 
|  | * Interrupt numbers and addresses, & info obtained from the device tree. | 
|  | */ | 
|  | static int awacs_irq, awacs_tx_irq, awacs_rx_irq; | 
|  | static volatile struct awacs_regs __iomem *awacs; | 
|  | static volatile u32 __iomem *i2s; | 
|  | static volatile struct dbdma_regs __iomem *awacs_txdma, *awacs_rxdma; | 
|  | static int awacs_rate_index; | 
|  | static int awacs_subframe; | 
|  | static struct device_node* awacs_node; | 
|  | static struct device_node* i2s_node; | 
|  |  | 
|  | static char awacs_name[64]; | 
|  | static int awacs_revision; | 
|  | static int awacs_sleeping; | 
|  | static DECLARE_MUTEX(dmasound_sem); | 
|  |  | 
|  | static int sound_device_id;		/* exists after iMac revA */ | 
|  | static int hw_can_byteswap = 1 ;	/* most pmac sound h/w can */ | 
|  |  | 
|  | /* model info */ | 
|  | /* To be replaced with better interaction with pmac_feature.c */ | 
|  | static int is_pbook_3X00; | 
|  | static int is_pbook_g3; | 
|  |  | 
|  | /* expansion info */ | 
|  | static int has_perch; | 
|  | static int has_ziva; | 
|  |  | 
|  | /* for earlier powerbooks which need fiddling with mac-io to enable | 
|  | * cd etc. | 
|  | */ | 
|  | static unsigned char __iomem *latch_base; | 
|  | static unsigned char __iomem *macio_base; | 
|  |  | 
|  | /* | 
|  | * Space for the DBDMA command blocks. | 
|  | */ | 
|  | static void *awacs_tx_cmd_space; | 
|  | static volatile struct dbdma_cmd *awacs_tx_cmds; | 
|  | static int number_of_tx_cmd_buffers; | 
|  |  | 
|  | static void *awacs_rx_cmd_space; | 
|  | static volatile struct dbdma_cmd *awacs_rx_cmds; | 
|  | static int number_of_rx_cmd_buffers; | 
|  |  | 
|  | /* | 
|  | * Cached values of AWACS registers (we can't read them). | 
|  | * Except on the burgundy (and screamer). XXX | 
|  | */ | 
|  |  | 
|  | int awacs_reg[8]; | 
|  | int awacs_reg1_save; | 
|  |  | 
|  | /* tracking values for the mixer contents | 
|  | */ | 
|  |  | 
|  | static int spk_vol; | 
|  | static int line_vol; | 
|  | static int passthru_vol; | 
|  |  | 
|  | static int ip_gain;           /* mic preamp settings */ | 
|  | static int rec_lev = 0x4545 ; /* default CD gain 69 % */ | 
|  | static int mic_lev; | 
|  | static int cd_lev = 0x6363 ; /* 99 % */ | 
|  | static int line_lev; | 
|  |  | 
|  | static int hdp_connected; | 
|  |  | 
|  | /* | 
|  | * Stuff for outputting a beep.  The values range from -327 to +327 | 
|  | * so we can multiply by an amplitude in the range 0..100 to get a | 
|  | * signed short value to put in the output buffer. | 
|  | */ | 
|  | static short beep_wform[256] = { | 
|  | 0,	40,	79,	117,	153,	187,	218,	245, | 
|  | 269,	288,	304,	316,	323,	327,	327,	324, | 
|  | 318,	310,	299,	288,	275,	262,	249,	236, | 
|  | 224,	213,	204,	196,	190,	186,	183,	182, | 
|  | 182,	183,	186,	189,	192,	196,	200,	203, | 
|  | 206,	208,	209,	209,	209,	207,	204,	201, | 
|  | 197,	193,	188,	183,	179,	174,	170,	166, | 
|  | 163,	161,	160,	159,	159,	160,	161,	162, | 
|  | 164,	166,	168,	169,	171,	171,	171,	170, | 
|  | 169,	167,	163,	159,	155,	150,	144,	139, | 
|  | 133,	128,	122,	117,	113,	110,	107,	105, | 
|  | 103,	103,	103,	103,	104,	104,	105,	105, | 
|  | 105,	103,	101,	97,	92,	86,	78,	68, | 
|  | 58,	45,	32,	18,	3,	-11,	-26,	-41, | 
|  | -55,	-68,	-79,	-88,	-95,	-100,	-102,	-102, | 
|  | -99,	-93,	-85,	-75,	-62,	-48,	-33,	-16, | 
|  | 0,	16,	33,	48,	62,	75,	85,	93, | 
|  | 99,	102,	102,	100,	95,	88,	79,	68, | 
|  | 55,	41,	26,	11,	-3,	-18,	-32,	-45, | 
|  | -58,	-68,	-78,	-86,	-92,	-97,	-101,	-103, | 
|  | -105,	-105,	-105,	-104,	-104,	-103,	-103,	-103, | 
|  | -103,	-105,	-107,	-110,	-113,	-117,	-122,	-128, | 
|  | -133,	-139,	-144,	-150,	-155,	-159,	-163,	-167, | 
|  | -169,	-170,	-171,	-171,	-171,	-169,	-168,	-166, | 
|  | -164,	-162,	-161,	-160,	-159,	-159,	-160,	-161, | 
|  | -163,	-166,	-170,	-174,	-179,	-183,	-188,	-193, | 
|  | -197,	-201,	-204,	-207,	-209,	-209,	-209,	-208, | 
|  | -206,	-203,	-200,	-196,	-192,	-189,	-186,	-183, | 
|  | -182,	-182,	-183,	-186,	-190,	-196,	-204,	-213, | 
|  | -224,	-236,	-249,	-262,	-275,	-288,	-299,	-310, | 
|  | -318,	-324,	-327,	-327,	-323,	-316,	-304,	-288, | 
|  | -269,	-245,	-218,	-187,	-153,	-117,	-79,	-40, | 
|  | }; | 
|  |  | 
|  | /* beep support */ | 
|  | #define BEEP_SRATE	22050	/* 22050 Hz sample rate */ | 
|  | #define BEEP_BUFLEN	512 | 
|  | #define BEEP_VOLUME	15	/* 0 - 100 */ | 
|  |  | 
|  | static int beep_vol = BEEP_VOLUME; | 
|  | static int beep_playing; | 
|  | static int awacs_beep_state; | 
|  | static short *beep_buf; | 
|  | static void *beep_dbdma_cmd_space; | 
|  | static volatile struct dbdma_cmd *beep_dbdma_cmd; | 
|  |  | 
|  | /* Burgundy functions */ | 
|  | static void awacs_burgundy_wcw(unsigned addr,unsigned newval); | 
|  | static unsigned awacs_burgundy_rcw(unsigned addr); | 
|  | static void awacs_burgundy_write_volume(unsigned address, int volume); | 
|  | static int awacs_burgundy_read_volume(unsigned address); | 
|  | static void awacs_burgundy_write_mvolume(unsigned address, int volume); | 
|  | static int awacs_burgundy_read_mvolume(unsigned address); | 
|  |  | 
|  | /* we will allocate a single 'emergency' dbdma cmd block to use if the | 
|  | tx status comes up "DEAD".  This happens on some PowerComputing Pmac | 
|  | clones, either owing to a bug in dbdma or some interaction between | 
|  | IDE and sound.  However, this measure would deal with DEAD status if | 
|  | if appeared elsewhere. | 
|  |  | 
|  | for the sake of memory efficiency we'll allocate this cmd as part of | 
|  | the beep cmd stuff. | 
|  | */ | 
|  |  | 
|  | static volatile struct dbdma_cmd *emergency_dbdma_cmd; | 
|  |  | 
|  | #ifdef CONFIG_PMAC_PBOOK | 
|  | /* | 
|  | * Stuff for restoring after a sleep. | 
|  | */ | 
|  | static int awacs_sleep_notify(struct pmu_sleep_notifier *self, int when); | 
|  | struct pmu_sleep_notifier awacs_sleep_notifier = { | 
|  | awacs_sleep_notify, SLEEP_LEVEL_SOUND, | 
|  | }; | 
|  | #endif /* CONFIG_PMAC_PBOOK */ | 
|  |  | 
|  | /* for (soft) sample rate translations */ | 
|  | int expand_bal;		/* Balance factor for expanding (not volume!) */ | 
|  | int expand_read_bal;	/* Balance factor for expanding reads (not volume!) */ | 
|  |  | 
|  | /*** Low level stuff *********************************************************/ | 
|  |  | 
|  | static void *PMacAlloc(unsigned int size, int flags); | 
|  | static void PMacFree(void *ptr, unsigned int size); | 
|  | static int PMacIrqInit(void); | 
|  | #ifdef MODULE | 
|  | static void PMacIrqCleanup(void); | 
|  | #endif | 
|  | static void PMacSilence(void); | 
|  | static void PMacInit(void); | 
|  | static int PMacSetFormat(int format); | 
|  | static int PMacSetVolume(int volume); | 
|  | static void PMacPlay(void); | 
|  | static void PMacRecord(void); | 
|  | static irqreturn_t pmac_awacs_tx_intr(int irq, void *devid, struct pt_regs *regs); | 
|  | static irqreturn_t pmac_awacs_rx_intr(int irq, void *devid, struct pt_regs *regs); | 
|  | static irqreturn_t pmac_awacs_intr(int irq, void *devid, struct pt_regs *regs); | 
|  | static void awacs_write(int val); | 
|  | static int awacs_get_volume(int reg, int lshift); | 
|  | static int awacs_volume_setter(int volume, int n, int mute, int lshift); | 
|  |  | 
|  |  | 
|  | /*** Mid level stuff **********************************************************/ | 
|  |  | 
|  | static int PMacMixerIoctl(u_int cmd, u_long arg); | 
|  | static int PMacWriteSqSetup(void); | 
|  | static int PMacReadSqSetup(void); | 
|  | static void PMacAbortRead(void); | 
|  |  | 
|  | extern TRANS transAwacsNormal ; | 
|  | extern TRANS transAwacsExpand ; | 
|  | extern TRANS transAwacsNormalRead ; | 
|  | extern TRANS transAwacsExpandRead ; | 
|  |  | 
|  | extern int daca_init(void); | 
|  | extern void daca_cleanup(void); | 
|  | extern int daca_set_volume(uint left_vol, uint right_vol); | 
|  | extern void daca_get_volume(uint * left_vol, uint  *right_vol); | 
|  | extern int daca_enter_sleep(void); | 
|  | extern int daca_leave_sleep(void); | 
|  |  | 
|  | #define TRY_LOCK()	\ | 
|  | if ((rc = down_interruptible(&dmasound_sem)) != 0)	\ | 
|  | return rc; | 
|  | #define LOCK()		down(&dmasound_sem); | 
|  |  | 
|  | #define UNLOCK()	up(&dmasound_sem); | 
|  |  | 
|  | /* We use different versions that the ones provided in dmasound.h | 
|  | * | 
|  | * FIXME: Use different names ;) | 
|  | */ | 
|  | #undef IOCTL_IN | 
|  | #undef IOCTL_OUT | 
|  |  | 
|  | #define IOCTL_IN(arg, ret)	\ | 
|  | rc = get_user(ret, (int __user *)(arg)); \ | 
|  | if (rc) break; | 
|  | #define IOCTL_OUT(arg, ret)	\ | 
|  | ioctl_return2((int __user *)(arg), ret) | 
|  |  | 
|  | static inline int ioctl_return2(int __user *addr, int value) | 
|  | { | 
|  | return value < 0 ? value : put_user(value, addr); | 
|  | } | 
|  |  | 
|  |  | 
|  | /*** AE - TUMBLER / SNAPPER START ************************************************/ | 
|  |  | 
|  |  | 
|  | int gpio_audio_reset, gpio_audio_reset_pol; | 
|  | int gpio_amp_mute, gpio_amp_mute_pol; | 
|  | int gpio_headphone_mute, gpio_headphone_mute_pol; | 
|  | int gpio_headphone_detect, gpio_headphone_detect_pol; | 
|  | int gpio_headphone_irq; | 
|  |  | 
|  | int | 
|  | setup_audio_gpio(const char *name, const char* compatible, int *gpio_addr, int* gpio_pol) | 
|  | { | 
|  | struct device_node *np; | 
|  | u32* pp; | 
|  |  | 
|  | np = find_devices("gpio"); | 
|  | if (!np) | 
|  | return -ENODEV; | 
|  |  | 
|  | np = np->child; | 
|  | while(np != 0) { | 
|  | if (name) { | 
|  | char *property = get_property(np,"audio-gpio",NULL); | 
|  | if (property != 0 && strcmp(property,name) == 0) | 
|  | break; | 
|  | } else if (compatible && device_is_compatible(np, compatible)) | 
|  | break; | 
|  | np = np->sibling; | 
|  | } | 
|  | if (!np) | 
|  | return -ENODEV; | 
|  | pp = (u32 *)get_property(np, "AAPL,address", NULL); | 
|  | if (!pp) | 
|  | return -ENODEV; | 
|  | *gpio_addr = (*pp) & 0x0000ffff; | 
|  | pp = (u32 *)get_property(np, "audio-gpio-active-state", NULL); | 
|  | if (pp) | 
|  | *gpio_pol = *pp; | 
|  | else | 
|  | *gpio_pol = 1; | 
|  | if (np->n_intrs > 0) | 
|  | return np->intrs[0].line; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static inline void | 
|  | write_audio_gpio(int gpio_addr, int data) | 
|  | { | 
|  | if (!gpio_addr) | 
|  | return; | 
|  | pmac_call_feature(PMAC_FTR_WRITE_GPIO, NULL, gpio_addr, data ? 0x05 : 0x04); | 
|  | } | 
|  |  | 
|  | static inline int | 
|  | read_audio_gpio(int gpio_addr) | 
|  | { | 
|  | if (!gpio_addr) | 
|  | return 0; | 
|  | return ((pmac_call_feature(PMAC_FTR_READ_GPIO, NULL, gpio_addr, 0) & 0x02) !=0); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Headphone interrupt via GPIO (Tumbler, Snapper, DACA) | 
|  | */ | 
|  | static irqreturn_t | 
|  | headphone_intr(int irq, void *devid, struct pt_regs *regs) | 
|  | { | 
|  | unsigned long flags; | 
|  |  | 
|  | spin_lock_irqsave(&dmasound.lock, flags); | 
|  | if (read_audio_gpio(gpio_headphone_detect) == gpio_headphone_detect_pol) { | 
|  | printk(KERN_INFO "Audio jack plugged, muting speakers.\n"); | 
|  | write_audio_gpio(gpio_headphone_mute, !gpio_headphone_mute_pol); | 
|  | write_audio_gpio(gpio_amp_mute, gpio_amp_mute_pol); | 
|  | tas_output_device_change(sound_device_id,TAS_OUTPUT_HEADPHONES,0); | 
|  | } else { | 
|  | printk(KERN_INFO "Audio jack unplugged, enabling speakers.\n"); | 
|  | write_audio_gpio(gpio_amp_mute, !gpio_amp_mute_pol); | 
|  | write_audio_gpio(gpio_headphone_mute, gpio_headphone_mute_pol); | 
|  | tas_output_device_change(sound_device_id,TAS_OUTPUT_INTERNAL_SPKR,0); | 
|  | } | 
|  | spin_unlock_irqrestore(&dmasound.lock, flags); | 
|  | return IRQ_HANDLED; | 
|  | } | 
|  |  | 
|  |  | 
|  | /* Initialize tumbler */ | 
|  |  | 
|  | static int | 
|  | tas_dmasound_init(void) | 
|  | { | 
|  | setup_audio_gpio( | 
|  | "audio-hw-reset", | 
|  | NULL, | 
|  | &gpio_audio_reset, | 
|  | &gpio_audio_reset_pol); | 
|  | setup_audio_gpio( | 
|  | "amp-mute", | 
|  | NULL, | 
|  | &gpio_amp_mute, | 
|  | &gpio_amp_mute_pol); | 
|  | setup_audio_gpio("headphone-mute", | 
|  | NULL, | 
|  | &gpio_headphone_mute, | 
|  | &gpio_headphone_mute_pol); | 
|  | gpio_headphone_irq = setup_audio_gpio( | 
|  | "headphone-detect", | 
|  | NULL, | 
|  | &gpio_headphone_detect, | 
|  | &gpio_headphone_detect_pol); | 
|  | /* Fix some broken OF entries in desktop machines */ | 
|  | if (!gpio_headphone_irq) | 
|  | gpio_headphone_irq = setup_audio_gpio( | 
|  | NULL, | 
|  | "keywest-gpio15", | 
|  | &gpio_headphone_detect, | 
|  | &gpio_headphone_detect_pol); | 
|  |  | 
|  | write_audio_gpio(gpio_audio_reset, gpio_audio_reset_pol); | 
|  | msleep(100); | 
|  | write_audio_gpio(gpio_audio_reset, !gpio_audio_reset_pol); | 
|  | msleep(100); | 
|  | if (gpio_headphone_irq) { | 
|  | if (request_irq(gpio_headphone_irq,headphone_intr,0,"Headphone detect",NULL) < 0) { | 
|  | printk(KERN_ERR "tumbler: Can't request headphone interrupt\n"); | 
|  | gpio_headphone_irq = 0; | 
|  | } else { | 
|  | u8 val; | 
|  | /* Activate headphone status interrupts */ | 
|  | val = pmac_call_feature(PMAC_FTR_READ_GPIO, NULL, gpio_headphone_detect, 0); | 
|  | pmac_call_feature(PMAC_FTR_WRITE_GPIO, NULL, gpio_headphone_detect, val | 0x80); | 
|  | /* Trigger it */ | 
|  | headphone_intr(0,NULL,NULL); | 
|  | } | 
|  | } | 
|  | if (!gpio_headphone_irq) { | 
|  | /* Some machine enter this case ? */ | 
|  | printk(KERN_WARNING "tumbler: Headphone detect IRQ not found, enabling all outputs !\n"); | 
|  | write_audio_gpio(gpio_amp_mute, !gpio_amp_mute_pol); | 
|  | write_audio_gpio(gpio_headphone_mute, !gpio_headphone_mute_pol); | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  |  | 
|  | static int | 
|  | tas_dmasound_cleanup(void) | 
|  | { | 
|  | if (gpio_headphone_irq) | 
|  | free_irq(gpio_headphone_irq, NULL); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* We don't support 48k yet */ | 
|  | static int tas_freqs[1] = { 44100 } ; | 
|  | static int tas_freqs_ok[1] = { 1 } ; | 
|  |  | 
|  | /* don't know what to do really - just have to leave it where | 
|  | * OF left things | 
|  | */ | 
|  |  | 
|  | static int | 
|  | tas_set_frame_rate(void) | 
|  | { | 
|  | if (i2s) { | 
|  | out_le32(i2s + (I2S_REG_SERIAL_FORMAT >> 2), 0x41190000); | 
|  | out_le32(i2s + (I2S_REG_DATAWORD_SIZES >> 2), 0x02000200); | 
|  | } | 
|  | dmasound.hard.speed = 44100 ; | 
|  | awacs_rate_index = 0 ; | 
|  | return 44100 ; | 
|  | } | 
|  |  | 
|  | static int | 
|  | tas_mixer_ioctl(u_int cmd, u_long arg) | 
|  | { | 
|  | int __user *argp = (int __user *)arg; | 
|  | int data; | 
|  | int rc; | 
|  |  | 
|  | rc=tas_device_ioctl(cmd, arg); | 
|  | if (rc != -EINVAL) { | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | if ((cmd & ~0xff) == MIXER_WRITE(0) && | 
|  | tas_supported_mixers() & (1<<(cmd & 0xff))) { | 
|  | rc = get_user(data, argp); | 
|  | if (rc<0) return rc; | 
|  | tas_set_mixer_level(cmd & 0xff, data); | 
|  | tas_get_mixer_level(cmd & 0xff, &data); | 
|  | return ioctl_return2(argp, data); | 
|  | } | 
|  | if ((cmd & ~0xff) == MIXER_READ(0) && | 
|  | tas_supported_mixers() & (1<<(cmd & 0xff))) { | 
|  | tas_get_mixer_level(cmd & 0xff, &data); | 
|  | return ioctl_return2(argp, data); | 
|  | } | 
|  |  | 
|  | switch(cmd) { | 
|  | case SOUND_MIXER_READ_DEVMASK: | 
|  | data = tas_supported_mixers() | SOUND_MASK_SPEAKER; | 
|  | rc = IOCTL_OUT(arg, data); | 
|  | break; | 
|  | case SOUND_MIXER_READ_STEREODEVS: | 
|  | data = tas_stereo_mixers(); | 
|  | rc = IOCTL_OUT(arg, data); | 
|  | break; | 
|  | case SOUND_MIXER_READ_CAPS: | 
|  | rc = IOCTL_OUT(arg, 0); | 
|  | break; | 
|  | case SOUND_MIXER_READ_RECMASK: | 
|  | // XXX FIXME: find a way to check what is really available */ | 
|  | data = SOUND_MASK_LINE | SOUND_MASK_MIC; | 
|  | rc = IOCTL_OUT(arg, data); | 
|  | break; | 
|  | case SOUND_MIXER_READ_RECSRC: | 
|  | if (awacs_reg[0] & MASK_MUX_AUDIN) | 
|  | data |= SOUND_MASK_LINE; | 
|  | if (awacs_reg[0] & MASK_MUX_MIC) | 
|  | data |= SOUND_MASK_MIC; | 
|  | rc = IOCTL_OUT(arg, data); | 
|  | break; | 
|  | case SOUND_MIXER_WRITE_RECSRC: | 
|  | IOCTL_IN(arg, data); | 
|  | data =0; | 
|  | rc = IOCTL_OUT(arg, data); | 
|  | break; | 
|  | case SOUND_MIXER_WRITE_SPEAKER:	/* really bell volume */ | 
|  | IOCTL_IN(arg, data); | 
|  | beep_vol = data & 0xff; | 
|  | /* fall through */ | 
|  | case SOUND_MIXER_READ_SPEAKER: | 
|  | rc = IOCTL_OUT(arg, (beep_vol<<8) | beep_vol); | 
|  | break; | 
|  | case SOUND_MIXER_OUTMASK: | 
|  | case SOUND_MIXER_OUTSRC: | 
|  | default: | 
|  | rc = -EINVAL; | 
|  | } | 
|  |  | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | static void __init | 
|  | tas_init_frame_rates(unsigned int *prop, unsigned int l) | 
|  | { | 
|  | int i ; | 
|  | if (prop) { | 
|  | for (i=0; i<1; i++) | 
|  | tas_freqs_ok[i] = 0; | 
|  | for (l /= sizeof(int); l > 0; --l) { | 
|  | unsigned int r = *prop++; | 
|  | /* Apple 'Fixed' format */ | 
|  | if (r >= 0x10000) | 
|  | r >>= 16; | 
|  | for (i = 0; i < 1; ++i) { | 
|  | if (r == tas_freqs[i]) { | 
|  | tas_freqs_ok[i] = 1; | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | /* else we assume that all the rates are available */ | 
|  | } | 
|  |  | 
|  |  | 
|  | /*** AE - TUMBLER / SNAPPER END ************************************************/ | 
|  |  | 
|  |  | 
|  |  | 
|  | /*** Low level stuff *********************************************************/ | 
|  |  | 
|  | /* | 
|  | * PCI PowerMac, with AWACS, Screamer, Burgundy, DACA or Tumbler and DBDMA. | 
|  | */ | 
|  | static void *PMacAlloc(unsigned int size, int flags) | 
|  | { | 
|  | return kmalloc(size, flags); | 
|  | } | 
|  |  | 
|  | static void PMacFree(void *ptr, unsigned int size) | 
|  | { | 
|  | kfree(ptr); | 
|  | } | 
|  |  | 
|  | static int __init PMacIrqInit(void) | 
|  | { | 
|  | if (awacs) | 
|  | if (request_irq(awacs_irq, pmac_awacs_intr, 0, "Built-in Sound misc", NULL)) | 
|  | return 0; | 
|  | if (request_irq(awacs_tx_irq, pmac_awacs_tx_intr, 0, "Built-in Sound out", NULL) | 
|  | || request_irq(awacs_rx_irq, pmac_awacs_rx_intr, 0, "Built-in Sound in", NULL)) | 
|  | return 0; | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | #ifdef MODULE | 
|  | static void PMacIrqCleanup(void) | 
|  | { | 
|  | /* turn off input & output dma */ | 
|  | DBDMA_DO_STOP(awacs_txdma); | 
|  | DBDMA_DO_STOP(awacs_rxdma); | 
|  |  | 
|  | if (awacs) | 
|  | /* disable interrupts from awacs interface */ | 
|  | out_le32(&awacs->control, in_le32(&awacs->control) & 0xfff); | 
|  |  | 
|  | /* Switch off the sound clock */ | 
|  | pmac_call_feature(PMAC_FTR_SOUND_CHIP_ENABLE, awacs_node, 0, 0); | 
|  | /* Make sure proper bits are set on pismo & tipb */ | 
|  | if ((machine_is_compatible("PowerBook3,1") || | 
|  | machine_is_compatible("PowerBook3,2")) && awacs) { | 
|  | awacs_reg[1] |= MASK_PAROUT0 | MASK_PAROUT1; | 
|  | awacs_write(MASK_ADDR1 | awacs_reg[1]); | 
|  | msleep(200); | 
|  | } | 
|  | if (awacs) | 
|  | free_irq(awacs_irq, NULL); | 
|  | free_irq(awacs_tx_irq, NULL); | 
|  | free_irq(awacs_rx_irq, NULL); | 
|  |  | 
|  | if (awacs) | 
|  | iounmap(awacs); | 
|  | if (i2s) | 
|  | iounmap(i2s); | 
|  | iounmap(awacs_txdma); | 
|  | iounmap(awacs_rxdma); | 
|  |  | 
|  | release_OF_resource(awacs_node, 0); | 
|  | release_OF_resource(awacs_node, 1); | 
|  | release_OF_resource(awacs_node, 2); | 
|  |  | 
|  | if (awacs_tx_cmd_space) | 
|  | kfree(awacs_tx_cmd_space); | 
|  | if (awacs_rx_cmd_space) | 
|  | kfree(awacs_rx_cmd_space); | 
|  | if (beep_dbdma_cmd_space) | 
|  | kfree(beep_dbdma_cmd_space); | 
|  | if (beep_buf) | 
|  | kfree(beep_buf); | 
|  | #ifdef CONFIG_PMAC_PBOOK | 
|  | pmu_unregister_sleep_notifier(&awacs_sleep_notifier); | 
|  | #endif | 
|  | } | 
|  | #endif /* MODULE */ | 
|  |  | 
|  | static void PMacSilence(void) | 
|  | { | 
|  | /* turn off output dma */ | 
|  | DBDMA_DO_STOP(awacs_txdma); | 
|  | } | 
|  |  | 
|  | /* don't know what to do really - just have to leave it where | 
|  | * OF left things | 
|  | */ | 
|  |  | 
|  | static int daca_set_frame_rate(void) | 
|  | { | 
|  | if (i2s) { | 
|  | out_le32(i2s + (I2S_REG_SERIAL_FORMAT >> 2), 0x41190000); | 
|  | out_le32(i2s + (I2S_REG_DATAWORD_SIZES >> 2), 0x02000200); | 
|  | } | 
|  | dmasound.hard.speed = 44100 ; | 
|  | awacs_rate_index = 0 ; | 
|  | return 44100 ; | 
|  | } | 
|  |  | 
|  | static int awacs_freqs[8] = { | 
|  | 44100, 29400, 22050, 17640, 14700, 11025, 8820, 7350 | 
|  | }; | 
|  | static int awacs_freqs_ok[8] = { 1, 1, 1, 1, 1, 1, 1, 1 }; | 
|  |  | 
|  | static int | 
|  | awacs_set_frame_rate(int desired, int catch_r) | 
|  | { | 
|  | int tolerance, i = 8 ; | 
|  | /* | 
|  | * If we have a sample rate which is within catchRadius percent | 
|  | * of the requested value, we don't have to expand the samples. | 
|  | * Otherwise choose the next higher rate. | 
|  | * N.B.: burgundy awacs only works at 44100 Hz. | 
|  | */ | 
|  | do { | 
|  | tolerance = catch_r * awacs_freqs[--i] / 100; | 
|  | if (awacs_freqs_ok[i] | 
|  | && dmasound.soft.speed <= awacs_freqs[i] + tolerance) | 
|  | break; | 
|  | } while (i > 0); | 
|  | dmasound.hard.speed = awacs_freqs[i]; | 
|  | awacs_rate_index = i; | 
|  |  | 
|  | out_le32(&awacs->control, MASK_IEPC | (i << 8) | 0x11 ); | 
|  | awacs_reg[1] = (awacs_reg[1] & ~MASK_SAMPLERATE) | (i << 3); | 
|  | awacs_write(awacs_reg[1] | MASK_ADDR1); | 
|  | return dmasound.hard.speed; | 
|  | } | 
|  |  | 
|  | static int | 
|  | burgundy_set_frame_rate(void) | 
|  | { | 
|  | awacs_rate_index = 0 ; | 
|  | awacs_reg[1] = (awacs_reg[1] & ~MASK_SAMPLERATE) ; | 
|  | /* XXX disable error interrupt on burgundy for now */ | 
|  | out_le32(&awacs->control, MASK_IEPC | 0 | 0x11 | MASK_IEE); | 
|  | return 44100 ; | 
|  | } | 
|  |  | 
|  | static int | 
|  | set_frame_rate(int desired, int catch_r) | 
|  | { | 
|  | switch (awacs_revision) { | 
|  | case AWACS_BURGUNDY: | 
|  | dmasound.hard.speed = burgundy_set_frame_rate(); | 
|  | break ; | 
|  | case AWACS_TUMBLER: | 
|  | case AWACS_SNAPPER: | 
|  | dmasound.hard.speed = tas_set_frame_rate(); | 
|  | break ; | 
|  | case AWACS_DACA: | 
|  | dmasound.hard.speed = | 
|  | daca_set_frame_rate(); | 
|  | break ; | 
|  | default: | 
|  | dmasound.hard.speed = awacs_set_frame_rate(desired, | 
|  | catch_r); | 
|  | break ; | 
|  | } | 
|  | return dmasound.hard.speed ; | 
|  | } | 
|  |  | 
|  | static void | 
|  | awacs_recalibrate(void) | 
|  | { | 
|  | /* Sorry for the horrible delays... I hope to get that improved | 
|  | * by making the whole PM process asynchronous in a future version | 
|  | */ | 
|  | msleep(750); | 
|  | awacs_reg[1] |= MASK_CMUTE | MASK_AMUTE; | 
|  | awacs_write(awacs_reg[1] | MASK_RECALIBRATE | MASK_ADDR1); | 
|  | msleep(1000); | 
|  | awacs_write(awacs_reg[1] | MASK_ADDR1); | 
|  | } | 
|  |  | 
|  | static void PMacInit(void) | 
|  | { | 
|  | int tolerance; | 
|  |  | 
|  | switch (dmasound.soft.format) { | 
|  | case AFMT_S16_LE: | 
|  | case AFMT_U16_LE: | 
|  | if (hw_can_byteswap) | 
|  | dmasound.hard.format = AFMT_S16_LE; | 
|  | else | 
|  | dmasound.hard.format = AFMT_S16_BE; | 
|  | break; | 
|  | default: | 
|  | dmasound.hard.format = AFMT_S16_BE; | 
|  | break; | 
|  | } | 
|  | dmasound.hard.stereo = 1; | 
|  | dmasound.hard.size = 16; | 
|  |  | 
|  | /* set dmasound.hard.speed - on the basis of what we want (soft) | 
|  | * and the tolerance we'll allow. | 
|  | */ | 
|  | set_frame_rate(dmasound.soft.speed, catchRadius) ; | 
|  |  | 
|  | tolerance = (catchRadius * dmasound.hard.speed) / 100; | 
|  | if (dmasound.soft.speed >= dmasound.hard.speed - tolerance) { | 
|  | dmasound.trans_write = &transAwacsNormal; | 
|  | dmasound.trans_read = &transAwacsNormalRead; | 
|  | } else { | 
|  | dmasound.trans_write = &transAwacsExpand; | 
|  | dmasound.trans_read = &transAwacsExpandRead; | 
|  | } | 
|  |  | 
|  | if (awacs) { | 
|  | if (hw_can_byteswap && (dmasound.hard.format == AFMT_S16_LE)) | 
|  | out_le32(&awacs->byteswap, BS_VAL); | 
|  | else | 
|  | out_le32(&awacs->byteswap, 0); | 
|  | } | 
|  |  | 
|  | expand_bal = -dmasound.soft.speed; | 
|  | expand_read_bal = -dmasound.soft.speed; | 
|  | } | 
|  |  | 
|  | static int PMacSetFormat(int format) | 
|  | { | 
|  | int size; | 
|  | int req_format = format; | 
|  |  | 
|  | switch (format) { | 
|  | case AFMT_QUERY: | 
|  | return dmasound.soft.format; | 
|  | case AFMT_MU_LAW: | 
|  | case AFMT_A_LAW: | 
|  | case AFMT_U8: | 
|  | case AFMT_S8: | 
|  | size = 8; | 
|  | break; | 
|  | case AFMT_S16_LE: | 
|  | if(!hw_can_byteswap) | 
|  | format = AFMT_S16_BE; | 
|  | case AFMT_S16_BE: | 
|  | size = 16; | 
|  | break; | 
|  | case AFMT_U16_LE: | 
|  | if(!hw_can_byteswap) | 
|  | format = AFMT_U16_BE; | 
|  | case AFMT_U16_BE: | 
|  | size = 16; | 
|  | break; | 
|  | default: /* :-) */ | 
|  | printk(KERN_ERR "dmasound: unknown format 0x%x, using AFMT_U8\n", | 
|  | format); | 
|  | size = 8; | 
|  | format = AFMT_U8; | 
|  | } | 
|  |  | 
|  | if (req_format == format) { | 
|  | dmasound.soft.format = format; | 
|  | dmasound.soft.size = size; | 
|  | if (dmasound.minDev == SND_DEV_DSP) { | 
|  | dmasound.dsp.format = format; | 
|  | dmasound.dsp.size = size; | 
|  | } | 
|  | } | 
|  |  | 
|  | return format; | 
|  | } | 
|  |  | 
|  | #define AWACS_VOLUME_TO_MASK(x)	(15 - ((((x) - 1) * 15) / 99)) | 
|  | #define AWACS_MASK_TO_VOLUME(y)	(100 - ((y) * 99 / 15)) | 
|  |  | 
|  | static int awacs_get_volume(int reg, int lshift) | 
|  | { | 
|  | int volume; | 
|  |  | 
|  | volume = AWACS_MASK_TO_VOLUME((reg >> lshift) & 0xf); | 
|  | volume |= AWACS_MASK_TO_VOLUME(reg & 0xf) << 8; | 
|  | return volume; | 
|  | } | 
|  |  | 
|  | static int awacs_volume_setter(int volume, int n, int mute, int lshift) | 
|  | { | 
|  | int r1, rn; | 
|  |  | 
|  | if (mute && volume == 0) { | 
|  | r1 = awacs_reg[1] | mute; | 
|  | } else { | 
|  | r1 = awacs_reg[1] & ~mute; | 
|  | rn = awacs_reg[n] & ~(0xf | (0xf << lshift)); | 
|  | rn |= ((AWACS_VOLUME_TO_MASK(volume & 0xff) & 0xf) << lshift); | 
|  | rn |= AWACS_VOLUME_TO_MASK((volume >> 8) & 0xff) & 0xf; | 
|  | awacs_reg[n] = rn; | 
|  | awacs_write((n << 12) | rn); | 
|  | volume = awacs_get_volume(rn, lshift); | 
|  | } | 
|  | if (r1 != awacs_reg[1]) { | 
|  | awacs_reg[1] = r1; | 
|  | awacs_write(r1 | MASK_ADDR1); | 
|  | } | 
|  | return volume; | 
|  | } | 
|  |  | 
|  | static int PMacSetVolume(int volume) | 
|  | { | 
|  | printk(KERN_WARNING "Bogus call to PMacSetVolume !\n"); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static void awacs_setup_for_beep(int speed) | 
|  | { | 
|  | out_le32(&awacs->control, | 
|  | (in_le32(&awacs->control) & ~0x1f00) | 
|  | | ((speed > 0 ? speed : awacs_rate_index) << 8)); | 
|  |  | 
|  | if (hw_can_byteswap && (dmasound.hard.format == AFMT_S16_LE) && speed == -1) | 
|  | out_le32(&awacs->byteswap, BS_VAL); | 
|  | else | 
|  | out_le32(&awacs->byteswap, 0); | 
|  | } | 
|  |  | 
|  | /* CHECK: how much of this *really* needs IRQs masked? */ | 
|  | static void __PMacPlay(void) | 
|  | { | 
|  | volatile struct dbdma_cmd *cp; | 
|  | int next_frg, count; | 
|  |  | 
|  | count = 300 ; /* > two cycles at the lowest sample rate */ | 
|  |  | 
|  | /* what we want to send next */ | 
|  | next_frg = (write_sq.front + write_sq.active) % write_sq.max_count; | 
|  |  | 
|  | if (awacs_beep_state) { | 
|  | /* sound takes precedence over beeps */ | 
|  | /* stop the dma channel */ | 
|  | out_le32(&awacs_txdma->control, (RUN|PAUSE|FLUSH|WAKE) << 16); | 
|  | while ( (in_le32(&awacs_txdma->status) & RUN) && count--) | 
|  | udelay(1); | 
|  | if (awacs) | 
|  | awacs_setup_for_beep(-1); | 
|  | out_le32(&awacs_txdma->cmdptr, | 
|  | virt_to_bus(&(awacs_tx_cmds[next_frg]))); | 
|  |  | 
|  | beep_playing = 0; | 
|  | awacs_beep_state = 0; | 
|  | } | 
|  | /* this won't allow more than two frags to be in the output queue at | 
|  | once. (or one, if the max frags is 2 - because count can't exceed | 
|  | 2 in that case) | 
|  | */ | 
|  | while (write_sq.active < 2 && write_sq.active < write_sq.count) { | 
|  | count = (write_sq.count == write_sq.active + 1) ? | 
|  | write_sq.rear_size:write_sq.block_size ; | 
|  | if (count < write_sq.block_size) { | 
|  | if (!write_sq.syncing) /* last block not yet filled,*/ | 
|  | break; 	/* and we're not syncing or POST-ed */ | 
|  | else { | 
|  | /* pretend the block is full to force a new | 
|  | block to be started on the next write */ | 
|  | write_sq.rear_size = write_sq.block_size ; | 
|  | write_sq.syncing &= ~2 ; /* clear POST */ | 
|  | } | 
|  | } | 
|  | cp = &awacs_tx_cmds[next_frg]; | 
|  | st_le16(&cp->req_count, count); | 
|  | st_le16(&cp->xfer_status, 0); | 
|  | st_le16(&cp->command, OUTPUT_MORE + INTR_ALWAYS); | 
|  | /* put a STOP at the end of the queue - but only if we have | 
|  | space for it.  This means that, if we under-run and we only | 
|  | have two fragments, we might re-play sound from an existing | 
|  | queued frag.  I guess the solution to that is not to set two | 
|  | frags if you are likely to under-run... | 
|  | */ | 
|  | if (write_sq.count < write_sq.max_count) { | 
|  | if (++next_frg >= write_sq.max_count) | 
|  | next_frg = 0 ; /* wrap */ | 
|  | /* if we get here then we've underrun so we will stop*/ | 
|  | st_le16(&awacs_tx_cmds[next_frg].command, DBDMA_STOP); | 
|  | } | 
|  | /* set the dbdma controller going, if it is not already */ | 
|  | if (write_sq.active == 0) | 
|  | out_le32(&awacs_txdma->cmdptr, virt_to_bus(cp)); | 
|  | (void)in_le32(&awacs_txdma->status); | 
|  | out_le32(&awacs_txdma->control, ((RUN|WAKE) << 16) + (RUN|WAKE)); | 
|  | ++write_sq.active; | 
|  | } | 
|  | } | 
|  |  | 
|  | static void PMacPlay(void) | 
|  | { | 
|  | LOCK(); | 
|  | if (!awacs_sleeping) { | 
|  | unsigned long flags; | 
|  |  | 
|  | spin_lock_irqsave(&dmasound.lock, flags); | 
|  | __PMacPlay(); | 
|  | spin_unlock_irqrestore(&dmasound.lock, flags); | 
|  | } | 
|  | UNLOCK(); | 
|  | } | 
|  |  | 
|  | static void PMacRecord(void) | 
|  | { | 
|  | unsigned long flags; | 
|  |  | 
|  | if (read_sq.active) | 
|  | return; | 
|  |  | 
|  | spin_lock_irqsave(&dmasound.lock, flags); | 
|  |  | 
|  | /* This is all we have to do......Just start it up. | 
|  | */ | 
|  | out_le32(&awacs_rxdma->control, ((RUN|WAKE) << 16) + (RUN|WAKE)); | 
|  | read_sq.active = 1; | 
|  |  | 
|  | spin_unlock_irqrestore(&dmasound.lock, flags); | 
|  | } | 
|  |  | 
|  | /* if the TX status comes up "DEAD" - reported on some Power Computing machines | 
|  | we need to re-start the dbdma - but from a different physical start address | 
|  | and with a different transfer length.  It would get very messy to do this | 
|  | with the normal dbdma_cmd blocks - we would have to re-write the buffer start | 
|  | addresses each time.  So, we will keep a single dbdma_cmd block which can be | 
|  | fiddled with. | 
|  | When DEAD status is first reported the content of the faulted dbdma block is | 
|  | copied into the emergency buffer and we note that the buffer is in use. | 
|  | we then bump the start physical address by the amount that was successfully | 
|  | output before it died. | 
|  | On any subsequent DEAD result we just do the bump-ups (we know that we are | 
|  | already using the emergency dbdma_cmd). | 
|  | CHECK: this just tries to "do it".  It is possible that we should abandon | 
|  | xfers when the number of residual bytes gets below a certain value - I can | 
|  | see that this might cause a loop-forever if too small a transfer causes | 
|  | DEAD status.  However this is a TODO for now - we'll see what gets reported. | 
|  | When we get a successful transfer result with the emergency buffer we just | 
|  | pretend that it completed using the original dmdma_cmd and carry on.  The | 
|  | 'next_cmd' field will already point back to the original loop of blocks. | 
|  | */ | 
|  |  | 
|  | static irqreturn_t | 
|  | pmac_awacs_tx_intr(int irq, void *devid, struct pt_regs *regs) | 
|  | { | 
|  | int i = write_sq.front; | 
|  | int stat; | 
|  | int i_nowrap = write_sq.front; | 
|  | volatile struct dbdma_cmd *cp; | 
|  | /* != 0 when we are dealing with a DEAD xfer */ | 
|  | static int emergency_in_use; | 
|  |  | 
|  | spin_lock(&dmasound.lock); | 
|  | while (write_sq.active > 0) { /* we expect to have done something*/ | 
|  | if (emergency_in_use) /* we are dealing with DEAD xfer */ | 
|  | cp = emergency_dbdma_cmd ; | 
|  | else | 
|  | cp = &awacs_tx_cmds[i]; | 
|  | stat = ld_le16(&cp->xfer_status); | 
|  | if (stat & DEAD) { | 
|  | unsigned short req, res ; | 
|  | unsigned int phy ; | 
|  | #ifdef DEBUG_DMASOUND | 
|  | printk("dmasound_pmac: tx-irq: xfer died - patching it up...\n") ; | 
|  | #endif | 
|  | /* to clear DEAD status we must first clear RUN | 
|  | set it to quiescent to be on the safe side */ | 
|  | (void)in_le32(&awacs_txdma->status); | 
|  | out_le32(&awacs_txdma->control, | 
|  | (RUN|PAUSE|FLUSH|WAKE) << 16); | 
|  | write_sq.died++ ; | 
|  | if (!emergency_in_use) { /* new problem */ | 
|  | memcpy((void *)emergency_dbdma_cmd, (void *)cp, | 
|  | sizeof(struct dbdma_cmd)); | 
|  | emergency_in_use = 1; | 
|  | cp = emergency_dbdma_cmd; | 
|  | } | 
|  | /* now bump the values to reflect the amount | 
|  | we haven't yet shifted */ | 
|  | req = ld_le16(&cp->req_count); | 
|  | res = ld_le16(&cp->res_count); | 
|  | phy = ld_le32(&cp->phy_addr); | 
|  | phy += (req - res); | 
|  | st_le16(&cp->req_count, res); | 
|  | st_le16(&cp->res_count, 0); | 
|  | st_le16(&cp->xfer_status, 0); | 
|  | st_le32(&cp->phy_addr, phy); | 
|  | st_le32(&cp->cmd_dep, virt_to_bus(&awacs_tx_cmds[(i+1)%write_sq.max_count])); | 
|  | st_le16(&cp->command, OUTPUT_MORE | BR_ALWAYS | INTR_ALWAYS); | 
|  |  | 
|  | /* point at our patched up command block */ | 
|  | out_le32(&awacs_txdma->cmdptr, virt_to_bus(cp)); | 
|  | /* we must re-start the controller */ | 
|  | (void)in_le32(&awacs_txdma->status); | 
|  | /* should complete clearing the DEAD status */ | 
|  | out_le32(&awacs_txdma->control, | 
|  | ((RUN|WAKE) << 16) + (RUN|WAKE)); | 
|  | break; /* this block is still going */ | 
|  | } | 
|  | if ((stat & ACTIVE) == 0) | 
|  | break;	/* this frame is still going */ | 
|  | if (emergency_in_use) | 
|  | emergency_in_use = 0 ; /* done that */ | 
|  | --write_sq.count; | 
|  | --write_sq.active; | 
|  | i_nowrap++; | 
|  | if (++i >= write_sq.max_count) | 
|  | i = 0; | 
|  | } | 
|  |  | 
|  | /* if we stopped and we were not sync-ing - then we under-ran */ | 
|  | if( write_sq.syncing == 0 ){ | 
|  | stat = in_le32(&awacs_txdma->status) ; | 
|  | /* we hit the dbdma_stop */ | 
|  | if( (stat & ACTIVE) == 0 ) write_sq.xruns++ ; | 
|  | } | 
|  |  | 
|  | /* if we used some data up then wake the writer to supply some more*/ | 
|  | if (i_nowrap != write_sq.front) | 
|  | WAKE_UP(write_sq.action_queue); | 
|  | write_sq.front = i; | 
|  |  | 
|  | /* but make sure we funnel what we've already got */\ | 
|  | if (!awacs_sleeping) | 
|  | __PMacPlay(); | 
|  |  | 
|  | /* make the wake-on-empty conditional on syncing */ | 
|  | if (!write_sq.active && (write_sq.syncing & 1)) | 
|  | WAKE_UP(write_sq.sync_queue); /* any time we're empty */ | 
|  | spin_unlock(&dmasound.lock); | 
|  | return IRQ_HANDLED; | 
|  | } | 
|  |  | 
|  |  | 
|  | static irqreturn_t | 
|  | pmac_awacs_rx_intr(int irq, void *devid, struct pt_regs *regs) | 
|  | { | 
|  | int stat ; | 
|  | /* For some reason on my PowerBook G3, I get one interrupt | 
|  | * when the interrupt vector is installed (like something is | 
|  | * pending).  This happens before the dbdma is initialized by | 
|  | * us, so I just check the command pointer and if it is zero, | 
|  | * just blow it off. | 
|  | */ | 
|  | if (in_le32(&awacs_rxdma->cmdptr) == 0) | 
|  | return IRQ_HANDLED; | 
|  |  | 
|  | /* We also want to blow 'em off when shutting down. | 
|  | */ | 
|  | if (read_sq.active == 0) | 
|  | return IRQ_HANDLED; | 
|  |  | 
|  | spin_lock(&dmasound.lock); | 
|  | /* Check multiple buffers in case we were held off from | 
|  | * interrupt processing for a long time.  Geeze, I really hope | 
|  | * this doesn't happen. | 
|  | */ | 
|  | while ((stat=awacs_rx_cmds[read_sq.rear].xfer_status)) { | 
|  |  | 
|  | /* if we got a "DEAD" status then just log it for now. | 
|  | and try to restart dma. | 
|  | TODO: figure out how best to fix it up | 
|  | */ | 
|  | if (stat & DEAD){ | 
|  | #ifdef DEBUG_DMASOUND | 
|  | printk("dmasound_pmac: rx-irq: DIED - attempting resurection\n"); | 
|  | #endif | 
|  | /* to clear DEAD status we must first clear RUN | 
|  | set it to quiescent to be on the safe side */ | 
|  | (void)in_le32(&awacs_txdma->status); | 
|  | out_le32(&awacs_txdma->control, | 
|  | (RUN|PAUSE|FLUSH|WAKE) << 16); | 
|  | awacs_rx_cmds[read_sq.rear].xfer_status = 0; | 
|  | awacs_rx_cmds[read_sq.rear].res_count = 0; | 
|  | read_sq.died++ ; | 
|  | (void)in_le32(&awacs_txdma->status); | 
|  | /* re-start the same block */ | 
|  | out_le32(&awacs_rxdma->cmdptr, | 
|  | virt_to_bus(&awacs_rx_cmds[read_sq.rear])); | 
|  | /* we must re-start the controller */ | 
|  | (void)in_le32(&awacs_rxdma->status); | 
|  | /* should complete clearing the DEAD status */ | 
|  | out_le32(&awacs_rxdma->control, | 
|  | ((RUN|WAKE) << 16) + (RUN|WAKE)); | 
|  | spin_unlock(&dmasound.lock); | 
|  | return IRQ_HANDLED; /* try this block again */ | 
|  | } | 
|  | /* Clear status and move on to next buffer. | 
|  | */ | 
|  | awacs_rx_cmds[read_sq.rear].xfer_status = 0; | 
|  | read_sq.rear++; | 
|  |  | 
|  | /* Wrap the buffer ring. | 
|  | */ | 
|  | if (read_sq.rear >= read_sq.max_active) | 
|  | read_sq.rear = 0; | 
|  |  | 
|  | /* If we have caught up to the front buffer, bump it. | 
|  | * This will cause weird (but not fatal) results if the | 
|  | * read loop is currently using this buffer.  The user is | 
|  | * behind in this case anyway, so weird things are going | 
|  | * to happen. | 
|  | */ | 
|  | if (read_sq.rear == read_sq.front) { | 
|  | read_sq.front++; | 
|  | read_sq.xruns++ ; /* we overan */ | 
|  | if (read_sq.front >= read_sq.max_active) | 
|  | read_sq.front = 0; | 
|  | } | 
|  | } | 
|  |  | 
|  | WAKE_UP(read_sq.action_queue); | 
|  | spin_unlock(&dmasound.lock); | 
|  | return IRQ_HANDLED; | 
|  | } | 
|  |  | 
|  |  | 
|  | static irqreturn_t | 
|  | pmac_awacs_intr(int irq, void *devid, struct pt_regs *regs) | 
|  | { | 
|  | int ctrl; | 
|  | int status; | 
|  | int r1; | 
|  |  | 
|  | spin_lock(&dmasound.lock); | 
|  | ctrl = in_le32(&awacs->control); | 
|  | status = in_le32(&awacs->codec_stat); | 
|  |  | 
|  | if (ctrl & MASK_PORTCHG) { | 
|  | /* tested on Screamer, should work on others too */ | 
|  | if (awacs_revision == AWACS_SCREAMER) { | 
|  | if (((status & MASK_HDPCONN) >> 3) && (hdp_connected == 0)) { | 
|  | hdp_connected = 1; | 
|  |  | 
|  | r1 = awacs_reg[1] | MASK_SPKMUTE; | 
|  | awacs_reg[1] = r1; | 
|  | awacs_write(r1 | MASK_ADDR_MUTE); | 
|  | } else if (((status & MASK_HDPCONN) >> 3 == 0) && (hdp_connected == 1)) { | 
|  | hdp_connected = 0; | 
|  |  | 
|  | r1 = awacs_reg[1] & ~MASK_SPKMUTE; | 
|  | awacs_reg[1] = r1; | 
|  | awacs_write(r1 | MASK_ADDR_MUTE); | 
|  | } | 
|  | } | 
|  | } | 
|  | if (ctrl & MASK_CNTLERR) { | 
|  | int err = (in_le32(&awacs->codec_stat) & MASK_ERRCODE) >> 16; | 
|  | /* CHECK: we just swallow burgundy errors at the moment..*/ | 
|  | if (err != 0 && awacs_revision != AWACS_BURGUNDY) | 
|  | printk(KERN_ERR "dmasound_pmac: error %x\n", err); | 
|  | } | 
|  | /* Writing 1s to the CNTLERR and PORTCHG bits clears them... */ | 
|  | out_le32(&awacs->control, ctrl); | 
|  | spin_unlock(&dmasound.lock); | 
|  | return IRQ_HANDLED; | 
|  | } | 
|  |  | 
|  | static void | 
|  | awacs_write(int val) | 
|  | { | 
|  | int count = 300 ; | 
|  | if (awacs_revision >= AWACS_DACA || !awacs) | 
|  | return ; | 
|  |  | 
|  | while ((in_le32(&awacs->codec_ctrl) & MASK_NEWECMD) && count--) | 
|  | udelay(1) ;	/* timeout is > 2 samples at lowest rate */ | 
|  | out_le32(&awacs->codec_ctrl, val | (awacs_subframe << 22)); | 
|  | (void)in_le32(&awacs->byteswap); | 
|  | } | 
|  |  | 
|  | /* this is called when the beep timer expires... it will be called even | 
|  | if the beep has been overidden by other sound output. | 
|  | */ | 
|  | static void awacs_nosound(unsigned long xx) | 
|  | { | 
|  | unsigned long flags; | 
|  | int count = 600 ; /* > four samples at lowest rate */ | 
|  |  | 
|  | spin_lock_irqsave(&dmasound.lock, flags); | 
|  | if (beep_playing) { | 
|  | st_le16(&beep_dbdma_cmd->command, DBDMA_STOP); | 
|  | out_le32(&awacs_txdma->control, (RUN|PAUSE|FLUSH|WAKE) << 16); | 
|  | while ((in_le32(&awacs_txdma->status) & RUN) && count--) | 
|  | udelay(1); | 
|  | if (awacs) | 
|  | awacs_setup_for_beep(-1); | 
|  | beep_playing = 0; | 
|  | } | 
|  | spin_unlock_irqrestore(&dmasound.lock, flags); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * We generate the beep with a single dbdma command that loops a buffer | 
|  | * forever - without generating interrupts. | 
|  | * | 
|  | * So, to stop it you have to stop dma output as per awacs_nosound. | 
|  | */ | 
|  | static int awacs_beep_event(struct input_dev *dev, unsigned int type, | 
|  | unsigned int code, int hz) | 
|  | { | 
|  | unsigned long flags; | 
|  | int beep_speed = 0; | 
|  | int srate; | 
|  | int period, ncycles, nsamples; | 
|  | int i, j, f; | 
|  | short *p; | 
|  | static int beep_hz_cache; | 
|  | static int beep_nsamples_cache; | 
|  | static int beep_volume_cache; | 
|  |  | 
|  | if (type != EV_SND) | 
|  | return -1; | 
|  | switch (code) { | 
|  | case SND_BELL: | 
|  | if (hz) | 
|  | hz = 1000; | 
|  | break; | 
|  | case SND_TONE: | 
|  | break; | 
|  | default: | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | if (beep_buf == NULL) | 
|  | return -1; | 
|  |  | 
|  | /* quick-hack fix for DACA, Burgundy & Tumbler */ | 
|  |  | 
|  | if (awacs_revision >= AWACS_DACA){ | 
|  | srate = 44100 ; | 
|  | } else { | 
|  | for (i = 0; i < 8 && awacs_freqs[i] >= BEEP_SRATE; ++i) | 
|  | if (awacs_freqs_ok[i]) | 
|  | beep_speed = i; | 
|  | srate = awacs_freqs[beep_speed]; | 
|  | } | 
|  |  | 
|  | if (hz <= srate / BEEP_BUFLEN || hz > srate / 2) { | 
|  | /* cancel beep currently playing */ | 
|  | awacs_nosound(0); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | spin_lock_irqsave(&dmasound.lock, flags); | 
|  | if (beep_playing || write_sq.active || beep_buf == NULL) { | 
|  | spin_unlock_irqrestore(&dmasound.lock, flags); | 
|  | return -1;		/* too hard, sorry :-( */ | 
|  | } | 
|  | beep_playing = 1; | 
|  | st_le16(&beep_dbdma_cmd->command, OUTPUT_MORE + BR_ALWAYS); | 
|  | spin_unlock_irqrestore(&dmasound.lock, flags); | 
|  |  | 
|  | if (hz == beep_hz_cache && beep_vol == beep_volume_cache) { | 
|  | nsamples = beep_nsamples_cache; | 
|  | } else { | 
|  | period = srate * 256 / hz;	/* fixed point */ | 
|  | ncycles = BEEP_BUFLEN * 256 / period; | 
|  | nsamples = (period * ncycles) >> 8; | 
|  | f = ncycles * 65536 / nsamples; | 
|  | j = 0; | 
|  | p = beep_buf; | 
|  | for (i = 0; i < nsamples; ++i, p += 2) { | 
|  | p[0] = p[1] = beep_wform[j >> 8] * beep_vol; | 
|  | j = (j + f) & 0xffff; | 
|  | } | 
|  | beep_hz_cache = hz; | 
|  | beep_volume_cache = beep_vol; | 
|  | beep_nsamples_cache = nsamples; | 
|  | } | 
|  |  | 
|  | st_le16(&beep_dbdma_cmd->req_count, nsamples*4); | 
|  | st_le16(&beep_dbdma_cmd->xfer_status, 0); | 
|  | st_le32(&beep_dbdma_cmd->cmd_dep, virt_to_bus(beep_dbdma_cmd)); | 
|  | st_le32(&beep_dbdma_cmd->phy_addr, virt_to_bus(beep_buf)); | 
|  | awacs_beep_state = 1; | 
|  |  | 
|  | spin_lock_irqsave(&dmasound.lock, flags); | 
|  | if (beep_playing) {	/* i.e. haven't been terminated already */ | 
|  | int count = 300 ; | 
|  | out_le32(&awacs_txdma->control, (RUN|WAKE|FLUSH|PAUSE) << 16); | 
|  | while ((in_le32(&awacs_txdma->status) & RUN) && count--) | 
|  | udelay(1); /* timeout > 2 samples at lowest rate*/ | 
|  | if (awacs) | 
|  | awacs_setup_for_beep(beep_speed); | 
|  | out_le32(&awacs_txdma->cmdptr, virt_to_bus(beep_dbdma_cmd)); | 
|  | (void)in_le32(&awacs_txdma->status); | 
|  | out_le32(&awacs_txdma->control, RUN | (RUN << 16)); | 
|  | } | 
|  | spin_unlock_irqrestore(&dmasound.lock, flags); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* used in init and for wake-up */ | 
|  |  | 
|  | static void | 
|  | load_awacs(void) | 
|  | { | 
|  | awacs_write(awacs_reg[0] + MASK_ADDR0); | 
|  | awacs_write(awacs_reg[1] + MASK_ADDR1); | 
|  | awacs_write(awacs_reg[2] + MASK_ADDR2); | 
|  | awacs_write(awacs_reg[4] + MASK_ADDR4); | 
|  |  | 
|  | if (awacs_revision == AWACS_SCREAMER) { | 
|  | awacs_write(awacs_reg[5] + MASK_ADDR5); | 
|  | msleep(100); | 
|  | awacs_write(awacs_reg[6] + MASK_ADDR6); | 
|  | msleep(2); | 
|  | awacs_write(awacs_reg[1] + MASK_ADDR1); | 
|  | awacs_write(awacs_reg[7] + MASK_ADDR7); | 
|  | } | 
|  | if (awacs) { | 
|  | if (hw_can_byteswap && (dmasound.hard.format == AFMT_S16_LE)) | 
|  | out_le32(&awacs->byteswap, BS_VAL); | 
|  | else | 
|  | out_le32(&awacs->byteswap, 0); | 
|  | } | 
|  | } | 
|  |  | 
|  | #ifdef CONFIG_PMAC_PBOOK | 
|  | /* | 
|  | * Save state when going to sleep, restore it afterwards. | 
|  | */ | 
|  | /* FIXME: sort out disabling/re-enabling of read stuff as well */ | 
|  | static int awacs_sleep_notify(struct pmu_sleep_notifier *self, int when) | 
|  | { | 
|  | unsigned long flags; | 
|  |  | 
|  | switch (when) { | 
|  | case PBOOK_SLEEP_NOW: | 
|  | LOCK(); | 
|  | awacs_sleeping = 1; | 
|  | /* Tell the rest of the driver we are now going to sleep */ | 
|  | mb(); | 
|  | if (awacs_revision == AWACS_SCREAMER || | 
|  | awacs_revision == AWACS_AWACS) { | 
|  | awacs_reg1_save = awacs_reg[1]; | 
|  | awacs_reg[1] |= MASK_AMUTE | MASK_CMUTE; | 
|  | awacs_write(MASK_ADDR1 | awacs_reg[1]); | 
|  | } | 
|  |  | 
|  | PMacSilence(); | 
|  | /* stop rx - if going - a bit of a daft user... but */ | 
|  | out_le32(&awacs_rxdma->control, (RUN|WAKE|FLUSH << 16)); | 
|  | /* deny interrupts */ | 
|  | if (awacs) | 
|  | disable_irq(awacs_irq); | 
|  | disable_irq(awacs_tx_irq); | 
|  | disable_irq(awacs_rx_irq); | 
|  | /* Chip specific sleep code */ | 
|  | switch (awacs_revision) { | 
|  | case AWACS_TUMBLER: | 
|  | case AWACS_SNAPPER: | 
|  | write_audio_gpio(gpio_headphone_mute, gpio_headphone_mute_pol); | 
|  | write_audio_gpio(gpio_amp_mute, gpio_amp_mute_pol); | 
|  | tas_enter_sleep(); | 
|  | write_audio_gpio(gpio_audio_reset, gpio_audio_reset_pol); | 
|  | break ; | 
|  | case AWACS_DACA: | 
|  | daca_enter_sleep(); | 
|  | break ; | 
|  | case AWACS_BURGUNDY: | 
|  | break ; | 
|  | case AWACS_SCREAMER: | 
|  | case AWACS_AWACS: | 
|  | default: | 
|  | out_le32(&awacs->control, 0x11) ; | 
|  | break ; | 
|  | } | 
|  | /* Disable sound clock */ | 
|  | pmac_call_feature(PMAC_FTR_SOUND_CHIP_ENABLE, awacs_node, 0, 0); | 
|  | /* According to Darwin, we do that after turning off the sound | 
|  | * chip clock. All this will have to be cleaned up once we properly | 
|  | * parse the OF sound-objects | 
|  | */ | 
|  | if ((machine_is_compatible("PowerBook3,1") || | 
|  | machine_is_compatible("PowerBook3,2")) && awacs) { | 
|  | awacs_reg[1] |= MASK_PAROUT0 | MASK_PAROUT1; | 
|  | awacs_write(MASK_ADDR1 | awacs_reg[1]); | 
|  | msleep(200); | 
|  | } | 
|  | break; | 
|  | case PBOOK_WAKE: | 
|  | /* Enable sound clock */ | 
|  | pmac_call_feature(PMAC_FTR_SOUND_CHIP_ENABLE, awacs_node, 0, 1); | 
|  | if ((machine_is_compatible("PowerBook3,1") || | 
|  | machine_is_compatible("PowerBook3,2")) && awacs) { | 
|  | msleep(100); | 
|  | awacs_reg[1] &= ~(MASK_PAROUT0 | MASK_PAROUT1); | 
|  | awacs_write(MASK_ADDR1 | awacs_reg[1]); | 
|  | msleep(300); | 
|  | } else | 
|  | msleep(1000); | 
|  | /* restore settings */ | 
|  | switch (awacs_revision) { | 
|  | case AWACS_TUMBLER: | 
|  | case AWACS_SNAPPER: | 
|  | write_audio_gpio(gpio_headphone_mute, gpio_headphone_mute_pol); | 
|  | write_audio_gpio(gpio_amp_mute, gpio_amp_mute_pol); | 
|  | write_audio_gpio(gpio_audio_reset, gpio_audio_reset_pol); | 
|  | msleep(100); | 
|  | write_audio_gpio(gpio_audio_reset, !gpio_audio_reset_pol); | 
|  | msleep(150); | 
|  | tas_leave_sleep(); /* Stub for now */ | 
|  | headphone_intr(0,NULL,NULL); | 
|  | break; | 
|  | case AWACS_DACA: | 
|  | msleep(10); /* Check this !!! */ | 
|  | daca_leave_sleep(); | 
|  | break ;		/* dont know how yet */ | 
|  | case AWACS_BURGUNDY: | 
|  | break ; | 
|  | case AWACS_SCREAMER: | 
|  | case AWACS_AWACS: | 
|  | default: | 
|  | load_awacs() ; | 
|  | break ; | 
|  | } | 
|  | /* Recalibrate chip */ | 
|  | if (awacs_revision == AWACS_SCREAMER && awacs) | 
|  | awacs_recalibrate(); | 
|  | /* Make sure dma is stopped */ | 
|  | PMacSilence(); | 
|  | if (awacs) | 
|  | enable_irq(awacs_irq); | 
|  | enable_irq(awacs_tx_irq); | 
|  | enable_irq(awacs_rx_irq); | 
|  | if (awacs) { | 
|  | /* OK, allow ints back again */ | 
|  | out_le32(&awacs->control, MASK_IEPC | 
|  | | (awacs_rate_index << 8) | 0x11 | 
|  | | (awacs_revision < AWACS_DACA ? MASK_IEE: 0)); | 
|  | } | 
|  | if (macio_base && is_pbook_g3) { | 
|  | /* FIXME: should restore the setup we had...*/ | 
|  | out_8(macio_base + 0x37, 3); | 
|  | } else if (is_pbook_3X00) { | 
|  | in_8(latch_base + 0x190); | 
|  | } | 
|  | /* Remove mute */ | 
|  | if (awacs_revision == AWACS_SCREAMER || | 
|  | awacs_revision == AWACS_AWACS) { | 
|  | awacs_reg[1] = awacs_reg1_save; | 
|  | awacs_write(MASK_ADDR1 | awacs_reg[1]); | 
|  | } | 
|  | awacs_sleeping = 0; | 
|  | /* Resume pending sounds. */ | 
|  | /* we don't try to restart input... */ | 
|  | spin_lock_irqsave(&dmasound.lock, flags); | 
|  | __PMacPlay(); | 
|  | spin_unlock_irqrestore(&dmasound.lock, flags); | 
|  | UNLOCK(); | 
|  | } | 
|  | return PBOOK_SLEEP_OK; | 
|  | } | 
|  | #endif /* CONFIG_PMAC_PBOOK */ | 
|  |  | 
|  |  | 
|  | /* All the burgundy functions: */ | 
|  |  | 
|  | /* Waits for busy flag to clear */ | 
|  | inline static void | 
|  | awacs_burgundy_busy_wait(void) | 
|  | { | 
|  | int count = 50; /* > 2 samples at 44k1 */ | 
|  | while ((in_le32(&awacs->codec_ctrl) & MASK_NEWECMD) && count--) | 
|  | udelay(1) ; | 
|  | } | 
|  |  | 
|  | inline static void | 
|  | awacs_burgundy_extend_wait(void) | 
|  | { | 
|  | int count = 50 ; /* > 2 samples at 44k1 */ | 
|  | while ((!(in_le32(&awacs->codec_stat) & MASK_EXTEND)) && count--) | 
|  | udelay(1) ; | 
|  | count = 50; | 
|  | while ((in_le32(&awacs->codec_stat) & MASK_EXTEND) && count--) | 
|  | udelay(1); | 
|  | } | 
|  |  | 
|  | static void | 
|  | awacs_burgundy_wcw(unsigned addr, unsigned val) | 
|  | { | 
|  | out_le32(&awacs->codec_ctrl, addr + 0x200c00 + (val & 0xff)); | 
|  | awacs_burgundy_busy_wait(); | 
|  | out_le32(&awacs->codec_ctrl, addr + 0x200d00 +((val>>8) & 0xff)); | 
|  | awacs_burgundy_busy_wait(); | 
|  | out_le32(&awacs->codec_ctrl, addr + 0x200e00 +((val>>16) & 0xff)); | 
|  | awacs_burgundy_busy_wait(); | 
|  | out_le32(&awacs->codec_ctrl, addr + 0x200f00 +((val>>24) & 0xff)); | 
|  | awacs_burgundy_busy_wait(); | 
|  | } | 
|  |  | 
|  | static unsigned | 
|  | awacs_burgundy_rcw(unsigned addr) | 
|  | { | 
|  | unsigned val = 0; | 
|  | unsigned long flags; | 
|  |  | 
|  | /* should have timeouts here */ | 
|  | spin_lock_irqsave(&dmasound.lock, flags); | 
|  |  | 
|  | out_le32(&awacs->codec_ctrl, addr + 0x100000); | 
|  | awacs_burgundy_busy_wait(); | 
|  | awacs_burgundy_extend_wait(); | 
|  | val += (in_le32(&awacs->codec_stat) >> 4) & 0xff; | 
|  |  | 
|  | out_le32(&awacs->codec_ctrl, addr + 0x100100); | 
|  | awacs_burgundy_busy_wait(); | 
|  | awacs_burgundy_extend_wait(); | 
|  | val += ((in_le32(&awacs->codec_stat)>>4) & 0xff) <<8; | 
|  |  | 
|  | out_le32(&awacs->codec_ctrl, addr + 0x100200); | 
|  | awacs_burgundy_busy_wait(); | 
|  | awacs_burgundy_extend_wait(); | 
|  | val += ((in_le32(&awacs->codec_stat)>>4) & 0xff) <<16; | 
|  |  | 
|  | out_le32(&awacs->codec_ctrl, addr + 0x100300); | 
|  | awacs_burgundy_busy_wait(); | 
|  | awacs_burgundy_extend_wait(); | 
|  | val += ((in_le32(&awacs->codec_stat)>>4) & 0xff) <<24; | 
|  |  | 
|  | spin_unlock_irqrestore(&dmasound.lock, flags); | 
|  |  | 
|  | return val; | 
|  | } | 
|  |  | 
|  |  | 
|  | static void | 
|  | awacs_burgundy_wcb(unsigned addr, unsigned val) | 
|  | { | 
|  | out_le32(&awacs->codec_ctrl, addr + 0x300000 + (val & 0xff)); | 
|  | awacs_burgundy_busy_wait(); | 
|  | } | 
|  |  | 
|  | static unsigned | 
|  | awacs_burgundy_rcb(unsigned addr) | 
|  | { | 
|  | unsigned val = 0; | 
|  | unsigned long flags; | 
|  |  | 
|  | /* should have timeouts here */ | 
|  | spin_lock_irqsave(&dmasound.lock, flags); | 
|  |  | 
|  | out_le32(&awacs->codec_ctrl, addr + 0x100000); | 
|  | awacs_burgundy_busy_wait(); | 
|  | awacs_burgundy_extend_wait(); | 
|  | val += (in_le32(&awacs->codec_stat) >> 4) & 0xff; | 
|  |  | 
|  | spin_unlock_irqrestore(&dmasound.lock, flags); | 
|  |  | 
|  | return val; | 
|  | } | 
|  |  | 
|  | static int | 
|  | awacs_burgundy_check(void) | 
|  | { | 
|  | /* Checks to see the chip is alive and kicking */ | 
|  | int error = in_le32(&awacs->codec_ctrl) & MASK_ERRCODE; | 
|  |  | 
|  | return error == 0xf0000; | 
|  | } | 
|  |  | 
|  | static int | 
|  | awacs_burgundy_init(void) | 
|  | { | 
|  | if (awacs_burgundy_check()) { | 
|  | printk(KERN_WARNING "dmasound_pmac: burgundy not working :-(\n"); | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | awacs_burgundy_wcb(MASK_ADDR_BURGUNDY_OUTPUTENABLES, | 
|  | DEF_BURGUNDY_OUTPUTENABLES); | 
|  | awacs_burgundy_wcb(MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES, | 
|  | DEF_BURGUNDY_MORE_OUTPUTENABLES); | 
|  | awacs_burgundy_wcw(MASK_ADDR_BURGUNDY_OUTPUTSELECTS, | 
|  | DEF_BURGUNDY_OUTPUTSELECTS); | 
|  |  | 
|  | awacs_burgundy_wcb(MASK_ADDR_BURGUNDY_INPSEL21, | 
|  | DEF_BURGUNDY_INPSEL21); | 
|  | awacs_burgundy_wcb(MASK_ADDR_BURGUNDY_INPSEL3, | 
|  | DEF_BURGUNDY_INPSEL3); | 
|  | awacs_burgundy_wcb(MASK_ADDR_BURGUNDY_GAINCD, | 
|  | DEF_BURGUNDY_GAINCD); | 
|  | awacs_burgundy_wcb(MASK_ADDR_BURGUNDY_GAINLINE, | 
|  | DEF_BURGUNDY_GAINLINE); | 
|  | awacs_burgundy_wcb(MASK_ADDR_BURGUNDY_GAINMIC, | 
|  | DEF_BURGUNDY_GAINMIC); | 
|  | awacs_burgundy_wcb(MASK_ADDR_BURGUNDY_GAINMODEM, | 
|  | DEF_BURGUNDY_GAINMODEM); | 
|  |  | 
|  | awacs_burgundy_wcb(MASK_ADDR_BURGUNDY_ATTENSPEAKER, | 
|  | DEF_BURGUNDY_ATTENSPEAKER); | 
|  | awacs_burgundy_wcb(MASK_ADDR_BURGUNDY_ATTENLINEOUT, | 
|  | DEF_BURGUNDY_ATTENLINEOUT); | 
|  | awacs_burgundy_wcb(MASK_ADDR_BURGUNDY_ATTENHP, | 
|  | DEF_BURGUNDY_ATTENHP); | 
|  |  | 
|  | awacs_burgundy_wcw(MASK_ADDR_BURGUNDY_MASTER_VOLUME, | 
|  | DEF_BURGUNDY_MASTER_VOLUME); | 
|  | awacs_burgundy_wcw(MASK_ADDR_BURGUNDY_VOLCD, | 
|  | DEF_BURGUNDY_VOLCD); | 
|  | awacs_burgundy_wcw(MASK_ADDR_BURGUNDY_VOLLINE, | 
|  | DEF_BURGUNDY_VOLLINE); | 
|  | awacs_burgundy_wcw(MASK_ADDR_BURGUNDY_VOLMIC, | 
|  | DEF_BURGUNDY_VOLMIC); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static void | 
|  | awacs_burgundy_write_volume(unsigned address, int volume) | 
|  | { | 
|  | int hardvolume,lvolume,rvolume; | 
|  |  | 
|  | lvolume = (volume & 0xff) ? (volume & 0xff) + 155 : 0; | 
|  | rvolume = ((volume >>8)&0xff) ? ((volume >> 8)&0xff ) + 155 : 0; | 
|  |  | 
|  | hardvolume = lvolume + (rvolume << 16); | 
|  |  | 
|  | awacs_burgundy_wcw(address, hardvolume); | 
|  | } | 
|  |  | 
|  | static int | 
|  | awacs_burgundy_read_volume(unsigned address) | 
|  | { | 
|  | int softvolume,wvolume; | 
|  |  | 
|  | wvolume = awacs_burgundy_rcw(address); | 
|  |  | 
|  | softvolume = (wvolume & 0xff) - 155; | 
|  | softvolume += (((wvolume >> 16) & 0xff) - 155)<<8; | 
|  |  | 
|  | return softvolume > 0 ? softvolume : 0; | 
|  | } | 
|  |  | 
|  | static int | 
|  | awacs_burgundy_read_mvolume(unsigned address) | 
|  | { | 
|  | int lvolume,rvolume,wvolume; | 
|  |  | 
|  | wvolume = awacs_burgundy_rcw(address); | 
|  |  | 
|  | wvolume &= 0xffff; | 
|  |  | 
|  | rvolume = (wvolume & 0xff) - 155; | 
|  | lvolume = ((wvolume & 0xff00)>>8) - 155; | 
|  |  | 
|  | return lvolume + (rvolume << 8); | 
|  | } | 
|  |  | 
|  | static void | 
|  | awacs_burgundy_write_mvolume(unsigned address, int volume) | 
|  | { | 
|  | int lvolume,rvolume,hardvolume; | 
|  |  | 
|  | lvolume = (volume &0xff) ? (volume & 0xff) + 155 :0; | 
|  | rvolume = ((volume >>8) & 0xff) ? (volume >> 8) + 155 :0; | 
|  |  | 
|  | hardvolume = lvolume + (rvolume << 8); | 
|  | hardvolume += (hardvolume << 16); | 
|  |  | 
|  | awacs_burgundy_wcw(address, hardvolume); | 
|  | } | 
|  |  | 
|  | /* End burgundy functions */ | 
|  |  | 
|  | /* Set up output volumes on machines with the 'perch/whisper' extension card. | 
|  | * this has an SGS i2c chip (7433) which is accessed using the cuda. | 
|  | * | 
|  | * TODO: split this out and make use of the other parts of the SGS chip to | 
|  | * do Bass, Treble etc. | 
|  | */ | 
|  |  | 
|  | static void | 
|  | awacs_enable_amp(int spkr_vol) | 
|  | { | 
|  | #ifdef CONFIG_ADB_CUDA | 
|  | struct adb_request req; | 
|  |  | 
|  | if (sys_ctrler != SYS_CTRLER_CUDA) | 
|  | return; | 
|  |  | 
|  | /* turn on headphones */ | 
|  | cuda_request(&req, NULL, 5, CUDA_PACKET, CUDA_GET_SET_IIC, | 
|  | 0x8a, 4, 0); | 
|  | while (!req.complete) cuda_poll(); | 
|  | cuda_request(&req, NULL, 5, CUDA_PACKET, CUDA_GET_SET_IIC, | 
|  | 0x8a, 6, 0); | 
|  | while (!req.complete) cuda_poll(); | 
|  |  | 
|  | /* turn on speaker */ | 
|  | cuda_request(&req, NULL, 5, CUDA_PACKET, CUDA_GET_SET_IIC, | 
|  | 0x8a, 3, (100 - (spkr_vol & 0xff)) * 32 / 100); | 
|  | while (!req.complete) cuda_poll(); | 
|  | cuda_request(&req, NULL, 5, CUDA_PACKET, CUDA_GET_SET_IIC, | 
|  | 0x8a, 5, (100 - ((spkr_vol >> 8) & 0xff)) * 32 / 100); | 
|  | while (!req.complete) cuda_poll(); | 
|  |  | 
|  | cuda_request(&req, NULL, 5, CUDA_PACKET, | 
|  | CUDA_GET_SET_IIC, 0x8a, 1, 0x29); | 
|  | while (!req.complete) cuda_poll(); | 
|  | #endif /* CONFIG_ADB_CUDA */ | 
|  | } | 
|  |  | 
|  |  | 
|  | /*** Mid level stuff *********************************************************/ | 
|  |  | 
|  |  | 
|  | /* | 
|  | * /dev/mixer abstraction | 
|  | */ | 
|  |  | 
|  | static void do_line_lev(int data) | 
|  | { | 
|  | line_lev = data ; | 
|  | awacs_reg[0] &= ~MASK_MUX_AUDIN; | 
|  | if ((data & 0xff) >= 50) | 
|  | awacs_reg[0] |= MASK_MUX_AUDIN; | 
|  | awacs_write(MASK_ADDR0 | awacs_reg[0]); | 
|  | } | 
|  |  | 
|  | static void do_ip_gain(int data) | 
|  | { | 
|  | ip_gain = data ; | 
|  | data &= 0xff; | 
|  | awacs_reg[0] &= ~MASK_GAINLINE; | 
|  | if (awacs_revision == AWACS_SCREAMER) { | 
|  | awacs_reg[6] &= ~MASK_MIC_BOOST ; | 
|  | if (data >= 33) { | 
|  | awacs_reg[0] |= MASK_GAINLINE; | 
|  | if( data >= 66) | 
|  | awacs_reg[6] |= MASK_MIC_BOOST ; | 
|  | } | 
|  | awacs_write(MASK_ADDR6 | awacs_reg[6]) ; | 
|  | } else { | 
|  | if (data >= 50) | 
|  | awacs_reg[0] |= MASK_GAINLINE; | 
|  | } | 
|  | awacs_write(MASK_ADDR0 | awacs_reg[0]); | 
|  | } | 
|  |  | 
|  | static void do_mic_lev(int data) | 
|  | { | 
|  | mic_lev = data ; | 
|  | data &= 0xff; | 
|  | awacs_reg[0] &= ~MASK_MUX_MIC; | 
|  | if (data >= 50) | 
|  | awacs_reg[0] |= MASK_MUX_MIC; | 
|  | awacs_write(MASK_ADDR0 | awacs_reg[0]); | 
|  | } | 
|  |  | 
|  | static void do_cd_lev(int data) | 
|  | { | 
|  | cd_lev = data ; | 
|  | awacs_reg[0] &= ~MASK_MUX_CD; | 
|  | if ((data & 0xff) >= 50) | 
|  | awacs_reg[0] |= MASK_MUX_CD; | 
|  | awacs_write(MASK_ADDR0 | awacs_reg[0]); | 
|  | } | 
|  |  | 
|  | static void do_rec_lev(int data) | 
|  | { | 
|  | int left, right ; | 
|  | rec_lev = data ; | 
|  | /* need to fudge this to use the volume setter routine */ | 
|  | left = 100 - (data & 0xff) ; if( left < 0 ) left = 0 ; | 
|  | right = 100 - ((data >> 8) & 0xff) ; if( right < 0 ) right = 0 ; | 
|  | left |= (right << 8 ); | 
|  | left = awacs_volume_setter(left, 0, 0, 4); | 
|  | } | 
|  |  | 
|  | static void do_passthru_vol(int data) | 
|  | { | 
|  | passthru_vol = data ; | 
|  | awacs_reg[1] &= ~MASK_LOOPTHRU; | 
|  | if (awacs_revision == AWACS_SCREAMER) { | 
|  | if( data ) { /* switch it on for non-zero */ | 
|  | awacs_reg[1] |= MASK_LOOPTHRU; | 
|  | awacs_write(MASK_ADDR1 | awacs_reg[1]); | 
|  | } | 
|  | data = awacs_volume_setter(data, 5, 0, 6) ; | 
|  | } else { | 
|  | if ((data & 0xff) >= 50) | 
|  | awacs_reg[1] |= MASK_LOOPTHRU; | 
|  | awacs_write(MASK_ADDR1 | awacs_reg[1]); | 
|  | data = (awacs_reg[1] & MASK_LOOPTHRU)? 100: 0; | 
|  | } | 
|  | } | 
|  |  | 
|  | static int awacs_mixer_ioctl(u_int cmd, u_long arg) | 
|  | { | 
|  | int data; | 
|  | int rc; | 
|  |  | 
|  | switch (cmd) { | 
|  | case SOUND_MIXER_READ_CAPS: | 
|  | /* say we will allow multiple inputs?  prob. wrong | 
|  | so I'm switching it to single */ | 
|  | return IOCTL_OUT(arg, 1); | 
|  | case SOUND_MIXER_READ_DEVMASK: | 
|  | data  = SOUND_MASK_VOLUME | SOUND_MASK_SPEAKER | 
|  | | SOUND_MASK_LINE | SOUND_MASK_MIC | SOUND_MASK_CD | 
|  | | SOUND_MASK_IGAIN | SOUND_MASK_RECLEV | 
|  | | SOUND_MASK_ALTPCM | 
|  | | SOUND_MASK_MONITOR; | 
|  | rc = IOCTL_OUT(arg, data); | 
|  | break; | 
|  | case SOUND_MIXER_READ_RECMASK: | 
|  | data = SOUND_MASK_LINE | SOUND_MASK_MIC | SOUND_MASK_CD; | 
|  | rc = IOCTL_OUT(arg, data); | 
|  | break; | 
|  | case SOUND_MIXER_READ_RECSRC: | 
|  | data = 0; | 
|  | if (awacs_reg[0] & MASK_MUX_AUDIN) | 
|  | data |= SOUND_MASK_LINE; | 
|  | if (awacs_reg[0] & MASK_MUX_MIC) | 
|  | data |= SOUND_MASK_MIC; | 
|  | if (awacs_reg[0] & MASK_MUX_CD) | 
|  | data |= SOUND_MASK_CD; | 
|  | rc = IOCTL_OUT(arg, data); | 
|  | break; | 
|  | case SOUND_MIXER_WRITE_RECSRC: | 
|  | IOCTL_IN(arg, data); | 
|  | data &= (SOUND_MASK_LINE | SOUND_MASK_MIC | SOUND_MASK_CD); | 
|  | awacs_reg[0] &= ~(MASK_MUX_CD | MASK_MUX_MIC | 
|  | | MASK_MUX_AUDIN); | 
|  | if (data & SOUND_MASK_LINE) | 
|  | awacs_reg[0] |= MASK_MUX_AUDIN; | 
|  | if (data & SOUND_MASK_MIC) | 
|  | awacs_reg[0] |= MASK_MUX_MIC; | 
|  | if (data & SOUND_MASK_CD) | 
|  | awacs_reg[0] |= MASK_MUX_CD; | 
|  | awacs_write(awacs_reg[0] | MASK_ADDR0); | 
|  | rc = IOCTL_OUT(arg, data); | 
|  | break; | 
|  | case SOUND_MIXER_READ_STEREODEVS: | 
|  | data = SOUND_MASK_VOLUME | SOUND_MASK_SPEAKER| SOUND_MASK_RECLEV  ; | 
|  | if (awacs_revision == AWACS_SCREAMER) | 
|  | data |= SOUND_MASK_MONITOR ; | 
|  | rc = IOCTL_OUT(arg, data); | 
|  | break; | 
|  | case SOUND_MIXER_WRITE_VOLUME: | 
|  | IOCTL_IN(arg, data); | 
|  | line_vol = data ; | 
|  | awacs_volume_setter(data, 2, 0, 6); | 
|  | /* fall through */ | 
|  | case SOUND_MIXER_READ_VOLUME: | 
|  | rc = IOCTL_OUT(arg, line_vol); | 
|  | break; | 
|  | case SOUND_MIXER_WRITE_SPEAKER: | 
|  | IOCTL_IN(arg, data); | 
|  | spk_vol = data ; | 
|  | if (has_perch) | 
|  | awacs_enable_amp(data); | 
|  | else | 
|  | (void)awacs_volume_setter(data, 4, MASK_CMUTE, 6); | 
|  | /* fall though */ | 
|  | case SOUND_MIXER_READ_SPEAKER: | 
|  | rc = IOCTL_OUT(arg, spk_vol); | 
|  | break; | 
|  | case SOUND_MIXER_WRITE_ALTPCM:	/* really bell volume */ | 
|  | IOCTL_IN(arg, data); | 
|  | beep_vol = data & 0xff; | 
|  | /* fall through */ | 
|  | case SOUND_MIXER_READ_ALTPCM: | 
|  | rc = IOCTL_OUT(arg, beep_vol); | 
|  | break; | 
|  | case SOUND_MIXER_WRITE_LINE: | 
|  | IOCTL_IN(arg, data); | 
|  | do_line_lev(data) ; | 
|  | /* fall through */ | 
|  | case SOUND_MIXER_READ_LINE: | 
|  | rc = IOCTL_OUT(arg, line_lev); | 
|  | break; | 
|  | case SOUND_MIXER_WRITE_IGAIN: | 
|  | IOCTL_IN(arg, data); | 
|  | do_ip_gain(data) ; | 
|  | /* fall through */ | 
|  | case SOUND_MIXER_READ_IGAIN: | 
|  | rc = IOCTL_OUT(arg, ip_gain); | 
|  | break; | 
|  | case SOUND_MIXER_WRITE_MIC: | 
|  | IOCTL_IN(arg, data); | 
|  | do_mic_lev(data); | 
|  | /* fall through */ | 
|  | case SOUND_MIXER_READ_MIC: | 
|  | rc = IOCTL_OUT(arg, mic_lev); | 
|  | break; | 
|  | case SOUND_MIXER_WRITE_CD: | 
|  | IOCTL_IN(arg, data); | 
|  | do_cd_lev(data); | 
|  | /* fall through */ | 
|  | case SOUND_MIXER_READ_CD: | 
|  | rc = IOCTL_OUT(arg, cd_lev); | 
|  | break; | 
|  | case SOUND_MIXER_WRITE_RECLEV: | 
|  | IOCTL_IN(arg, data); | 
|  | do_rec_lev(data) ; | 
|  | /* fall through */ | 
|  | case SOUND_MIXER_READ_RECLEV: | 
|  | rc = IOCTL_OUT(arg, rec_lev); | 
|  | break; | 
|  | case MIXER_WRITE(SOUND_MIXER_MONITOR): | 
|  | IOCTL_IN(arg, data); | 
|  | do_passthru_vol(data) ; | 
|  | /* fall through */ | 
|  | case MIXER_READ(SOUND_MIXER_MONITOR): | 
|  | rc = IOCTL_OUT(arg, passthru_vol); | 
|  | break; | 
|  | default: | 
|  | rc = -EINVAL; | 
|  | } | 
|  |  | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | static void awacs_mixer_init(void) | 
|  | { | 
|  | awacs_volume_setter(line_vol, 2, 0, 6); | 
|  | if (has_perch) | 
|  | awacs_enable_amp(spk_vol); | 
|  | else | 
|  | (void)awacs_volume_setter(spk_vol, 4, MASK_CMUTE, 6); | 
|  | do_line_lev(line_lev) ; | 
|  | do_ip_gain(ip_gain) ; | 
|  | do_mic_lev(mic_lev) ; | 
|  | do_cd_lev(cd_lev) ; | 
|  | do_rec_lev(rec_lev) ; | 
|  | do_passthru_vol(passthru_vol) ; | 
|  | } | 
|  |  | 
|  | static int burgundy_mixer_ioctl(u_int cmd, u_long arg) | 
|  | { | 
|  | int data; | 
|  | int rc; | 
|  |  | 
|  | /* We are, we are, we are... Burgundy or better */ | 
|  | switch(cmd) { | 
|  | case SOUND_MIXER_READ_DEVMASK: | 
|  | data = SOUND_MASK_VOLUME | SOUND_MASK_CD | | 
|  | SOUND_MASK_LINE | SOUND_MASK_MIC | | 
|  | SOUND_MASK_SPEAKER | SOUND_MASK_ALTPCM; | 
|  | rc = IOCTL_OUT(arg, data); | 
|  | break; | 
|  | case SOUND_MIXER_READ_RECMASK: | 
|  | data = SOUND_MASK_LINE | SOUND_MASK_MIC | 
|  | | SOUND_MASK_CD; | 
|  | rc = IOCTL_OUT(arg, data); | 
|  | break; | 
|  | case SOUND_MIXER_READ_RECSRC: | 
|  | data = 0; | 
|  | if (awacs_reg[0] & MASK_MUX_AUDIN) | 
|  | data |= SOUND_MASK_LINE; | 
|  | if (awacs_reg[0] & MASK_MUX_MIC) | 
|  | data |= SOUND_MASK_MIC; | 
|  | if (awacs_reg[0] & MASK_MUX_CD) | 
|  | data |= SOUND_MASK_CD; | 
|  | rc = IOCTL_OUT(arg, data); | 
|  | break; | 
|  | case SOUND_MIXER_WRITE_RECSRC: | 
|  | IOCTL_IN(arg, data); | 
|  | data &= (SOUND_MASK_LINE | 
|  | | SOUND_MASK_MIC | SOUND_MASK_CD); | 
|  | awacs_reg[0] &= ~(MASK_MUX_CD | MASK_MUX_MIC | 
|  | | MASK_MUX_AUDIN); | 
|  | if (data & SOUND_MASK_LINE) | 
|  | awacs_reg[0] |= MASK_MUX_AUDIN; | 
|  | if (data & SOUND_MASK_MIC) | 
|  | awacs_reg[0] |= MASK_MUX_MIC; | 
|  | if (data & SOUND_MASK_CD) | 
|  | awacs_reg[0] |= MASK_MUX_CD; | 
|  | awacs_write(awacs_reg[0] | MASK_ADDR0); | 
|  | rc = IOCTL_OUT(arg, data); | 
|  | break; | 
|  | case SOUND_MIXER_READ_STEREODEVS: | 
|  | data = SOUND_MASK_VOLUME | SOUND_MASK_SPEAKER | 
|  | | SOUND_MASK_RECLEV | SOUND_MASK_CD | 
|  | | SOUND_MASK_LINE; | 
|  | rc = IOCTL_OUT(arg, data); | 
|  | break; | 
|  | case SOUND_MIXER_READ_CAPS: | 
|  | rc = IOCTL_OUT(arg, 0); | 
|  | break; | 
|  | case SOUND_MIXER_WRITE_VOLUME: | 
|  | IOCTL_IN(arg, data); | 
|  | awacs_burgundy_write_mvolume(MASK_ADDR_BURGUNDY_MASTER_VOLUME, data); | 
|  | /* Fall through */ | 
|  | case SOUND_MIXER_READ_VOLUME: | 
|  | rc = IOCTL_OUT(arg, awacs_burgundy_read_mvolume(MASK_ADDR_BURGUNDY_MASTER_VOLUME)); | 
|  | break; | 
|  | case SOUND_MIXER_WRITE_SPEAKER: | 
|  | IOCTL_IN(arg, data); | 
|  | if (!(data & 0xff)) { | 
|  | /* Mute the left speaker */ | 
|  | awacs_burgundy_wcb(MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES, | 
|  | awacs_burgundy_rcb(MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES) & ~0x2); | 
|  | } else { | 
|  | /* Unmute the left speaker */ | 
|  | awacs_burgundy_wcb(MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES, | 
|  | awacs_burgundy_rcb(MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES) | 0x2); | 
|  | } | 
|  | if (!(data & 0xff00)) { | 
|  | /* Mute the right speaker */ | 
|  | awacs_burgundy_wcb(MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES, | 
|  | awacs_burgundy_rcb(MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES) & ~0x4); | 
|  | } else { | 
|  | /* Unmute the right speaker */ | 
|  | awacs_burgundy_wcb(MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES, | 
|  | awacs_burgundy_rcb(MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES) | 0x4); | 
|  | } | 
|  |  | 
|  | data = (((data&0xff)*16)/100 > 0xf ? 0xf : | 
|  | (((data&0xff)*16)/100)) + | 
|  | ((((data>>8)*16)/100 > 0xf ? 0xf : | 
|  | ((((data>>8)*16)/100)))<<4); | 
|  |  | 
|  | awacs_burgundy_wcb(MASK_ADDR_BURGUNDY_ATTENSPEAKER, ~data); | 
|  | /* Fall through */ | 
|  | case SOUND_MIXER_READ_SPEAKER: | 
|  | data = awacs_burgundy_rcb(MASK_ADDR_BURGUNDY_ATTENSPEAKER); | 
|  | data = (((data & 0xf)*100)/16) + ((((data>>4)*100)/16)<<8); | 
|  | rc = IOCTL_OUT(arg, (~data) & 0x0000ffff); | 
|  | break; | 
|  | case SOUND_MIXER_WRITE_ALTPCM:	/* really bell volume */ | 
|  | IOCTL_IN(arg, data); | 
|  | beep_vol = data & 0xff; | 
|  | /* fall through */ | 
|  | case SOUND_MIXER_READ_ALTPCM: | 
|  | rc = IOCTL_OUT(arg, beep_vol); | 
|  | break; | 
|  | case SOUND_MIXER_WRITE_LINE: | 
|  | IOCTL_IN(arg, data); | 
|  | awacs_burgundy_write_volume(MASK_ADDR_BURGUNDY_VOLLINE, data); | 
|  |  | 
|  | /* fall through */ | 
|  | case SOUND_MIXER_READ_LINE: | 
|  | data = awacs_burgundy_read_volume(MASK_ADDR_BURGUNDY_VOLLINE); | 
|  | rc = IOCTL_OUT(arg, data); | 
|  | break; | 
|  | case SOUND_MIXER_WRITE_MIC: | 
|  | IOCTL_IN(arg, data); | 
|  | /* Mic is mono device */ | 
|  | data = (data << 8) + (data << 24); | 
|  | awacs_burgundy_write_volume(MASK_ADDR_BURGUNDY_VOLMIC, data); | 
|  | /* fall through */ | 
|  | case SOUND_MIXER_READ_MIC: | 
|  | data = awacs_burgundy_read_volume(MASK_ADDR_BURGUNDY_VOLMIC); | 
|  | data <<= 24; | 
|  | rc = IOCTL_OUT(arg, data); | 
|  | break; | 
|  | case SOUND_MIXER_WRITE_CD: | 
|  | IOCTL_IN(arg, data); | 
|  | awacs_burgundy_write_volume(MASK_ADDR_BURGUNDY_VOLCD, data); | 
|  | /* fall through */ | 
|  | case SOUND_MIXER_READ_CD: | 
|  | data = awacs_burgundy_read_volume(MASK_ADDR_BURGUNDY_VOLCD); | 
|  | rc = IOCTL_OUT(arg, data); | 
|  | break; | 
|  | case SOUND_MIXER_WRITE_RECLEV: | 
|  | IOCTL_IN(arg, data); | 
|  | data = awacs_volume_setter(data, 0, 0, 4); | 
|  | rc = IOCTL_OUT(arg, data); | 
|  | break; | 
|  | case SOUND_MIXER_READ_RECLEV: | 
|  | data = awacs_get_volume(awacs_reg[0], 4); | 
|  | rc = IOCTL_OUT(arg, data); | 
|  | break; | 
|  | case SOUND_MIXER_OUTMASK: | 
|  | case SOUND_MIXER_OUTSRC: | 
|  | default: | 
|  | rc = -EINVAL; | 
|  | } | 
|  |  | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | static int daca_mixer_ioctl(u_int cmd, u_long arg) | 
|  | { | 
|  | int data; | 
|  | int rc; | 
|  |  | 
|  | /* And the DACA's no genius either! */ | 
|  |  | 
|  | switch(cmd) { | 
|  | case SOUND_MIXER_READ_DEVMASK: | 
|  | data = SOUND_MASK_VOLUME; | 
|  | rc = IOCTL_OUT(arg, data); | 
|  | break; | 
|  | case SOUND_MIXER_READ_RECMASK: | 
|  | data = 0; | 
|  | rc = IOCTL_OUT(arg, data); | 
|  | break; | 
|  | case SOUND_MIXER_READ_RECSRC: | 
|  | data = 0; | 
|  | rc = IOCTL_OUT(arg, data); | 
|  | break; | 
|  | case SOUND_MIXER_WRITE_RECSRC: | 
|  | IOCTL_IN(arg, data); | 
|  | data =0; | 
|  | rc = IOCTL_OUT(arg, data); | 
|  | break; | 
|  | case SOUND_MIXER_READ_STEREODEVS: | 
|  | data = SOUND_MASK_VOLUME; | 
|  | rc = IOCTL_OUT(arg, data); | 
|  | break; | 
|  | case SOUND_MIXER_READ_CAPS: | 
|  | rc = IOCTL_OUT(arg, 0); | 
|  | break; | 
|  | case SOUND_MIXER_WRITE_VOLUME: | 
|  | IOCTL_IN(arg, data); | 
|  | daca_set_volume(data, data); | 
|  | /* Fall through */ | 
|  | case SOUND_MIXER_READ_VOLUME: | 
|  | daca_get_volume(& data, &data); | 
|  | rc = IOCTL_OUT(arg, data); | 
|  | break; | 
|  | case SOUND_MIXER_OUTMASK: | 
|  | case SOUND_MIXER_OUTSRC: | 
|  | default: | 
|  | rc = -EINVAL; | 
|  | } | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | static int PMacMixerIoctl(u_int cmd, u_long arg) | 
|  | { | 
|  | int rc; | 
|  |  | 
|  | /* Different IOCTLS for burgundy and, eventually, DACA & Tumbler */ | 
|  |  | 
|  | TRY_LOCK(); | 
|  |  | 
|  | switch (awacs_revision){ | 
|  | case AWACS_BURGUNDY: | 
|  | rc = burgundy_mixer_ioctl(cmd, arg); | 
|  | break ; | 
|  | case AWACS_DACA: | 
|  | rc = daca_mixer_ioctl(cmd, arg); | 
|  | break; | 
|  | case AWACS_TUMBLER: | 
|  | case AWACS_SNAPPER: | 
|  | rc = tas_mixer_ioctl(cmd, arg); | 
|  | break ; | 
|  | default: /* ;-)) */ | 
|  | rc = awacs_mixer_ioctl(cmd, arg); | 
|  | } | 
|  |  | 
|  | UNLOCK(); | 
|  |  | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | static void PMacMixerInit(void) | 
|  | { | 
|  | switch (awacs_revision) { | 
|  | case AWACS_TUMBLER: | 
|  | printk("AE-Init tumbler mixer\n"); | 
|  | break ; | 
|  | case AWACS_SNAPPER: | 
|  | printk("AE-Init snapper mixer\n"); | 
|  | break ; | 
|  | case AWACS_DACA: | 
|  | case AWACS_BURGUNDY: | 
|  | break ;	/* don't know yet */ | 
|  | case AWACS_AWACS: | 
|  | case AWACS_SCREAMER: | 
|  | default: | 
|  | awacs_mixer_init() ; | 
|  | break ; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Write/Read sq setup functions: | 
|  | Check to see if we have enough (or any) dbdma cmd buffers for the | 
|  | user's fragment settings.  If not, allocate some. If this fails we will | 
|  | point at the beep buffer - as an emergency provision - to stop dma tromping | 
|  | on some random bit of memory (if someone lets it go anyway). | 
|  | The command buffers are then set up to point to the fragment buffers | 
|  | (allocated elsewhere).  We need n+1 commands the last of which holds | 
|  | a NOP + loop to start. | 
|  | */ | 
|  |  | 
|  | static int PMacWriteSqSetup(void) | 
|  | { | 
|  | int i, count = 600 ; | 
|  | volatile struct dbdma_cmd *cp; | 
|  |  | 
|  | LOCK(); | 
|  |  | 
|  | /* stop the controller from doing any output - if it isn't already. | 
|  | it _should_ be before this is called anyway */ | 
|  |  | 
|  | out_le32(&awacs_txdma->control, (RUN|PAUSE|FLUSH|WAKE) << 16); | 
|  | while ((in_le32(&awacs_txdma->status) & RUN) && count--) | 
|  | udelay(1); | 
|  | #ifdef DEBUG_DMASOUND | 
|  | if (count <= 0) | 
|  | printk("dmasound_pmac: write sq setup: timeout waiting for dma to stop\n"); | 
|  | #endif | 
|  |  | 
|  | if ((write_sq.max_count + 1) > number_of_tx_cmd_buffers) { | 
|  | if (awacs_tx_cmd_space) | 
|  | kfree(awacs_tx_cmd_space); | 
|  | number_of_tx_cmd_buffers = 0; | 
|  |  | 
|  | /* we need nbufs + 1 (for the loop) and we should request + 1 | 
|  | again because the DBDMA_ALIGN might pull the start up by up | 
|  | to sizeof(struct dbdma_cmd) - 4. | 
|  | */ | 
|  |  | 
|  | awacs_tx_cmd_space = kmalloc | 
|  | ((write_sq.max_count + 1 + 1) * sizeof(struct dbdma_cmd), | 
|  | GFP_KERNEL); | 
|  | if (awacs_tx_cmd_space == NULL) { | 
|  | /* don't leave it dangling - nasty but better than a | 
|  | random address */ | 
|  | out_le32(&awacs_txdma->cmdptr, virt_to_bus(beep_dbdma_cmd)); | 
|  | printk(KERN_ERR | 
|  | "dmasound_pmac: can't allocate dbdma cmd buffers" | 
|  | ", driver disabled\n"); | 
|  | UNLOCK(); | 
|  | return -ENOMEM; | 
|  | } | 
|  | awacs_tx_cmds = (volatile struct dbdma_cmd *) | 
|  | DBDMA_ALIGN(awacs_tx_cmd_space); | 
|  | number_of_tx_cmd_buffers = write_sq.max_count + 1; | 
|  | } | 
|  |  | 
|  | cp = awacs_tx_cmds; | 
|  | memset((void *)cp, 0, (write_sq.max_count+1) * sizeof(struct dbdma_cmd)); | 
|  | for (i = 0; i < write_sq.max_count; ++i, ++cp) { | 
|  | st_le32(&cp->phy_addr, virt_to_bus(write_sq.buffers[i])); | 
|  | } | 
|  | st_le16(&cp->command, DBDMA_NOP + BR_ALWAYS); | 
|  | st_le32(&cp->cmd_dep, virt_to_bus(awacs_tx_cmds)); | 
|  | /* point the controller at the command stack - ready to go */ | 
|  | out_le32(&awacs_txdma->cmdptr, virt_to_bus(awacs_tx_cmds)); | 
|  | UNLOCK(); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int PMacReadSqSetup(void) | 
|  | { | 
|  | int i, count = 600; | 
|  | volatile struct dbdma_cmd *cp; | 
|  |  | 
|  | LOCK(); | 
|  |  | 
|  | /* stop the controller from doing any input - if it isn't already. | 
|  | it _should_ be before this is called anyway */ | 
|  |  | 
|  | out_le32(&awacs_rxdma->control, (RUN|PAUSE|FLUSH|WAKE) << 16); | 
|  | while ((in_le32(&awacs_rxdma->status) & RUN) && count--) | 
|  | udelay(1); | 
|  | #ifdef DEBUG_DMASOUND | 
|  | if (count <= 0) | 
|  | printk("dmasound_pmac: read sq setup: timeout waiting for dma to stop\n"); | 
|  | #endif | 
|  |  | 
|  | if ((read_sq.max_count+1) > number_of_rx_cmd_buffers ) { | 
|  | if (awacs_rx_cmd_space) | 
|  | kfree(awacs_rx_cmd_space); | 
|  | number_of_rx_cmd_buffers = 0; | 
|  |  | 
|  | /* we need nbufs + 1 (for the loop) and we should request + 1 again | 
|  | because the DBDMA_ALIGN might pull the start up by up to | 
|  | sizeof(struct dbdma_cmd) - 4 (assuming kmalloc aligns 32 bits). | 
|  | */ | 
|  |  | 
|  | awacs_rx_cmd_space = kmalloc | 
|  | ((read_sq.max_count + 1 + 1) * sizeof(struct dbdma_cmd), | 
|  | GFP_KERNEL); | 
|  | if (awacs_rx_cmd_space == NULL) { | 
|  | /* don't leave it dangling - nasty but better than a | 
|  | random address */ | 
|  | out_le32(&awacs_rxdma->cmdptr, virt_to_bus(beep_dbdma_cmd)); | 
|  | printk(KERN_ERR | 
|  | "dmasound_pmac: can't allocate dbdma cmd buffers" | 
|  | ", driver disabled\n"); | 
|  | UNLOCK(); | 
|  | return -ENOMEM; | 
|  | } | 
|  | awacs_rx_cmds = (volatile struct dbdma_cmd *) | 
|  | DBDMA_ALIGN(awacs_rx_cmd_space); | 
|  | number_of_rx_cmd_buffers = read_sq.max_count + 1 ; | 
|  | } | 
|  | cp = awacs_rx_cmds; | 
|  | memset((void *)cp, 0, (read_sq.max_count+1) * sizeof(struct dbdma_cmd)); | 
|  |  | 
|  | /* Set dma buffers up in a loop */ | 
|  | for (i = 0; i < read_sq.max_count; i++,cp++) { | 
|  | st_le32(&cp->phy_addr, virt_to_bus(read_sq.buffers[i])); | 
|  | st_le16(&cp->command, INPUT_MORE + INTR_ALWAYS); | 
|  | st_le16(&cp->req_count, read_sq.block_size); | 
|  | st_le16(&cp->xfer_status, 0); | 
|  | } | 
|  |  | 
|  | /* The next two lines make the thing loop around. | 
|  | */ | 
|  | st_le16(&cp->command, DBDMA_NOP + BR_ALWAYS); | 
|  | st_le32(&cp->cmd_dep, virt_to_bus(awacs_rx_cmds)); | 
|  | /* point the controller at the command stack - ready to go */ | 
|  | out_le32(&awacs_rxdma->cmdptr, virt_to_bus(awacs_rx_cmds)); | 
|  |  | 
|  | UNLOCK(); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* TODO: this needs work to guarantee that when it returns DMA has stopped | 
|  | but in a more elegant way than is done here.... | 
|  | */ | 
|  |  | 
|  | static void PMacAbortRead(void) | 
|  | { | 
|  | int i; | 
|  | volatile struct dbdma_cmd *cp; | 
|  |  | 
|  | LOCK(); | 
|  | /* give it a chance to update the output and provide the IRQ | 
|  | that is expected. | 
|  | */ | 
|  |  | 
|  | out_le32(&awacs_rxdma->control, ((FLUSH) << 16) + FLUSH ); | 
|  |  | 
|  | cp = awacs_rx_cmds; | 
|  | for (i = 0; i < read_sq.max_count; i++,cp++) | 
|  | st_le16(&cp->command, DBDMA_STOP); | 
|  | /* | 
|  | * We should probably wait for the thing to stop before we | 
|  | * release the memory. | 
|  | */ | 
|  |  | 
|  | msleep(100) ; /* give it a (small) chance to act */ | 
|  |  | 
|  | /* apply the sledgehammer approach - just stop it now */ | 
|  |  | 
|  | out_le32(&awacs_rxdma->control, (RUN|PAUSE|FLUSH|WAKE) << 16); | 
|  | UNLOCK(); | 
|  | } | 
|  |  | 
|  | extern char *get_afmt_string(int); | 
|  | static int PMacStateInfo(char *b, size_t sp) | 
|  | { | 
|  | int i, len = 0; | 
|  | len = sprintf(b,"HW rates: "); | 
|  | switch (awacs_revision){ | 
|  | case AWACS_DACA: | 
|  | case AWACS_BURGUNDY: | 
|  | len += sprintf(b,"44100 ") ; | 
|  | break ; | 
|  | case AWACS_TUMBLER: | 
|  | case AWACS_SNAPPER: | 
|  | for (i=0; i<1; i++){ | 
|  | if (tas_freqs_ok[i]) | 
|  | len += sprintf(b+len,"%d ", tas_freqs[i]) ; | 
|  | } | 
|  | break ; | 
|  | case AWACS_AWACS: | 
|  | case AWACS_SCREAMER: | 
|  | default: | 
|  | for (i=0; i<8; i++){ | 
|  | if (awacs_freqs_ok[i]) | 
|  | len += sprintf(b+len,"%d ", awacs_freqs[i]) ; | 
|  | } | 
|  | break ; | 
|  | } | 
|  | len += sprintf(b+len,"s/sec\n") ; | 
|  | if (len < sp) { | 
|  | len += sprintf(b+len,"HW AFMTS: "); | 
|  | i = AFMT_U16_BE ; | 
|  | while (i) { | 
|  | if (i & dmasound.mach.hardware_afmts) | 
|  | len += sprintf(b+len,"%s ", | 
|  | get_afmt_string(i & dmasound.mach.hardware_afmts)); | 
|  | i >>= 1 ; | 
|  | } | 
|  | len += sprintf(b+len,"\n") ; | 
|  | } | 
|  | return len ; | 
|  | } | 
|  |  | 
|  | /*** Machine definitions *****************************************************/ | 
|  |  | 
|  | static SETTINGS def_hard = { | 
|  | .format	= AFMT_S16_BE, | 
|  | .stereo	= 1, | 
|  | .size	= 16, | 
|  | .speed	= 44100 | 
|  | } ; | 
|  |  | 
|  | static SETTINGS def_soft = { | 
|  | .format	= AFMT_S16_BE, | 
|  | .stereo	= 1, | 
|  | .size	= 16, | 
|  | .speed	= 44100 | 
|  | } ; | 
|  |  | 
|  | static MACHINE machPMac = { | 
|  | .name		= awacs_name, | 
|  | .name2		= "PowerMac Built-in Sound", | 
|  | .owner		= THIS_MODULE, | 
|  | .dma_alloc	= PMacAlloc, | 
|  | .dma_free	= PMacFree, | 
|  | .irqinit	= PMacIrqInit, | 
|  | #ifdef MODULE | 
|  | .irqcleanup	= PMacIrqCleanup, | 
|  | #endif /* MODULE */ | 
|  | .init		= PMacInit, | 
|  | .silence	= PMacSilence, | 
|  | .setFormat	= PMacSetFormat, | 
|  | .setVolume	= PMacSetVolume, | 
|  | .play		= PMacPlay, | 
|  | .record		= NULL,		/* default to no record */ | 
|  | .mixer_init	= PMacMixerInit, | 
|  | .mixer_ioctl	= PMacMixerIoctl, | 
|  | .write_sq_setup	= PMacWriteSqSetup, | 
|  | .read_sq_setup	= PMacReadSqSetup, | 
|  | .state_info	= PMacStateInfo, | 
|  | .abort_read	= PMacAbortRead, | 
|  | .min_dsp_speed	= 7350, | 
|  | .max_dsp_speed	= 44100, | 
|  | .version	= ((DMASOUND_AWACS_REVISION<<8) + DMASOUND_AWACS_EDITION) | 
|  | }; | 
|  |  | 
|  |  | 
|  | /*** Config & Setup **********************************************************/ | 
|  |  | 
|  | /* Check for pmac models that we care about in terms of special actions. | 
|  | */ | 
|  |  | 
|  | void __init | 
|  | set_model(void) | 
|  | { | 
|  | /* portables/lap-tops */ | 
|  |  | 
|  | if (machine_is_compatible("AAPL,3400/2400") || | 
|  | machine_is_compatible("AAPL,3500"))	{ | 
|  | is_pbook_3X00 = 1 ; | 
|  | } | 
|  | if (machine_is_compatible("PowerBook1,1")  || /* lombard */ | 
|  | machine_is_compatible("AAPL,PowerBook1998")){ /* wallstreet */ | 
|  | is_pbook_g3 = 1 ; | 
|  | return ; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Get the OF node that tells us about the registers, interrupts etc. to use | 
|  | for sound IO. | 
|  |  | 
|  | On most machines the sound IO OF node is the 'davbus' node.  On newer pmacs | 
|  | with DACA (& Tumbler) the node to use is i2s-a.  On much older machines i.e. | 
|  | before 9500 there is no davbus node and we have to use the 'awacs' property. | 
|  |  | 
|  | In the latter case we signal this by setting the codec value - so that the | 
|  | code that looks for chip properties knows how to go about it. | 
|  | */ | 
|  |  | 
|  | static struct device_node* __init | 
|  | get_snd_io_node(void) | 
|  | { | 
|  | struct device_node *np = NULL; | 
|  |  | 
|  | /* set up awacs_node for early OF which doesn't have a full set of | 
|  | * properties on davbus | 
|  | */ | 
|  |  | 
|  | awacs_node = find_devices("awacs"); | 
|  | if (awacs_node) | 
|  | awacs_revision = AWACS_AWACS; | 
|  |  | 
|  | /* powermac models after 9500 (other than those which use DACA or | 
|  | * Tumbler) have a node called "davbus". | 
|  | */ | 
|  | np = find_devices("davbus"); | 
|  | /* | 
|  | * if we didn't find a davbus device, try 'i2s-a' since | 
|  | * this seems to be what iBooks (& Tumbler) have. | 
|  | */ | 
|  | if (np == NULL) | 
|  | np = i2s_node = find_devices("i2s-a"); | 
|  |  | 
|  | /* if we didn't find this - perhaps we are on an early model | 
|  | * which _only_ has an 'awacs' node | 
|  | */ | 
|  | if (np == NULL && awacs_node) | 
|  | np = awacs_node ; | 
|  |  | 
|  | /* if we failed all these return null - this will cause the | 
|  | * driver to give up... | 
|  | */ | 
|  | return np ; | 
|  | } | 
|  |  | 
|  | /* Get the OF node that contains the info about the sound chip, inputs s-rates | 
|  | etc. | 
|  | This node does not exist (or contains much reduced info) on earlier machines | 
|  | we have to deduce the info other ways for these. | 
|  | */ | 
|  |  | 
|  | static struct device_node* __init | 
|  | get_snd_info_node(struct device_node *io) | 
|  | { | 
|  | struct device_node *info; | 
|  |  | 
|  | info = find_devices("sound"); | 
|  | while (info && info->parent != io) | 
|  | info = info->next; | 
|  | return info; | 
|  | } | 
|  |  | 
|  | /* Find out what type of codec we have. | 
|  | */ | 
|  |  | 
|  | static int __init | 
|  | get_codec_type(struct device_node *info) | 
|  | { | 
|  | /* already set if pre-davbus model and info will be NULL */ | 
|  | int codec = awacs_revision ; | 
|  |  | 
|  | if (info) { | 
|  | /* must do awacs first to allow screamer to overide it */ | 
|  | if (device_is_compatible(info, "awacs")) | 
|  | codec = AWACS_AWACS ; | 
|  | if (device_is_compatible(info, "screamer")) | 
|  | codec = AWACS_SCREAMER; | 
|  | if (device_is_compatible(info, "burgundy")) | 
|  | codec = AWACS_BURGUNDY ; | 
|  | if (device_is_compatible(info, "daca")) | 
|  | codec = AWACS_DACA; | 
|  | if (device_is_compatible(info, "tumbler")) | 
|  | codec = AWACS_TUMBLER; | 
|  | if (device_is_compatible(info, "snapper")) | 
|  | codec = AWACS_SNAPPER; | 
|  | } | 
|  | return codec ; | 
|  | } | 
|  |  | 
|  | /* find out what type, if any, of expansion card we have | 
|  | */ | 
|  | static void __init | 
|  | get_expansion_type(void) | 
|  | { | 
|  | if (find_devices("perch") != NULL) | 
|  | has_perch = 1; | 
|  |  | 
|  | if (find_devices("pb-ziva-pc") != NULL) | 
|  | has_ziva = 1; | 
|  | /* need to work out how we deal with iMac SRS module */ | 
|  | } | 
|  |  | 
|  | /* set up frame rates. | 
|  | * I suspect that these routines don't quite go about it the right way: | 
|  | * - where there is more than one rate - I think that the first property | 
|  | * value is the number of rates. | 
|  | * TODO: check some more device trees and modify accordingly | 
|  | *       Set dmasound.mach.max_dsp_rate on the basis of these routines. | 
|  | */ | 
|  |  | 
|  | static void __init | 
|  | awacs_init_frame_rates(unsigned int *prop, unsigned int l) | 
|  | { | 
|  | int i ; | 
|  | if (prop) { | 
|  | for (i=0; i<8; i++) | 
|  | awacs_freqs_ok[i] = 0 ; | 
|  | for (l /= sizeof(int); l > 0; --l) { | 
|  | unsigned int r = *prop++; | 
|  | /* Apple 'Fixed' format */ | 
|  | if (r >= 0x10000) | 
|  | r >>= 16; | 
|  | for (i = 0; i < 8; ++i) { | 
|  | if (r == awacs_freqs[i]) { | 
|  | awacs_freqs_ok[i] = 1; | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | /* else we assume that all the rates are available */ | 
|  | } | 
|  |  | 
|  | static void __init | 
|  | burgundy_init_frame_rates(unsigned int *prop, unsigned int l) | 
|  | { | 
|  | int temp[9] ; | 
|  | int i = 0 ; | 
|  | if (prop) { | 
|  | for (l /= sizeof(int); l > 0; --l) { | 
|  | unsigned int r = *prop++; | 
|  | /* Apple 'Fixed' format */ | 
|  | if (r >= 0x10000) | 
|  | r >>= 16; | 
|  | temp[i] = r ; | 
|  | i++ ; if(i>=9) i=8; | 
|  | } | 
|  | } | 
|  | #ifdef DEBUG_DMASOUND | 
|  | if (i > 1){ | 
|  | int j; | 
|  | printk("dmasound_pmac: burgundy with multiple frame rates\n"); | 
|  | for(j=0; j<i; j++) | 
|  | printk("%d ", temp[j]) ; | 
|  | printk("\n") ; | 
|  | } | 
|  | #endif | 
|  | } | 
|  |  | 
|  | static void __init | 
|  | daca_init_frame_rates(unsigned int *prop, unsigned int l) | 
|  | { | 
|  | int temp[9] ; | 
|  | int i = 0 ; | 
|  | if (prop) { | 
|  | for (l /= sizeof(int); l > 0; --l) { | 
|  | unsigned int r = *prop++; | 
|  | /* Apple 'Fixed' format */ | 
|  | if (r >= 0x10000) | 
|  | r >>= 16; | 
|  | temp[i] = r ; | 
|  | i++ ; if(i>=9) i=8; | 
|  |  | 
|  | } | 
|  | } | 
|  | #ifdef DEBUG_DMASOUND | 
|  | if (i > 1){ | 
|  | int j; | 
|  | printk("dmasound_pmac: DACA with multiple frame rates\n"); | 
|  | for(j=0; j<i; j++) | 
|  | printk("%d ", temp[j]) ; | 
|  | printk("\n") ; | 
|  | } | 
|  | #endif | 
|  | } | 
|  |  | 
|  | static void __init | 
|  | init_frame_rates(unsigned int *prop, unsigned int l) | 
|  | { | 
|  | switch (awacs_revision) { | 
|  | case AWACS_TUMBLER: | 
|  | case AWACS_SNAPPER: | 
|  | tas_init_frame_rates(prop, l); | 
|  | break ; | 
|  | case AWACS_DACA: | 
|  | daca_init_frame_rates(prop, l); | 
|  | break ; | 
|  | case AWACS_BURGUNDY: | 
|  | burgundy_init_frame_rates(prop, l); | 
|  | break ; | 
|  | default: | 
|  | awacs_init_frame_rates(prop, l); | 
|  | break ; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* find things/machines that can't do mac-io byteswap | 
|  | */ | 
|  |  | 
|  | static void __init | 
|  | set_hw_byteswap(struct device_node *io) | 
|  | { | 
|  | struct device_node *mio ; | 
|  | unsigned int kl = 0 ; | 
|  |  | 
|  | /* if seems that Keylargo can't byte-swap  */ | 
|  |  | 
|  | for (mio = io->parent; mio ; mio = mio->parent) { | 
|  | if (strcmp(mio->name, "mac-io") == 0) { | 
|  | if (device_is_compatible(mio, "Keylargo")) | 
|  | kl = 1; | 
|  | break; | 
|  | } | 
|  | } | 
|  | hw_can_byteswap = !kl; | 
|  | } | 
|  |  | 
|  | /* Allocate the resources necessary for beep generation.  This cannot be (quite) | 
|  | done statically (yet) because we cannot do virt_to_bus() on static vars when | 
|  | the code is loaded as a module. | 
|  |  | 
|  | for the sake of saving the possibility that two allocations will incur the | 
|  | overhead of two pull-ups in DBDMA_ALIGN() we allocate the 'emergency' dmdma | 
|  | command here as well... even tho' it is not part of the beep process. | 
|  | */ | 
|  |  | 
|  | int32_t | 
|  | __init setup_beep(void) | 
|  | { | 
|  | /* Initialize beep stuff */ | 
|  | /* want one cmd buffer for beeps, and a second one for emergencies | 
|  | - i.e. dbdma error conditions. | 
|  | ask for three to allow for pull up in DBDMA_ALIGN(). | 
|  | */ | 
|  | beep_dbdma_cmd_space = | 
|  | kmalloc((2 + 1) * sizeof(struct dbdma_cmd), GFP_KERNEL); | 
|  | if(beep_dbdma_cmd_space == NULL) { | 
|  | printk(KERN_ERR "dmasound_pmac: no beep dbdma cmd space\n") ; | 
|  | return -ENOMEM ; | 
|  | } | 
|  | beep_dbdma_cmd = (volatile struct dbdma_cmd *) | 
|  | DBDMA_ALIGN(beep_dbdma_cmd_space); | 
|  | /* set up emergency dbdma cmd */ | 
|  | emergency_dbdma_cmd = beep_dbdma_cmd+1 ; | 
|  | beep_buf = (short *) kmalloc(BEEP_BUFLEN * 4, GFP_KERNEL); | 
|  | if (beep_buf == NULL) { | 
|  | printk(KERN_ERR "dmasound_pmac: no memory for beep buffer\n"); | 
|  | if( beep_dbdma_cmd_space ) kfree(beep_dbdma_cmd_space) ; | 
|  | return -ENOMEM ; | 
|  | } | 
|  | return 0 ; | 
|  | } | 
|  |  | 
|  | static struct input_dev awacs_beep_dev = { | 
|  | .evbit		= { BIT(EV_SND) }, | 
|  | .sndbit		= { BIT(SND_BELL) | BIT(SND_TONE) }, | 
|  | .event		= awacs_beep_event, | 
|  | .name		= "dmasound beeper", | 
|  | .phys		= "macio/input0", /* what the heck is this?? */ | 
|  | .id		= { | 
|  | .bustype	= BUS_HOST, | 
|  | }, | 
|  | }; | 
|  |  | 
|  | int __init dmasound_awacs_init(void) | 
|  | { | 
|  | struct device_node *io = NULL, *info = NULL; | 
|  | int vol, res; | 
|  |  | 
|  | if (_machine != _MACH_Pmac) | 
|  | return -ENODEV; | 
|  |  | 
|  | awacs_subframe = 0; | 
|  | awacs_revision = 0; | 
|  | hw_can_byteswap = 1 ; /* most can */ | 
|  |  | 
|  | /* look for models we need to handle specially */ | 
|  | set_model() ; | 
|  |  | 
|  | /* find the OF node that tells us about the dbdma stuff | 
|  | */ | 
|  | io = get_snd_io_node(); | 
|  | if (io == NULL) { | 
|  | #ifdef DEBUG_DMASOUND | 
|  | printk("dmasound_pmac: couldn't find sound io OF node\n"); | 
|  | #endif | 
|  | return -ENODEV ; | 
|  | } | 
|  |  | 
|  | /* find the OF node that tells us about the sound sub-system | 
|  | * this doesn't exist on pre-davbus machines (earlier than 9500) | 
|  | */ | 
|  | if (awacs_revision != AWACS_AWACS) { /* set for pre-davbus */ | 
|  | info = get_snd_info_node(io) ; | 
|  | if (info == NULL){ | 
|  | #ifdef DEBUG_DMASOUND | 
|  | printk("dmasound_pmac: couldn't find 'sound' OF node\n"); | 
|  | #endif | 
|  | return -ENODEV ; | 
|  | } | 
|  | } | 
|  |  | 
|  | awacs_revision = get_codec_type(info) ; | 
|  | if (awacs_revision == 0) { | 
|  | #ifdef DEBUG_DMASOUND | 
|  | printk("dmasound_pmac: couldn't find a Codec we can handle\n"); | 
|  | #endif | 
|  | return -ENODEV ; /* we don't know this type of h/w */ | 
|  | } | 
|  |  | 
|  | /* set up perch, ziva, SRS or whatever else we have as sound | 
|  | *  expansion. | 
|  | */ | 
|  | get_expansion_type(); | 
|  |  | 
|  | /* we've now got enough information to make up the audio topology. | 
|  | * we will map the sound part of mac-io now so that we can probe for | 
|  | * other info if necessary (early AWACS we want to read chip ids) | 
|  | */ | 
|  |  | 
|  | if (io->n_addrs < 3 || io->n_intrs < 3) { | 
|  | /* OK - maybe we need to use the 'awacs' node (on earlier | 
|  | * machines). | 
|  | */ | 
|  | if (awacs_node) { | 
|  | io = awacs_node ; | 
|  | if (io->n_addrs < 3 || io->n_intrs < 3) { | 
|  | printk("dmasound_pmac: can't use %s" | 
|  | " (%d addrs, %d intrs)\n", | 
|  | io->full_name, io->n_addrs, io->n_intrs); | 
|  | return -ENODEV; | 
|  | } | 
|  | } else { | 
|  | printk("dmasound_pmac: can't use %s (%d addrs, %d intrs)\n", | 
|  | io->full_name, io->n_addrs, io->n_intrs); | 
|  | } | 
|  | } | 
|  |  | 
|  | if (!request_OF_resource(io, 0, NULL)) { | 
|  | printk(KERN_ERR "dmasound: can't request IO resource !\n"); | 
|  | return -ENODEV; | 
|  | } | 
|  | if (!request_OF_resource(io, 1, " (tx dma)")) { | 
|  | release_OF_resource(io, 0); | 
|  | printk(KERN_ERR "dmasound: can't request TX DMA resource !\n"); | 
|  | return -ENODEV; | 
|  | } | 
|  |  | 
|  | if (!request_OF_resource(io, 2, " (rx dma)")) { | 
|  | release_OF_resource(io, 0); | 
|  | release_OF_resource(io, 1); | 
|  | printk(KERN_ERR "dmasound: can't request RX DMA resource !\n"); | 
|  | return -ENODEV; | 
|  | } | 
|  |  | 
|  | /* all OF versions I've seen use this value */ | 
|  | if (i2s_node) | 
|  | i2s = ioremap(io->addrs[0].address, 0x1000); | 
|  | else | 
|  | awacs = ioremap(io->addrs[0].address, 0x1000); | 
|  | awacs_txdma = ioremap(io->addrs[1].address, 0x100); | 
|  | awacs_rxdma = ioremap(io->addrs[2].address, 0x100); | 
|  |  | 
|  | /* first of all make sure that the chip is powered up....*/ | 
|  | pmac_call_feature(PMAC_FTR_SOUND_CHIP_ENABLE, io, 0, 1); | 
|  | if (awacs_revision == AWACS_SCREAMER && awacs) | 
|  | awacs_recalibrate(); | 
|  |  | 
|  | awacs_irq = io->intrs[0].line; | 
|  | awacs_tx_irq = io->intrs[1].line; | 
|  | awacs_rx_irq = io->intrs[2].line; | 
|  |  | 
|  | /* Hack for legacy crap that will be killed someday */ | 
|  | awacs_node = io; | 
|  |  | 
|  | /* if we have an awacs or screamer - probe the chip to make | 
|  | * sure we have the right revision. | 
|  | */ | 
|  |  | 
|  | if (awacs_revision <= AWACS_SCREAMER){ | 
|  | uint32_t temp, rev, mfg ; | 
|  | /* find out the awacs revision from the chip */ | 
|  | temp = in_le32(&awacs->codec_stat); | 
|  | rev = (temp >> 12) & 0xf; | 
|  | mfg = (temp >>  8) & 0xf; | 
|  | #ifdef DEBUG_DMASOUND | 
|  | printk("dmasound_pmac: Awacs/Screamer Codec Mfct: %d Rev %d\n", mfg, rev); | 
|  | #endif | 
|  | if (rev >= AWACS_SCREAMER) | 
|  | awacs_revision = AWACS_SCREAMER ; | 
|  | else | 
|  | awacs_revision = rev ; | 
|  | } | 
|  |  | 
|  | dmasound.mach = machPMac; | 
|  |  | 
|  | /* find out other bits & pieces from OF, these may be present | 
|  | only on some models ... so be careful. | 
|  | */ | 
|  |  | 
|  | /* in the absence of a frame rates property we will use the defaults | 
|  | */ | 
|  |  | 
|  | if (info) { | 
|  | unsigned int *prop, l; | 
|  |  | 
|  | sound_device_id = 0; | 
|  | /* device ID appears post g3 b&w */ | 
|  | prop = (unsigned int *)get_property(info, "device-id", NULL); | 
|  | if (prop != 0) | 
|  | sound_device_id = *prop; | 
|  |  | 
|  | /* look for a property saying what sample rates | 
|  | are available */ | 
|  |  | 
|  | prop = (unsigned int *)get_property(info, "sample-rates", &l); | 
|  | if (prop == 0) | 
|  | prop = (unsigned int *) get_property | 
|  | (info, "output-frame-rates", &l); | 
|  |  | 
|  | /* if it's there use it to set up frame rates */ | 
|  | init_frame_rates(prop, l) ; | 
|  | } | 
|  |  | 
|  | if (awacs) | 
|  | out_le32(&awacs->control, 0x11); /* set everything quiesent */ | 
|  |  | 
|  | set_hw_byteswap(io) ; /* figure out if the h/w can do it */ | 
|  |  | 
|  | #ifdef CONFIG_NVRAM | 
|  | /* get default volume from nvram */ | 
|  | vol = ((pmac_xpram_read( 8 ) & 7 ) << 1 ); | 
|  | #else | 
|  | vol = 0; | 
|  | #endif | 
|  |  | 
|  | /* set up tracking values */ | 
|  | spk_vol = vol * 100 ; | 
|  | spk_vol /= 7 ; /* get set value to a percentage */ | 
|  | spk_vol |= (spk_vol << 8) ; /* equal left & right */ | 
|  | line_vol = passthru_vol = spk_vol ; | 
|  |  | 
|  | /* fill regs that are shared between AWACS & Burgundy */ | 
|  |  | 
|  | awacs_reg[2] = vol + (vol << 6); | 
|  | awacs_reg[4] = vol + (vol << 6); | 
|  | awacs_reg[5] = vol + (vol << 6); /* screamer has loopthru vol control */ | 
|  | awacs_reg[6] = 0; /* maybe should be vol << 3 for PCMCIA speaker */ | 
|  | awacs_reg[7] = 0; | 
|  |  | 
|  | awacs_reg[0] = MASK_MUX_CD; | 
|  | awacs_reg[1] = MASK_LOOPTHRU; | 
|  |  | 
|  | /* FIXME: Only machines with external SRS module need MASK_PAROUT */ | 
|  | if (has_perch || sound_device_id == 0x5 | 
|  | || /*sound_device_id == 0x8 ||*/ sound_device_id == 0xb) | 
|  | awacs_reg[1] |= MASK_PAROUT0 | MASK_PAROUT1; | 
|  |  | 
|  | switch (awacs_revision) { | 
|  | case AWACS_TUMBLER: | 
|  | tas_register_driver(&tas3001c_hooks); | 
|  | tas_init(I2C_DRIVERID_TAS3001C, I2C_DRIVERNAME_TAS3001C); | 
|  | tas_dmasound_init(); | 
|  | tas_post_init(); | 
|  | break ; | 
|  | case AWACS_SNAPPER: | 
|  | tas_register_driver(&tas3004_hooks); | 
|  | tas_init(I2C_DRIVERID_TAS3004,I2C_DRIVERNAME_TAS3004); | 
|  | tas_dmasound_init(); | 
|  | tas_post_init(); | 
|  | break; | 
|  | case AWACS_DACA: | 
|  | daca_init(); | 
|  | break; | 
|  | case AWACS_BURGUNDY: | 
|  | awacs_burgundy_init(); | 
|  | break ; | 
|  | case AWACS_SCREAMER: | 
|  | case AWACS_AWACS: | 
|  | default: | 
|  | load_awacs(); | 
|  | break ; | 
|  | } | 
|  |  | 
|  | /* enable/set-up external modules - when we know how */ | 
|  |  | 
|  | if (has_perch) | 
|  | awacs_enable_amp(100 * 0x101); | 
|  |  | 
|  | /* Reset dbdma channels */ | 
|  | out_le32(&awacs_txdma->control, (RUN|PAUSE|FLUSH|WAKE|DEAD) << 16); | 
|  | while (in_le32(&awacs_txdma->status) & RUN) | 
|  | udelay(1); | 
|  | out_le32(&awacs_rxdma->control, (RUN|PAUSE|FLUSH|WAKE|DEAD) << 16); | 
|  | while (in_le32(&awacs_rxdma->status) & RUN) | 
|  | udelay(1); | 
|  |  | 
|  | /* Initialize beep stuff */ | 
|  | if ((res=setup_beep())) | 
|  | return res ; | 
|  |  | 
|  | #ifdef CONFIG_PMAC_PBOOK | 
|  | pmu_register_sleep_notifier(&awacs_sleep_notifier); | 
|  | #endif /* CONFIG_PMAC_PBOOK */ | 
|  |  | 
|  | /* Powerbooks have odd ways of enabling inputs such as | 
|  | an expansion-bay CD or sound from an internal modem | 
|  | or a PC-card modem. */ | 
|  | if (is_pbook_3X00) { | 
|  | /* | 
|  | * Enable CD and PC-card sound inputs. | 
|  | * This is done by reading from address | 
|  | * f301a000, + 0x10 to enable the expansion-bay | 
|  | * CD sound input, + 0x80 to enable the PC-card | 
|  | * sound input.  The 0x100 enables the SCSI bus | 
|  | * terminator power. | 
|  | */ | 
|  | latch_base = ioremap (0xf301a000, 0x1000); | 
|  | in_8(latch_base + 0x190); | 
|  |  | 
|  | } else if (is_pbook_g3) { | 
|  | struct device_node* mio; | 
|  | macio_base = NULL; | 
|  | for (mio = io->parent; mio; mio = mio->parent) { | 
|  | if (strcmp(mio->name, "mac-io") == 0 | 
|  | && mio->n_addrs > 0) { | 
|  | macio_base = ioremap(mio->addrs[0].address, 0x40); | 
|  | break; | 
|  | } | 
|  | } | 
|  | /* | 
|  | * Enable CD sound input. | 
|  | * The relevant bits for writing to this byte are 0x8f. | 
|  | * I haven't found out what the 0x80 bit does. | 
|  | * For the 0xf bits, writing 3 or 7 enables the CD | 
|  | * input, any other value disables it.  Values | 
|  | * 1, 3, 5, 7 enable the microphone.  Values 0, 2, | 
|  | * 4, 6, 8 - f enable the input from the modem. | 
|  | *  -- paulus. | 
|  | */ | 
|  | if (macio_base) | 
|  | out_8(macio_base + 0x37, 3); | 
|  | } | 
|  |  | 
|  | if (hw_can_byteswap) | 
|  | dmasound.mach.hardware_afmts = (AFMT_S16_BE | AFMT_S16_LE) ; | 
|  | else | 
|  | dmasound.mach.hardware_afmts = AFMT_S16_BE ; | 
|  |  | 
|  | /* shut out chips that do output only. | 
|  | * may need to extend this to machines which have no inputs - even tho' | 
|  | * they use screamer - IIRC one of the powerbooks is like this. | 
|  | */ | 
|  |  | 
|  | if (awacs_revision != AWACS_DACA) { | 
|  | dmasound.mach.capabilities = DSP_CAP_DUPLEX ; | 
|  | dmasound.mach.record = PMacRecord ; | 
|  | } | 
|  |  | 
|  | dmasound.mach.default_hard = def_hard ; | 
|  | dmasound.mach.default_soft = def_soft ; | 
|  |  | 
|  | switch (awacs_revision) { | 
|  | case AWACS_BURGUNDY: | 
|  | sprintf(awacs_name, "PowerMac Burgundy ") ; | 
|  | break ; | 
|  | case AWACS_DACA: | 
|  | sprintf(awacs_name, "PowerMac DACA ") ; | 
|  | break ; | 
|  | case AWACS_TUMBLER: | 
|  | sprintf(awacs_name, "PowerMac Tumbler ") ; | 
|  | break ; | 
|  | case AWACS_SNAPPER: | 
|  | sprintf(awacs_name, "PowerMac Snapper ") ; | 
|  | break ; | 
|  | case AWACS_SCREAMER: | 
|  | sprintf(awacs_name, "PowerMac Screamer ") ; | 
|  | break ; | 
|  | case AWACS_AWACS: | 
|  | default: | 
|  | sprintf(awacs_name, "PowerMac AWACS rev %d ", awacs_revision) ; | 
|  | break ; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * XXX: we should handle errors here, but that would mean | 
|  | * rewriting the whole init code.  later.. | 
|  | */ | 
|  | input_register_device(&awacs_beep_dev); | 
|  |  | 
|  | return dmasound_init(); | 
|  | } | 
|  |  | 
|  | static void __exit dmasound_awacs_cleanup(void) | 
|  | { | 
|  | input_unregister_device(&awacs_beep_dev); | 
|  |  | 
|  | switch (awacs_revision) { | 
|  | case AWACS_TUMBLER: | 
|  | case AWACS_SNAPPER: | 
|  | tas_dmasound_cleanup(); | 
|  | tas_cleanup(); | 
|  | break ; | 
|  | case AWACS_DACA: | 
|  | daca_cleanup(); | 
|  | break; | 
|  | } | 
|  | dmasound_deinit(); | 
|  |  | 
|  | } | 
|  |  | 
|  | MODULE_DESCRIPTION("PowerMac built-in audio driver."); | 
|  | MODULE_LICENSE("GPL"); | 
|  |  | 
|  | module_init(dmasound_awacs_init); | 
|  | module_exit(dmasound_awacs_cleanup); |