aboutsummaryrefslogtreecommitdiff
path: root/sound/oss/dmasound/dmasound_awacs.c
diff options
context:
space:
mode:
Diffstat (limited to 'sound/oss/dmasound/dmasound_awacs.c')
-rw-r--r--sound/oss/dmasound/dmasound_awacs.c3176
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 00000000000..5281b88987f
--- /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);