| /* |
| * 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_PM |
| /* |
| * 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_PM */ |
| |
| /* 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, gfp_t 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, gfp_t 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); |
| |
| kfree(awacs_tx_cmd_space); |
| kfree(awacs_rx_cmd_space); |
| kfree(beep_dbdma_cmd_space); |
| kfree(beep_buf); |
| #ifdef CONFIG_PM |
| 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_PM |
| /* |
| * 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_PM */ |
| |
| |
| /* All the burgundy functions: */ |
| |
| /* Waits for busy flag to clear */ |
| static inline void |
| awacs_burgundy_busy_wait(void) |
| { |
| int count = 50; /* > 2 samples at 44k1 */ |
| while ((in_le32(&awacs->codec_ctrl) & MASK_NEWECMD) && count--) |
| udelay(1) ; |
| } |
| |
| static inline 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) { |
| 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 ) { |
| 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"); |
| 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_PM |
| pmu_register_sleep_notifier(&awacs_sleep_notifier); |
| #endif /* CONFIG_PM */ |
| |
| /* 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); |