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