diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:49:45 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:49:45 +0000 |
commit | 2c3c1048746a4622d8c89a29670120dc8fab93c4 (patch) | |
tree | 848558de17fb3008cdf4d861b01ac7781903ce39 /sound/oss/dmasound | |
parent | Initial commit. (diff) | |
download | linux-upstream.tar.xz linux-upstream.zip |
Adding upstream version 6.1.76.upstream/6.1.76upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sound/oss/dmasound')
-rw-r--r-- | sound/oss/dmasound/Kconfig | 46 | ||||
-rw-r--r-- | sound/oss/dmasound/Makefile | 8 | ||||
-rw-r--r-- | sound/oss/dmasound/dmasound.h | 253 | ||||
-rw-r--r-- | sound/oss/dmasound/dmasound_atari.c | 1621 | ||||
-rw-r--r-- | sound/oss/dmasound/dmasound_core.c | 1566 | ||||
-rw-r--r-- | sound/oss/dmasound/dmasound_paula.c | 739 | ||||
-rw-r--r-- | sound/oss/dmasound/dmasound_q40.c | 639 |
7 files changed, 4872 insertions, 0 deletions
diff --git a/sound/oss/dmasound/Kconfig b/sound/oss/dmasound/Kconfig new file mode 100644 index 000000000..1a3339859 --- /dev/null +++ b/sound/oss/dmasound/Kconfig @@ -0,0 +1,46 @@ +# SPDX-License-Identifier: GPL-2.0-only +config DMASOUND_ATARI + tristate "Atari DMA sound support" + depends on ATARI && SOUND + select DMASOUND + help + If you want to use the internal audio of your Atari in Linux, answer + Y to this question. This will provide a Sun-like /dev/audio, + compatible with the Linux/i386 sound system. Otherwise, say N. + + This driver is also available as a module ( = code which can be + inserted in and removed from the running kernel whenever you + want). If you want to compile it as a module, say M here and read + <file:Documentation/kbuild/modules.rst>. + +config DMASOUND_PAULA + tristate "Amiga DMA sound support" + depends on AMIGA && SOUND + select DMASOUND + help + If you want to use the internal audio of your Amiga in Linux, answer + Y to this question. This will provide a Sun-like /dev/audio, + compatible with the Linux/i386 sound system. Otherwise, say N. + + This driver is also available as a module ( = code which can be + inserted in and removed from the running kernel whenever you + want). If you want to compile it as a module, say M here and read + <file:Documentation/kbuild/modules.rst>. + +config DMASOUND_Q40 + tristate "Q40 sound support" + depends on Q40 && SOUND + select DMASOUND + help + If you want to use the internal audio of your Q40 in Linux, answer + Y to this question. This will provide a Sun-like /dev/audio, + compatible with the Linux/i386 sound system. Otherwise, say N. + + This driver is also available as a module ( = code which can be + inserted in and removed from the running kernel whenever you + want). If you want to compile it as a module, say M here and read + <file:Documentation/kbuild/modules.rst>. + +config DMASOUND + tristate + select SOUND_OSS_CORE diff --git a/sound/oss/dmasound/Makefile b/sound/oss/dmasound/Makefile new file mode 100644 index 000000000..de8ae8346 --- /dev/null +++ b/sound/oss/dmasound/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for the DMA sound driver +# + +obj-$(CONFIG_DMASOUND_ATARI) += dmasound_core.o dmasound_atari.o +obj-$(CONFIG_DMASOUND_PAULA) += dmasound_core.o dmasound_paula.o +obj-$(CONFIG_DMASOUND_Q40) += dmasound_core.o dmasound_q40.o diff --git a/sound/oss/dmasound/dmasound.h b/sound/oss/dmasound/dmasound.h new file mode 100644 index 000000000..f065840c0 --- /dev/null +++ b/sound/oss/dmasound/dmasound.h @@ -0,0 +1,253 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _dmasound_h_ +/* + * linux/sound/oss/dmasound/dmasound.h + * + * + * Minor numbers for the sound driver. + * + * Unfortunately Creative called the codec chip of SB as a DSP. For this + * reason the /dev/dsp is reserved for digitized audio use. There is a + * device for true DSP processors but it will be called something else. + * In v3.0 it's /dev/sndproc but this could be a temporary solution. + */ +#define _dmasound_h_ + +#include <linux/types.h> + +#define SND_NDEVS 256 /* Number of supported devices */ +#define SND_DEV_CTL 0 /* Control port /dev/mixer */ +#define SND_DEV_SEQ 1 /* Sequencer output /dev/sequencer (FM + synthesizer and MIDI output) */ +#define SND_DEV_MIDIN 2 /* Raw midi access */ +#define SND_DEV_DSP 3 /* Digitized voice /dev/dsp */ +#define SND_DEV_AUDIO 4 /* Sparc compatible /dev/audio */ +#define SND_DEV_DSP16 5 /* Like /dev/dsp but 16 bits/sample */ +#define SND_DEV_STATUS 6 /* /dev/sndstat */ +/* #7 not in use now. Was in 2.4. Free for use after v3.0. */ +#define SND_DEV_SEQ2 8 /* /dev/sequencer, level 2 interface */ +#define SND_DEV_SNDPROC 9 /* /dev/sndproc for programmable devices */ +#define SND_DEV_PSS SND_DEV_SNDPROC + +/* switch on various prinks */ +#define DEBUG_DMASOUND 1 + +#define MAX_AUDIO_DEV 5 +#define MAX_MIXER_DEV 4 +#define MAX_SYNTH_DEV 3 +#define MAX_MIDI_DEV 6 +#define MAX_TIMER_DEV 3 + +#define MAX_CATCH_RADIUS 10 + +#define le2be16(x) (((x)<<8 & 0xff00) | ((x)>>8 & 0x00ff)) +#define le2be16dbl(x) (((x)<<8 & 0xff00ff00) | ((x)>>8 & 0x00ff00ff)) + +#define IOCTL_IN(arg, ret) \ + do { int error = get_user(ret, (int __user *)(arg)); \ + if (error) return error; \ + } while (0) +#define IOCTL_OUT(arg, ret) ioctl_return((int __user *)(arg), ret) + +static inline int ioctl_return(int __user *addr, int value) +{ + return value < 0 ? value : put_user(value, addr); +} + + + /* + * Configuration + */ + +#undef HAS_8BIT_TABLES + +#if defined(CONFIG_DMASOUND_ATARI) || defined(CONFIG_DMASOUND_ATARI_MODULE) ||\ + defined(CONFIG_DMASOUND_PAULA) || defined(CONFIG_DMASOUND_PAULA_MODULE) ||\ + defined(CONFIG_DMASOUND_Q40) || defined(CONFIG_DMASOUND_Q40_MODULE) +#define HAS_8BIT_TABLES +#define MIN_BUFFERS 4 +#define MIN_BUFSIZE (1<<12) /* in bytes (- where does this come from ?) */ +#define MIN_FRAG_SIZE 8 /* not 100% sure about this */ +#define MAX_BUFSIZE (1<<17) /* Limit for Amiga is 128 kb */ +#define MAX_FRAG_SIZE 15 /* allow *4 for mono-8 => stereo-16 (for multi) */ + +#else /* is pmac and multi is off */ + +#define MIN_BUFFERS 2 +#define MIN_BUFSIZE (1<<8) /* in bytes */ +#define MIN_FRAG_SIZE 8 +#define MAX_BUFSIZE (1<<18) /* this is somewhat arbitrary for pmac */ +#define MAX_FRAG_SIZE 16 /* need to allow *4 for mono-8 => stereo-16 */ +#endif + +#define DEFAULT_N_BUFFERS 4 +#define DEFAULT_BUFF_SIZE (1<<15) + + /* + * Initialization + */ + +extern int dmasound_init(void); +extern void dmasound_deinit(void); + +/* description of the set-up applies to either hard or soft settings */ + +typedef struct { + int format; /* AFMT_* */ + int stereo; /* 0 = mono, 1 = stereo */ + int size; /* 8/16 bit*/ + int speed; /* speed */ +} SETTINGS; + + /* + * Machine definitions + */ + +typedef struct { + const char *name; + const char *name2; + struct module *owner; + void *(*dma_alloc)(unsigned int, gfp_t); + void (*dma_free)(void *, unsigned int); + int (*irqinit)(void); + void (*irqcleanup)(void); + void (*init)(void); + void (*silence)(void); + int (*setFormat)(int); + int (*setVolume)(int); + int (*setBass)(int); + int (*setTreble)(int); + int (*setGain)(int); + void (*play)(void); + void (*record)(void); /* optional */ + void (*mixer_init)(void); /* optional */ + int (*mixer_ioctl)(u_int, u_long); /* optional */ + int (*write_sq_setup)(void); /* optional */ + int (*read_sq_setup)(void); /* optional */ + int (*sq_open)(fmode_t); /* optional */ + int (*state_info)(char *, size_t); /* optional */ + void (*abort_read)(void); /* optional */ + int min_dsp_speed; + int max_dsp_speed; + int version ; + int hardware_afmts ; /* OSS says we only return h'ware info */ + /* when queried via SNDCTL_DSP_GETFMTS */ + int capabilities ; /* low-level reply to SNDCTL_DSP_GETCAPS */ + SETTINGS default_hard ; /* open() or init() should set something valid */ + SETTINGS default_soft ; /* you can make it look like old OSS, if you want to */ +} MACHINE; + + /* + * Low level stuff + */ + +typedef struct { + ssize_t (*ct_ulaw)(const u_char __user *, size_t, u_char *, ssize_t *, ssize_t); + ssize_t (*ct_alaw)(const u_char __user *, size_t, u_char *, ssize_t *, ssize_t); + ssize_t (*ct_s8)(const u_char __user *, size_t, u_char *, ssize_t *, ssize_t); + ssize_t (*ct_u8)(const u_char __user *, size_t, u_char *, ssize_t *, ssize_t); + ssize_t (*ct_s16be)(const u_char __user *, size_t, u_char *, ssize_t *, ssize_t); + ssize_t (*ct_u16be)(const u_char __user *, size_t, u_char *, ssize_t *, ssize_t); + ssize_t (*ct_s16le)(const u_char __user *, size_t, u_char *, ssize_t *, ssize_t); + ssize_t (*ct_u16le)(const u_char __user *, size_t, u_char *, ssize_t *, ssize_t); +} TRANS; + +struct sound_settings { + MACHINE mach; /* machine dependent things */ + SETTINGS hard; /* hardware settings */ + SETTINGS soft; /* software settings */ + SETTINGS dsp; /* /dev/dsp default settings */ + TRANS *trans_write; /* supported translations */ + int volume_left; /* volume (range is machine dependent) */ + int volume_right; + int bass; /* tone (range is machine dependent) */ + int treble; + int gain; + int minDev; /* minor device number currently open */ + spinlock_t lock; +}; + +extern struct sound_settings dmasound; + +#ifdef HAS_8BIT_TABLES +extern char dmasound_ulaw2dma8[]; +extern char dmasound_alaw2dma8[]; +#endif + + /* + * Mid level stuff + */ + +static inline int dmasound_set_volume(int volume) +{ + return dmasound.mach.setVolume(volume); +} + +static inline int dmasound_set_bass(int bass) +{ + return dmasound.mach.setBass ? dmasound.mach.setBass(bass) : 50; +} + +static inline int dmasound_set_treble(int treble) +{ + return dmasound.mach.setTreble ? dmasound.mach.setTreble(treble) : 50; +} + +static inline int dmasound_set_gain(int gain) +{ + return dmasound.mach.setGain ? dmasound.mach.setGain(gain) : 100; +} + + + /* + * Sound queue stuff, the heart of the driver + */ + +struct sound_queue { + /* buffers allocated for this queue */ + int numBufs; /* real limits on what the user can have */ + int bufSize; /* in bytes */ + char **buffers; + + /* current parameters */ + int locked ; /* params cannot be modified when != 0 */ + int user_frags ; /* user requests this many */ + int user_frag_size ; /* of this size */ + int max_count; /* actual # fragments <= numBufs */ + int block_size; /* internal block size in bytes */ + int max_active; /* in-use fragments <= max_count */ + + /* it shouldn't be necessary to declare any of these volatile */ + int front, rear, count; + int rear_size; + /* + * The use of the playing field depends on the hardware + * + * Atari, PMac: The number of frames that are loaded/playing + * + * Amiga: Bit 0 is set: a frame is loaded + * Bit 1 is set: a frame is playing + */ + int active; + wait_queue_head_t action_queue, open_queue, sync_queue; + int non_blocking; + int busy, syncing, xruns, died; +}; + +#define WAKE_UP(queue) (wake_up_interruptible(&queue)) + +extern struct sound_queue dmasound_write_sq; +#define write_sq dmasound_write_sq + +extern int dmasound_catchRadius; +#define catchRadius dmasound_catchRadius + +/* define the value to be put in the byte-swap reg in mac-io + when we want it to swap for us. +*/ +#define BS_VAL 1 + +#define SW_INPUT_VOLUME_SCALE 4 +#define SW_INPUT_VOLUME_DEFAULT (128 / SW_INPUT_VOLUME_SCALE) + +#endif /* _dmasound_h_ */ diff --git a/sound/oss/dmasound/dmasound_atari.c b/sound/oss/dmasound/dmasound_atari.c new file mode 100644 index 000000000..81c6a9830 --- /dev/null +++ b/sound/oss/dmasound/dmasound_atari.c @@ -0,0 +1,1621 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * linux/sound/oss/dmasound/dmasound_atari.c + * + * Atari TT and Falcon DMA Sound Driver + * + * See linux/sound/oss/dmasound/dmasound_core.c for copyright and credits + * prior to 28/01/2001 + * + * 28/01/2001 [0.1] Iain Sandoe + * - added versioning + * - put in and populated the hardware_afmts field. + * [0.2] - put in SNDCTL_DSP_GETCAPS value. + * 01/02/2001 [0.3] - put in default hard/soft settings. + */ + + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/soundcard.h> +#include <linux/mm.h> +#include <linux/spinlock.h> +#include <linux/interrupt.h> + +#include <linux/uaccess.h> +#include <asm/atariints.h> +#include <asm/atari_stram.h> + +#include "dmasound.h" + +#define DMASOUND_ATARI_REVISION 0 +#define DMASOUND_ATARI_EDITION 3 + +extern void atari_microwire_cmd(int cmd); + +static int is_falcon; +static int write_sq_ignore_int; /* ++TeSche: used for Falcon */ + +static int expand_bal; /* Balance factor for expanding (not volume!) */ +static int expand_data; /* Data for expanding */ + + +/*** Translations ************************************************************/ + + +/* ++TeSche: radically changed for new expanding purposes... + * + * These two routines now deal with copying/expanding/translating the samples + * from user space into our buffer at the right frequency. They take care about + * how much data there's actually to read, how much buffer space there is and + * to convert samples into the right frequency/encoding. They will only work on + * complete samples so it may happen they leave some bytes in the input stream + * if the user didn't write a multiple of the current sample size. They both + * return the number of bytes they've used from both streams so you may detect + * such a situation. Luckily all programs should be able to cope with that. + * + * I think I've optimized anything as far as one can do in plain C, all + * variables should fit in registers and the loops are really short. There's + * one loop for every possible situation. Writing a more generalized and thus + * parameterized loop would only produce slower code. Feel free to optimize + * this in assembler if you like. :) + * + * I think these routines belong here because they're not yet really hardware + * independent, especially the fact that the Falcon can play 16bit samples + * only in stereo is hardcoded in both of them! + * + * ++geert: split in even more functions (one per format) + */ + +static ssize_t ata_ct_law(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft); +static ssize_t ata_ct_s8(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft); +static ssize_t ata_ct_u8(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft); +static ssize_t ata_ct_s16be(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft); +static ssize_t ata_ct_u16be(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft); +static ssize_t ata_ct_s16le(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft); +static ssize_t ata_ct_u16le(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft); +static ssize_t ata_ctx_law(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft); +static ssize_t ata_ctx_s8(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft); +static ssize_t ata_ctx_u8(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft); +static ssize_t ata_ctx_s16be(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft); +static ssize_t ata_ctx_u16be(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft); +static ssize_t ata_ctx_s16le(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft); +static ssize_t ata_ctx_u16le(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft); + + +/*** Low level stuff *********************************************************/ + + +static void *AtaAlloc(unsigned int size, gfp_t flags); +static void AtaFree(void *, unsigned int size); +static int AtaIrqInit(void); +#ifdef MODULE +static void AtaIrqCleanUp(void); +#endif /* MODULE */ +static int AtaSetBass(int bass); +static int AtaSetTreble(int treble); +static void TTSilence(void); +static void TTInit(void); +static int TTSetFormat(int format); +static int TTSetVolume(int volume); +static int TTSetGain(int gain); +static void FalconSilence(void); +static void FalconInit(void); +static int FalconSetFormat(int format); +static int FalconSetVolume(int volume); +static void AtaPlayNextFrame(int index); +static void AtaPlay(void); +static irqreturn_t AtaInterrupt(int irq, void *dummy); + +/*** Mid level stuff *********************************************************/ + +static void TTMixerInit(void); +static void FalconMixerInit(void); +static int AtaMixerIoctl(u_int cmd, u_long arg); +static int TTMixerIoctl(u_int cmd, u_long arg); +static int FalconMixerIoctl(u_int cmd, u_long arg); +static int AtaWriteSqSetup(void); +static int AtaSqOpen(fmode_t mode); +static int TTStateInfo(char *buffer, size_t space); +static int FalconStateInfo(char *buffer, size_t space); + + +/*** Translations ************************************************************/ + + +static ssize_t ata_ct_law(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft) +{ + char *table = dmasound.soft.format == AFMT_MU_LAW ? dmasound_ulaw2dma8 + : dmasound_alaw2dma8; + ssize_t count, used; + u_char *p = &frame[*frameUsed]; + + count = min_t(unsigned long, userCount, frameLeft); + if (dmasound.soft.stereo) + count &= ~1; + used = count; + while (count > 0) { + u_char data; + if (get_user(data, userPtr++)) + return -EFAULT; + *p++ = table[data]; + count--; + } + *frameUsed += used; + return used; +} + + +static ssize_t ata_ct_s8(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft) +{ + ssize_t count, used; + void *p = &frame[*frameUsed]; + + count = min_t(unsigned long, userCount, frameLeft); + if (dmasound.soft.stereo) + count &= ~1; + used = count; + if (copy_from_user(p, userPtr, count)) + return -EFAULT; + *frameUsed += used; + return used; +} + + +static ssize_t ata_ct_u8(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft) +{ + ssize_t count, used; + + if (!dmasound.soft.stereo) { + u_char *p = &frame[*frameUsed]; + count = min_t(unsigned long, userCount, frameLeft); + used = count; + while (count > 0) { + u_char data; + if (get_user(data, userPtr++)) + return -EFAULT; + *p++ = data ^ 0x80; + count--; + } + } else { + u_short *p = (u_short *)&frame[*frameUsed]; + count = min_t(unsigned long, userCount, frameLeft)>>1; + used = count*2; + while (count > 0) { + u_short data; + if (get_user(data, (u_short __user *)userPtr)) + return -EFAULT; + userPtr += 2; + *p++ = data ^ 0x8080; + count--; + } + } + *frameUsed += used; + return used; +} + + +static ssize_t ata_ct_s16be(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft) +{ + ssize_t count, used; + + if (!dmasound.soft.stereo) { + u_short *p = (u_short *)&frame[*frameUsed]; + count = min_t(unsigned long, userCount, frameLeft)>>1; + used = count*2; + while (count > 0) { + u_short data; + if (get_user(data, (u_short __user *)userPtr)) + return -EFAULT; + userPtr += 2; + *p++ = data; + *p++ = data; + count--; + } + *frameUsed += used*2; + } else { + void *p = (u_short *)&frame[*frameUsed]; + count = min_t(unsigned long, userCount, frameLeft) & ~3; + used = count; + if (copy_from_user(p, userPtr, count)) + return -EFAULT; + *frameUsed += used; + } + return used; +} + + +static ssize_t ata_ct_u16be(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft) +{ + ssize_t count, used; + + if (!dmasound.soft.stereo) { + u_short *p = (u_short *)&frame[*frameUsed]; + count = min_t(unsigned long, userCount, frameLeft)>>1; + used = count*2; + while (count > 0) { + u_short data; + if (get_user(data, (u_short __user *)userPtr)) + return -EFAULT; + userPtr += 2; + data ^= 0x8000; + *p++ = data; + *p++ = data; + count--; + } + *frameUsed += used*2; + } else { + u_long *p = (u_long *)&frame[*frameUsed]; + count = min_t(unsigned long, userCount, frameLeft)>>2; + used = count*4; + while (count > 0) { + u_int data; + if (get_user(data, (u_int __user *)userPtr)) + return -EFAULT; + userPtr += 4; + *p++ = data ^ 0x80008000; + count--; + } + *frameUsed += used; + } + return used; +} + + +static ssize_t ata_ct_s16le(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft) +{ + ssize_t count, used; + + count = frameLeft; + if (!dmasound.soft.stereo) { + u_short *p = (u_short *)&frame[*frameUsed]; + count = min_t(unsigned long, userCount, frameLeft)>>1; + used = count*2; + while (count > 0) { + u_short data; + if (get_user(data, (u_short __user *)userPtr)) + return -EFAULT; + userPtr += 2; + data = le2be16(data); + *p++ = data; + *p++ = data; + count--; + } + *frameUsed += used*2; + } else { + u_long *p = (u_long *)&frame[*frameUsed]; + count = min_t(unsigned long, userCount, frameLeft)>>2; + used = count*4; + while (count > 0) { + u_long data; + if (get_user(data, (u_int __user *)userPtr)) + return -EFAULT; + userPtr += 4; + data = le2be16dbl(data); + *p++ = data; + count--; + } + *frameUsed += used; + } + return used; +} + + +static ssize_t ata_ct_u16le(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft) +{ + ssize_t count, used; + + count = frameLeft; + if (!dmasound.soft.stereo) { + u_short *p = (u_short *)&frame[*frameUsed]; + count = min_t(unsigned long, userCount, frameLeft)>>1; + used = count*2; + while (count > 0) { + u_short data; + if (get_user(data, (u_short __user *)userPtr)) + return -EFAULT; + userPtr += 2; + data = le2be16(data) ^ 0x8000; + *p++ = data; + *p++ = data; + } + *frameUsed += used*2; + } else { + u_long *p = (u_long *)&frame[*frameUsed]; + count = min_t(unsigned long, userCount, frameLeft)>>2; + used = count; + while (count > 0) { + u_long data; + if (get_user(data, (u_int __user *)userPtr)) + return -EFAULT; + userPtr += 4; + data = le2be16dbl(data) ^ 0x80008000; + *p++ = data; + count--; + } + *frameUsed += used; + } + return used; +} + + +static ssize_t ata_ctx_law(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft) +{ + char *table = dmasound.soft.format == AFMT_MU_LAW ? dmasound_ulaw2dma8 + : dmasound_alaw2dma8; + /* this should help gcc to stuff everything into registers */ + long bal = expand_bal; + long hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed; + ssize_t used, usedf; + + used = userCount; + usedf = frameLeft; + if (!dmasound.soft.stereo) { + u_char *p = &frame[*frameUsed]; + u_char data = expand_data; + while (frameLeft) { + u_char c; + if (bal < 0) { + if (!userCount) + break; + if (get_user(c, userPtr++)) + return -EFAULT; + data = table[c]; + userCount--; + bal += hSpeed; + } + *p++ = data; + frameLeft--; + bal -= sSpeed; + } + expand_data = data; + } else { + u_short *p = (u_short *)&frame[*frameUsed]; + u_short data = expand_data; + while (frameLeft >= 2) { + u_char c; + if (bal < 0) { + if (userCount < 2) + break; + if (get_user(c, userPtr++)) + return -EFAULT; + data = table[c] << 8; + if (get_user(c, userPtr++)) + return -EFAULT; + data |= table[c]; + userCount -= 2; + bal += hSpeed; + } + *p++ = data; + frameLeft -= 2; + bal -= sSpeed; + } + expand_data = data; + } + expand_bal = bal; + used -= userCount; + *frameUsed += usedf-frameLeft; + return used; +} + + +static ssize_t ata_ctx_s8(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft) +{ + /* this should help gcc to stuff everything into registers */ + long bal = expand_bal; + long hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed; + ssize_t used, usedf; + + used = userCount; + usedf = frameLeft; + if (!dmasound.soft.stereo) { + u_char *p = &frame[*frameUsed]; + u_char data = expand_data; + while (frameLeft) { + if (bal < 0) { + if (!userCount) + break; + if (get_user(data, userPtr++)) + return -EFAULT; + userCount--; + bal += hSpeed; + } + *p++ = data; + frameLeft--; + bal -= sSpeed; + } + expand_data = data; + } else { + u_short *p = (u_short *)&frame[*frameUsed]; + u_short data = expand_data; + while (frameLeft >= 2) { + if (bal < 0) { + if (userCount < 2) + break; + if (get_user(data, (u_short __user *)userPtr)) + return -EFAULT; + userPtr += 2; + userCount -= 2; + bal += hSpeed; + } + *p++ = data; + frameLeft -= 2; + bal -= sSpeed; + } + expand_data = data; + } + expand_bal = bal; + used -= userCount; + *frameUsed += usedf-frameLeft; + return used; +} + + +static ssize_t ata_ctx_u8(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft) +{ + /* this should help gcc to stuff everything into registers */ + long bal = expand_bal; + long hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed; + ssize_t used, usedf; + + used = userCount; + usedf = frameLeft; + if (!dmasound.soft.stereo) { + u_char *p = &frame[*frameUsed]; + u_char data = expand_data; + while (frameLeft) { + if (bal < 0) { + if (!userCount) + break; + if (get_user(data, userPtr++)) + return -EFAULT; + data ^= 0x80; + userCount--; + bal += hSpeed; + } + *p++ = data; + frameLeft--; + bal -= sSpeed; + } + expand_data = data; + } else { + u_short *p = (u_short *)&frame[*frameUsed]; + u_short data = expand_data; + while (frameLeft >= 2) { + if (bal < 0) { + if (userCount < 2) + break; + if (get_user(data, (u_short __user *)userPtr)) + return -EFAULT; + userPtr += 2; + data ^= 0x8080; + userCount -= 2; + bal += hSpeed; + } + *p++ = data; + frameLeft -= 2; + bal -= sSpeed; + } + expand_data = data; + } + expand_bal = bal; + used -= userCount; + *frameUsed += usedf-frameLeft; + return used; +} + + +static ssize_t ata_ctx_s16be(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft) +{ + /* this should help gcc to stuff everything into registers */ + long bal = expand_bal; + long hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed; + ssize_t used, usedf; + + used = userCount; + usedf = frameLeft; + if (!dmasound.soft.stereo) { + u_short *p = (u_short *)&frame[*frameUsed]; + u_short data = expand_data; + while (frameLeft >= 4) { + if (bal < 0) { + if (userCount < 2) + break; + if (get_user(data, (u_short __user *)userPtr)) + return -EFAULT; + userPtr += 2; + userCount -= 2; + bal += hSpeed; + } + *p++ = data; + *p++ = data; + frameLeft -= 4; + bal -= sSpeed; + } + expand_data = data; + } else { + u_long *p = (u_long *)&frame[*frameUsed]; + u_long data = expand_data; + while (frameLeft >= 4) { + if (bal < 0) { + if (userCount < 4) + break; + if (get_user(data, (u_int __user *)userPtr)) + return -EFAULT; + userPtr += 4; + userCount -= 4; + bal += hSpeed; + } + *p++ = data; + frameLeft -= 4; + bal -= sSpeed; + } + expand_data = data; + } + expand_bal = bal; + used -= userCount; + *frameUsed += usedf-frameLeft; + return used; +} + + +static ssize_t ata_ctx_u16be(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft) +{ + /* this should help gcc to stuff everything into registers */ + long bal = expand_bal; + long hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed; + ssize_t used, usedf; + + used = userCount; + usedf = frameLeft; + if (!dmasound.soft.stereo) { + u_short *p = (u_short *)&frame[*frameUsed]; + u_short data = expand_data; + while (frameLeft >= 4) { + if (bal < 0) { + if (userCount < 2) + break; + if (get_user(data, (u_short __user *)userPtr)) + return -EFAULT; + userPtr += 2; + data ^= 0x8000; + userCount -= 2; + bal += hSpeed; + } + *p++ = data; + *p++ = data; + frameLeft -= 4; + bal -= sSpeed; + } + expand_data = data; + } else { + u_long *p = (u_long *)&frame[*frameUsed]; + u_long data = expand_data; + while (frameLeft >= 4) { + if (bal < 0) { + if (userCount < 4) + break; + if (get_user(data, (u_int __user *)userPtr)) + return -EFAULT; + userPtr += 4; + data ^= 0x80008000; + userCount -= 4; + bal += hSpeed; + } + *p++ = data; + frameLeft -= 4; + bal -= sSpeed; + } + expand_data = data; + } + expand_bal = bal; + used -= userCount; + *frameUsed += usedf-frameLeft; + return used; +} + + +static ssize_t ata_ctx_s16le(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft) +{ + /* this should help gcc to stuff everything into registers */ + long bal = expand_bal; + long hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed; + ssize_t used, usedf; + + used = userCount; + usedf = frameLeft; + if (!dmasound.soft.stereo) { + u_short *p = (u_short *)&frame[*frameUsed]; + u_short data = expand_data; + while (frameLeft >= 4) { + if (bal < 0) { + if (userCount < 2) + break; + if (get_user(data, (u_short __user *)userPtr)) + return -EFAULT; + userPtr += 2; + data = le2be16(data); + userCount -= 2; + bal += hSpeed; + } + *p++ = data; + *p++ = data; + frameLeft -= 4; + bal -= sSpeed; + } + expand_data = data; + } else { + u_long *p = (u_long *)&frame[*frameUsed]; + u_long data = expand_data; + while (frameLeft >= 4) { + if (bal < 0) { + if (userCount < 4) + break; + if (get_user(data, (u_int __user *)userPtr)) + return -EFAULT; + userPtr += 4; + data = le2be16dbl(data); + userCount -= 4; + bal += hSpeed; + } + *p++ = data; + frameLeft -= 4; + bal -= sSpeed; + } + expand_data = data; + } + expand_bal = bal; + used -= userCount; + *frameUsed += usedf-frameLeft; + return used; +} + + +static ssize_t ata_ctx_u16le(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft) +{ + /* this should help gcc to stuff everything into registers */ + long bal = expand_bal; + long hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed; + ssize_t used, usedf; + + used = userCount; + usedf = frameLeft; + if (!dmasound.soft.stereo) { + u_short *p = (u_short *)&frame[*frameUsed]; + u_short data = expand_data; + while (frameLeft >= 4) { + if (bal < 0) { + if (userCount < 2) + break; + if (get_user(data, (u_short __user *)userPtr)) + return -EFAULT; + userPtr += 2; + data = le2be16(data) ^ 0x8000; + userCount -= 2; + bal += hSpeed; + } + *p++ = data; + *p++ = data; + frameLeft -= 4; + bal -= sSpeed; + } + expand_data = data; + } else { + u_long *p = (u_long *)&frame[*frameUsed]; + u_long data = expand_data; + while (frameLeft >= 4) { + if (bal < 0) { + if (userCount < 4) + break; + if (get_user(data, (u_int __user *)userPtr)) + return -EFAULT; + userPtr += 4; + data = le2be16dbl(data) ^ 0x80008000; + userCount -= 4; + bal += hSpeed; + } + *p++ = data; + frameLeft -= 4; + bal -= sSpeed; + } + expand_data = data; + } + expand_bal = bal; + used -= userCount; + *frameUsed += usedf-frameLeft; + return used; +} + + +static TRANS transTTNormal = { + .ct_ulaw = ata_ct_law, + .ct_alaw = ata_ct_law, + .ct_s8 = ata_ct_s8, + .ct_u8 = ata_ct_u8, +}; + +static TRANS transTTExpanding = { + .ct_ulaw = ata_ctx_law, + .ct_alaw = ata_ctx_law, + .ct_s8 = ata_ctx_s8, + .ct_u8 = ata_ctx_u8, +}; + +static TRANS transFalconNormal = { + .ct_ulaw = ata_ct_law, + .ct_alaw = ata_ct_law, + .ct_s8 = ata_ct_s8, + .ct_u8 = ata_ct_u8, + .ct_s16be = ata_ct_s16be, + .ct_u16be = ata_ct_u16be, + .ct_s16le = ata_ct_s16le, + .ct_u16le = ata_ct_u16le +}; + +static TRANS transFalconExpanding = { + .ct_ulaw = ata_ctx_law, + .ct_alaw = ata_ctx_law, + .ct_s8 = ata_ctx_s8, + .ct_u8 = ata_ctx_u8, + .ct_s16be = ata_ctx_s16be, + .ct_u16be = ata_ctx_u16be, + .ct_s16le = ata_ctx_s16le, + .ct_u16le = ata_ctx_u16le, +}; + + +/*** Low level stuff *********************************************************/ + + + +/* + * Atari (TT/Falcon) + */ + +static void *AtaAlloc(unsigned int size, gfp_t flags) +{ + return atari_stram_alloc(size, "dmasound"); +} + +static void AtaFree(void *obj, unsigned int size) +{ + atari_stram_free( obj ); +} + +static int __init AtaIrqInit(void) +{ + /* Set up timer A. Timer A + will receive a signal upon end of playing from the sound + hardware. Furthermore Timer A is able to count events + and will cause an interrupt after a programmed number + of events. So all we need to keep the music playing is + to provide the sound hardware with new data upon + an interrupt from timer A. */ + st_mfp.tim_ct_a = 0; /* ++roman: Stop timer before programming! */ + st_mfp.tim_dt_a = 1; /* Cause interrupt after first event. */ + st_mfp.tim_ct_a = 8; /* Turn on event counting. */ + /* Register interrupt handler. */ + if (request_irq(IRQ_MFP_TIMA, AtaInterrupt, 0, "DMA sound", + AtaInterrupt)) + return 0; + st_mfp.int_en_a |= 0x20; /* Turn interrupt on. */ + st_mfp.int_mk_a |= 0x20; + return 1; +} + +#ifdef MODULE +static void AtaIrqCleanUp(void) +{ + st_mfp.tim_ct_a = 0; /* stop timer */ + st_mfp.int_en_a &= ~0x20; /* turn interrupt off */ + free_irq(IRQ_MFP_TIMA, AtaInterrupt); +} +#endif /* MODULE */ + + +#define TONE_VOXWARE_TO_DB(v) \ + (((v) < 0) ? -12 : ((v) > 100) ? 12 : ((v) - 50) * 6 / 25) +#define TONE_DB_TO_VOXWARE(v) (((v) * 25 + ((v) > 0 ? 5 : -5)) / 6 + 50) + + +static int AtaSetBass(int bass) +{ + dmasound.bass = TONE_VOXWARE_TO_DB(bass); + atari_microwire_cmd(MW_LM1992_BASS(dmasound.bass)); + return TONE_DB_TO_VOXWARE(dmasound.bass); +} + + +static int AtaSetTreble(int treble) +{ + dmasound.treble = TONE_VOXWARE_TO_DB(treble); + atari_microwire_cmd(MW_LM1992_TREBLE(dmasound.treble)); + return TONE_DB_TO_VOXWARE(dmasound.treble); +} + + + +/* + * TT + */ + + +static void TTSilence(void) +{ + tt_dmasnd.ctrl = DMASND_CTRL_OFF; + atari_microwire_cmd(MW_LM1992_PSG_HIGH); /* mix in PSG signal 1:1 */ +} + + +static void TTInit(void) +{ + int mode, i, idx; + const int freq[4] = {50066, 25033, 12517, 6258}; + + /* search a frequency that fits into the allowed error range */ + + idx = -1; + for (i = 0; i < ARRAY_SIZE(freq); i++) + /* this isn't as much useful for a TT than for a Falcon, but + * then it doesn't hurt very much to implement it for a TT too. + */ + if ((100 * abs(dmasound.soft.speed - freq[i]) / freq[i]) < catchRadius) + idx = i; + if (idx > -1) { + dmasound.soft.speed = freq[idx]; + dmasound.trans_write = &transTTNormal; + } else + dmasound.trans_write = &transTTExpanding; + + TTSilence(); + dmasound.hard = dmasound.soft; + + if (dmasound.hard.speed > 50066) { + /* we would need to squeeze the sound, but we won't do that */ + dmasound.hard.speed = 50066; + mode = DMASND_MODE_50KHZ; + dmasound.trans_write = &transTTNormal; + } else if (dmasound.hard.speed > 25033) { + dmasound.hard.speed = 50066; + mode = DMASND_MODE_50KHZ; + } else if (dmasound.hard.speed > 12517) { + dmasound.hard.speed = 25033; + mode = DMASND_MODE_25KHZ; + } else if (dmasound.hard.speed > 6258) { + dmasound.hard.speed = 12517; + mode = DMASND_MODE_12KHZ; + } else { + dmasound.hard.speed = 6258; + mode = DMASND_MODE_6KHZ; + } + + tt_dmasnd.mode = (dmasound.hard.stereo ? + DMASND_MODE_STEREO : DMASND_MODE_MONO) | + DMASND_MODE_8BIT | mode; + + expand_bal = -dmasound.soft.speed; +} + + +static int TTSetFormat(int format) +{ + /* TT sound DMA supports only 8bit modes */ + + switch (format) { + case AFMT_QUERY: + return dmasound.soft.format; + case AFMT_MU_LAW: + case AFMT_A_LAW: + case AFMT_S8: + case AFMT_U8: + break; + default: + format = AFMT_S8; + } + + dmasound.soft.format = format; + dmasound.soft.size = 8; + if (dmasound.minDev == SND_DEV_DSP) { + dmasound.dsp.format = format; + dmasound.dsp.size = 8; + } + TTInit(); + + return format; +} + + +#define VOLUME_VOXWARE_TO_DB(v) \ + (((v) < 0) ? -40 : ((v) > 100) ? 0 : ((v) * 2) / 5 - 40) +#define VOLUME_DB_TO_VOXWARE(v) ((((v) + 40) * 5 + 1) / 2) + + +static int TTSetVolume(int volume) +{ + dmasound.volume_left = VOLUME_VOXWARE_TO_DB(volume & 0xff); + atari_microwire_cmd(MW_LM1992_BALLEFT(dmasound.volume_left)); + dmasound.volume_right = VOLUME_VOXWARE_TO_DB((volume & 0xff00) >> 8); + atari_microwire_cmd(MW_LM1992_BALRIGHT(dmasound.volume_right)); + return VOLUME_DB_TO_VOXWARE(dmasound.volume_left) | + (VOLUME_DB_TO_VOXWARE(dmasound.volume_right) << 8); +} + + +#define GAIN_VOXWARE_TO_DB(v) \ + (((v) < 0) ? -80 : ((v) > 100) ? 0 : ((v) * 4) / 5 - 80) +#define GAIN_DB_TO_VOXWARE(v) ((((v) + 80) * 5 + 1) / 4) + +static int TTSetGain(int gain) +{ + dmasound.gain = GAIN_VOXWARE_TO_DB(gain); + atari_microwire_cmd(MW_LM1992_VOLUME(dmasound.gain)); + return GAIN_DB_TO_VOXWARE(dmasound.gain); +} + + + +/* + * Falcon + */ + + +static void FalconSilence(void) +{ + /* stop playback, set sample rate 50kHz for PSG sound */ + tt_dmasnd.ctrl = DMASND_CTRL_OFF; + tt_dmasnd.mode = DMASND_MODE_50KHZ | DMASND_MODE_STEREO | DMASND_MODE_8BIT; + tt_dmasnd.int_div = 0; /* STE compatible divider */ + tt_dmasnd.int_ctrl = 0x0; + tt_dmasnd.cbar_src = 0x0000; /* no matrix inputs */ + tt_dmasnd.cbar_dst = 0x0000; /* no matrix outputs */ + tt_dmasnd.dac_src = 1; /* connect ADC to DAC, disconnect matrix */ + tt_dmasnd.adc_src = 3; /* ADC Input = PSG */ +} + + +static void FalconInit(void) +{ + int divider, i, idx; + const int freq[8] = {49170, 32780, 24585, 19668, 16390, 12292, 9834, 8195}; + + /* search a frequency that fits into the allowed error range */ + + idx = -1; + for (i = 0; i < ARRAY_SIZE(freq); i++) + /* if we will tolerate 3% error 8000Hz->8195Hz (2.38%) would + * be playable without expanding, but that now a kernel runtime + * option + */ + if ((100 * abs(dmasound.soft.speed - freq[i]) / freq[i]) < catchRadius) + idx = i; + if (idx > -1) { + dmasound.soft.speed = freq[idx]; + dmasound.trans_write = &transFalconNormal; + } else + dmasound.trans_write = &transFalconExpanding; + + FalconSilence(); + dmasound.hard = dmasound.soft; + + if (dmasound.hard.size == 16) { + /* the Falcon can play 16bit samples only in stereo */ + dmasound.hard.stereo = 1; + } + + if (dmasound.hard.speed > 49170) { + /* we would need to squeeze the sound, but we won't do that */ + dmasound.hard.speed = 49170; + divider = 1; + dmasound.trans_write = &transFalconNormal; + } else if (dmasound.hard.speed > 32780) { + dmasound.hard.speed = 49170; + divider = 1; + } else if (dmasound.hard.speed > 24585) { + dmasound.hard.speed = 32780; + divider = 2; + } else if (dmasound.hard.speed > 19668) { + dmasound.hard.speed = 24585; + divider = 3; + } else if (dmasound.hard.speed > 16390) { + dmasound.hard.speed = 19668; + divider = 4; + } else if (dmasound.hard.speed > 12292) { + dmasound.hard.speed = 16390; + divider = 5; + } else if (dmasound.hard.speed > 9834) { + dmasound.hard.speed = 12292; + divider = 7; + } else if (dmasound.hard.speed > 8195) { + dmasound.hard.speed = 9834; + divider = 9; + } else { + dmasound.hard.speed = 8195; + divider = 11; + } + tt_dmasnd.int_div = divider; + + /* Setup Falcon sound DMA for playback */ + tt_dmasnd.int_ctrl = 0x4; /* Timer A int at play end */ + tt_dmasnd.track_select = 0x0; /* play 1 track, track 1 */ + tt_dmasnd.cbar_src = 0x0001; /* DMA(25MHz) --> DAC */ + tt_dmasnd.cbar_dst = 0x0000; + tt_dmasnd.rec_track_select = 0; + tt_dmasnd.dac_src = 2; /* connect matrix to DAC */ + tt_dmasnd.adc_src = 0; /* ADC Input = Mic */ + + tt_dmasnd.mode = (dmasound.hard.stereo ? + DMASND_MODE_STEREO : DMASND_MODE_MONO) | + ((dmasound.hard.size == 8) ? + DMASND_MODE_8BIT : DMASND_MODE_16BIT) | + DMASND_MODE_6KHZ; + + expand_bal = -dmasound.soft.speed; +} + + +static int FalconSetFormat(int format) +{ + int size; + /* Falcon sound DMA supports 8bit and 16bit modes */ + + 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_BE: + case AFMT_U16_BE: + case AFMT_S16_LE: + case AFMT_U16_LE: + size = 16; + break; + default: /* :-) */ + size = 8; + format = AFMT_S8; + } + + dmasound.soft.format = format; + dmasound.soft.size = size; + if (dmasound.minDev == SND_DEV_DSP) { + dmasound.dsp.format = format; + dmasound.dsp.size = dmasound.soft.size; + } + + FalconInit(); + + return format; +} + + +/* This is for the Falcon output *attenuation* in 1.5dB steps, + * i.e. output level from 0 to -22.5dB in -1.5dB steps. + */ +#define VOLUME_VOXWARE_TO_ATT(v) \ + ((v) < 0 ? 15 : (v) > 100 ? 0 : 15 - (v) * 3 / 20) +#define VOLUME_ATT_TO_VOXWARE(v) (100 - (v) * 20 / 3) + + +static int FalconSetVolume(int volume) +{ + dmasound.volume_left = VOLUME_VOXWARE_TO_ATT(volume & 0xff); + dmasound.volume_right = VOLUME_VOXWARE_TO_ATT((volume & 0xff00) >> 8); + tt_dmasnd.output_atten = dmasound.volume_left << 8 | dmasound.volume_right << 4; + return VOLUME_ATT_TO_VOXWARE(dmasound.volume_left) | + VOLUME_ATT_TO_VOXWARE(dmasound.volume_right) << 8; +} + + +static void AtaPlayNextFrame(int index) +{ + char *start, *end; + + /* used by AtaPlay() if all doubts whether there really is something + * to be played are already wiped out. + */ + start = write_sq.buffers[write_sq.front]; + end = start+((write_sq.count == index) ? write_sq.rear_size + : write_sq.block_size); + /* end might not be a legal virtual address. */ + DMASNDSetEnd(virt_to_phys(end - 1) + 1); + DMASNDSetBase(virt_to_phys(start)); + /* Since only an even number of samples per frame can + be played, we might lose one byte here. (TO DO) */ + write_sq.front = (write_sq.front+1) % write_sq.max_count; + write_sq.active++; + tt_dmasnd.ctrl = DMASND_CTRL_ON | DMASND_CTRL_REPEAT; +} + + +static void AtaPlay(void) +{ + /* ++TeSche: Note that write_sq.active is no longer just a flag but + * holds the number of frames the DMA is currently programmed for + * instead, may be 0, 1 (currently being played) or 2 (pre-programmed). + * + * Changes done to write_sq.count and write_sq.active are a bit more + * subtle again so now I must admit I also prefer disabling the irq + * here rather than considering all possible situations. But the point + * is that disabling the irq doesn't have any bad influence on this + * version of the driver as we benefit from having pre-programmed the + * DMA wherever possible: There's no need to reload the DMA at the + * exact time of an interrupt but only at some time while the + * pre-programmed frame is playing! + */ + atari_disable_irq(IRQ_MFP_TIMA); + + if (write_sq.active == 2 || /* DMA is 'full' */ + write_sq.count <= 0) { /* nothing to do */ + atari_enable_irq(IRQ_MFP_TIMA); + return; + } + + if (write_sq.active == 0) { + /* looks like there's nothing 'in' the DMA yet, so try + * to put two frames into it (at least one is available). + */ + if (write_sq.count == 1 && + write_sq.rear_size < write_sq.block_size && + !write_sq.syncing) { + /* hmmm, the only existing frame is not + * yet filled and we're not syncing? + */ + atari_enable_irq(IRQ_MFP_TIMA); + return; + } + AtaPlayNextFrame(1); + if (write_sq.count == 1) { + /* no more frames */ + atari_enable_irq(IRQ_MFP_TIMA); + return; + } + if (write_sq.count == 2 && + write_sq.rear_size < write_sq.block_size && + !write_sq.syncing) { + /* hmmm, there were two frames, but the second + * one is not yet filled and we're not syncing? + */ + atari_enable_irq(IRQ_MFP_TIMA); + return; + } + AtaPlayNextFrame(2); + } else { + /* there's already a frame being played so we may only stuff + * one new into the DMA, but even if this may be the last + * frame existing the previous one is still on write_sq.count. + */ + if (write_sq.count == 2 && + write_sq.rear_size < write_sq.block_size && + !write_sq.syncing) { + /* hmmm, the only existing frame is not + * yet filled and we're not syncing? + */ + atari_enable_irq(IRQ_MFP_TIMA); + return; + } + AtaPlayNextFrame(2); + } + atari_enable_irq(IRQ_MFP_TIMA); +} + + +static irqreturn_t AtaInterrupt(int irq, void *dummy) +{ +#if 0 + /* ++TeSche: if you should want to test this... */ + static int cnt; + if (write_sq.active == 2) + if (++cnt == 10) { + /* simulate losing an interrupt */ + cnt = 0; + return IRQ_HANDLED; + } +#endif + spin_lock(&dmasound.lock); + if (write_sq_ignore_int && is_falcon) { + /* ++TeSche: Falcon only: ignore first irq because it comes + * immediately after starting a frame. after that, irqs come + * (almost) like on the TT. + */ + write_sq_ignore_int = 0; + goto out; + } + + if (!write_sq.active) { + /* playing was interrupted and sq_reset() has already cleared + * the sq variables, so better don't do anything here. + */ + WAKE_UP(write_sq.sync_queue); + goto out; + } + + /* Probably ;) one frame is finished. Well, in fact it may be that a + * pre-programmed one is also finished because there has been a long + * delay in interrupt delivery and we've completely lost one, but + * there's no way to detect such a situation. In such a case the last + * frame will be played more than once and the situation will recover + * as soon as the irq gets through. + */ + write_sq.count--; + write_sq.active--; + + if (!write_sq.active) { + tt_dmasnd.ctrl = DMASND_CTRL_OFF; + write_sq_ignore_int = 1; + } + + WAKE_UP(write_sq.action_queue); + /* At least one block of the queue is free now + so wake up a writing process blocked because + of a full queue. */ + + if ((write_sq.active != 1) || (write_sq.count != 1)) + /* We must be a bit carefully here: write_sq.count indicates the + * number of buffers used and not the number of frames to be + * played. If write_sq.count==1 and write_sq.active==1 that + * means the only remaining frame was already programmed + * earlier (and is currently running) so we mustn't call + * AtaPlay() here, otherwise we'll play one frame too much. + */ + AtaPlay(); + + if (!write_sq.active) WAKE_UP(write_sq.sync_queue); + /* We are not playing after AtaPlay(), so there + is nothing to play any more. Wake up a process + waiting for audio output to drain. */ +out: + spin_unlock(&dmasound.lock); + return IRQ_HANDLED; +} + + +/*** Mid level stuff *********************************************************/ + + +/* + * /dev/mixer abstraction + */ + +#define RECLEVEL_VOXWARE_TO_GAIN(v) \ + ((v) < 0 ? 0 : (v) > 100 ? 15 : (v) * 3 / 20) +#define RECLEVEL_GAIN_TO_VOXWARE(v) (((v) * 20 + 2) / 3) + + +static void __init TTMixerInit(void) +{ + atari_microwire_cmd(MW_LM1992_VOLUME(0)); + dmasound.volume_left = 0; + atari_microwire_cmd(MW_LM1992_BALLEFT(0)); + dmasound.volume_right = 0; + atari_microwire_cmd(MW_LM1992_BALRIGHT(0)); + atari_microwire_cmd(MW_LM1992_TREBLE(0)); + atari_microwire_cmd(MW_LM1992_BASS(0)); +} + +static void __init FalconMixerInit(void) +{ + dmasound.volume_left = (tt_dmasnd.output_atten & 0xf00) >> 8; + dmasound.volume_right = (tt_dmasnd.output_atten & 0xf0) >> 4; +} + +static int AtaMixerIoctl(u_int cmd, u_long arg) +{ + int data; + unsigned long flags; + switch (cmd) { + case SOUND_MIXER_READ_SPEAKER: + if (is_falcon || MACH_IS_TT) { + int porta; + spin_lock_irqsave(&dmasound.lock, flags); + sound_ym.rd_data_reg_sel = 14; + porta = sound_ym.rd_data_reg_sel; + spin_unlock_irqrestore(&dmasound.lock, flags); + return IOCTL_OUT(arg, porta & 0x40 ? 0 : 100); + } + break; + case SOUND_MIXER_WRITE_VOLUME: + IOCTL_IN(arg, data); + return IOCTL_OUT(arg, dmasound_set_volume(data)); + case SOUND_MIXER_WRITE_SPEAKER: + if (is_falcon || MACH_IS_TT) { + int porta; + IOCTL_IN(arg, data); + spin_lock_irqsave(&dmasound.lock, flags); + sound_ym.rd_data_reg_sel = 14; + porta = (sound_ym.rd_data_reg_sel & ~0x40) | + (data < 50 ? 0x40 : 0); + sound_ym.wd_data = porta; + spin_unlock_irqrestore(&dmasound.lock, flags); + return IOCTL_OUT(arg, porta & 0x40 ? 0 : 100); + } + } + return -EINVAL; +} + + +static int TTMixerIoctl(u_int cmd, u_long arg) +{ + int data; + switch (cmd) { + case SOUND_MIXER_READ_RECMASK: + return IOCTL_OUT(arg, 0); + case SOUND_MIXER_READ_DEVMASK: + return IOCTL_OUT(arg, + SOUND_MASK_VOLUME | SOUND_MASK_TREBLE | SOUND_MASK_BASS | + (MACH_IS_TT ? SOUND_MASK_SPEAKER : 0)); + case SOUND_MIXER_READ_STEREODEVS: + return IOCTL_OUT(arg, SOUND_MASK_VOLUME); + case SOUND_MIXER_READ_VOLUME: + return IOCTL_OUT(arg, + VOLUME_DB_TO_VOXWARE(dmasound.volume_left) | + (VOLUME_DB_TO_VOXWARE(dmasound.volume_right) << 8)); + case SOUND_MIXER_READ_BASS: + return IOCTL_OUT(arg, TONE_DB_TO_VOXWARE(dmasound.bass)); + case SOUND_MIXER_READ_TREBLE: + return IOCTL_OUT(arg, TONE_DB_TO_VOXWARE(dmasound.treble)); + case SOUND_MIXER_READ_OGAIN: + return IOCTL_OUT(arg, GAIN_DB_TO_VOXWARE(dmasound.gain)); + case SOUND_MIXER_WRITE_BASS: + IOCTL_IN(arg, data); + return IOCTL_OUT(arg, dmasound_set_bass(data)); + case SOUND_MIXER_WRITE_TREBLE: + IOCTL_IN(arg, data); + return IOCTL_OUT(arg, dmasound_set_treble(data)); + case SOUND_MIXER_WRITE_OGAIN: + IOCTL_IN(arg, data); + return IOCTL_OUT(arg, dmasound_set_gain(data)); + } + return AtaMixerIoctl(cmd, arg); +} + +static int FalconMixerIoctl(u_int cmd, u_long arg) +{ + int data; + switch (cmd) { + case SOUND_MIXER_READ_RECMASK: + return IOCTL_OUT(arg, SOUND_MASK_MIC); + case SOUND_MIXER_READ_DEVMASK: + return IOCTL_OUT(arg, SOUND_MASK_VOLUME | SOUND_MASK_MIC | SOUND_MASK_SPEAKER); + case SOUND_MIXER_READ_STEREODEVS: + return IOCTL_OUT(arg, SOUND_MASK_VOLUME | SOUND_MASK_MIC); + case SOUND_MIXER_READ_VOLUME: + return IOCTL_OUT(arg, + VOLUME_ATT_TO_VOXWARE(dmasound.volume_left) | + VOLUME_ATT_TO_VOXWARE(dmasound.volume_right) << 8); + case SOUND_MIXER_READ_CAPS: + return IOCTL_OUT(arg, SOUND_CAP_EXCL_INPUT); + case SOUND_MIXER_WRITE_MIC: + IOCTL_IN(arg, data); + tt_dmasnd.input_gain = + RECLEVEL_VOXWARE_TO_GAIN(data & 0xff) << 4 | + RECLEVEL_VOXWARE_TO_GAIN(data >> 8 & 0xff); + fallthrough; /* return set value */ + case SOUND_MIXER_READ_MIC: + return IOCTL_OUT(arg, + RECLEVEL_GAIN_TO_VOXWARE(tt_dmasnd.input_gain >> 4 & 0xf) | + RECLEVEL_GAIN_TO_VOXWARE(tt_dmasnd.input_gain & 0xf) << 8); + } + return AtaMixerIoctl(cmd, arg); +} + +static int AtaWriteSqSetup(void) +{ + write_sq_ignore_int = 0; + return 0 ; +} + +static int AtaSqOpen(fmode_t mode) +{ + write_sq_ignore_int = 1; + return 0 ; +} + +static int TTStateInfo(char *buffer, size_t space) +{ + int len = 0; + len += sprintf(buffer+len, "\tvol left %ddB [-40... 0]\n", + dmasound.volume_left); + len += sprintf(buffer+len, "\tvol right %ddB [-40... 0]\n", + dmasound.volume_right); + len += sprintf(buffer+len, "\tbass %ddB [-12...+12]\n", + dmasound.bass); + len += sprintf(buffer+len, "\ttreble %ddB [-12...+12]\n", + dmasound.treble); + if (len >= space) { + printk(KERN_ERR "dmasound_atari: overflowed state buffer alloc.\n") ; + len = space ; + } + return len; +} + +static int FalconStateInfo(char *buffer, size_t space) +{ + int len = 0; + len += sprintf(buffer+len, "\tvol left %ddB [-22.5 ... 0]\n", + dmasound.volume_left); + len += sprintf(buffer+len, "\tvol right %ddB [-22.5 ... 0]\n", + dmasound.volume_right); + if (len >= space) { + printk(KERN_ERR "dmasound_atari: overflowed state buffer alloc.\n") ; + len = space ; + } + return len; +} + + +/*** Machine definitions *****************************************************/ + +static SETTINGS def_hard_falcon = { + .format = AFMT_S8, + .stereo = 0, + .size = 8, + .speed = 8195 +} ; + +static SETTINGS def_hard_tt = { + .format = AFMT_S8, + .stereo = 0, + .size = 8, + .speed = 12517 +} ; + +static SETTINGS def_soft = { + .format = AFMT_U8, + .stereo = 0, + .size = 8, + .speed = 8000 +} ; + +static __initdata MACHINE machTT = { + .name = "Atari", + .name2 = "TT", + .owner = THIS_MODULE, + .dma_alloc = AtaAlloc, + .dma_free = AtaFree, + .irqinit = AtaIrqInit, +#ifdef MODULE + .irqcleanup = AtaIrqCleanUp, +#endif /* MODULE */ + .init = TTInit, + .silence = TTSilence, + .setFormat = TTSetFormat, + .setVolume = TTSetVolume, + .setBass = AtaSetBass, + .setTreble = AtaSetTreble, + .setGain = TTSetGain, + .play = AtaPlay, + .mixer_init = TTMixerInit, + .mixer_ioctl = TTMixerIoctl, + .write_sq_setup = AtaWriteSqSetup, + .sq_open = AtaSqOpen, + .state_info = TTStateInfo, + .min_dsp_speed = 6258, + .version = ((DMASOUND_ATARI_REVISION<<8) | DMASOUND_ATARI_EDITION), + .hardware_afmts = AFMT_S8, /* h'ware-supported formats *only* here */ + .capabilities = DSP_CAP_BATCH /* As per SNDCTL_DSP_GETCAPS */ +}; + +static __initdata MACHINE machFalcon = { + .name = "Atari", + .name2 = "FALCON", + .dma_alloc = AtaAlloc, + .dma_free = AtaFree, + .irqinit = AtaIrqInit, +#ifdef MODULE + .irqcleanup = AtaIrqCleanUp, +#endif /* MODULE */ + .init = FalconInit, + .silence = FalconSilence, + .setFormat = FalconSetFormat, + .setVolume = FalconSetVolume, + .setBass = AtaSetBass, + .setTreble = AtaSetTreble, + .play = AtaPlay, + .mixer_init = FalconMixerInit, + .mixer_ioctl = FalconMixerIoctl, + .write_sq_setup = AtaWriteSqSetup, + .sq_open = AtaSqOpen, + .state_info = FalconStateInfo, + .min_dsp_speed = 8195, + .version = ((DMASOUND_ATARI_REVISION<<8) | DMASOUND_ATARI_EDITION), + .hardware_afmts = (AFMT_S8 | AFMT_S16_BE), /* h'ware-supported formats *only* here */ + .capabilities = DSP_CAP_BATCH /* As per SNDCTL_DSP_GETCAPS */ +}; + + +/*** Config & Setup **********************************************************/ + + +static int __init dmasound_atari_init(void) +{ + if (MACH_IS_ATARI && ATARIHW_PRESENT(PCM_8BIT)) { + if (ATARIHW_PRESENT(CODEC)) { + dmasound.mach = machFalcon; + dmasound.mach.default_soft = def_soft ; + dmasound.mach.default_hard = def_hard_falcon ; + is_falcon = 1; + } else if (ATARIHW_PRESENT(MICROWIRE)) { + dmasound.mach = machTT; + dmasound.mach.default_soft = def_soft ; + dmasound.mach.default_hard = def_hard_tt ; + is_falcon = 0; + } else + return -ENODEV; + if ((st_mfp.int_en_a & st_mfp.int_mk_a & 0x20) == 0) + return dmasound_init(); + else { + printk("DMA sound driver: Timer A interrupt already in use\n"); + return -EBUSY; + } + } + return -ENODEV; +} + +static void __exit dmasound_atari_cleanup(void) +{ + dmasound_deinit(); +} + +module_init(dmasound_atari_init); +module_exit(dmasound_atari_cleanup); +MODULE_LICENSE("GPL"); diff --git a/sound/oss/dmasound/dmasound_core.c b/sound/oss/dmasound/dmasound_core.c new file mode 100644 index 000000000..164335d3c --- /dev/null +++ b/sound/oss/dmasound/dmasound_core.c @@ -0,0 +1,1566 @@ +/* + * linux/sound/oss/dmasound/dmasound_core.c + * + * + * OSS/Free compatible Atari TT/Falcon and Amiga DMA sound driver for + * Linux/m68k + * Extended to support Power Macintosh for Linux/ppc by Paul Mackerras + * + * (c) 1995 by Michael Schlueter & Michael Marte + * + * Michael Schlueter (michael@duck.syd.de) did the basic structure of the VFS + * interface and the u-law to signed byte conversion. + * + * Michael Marte (marte@informatik.uni-muenchen.de) did the sound queue, + * /dev/mixer, /dev/sndstat and complemented the VFS interface. He would like + * to thank: + * - Michael Schlueter for initial ideas and documentation on the MFP and + * the DMA sound hardware. + * - Therapy? for their CD 'Troublegum' which really made me rock. + * + * /dev/sndstat is based on code by Hannu Savolainen, the author of the + * VoxWare family of drivers. + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive + * for more details. + * + * History: + * + * 1995/8/25 First release + * + * 1995/9/02 Roman Hodek: + * - Fixed atari_stram_alloc() call, the timer + * programming and several race conditions + * 1995/9/14 Roman Hodek: + * - After some discussion with Michael Schlueter, + * revised the interrupt disabling + * - Slightly speeded up U8->S8 translation by using + * long operations where possible + * - Added 4:3 interpolation for /dev/audio + * + * 1995/9/20 Torsten Scherer: + * - Fixed a bug in sq_write and changed /dev/audio + * converting to play at 12517Hz instead of 6258Hz. + * + * 1995/9/23 Torsten Scherer: + * - Changed sq_interrupt() and sq_play() to pre-program + * the DMA for another frame while there's still one + * running. This allows the IRQ response to be + * arbitrarily delayed and playing will still continue. + * + * 1995/10/14 Guenther Kelleter, Torsten Scherer: + * - Better support for Falcon audio (the Falcon doesn't + * raise an IRQ at the end of a frame, but at the + * beginning instead!). uses 'if (codec_dma)' in lots + * of places to simply switch between Falcon and TT + * code. + * + * 1995/11/06 Torsten Scherer: + * - Started introducing a hardware abstraction scheme + * (may perhaps also serve for Amigas?) + * - Can now play samples at almost all frequencies by + * means of a more generalized expand routine + * - Takes a good deal of care to cut data only at + * sample sizes + * - Buffer size is now a kernel runtime option + * - Implemented fsync() & several minor improvements + * Guenther Kelleter: + * - Useful hints and bug fixes + * - Cross-checked it for Falcons + * + * 1996/3/9 Geert Uytterhoeven: + * - Support added for Amiga, A-law, 16-bit little + * endian. + * - Unification to drivers/sound/dmasound.c. + * + * 1996/4/6 Martin Mitchell: + * - Updated to 1.3 kernel. + * + * 1996/6/13 Topi Kanerva: + * - Fixed things that were broken (mainly the amiga + * 14-bit routines) + * - /dev/sndstat shows now the real hardware frequency + * - The lowpass filter is disabled by default now + * + * 1996/9/25 Geert Uytterhoeven: + * - Modularization + * + * 1998/6/10 Andreas Schwab: + * - Converted to use sound_core + * + * 1999/12/28 Richard Zidlicky: + * - Added support for Q40 + * + * 2000/2/27 Geert Uytterhoeven: + * - Clean up and split the code into 4 parts: + * o dmasound_core: machine-independent code + * o dmasound_atari: Atari TT and Falcon support + * o dmasound_awacs: Apple PowerMac support + * o dmasound_paula: Amiga support + * + * 2000/3/25 Geert Uytterhoeven: + * - Integration of dmasound_q40 + * - Small clean ups + * + * 2001/01/26 [1.0] Iain Sandoe + * - make /dev/sndstat show revision & edition info. + * - since dmasound.mach.sq_setup() can fail on pmac + * its type has been changed to int and the returns + * are checked. + * [1.1] - stop missing translations from being called. + * 2001/02/08 [1.2] - remove unused translation tables & move machine- + * specific tables to low-level. + * - return correct info. for SNDCTL_DSP_GETFMTS. + * [1.3] - implement SNDCTL_DSP_GETCAPS fully. + * [1.4] - make /dev/sndstat text length usage deterministic. + * - make /dev/sndstat call to low-level + * dmasound.mach.state_info() pass max space to ll driver. + * - tidy startup banners and output info. + * [1.5] - tidy up a little (removed some unused #defines in + * dmasound.h) + * - fix up HAS_RECORD conditionalisation. + * - add record code in places it is missing... + * - change buf-sizes to bytes to allow < 1kb for pmac + * if user param entry is < 256 the value is taken to + * be in kb > 256 is taken to be in bytes. + * - make default buff/frag params conditional on + * machine to allow smaller values for pmac. + * - made the ioctls, read & write comply with the OSS + * rules on setting params. + * - added parsing of _setup() params for record. + * 2001/04/04 [1.6] - fix bug where sample rates higher than maximum were + * being reported as OK. + * - fix open() to return -EBUSY as per OSS doc. when + * audio is in use - this is independent of O_NOBLOCK. + * - fix bug where SNDCTL_DSP_POST was blocking. + */ + + /* Record capability notes 30/01/2001: + * At present these observations apply only to pmac LL driver (the only one + * that can do record, at present). However, if other LL drivers for machines + * with record are added they may apply. + * + * The fragment parameters for the record and play channels are separate. + * However, if the driver is opened O_RDWR there is no way (in the current OSS + * API) to specify their values independently for the record and playback + * channels. Since the only common factor between the input & output is the + * sample rate (on pmac) it should be possible to open /dev/dspX O_WRONLY and + * /dev/dspY O_RDONLY. The input & output channels could then have different + * characteristics (other than the first that sets sample rate claiming the + * right to set it for ever). As it stands, the format, channels, number of + * bits & sample rate are assumed to be common. In the future perhaps these + * should be the responsibility of the LL driver - and then if a card really + * does not share items between record & playback they can be specified + * separately. +*/ + +/* Thread-safeness of shared_resources notes: 31/01/2001 + * If the user opens O_RDWR and then splits record & play between two threads + * both of which inherit the fd - and then starts changing things from both + * - we will have difficulty telling. + * + * It's bad application coding - but ... + * TODO: think about how to sort this out... without bogging everything down in + * semaphores. + * + * Similarly, the OSS spec says "all changes to parameters must be between + * open() and the first read() or write(). - and a bit later on (by + * implication) "between SNDCTL_DSP_RESET and the first read() or write() after + * it". If the app is multi-threaded and this rule is broken between threads + * we will have trouble spotting it - and the fault will be rather obscure :-( + * + * We will try and put out at least a kmsg if we see it happen... but I think + * it will be quite hard to trap it with an -EXXX return... because we can't + * see the fault until after the damage is done. +*/ + +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/sound.h> +#include <linux/init.h> +#include <linux/soundcard.h> +#include <linux/poll.h> +#include <linux/mutex.h> +#include <linux/sched/signal.h> + +#include <linux/uaccess.h> + +#include "dmasound.h" + +#define DMASOUND_CORE_REVISION 1 +#define DMASOUND_CORE_EDITION 6 + + /* + * Declarations + */ + +static DEFINE_MUTEX(dmasound_core_mutex); +int dmasound_catchRadius = 0; +module_param(dmasound_catchRadius, int, 0); + +static unsigned int numWriteBufs = DEFAULT_N_BUFFERS; +module_param(numWriteBufs, int, 0); +static unsigned int writeBufSize = DEFAULT_BUFF_SIZE ; /* in bytes */ +module_param(writeBufSize, int, 0); + +MODULE_LICENSE("GPL"); + +static int sq_unit = -1; +static int mixer_unit = -1; +static int state_unit = -1; +static int irq_installed; + +/* control over who can modify resources shared between play/record */ +static fmode_t shared_resource_owner; +static int shared_resources_initialised; + + /* + * Mid level stuff + */ + +struct sound_settings dmasound = { + .lock = __SPIN_LOCK_UNLOCKED(dmasound.lock) +}; + +static inline void sound_silence(void) +{ + dmasound.mach.silence(); /* _MUST_ stop DMA */ +} + +static inline int sound_set_format(int format) +{ + return dmasound.mach.setFormat(format); +} + + +static int sound_set_speed(int speed) +{ + if (speed < 0) + return dmasound.soft.speed; + + /* trap out-of-range speed settings. + at present we allow (arbitrarily) low rates - using soft + up-conversion - but we can't allow > max because there is + no soft down-conversion. + */ + if (dmasound.mach.max_dsp_speed && + (speed > dmasound.mach.max_dsp_speed)) + speed = dmasound.mach.max_dsp_speed ; + + dmasound.soft.speed = speed; + + if (dmasound.minDev == SND_DEV_DSP) + dmasound.dsp.speed = dmasound.soft.speed; + + return dmasound.soft.speed; +} + +static int sound_set_stereo(int stereo) +{ + if (stereo < 0) + return dmasound.soft.stereo; + + stereo = !!stereo; /* should be 0 or 1 now */ + + dmasound.soft.stereo = stereo; + if (dmasound.minDev == SND_DEV_DSP) + dmasound.dsp.stereo = stereo; + + return stereo; +} + +static ssize_t sound_copy_translate(TRANS *trans, const u_char __user *userPtr, + size_t userCount, u_char frame[], + ssize_t *frameUsed, ssize_t frameLeft) +{ + ssize_t (*ct_func)(const u_char __user *, size_t, u_char *, ssize_t *, ssize_t); + + switch (dmasound.soft.format) { + case AFMT_MU_LAW: + ct_func = trans->ct_ulaw; + break; + case AFMT_A_LAW: + ct_func = trans->ct_alaw; + break; + case AFMT_S8: + ct_func = trans->ct_s8; + break; + case AFMT_U8: + ct_func = trans->ct_u8; + break; + case AFMT_S16_BE: + ct_func = trans->ct_s16be; + break; + case AFMT_U16_BE: + ct_func = trans->ct_u16be; + break; + case AFMT_S16_LE: + ct_func = trans->ct_s16le; + break; + case AFMT_U16_LE: + ct_func = trans->ct_u16le; + break; + default: + return 0; + } + /* if the user has requested a non-existent translation don't try + to call it but just return 0 bytes moved + */ + if (ct_func) + return ct_func(userPtr, userCount, frame, frameUsed, frameLeft); + return 0; +} + + /* + * /dev/mixer abstraction + */ + +static struct { + int busy; + int modify_counter; +} mixer; + +static int mixer_open(struct inode *inode, struct file *file) +{ + mutex_lock(&dmasound_core_mutex); + if (!try_module_get(dmasound.mach.owner)) { + mutex_unlock(&dmasound_core_mutex); + return -ENODEV; + } + mixer.busy = 1; + mutex_unlock(&dmasound_core_mutex); + return 0; +} + +static int mixer_release(struct inode *inode, struct file *file) +{ + mutex_lock(&dmasound_core_mutex); + mixer.busy = 0; + module_put(dmasound.mach.owner); + mutex_unlock(&dmasound_core_mutex); + return 0; +} + +static int mixer_ioctl(struct file *file, u_int cmd, u_long arg) +{ + if (_SIOC_DIR(cmd) & _SIOC_WRITE) + mixer.modify_counter++; + switch (cmd) { + case OSS_GETVERSION: + return IOCTL_OUT(arg, SOUND_VERSION); + case SOUND_MIXER_INFO: + { + mixer_info info; + memset(&info, 0, sizeof(info)); + strscpy(info.id, dmasound.mach.name2, sizeof(info.id)); + strscpy(info.name, dmasound.mach.name2, sizeof(info.name)); + info.modify_counter = mixer.modify_counter; + if (copy_to_user((void __user *)arg, &info, sizeof(info))) + return -EFAULT; + return 0; + } + } + if (dmasound.mach.mixer_ioctl) + return dmasound.mach.mixer_ioctl(cmd, arg); + return -EINVAL; +} + +static long mixer_unlocked_ioctl(struct file *file, u_int cmd, u_long arg) +{ + int ret; + + mutex_lock(&dmasound_core_mutex); + ret = mixer_ioctl(file, cmd, arg); + mutex_unlock(&dmasound_core_mutex); + + return ret; +} + +static const struct file_operations mixer_fops = +{ + .owner = THIS_MODULE, + .llseek = no_llseek, + .unlocked_ioctl = mixer_unlocked_ioctl, + .compat_ioctl = compat_ptr_ioctl, + .open = mixer_open, + .release = mixer_release, +}; + +static void mixer_init(void) +{ + mixer_unit = register_sound_mixer(&mixer_fops, -1); + if (mixer_unit < 0) + return; + + mixer.busy = 0; + dmasound.treble = 0; + dmasound.bass = 0; + if (dmasound.mach.mixer_init) + dmasound.mach.mixer_init(); +} + + + /* + * Sound queue stuff, the heart of the driver + */ + +struct sound_queue dmasound_write_sq; +static void sq_reset_output(void) ; + +static int sq_allocate_buffers(struct sound_queue *sq, int num, int size) +{ + int i; + + if (sq->buffers) + return 0; + sq->numBufs = num; + sq->bufSize = size; + sq->buffers = kmalloc_array (num, sizeof(char *), GFP_KERNEL); + if (!sq->buffers) + return -ENOMEM; + for (i = 0; i < num; i++) { + sq->buffers[i] = dmasound.mach.dma_alloc(size, GFP_KERNEL); + if (!sq->buffers[i]) { + while (i--) + dmasound.mach.dma_free(sq->buffers[i], size); + kfree(sq->buffers); + sq->buffers = NULL; + return -ENOMEM; + } + } + return 0; +} + +static void sq_release_buffers(struct sound_queue *sq) +{ + int i; + + if (sq->buffers) { + for (i = 0; i < sq->numBufs; i++) + dmasound.mach.dma_free(sq->buffers[i], sq->bufSize); + kfree(sq->buffers); + sq->buffers = NULL; + } +} + + +static int sq_setup(struct sound_queue *sq) +{ + int (*setup_func)(void) = NULL; + int hard_frame ; + + if (sq->locked) { /* are we already set? - and not changeable */ +#ifdef DEBUG_DMASOUND +printk("dmasound_core: tried to sq_setup a locked queue\n") ; +#endif + return -EINVAL ; + } + sq->locked = 1 ; /* don't think we have a race prob. here _check_ */ + + /* make sure that the parameters are set up + This should have been done already... + */ + + dmasound.mach.init(); + + /* OK. If the user has set fragment parameters explicitly, then we + should leave them alone... as long as they are valid. + Invalid user fragment params can occur if we allow the whole buffer + to be used when the user requests the fragments sizes (with no soft + x-lation) and then the user subsequently sets a soft x-lation that + requires increased internal buffering. + + Othwerwise (if the user did not set them) OSS says that we should + select frag params on the basis of 0.5 s output & 0.1 s input + latency. (TODO. For now we will copy in the defaults.) + */ + + if (sq->user_frags <= 0) { + sq->max_count = sq->numBufs ; + sq->max_active = sq->numBufs ; + sq->block_size = sq->bufSize; + /* set up the user info */ + sq->user_frags = sq->numBufs ; + sq->user_frag_size = sq->bufSize ; + sq->user_frag_size *= + (dmasound.soft.size * (dmasound.soft.stereo+1) ) ; + sq->user_frag_size /= + (dmasound.hard.size * (dmasound.hard.stereo+1) ) ; + } else { + /* work out requested block size */ + sq->block_size = sq->user_frag_size ; + sq->block_size *= + (dmasound.hard.size * (dmasound.hard.stereo+1) ) ; + sq->block_size /= + (dmasound.soft.size * (dmasound.soft.stereo+1) ) ; + /* the user wants to write frag-size chunks */ + sq->block_size *= dmasound.hard.speed ; + sq->block_size /= dmasound.soft.speed ; + /* this only works for size values which are powers of 2 */ + hard_frame = + (dmasound.hard.size * (dmasound.hard.stereo+1))/8 ; + sq->block_size += (hard_frame - 1) ; + sq->block_size &= ~(hard_frame - 1) ; /* make sure we are aligned */ + /* let's just check for obvious mistakes */ + if ( sq->block_size <= 0 || sq->block_size > sq->bufSize) { +#ifdef DEBUG_DMASOUND +printk("dmasound_core: invalid frag size (user set %d)\n", sq->user_frag_size) ; +#endif + sq->block_size = sq->bufSize ; + } + if ( sq->user_frags <= sq->numBufs ) { + sq->max_count = sq->user_frags ; + /* if user has set max_active - then use it */ + sq->max_active = (sq->max_active <= sq->max_count) ? + sq->max_active : sq->max_count ; + } else { +#ifdef DEBUG_DMASOUND +printk("dmasound_core: invalid frag count (user set %d)\n", sq->user_frags) ; +#endif + sq->max_count = + sq->max_active = sq->numBufs ; + } + } + sq->front = sq->count = sq->rear_size = 0; + sq->syncing = 0; + sq->active = 0; + + if (sq == &write_sq) { + sq->rear = -1; + setup_func = dmasound.mach.write_sq_setup; + } + if (setup_func) + return setup_func(); + return 0 ; +} + +static inline void sq_play(void) +{ + dmasound.mach.play(); +} + +static ssize_t sq_write(struct file *file, const char __user *src, size_t uLeft, + loff_t *ppos) +{ + ssize_t uWritten = 0; + u_char *dest; + ssize_t uUsed = 0, bUsed, bLeft; + unsigned long flags ; + + /* ++TeSche: Is something like this necessary? + * Hey, that's an honest question! Or does any other part of the + * filesystem already checks this situation? I really don't know. + */ + if (uLeft == 0) + return 0; + + /* implement any changes we have made to the soft/hard params. + this is not satisfactory really, all we have done up to now is to + say what we would like - there hasn't been any real checking of capability + */ + + if (shared_resources_initialised == 0) { + dmasound.mach.init() ; + shared_resources_initialised = 1 ; + } + + /* set up the sq if it is not already done. This may seem a dumb place + to do it - but it is what OSS requires. It means that write() can + return memory allocation errors. To avoid this possibility use the + GETBLKSIZE or GETOSPACE ioctls (after you've fiddled with all the + params you want to change) - these ioctls also force the setup. + */ + + if (write_sq.locked == 0) { + if ((uWritten = sq_setup(&write_sq)) < 0) return uWritten ; + uWritten = 0 ; + } + +/* FIXME: I think that this may be the wrong behaviour when we get strapped + for time and the cpu is close to being (or actually) behind in sending data. + - because we've lost the time that the N samples, already in the buffer, + would have given us to get here with the next lot from the user. +*/ + /* The interrupt doesn't start to play the last, incomplete frame. + * Thus we can append to it without disabling the interrupts! (Note + * also that write_sq.rear isn't affected by the interrupt.) + */ + + /* as of 1.6 this behaviour changes if SNDCTL_DSP_POST has been issued: + this will mimic the behaviour of syncing and allow the sq_play() to + queue a partial fragment. Since sq_play() may/will be called from + the IRQ handler - at least on Pmac we have to deal with it. + The strategy - possibly not optimum - is to kill _POST status if we + get here. This seems, at least, reasonable - in the sense that POST + is supposed to indicate that we might not write before the queue + is drained - and if we get here in time then it does not apply. + */ + + spin_lock_irqsave(&dmasound.lock, flags); + write_sq.syncing &= ~2 ; /* take out POST status */ + spin_unlock_irqrestore(&dmasound.lock, flags); + + if (write_sq.count > 0 && + (bLeft = write_sq.block_size-write_sq.rear_size) > 0) { + dest = write_sq.buffers[write_sq.rear]; + bUsed = write_sq.rear_size; + uUsed = sound_copy_translate(dmasound.trans_write, src, uLeft, + dest, &bUsed, bLeft); + if (uUsed <= 0) + return uUsed; + src += uUsed; + uWritten += uUsed; + uLeft = (uUsed <= uLeft) ? (uLeft - uUsed) : 0 ; /* paranoia */ + write_sq.rear_size = bUsed; + } + + while (uLeft) { + DEFINE_WAIT(wait); + + while (write_sq.count >= write_sq.max_active) { + prepare_to_wait(&write_sq.action_queue, &wait, TASK_INTERRUPTIBLE); + sq_play(); + if (write_sq.non_blocking) { + finish_wait(&write_sq.action_queue, &wait); + return uWritten > 0 ? uWritten : -EAGAIN; + } + if (write_sq.count < write_sq.max_active) + break; + + schedule_timeout(HZ); + if (signal_pending(current)) { + finish_wait(&write_sq.action_queue, &wait); + return uWritten > 0 ? uWritten : -EINTR; + } + } + + finish_wait(&write_sq.action_queue, &wait); + + /* Here, we can avoid disabling the interrupt by first + * copying and translating the data, and then updating + * the write_sq variables. Until this is done, the interrupt + * won't see the new frame and we can work on it + * undisturbed. + */ + + dest = write_sq.buffers[(write_sq.rear+1) % write_sq.max_count]; + bUsed = 0; + bLeft = write_sq.block_size; + uUsed = sound_copy_translate(dmasound.trans_write, src, uLeft, + dest, &bUsed, bLeft); + if (uUsed <= 0) + break; + src += uUsed; + uWritten += uUsed; + uLeft = (uUsed <= uLeft) ? (uLeft - uUsed) : 0 ; /* paranoia */ + if (bUsed) { + write_sq.rear = (write_sq.rear+1) % write_sq.max_count; + write_sq.rear_size = bUsed; + write_sq.count++; + } + } /* uUsed may have been 0 */ + + sq_play(); + + return uUsed < 0? uUsed: uWritten; +} + +static __poll_t sq_poll(struct file *file, struct poll_table_struct *wait) +{ + __poll_t mask = 0; + int retVal; + + if (write_sq.locked == 0) { + if ((retVal = sq_setup(&write_sq)) < 0) + return retVal; + return 0; + } + if (file->f_mode & FMODE_WRITE ) + poll_wait(file, &write_sq.action_queue, wait); + if (file->f_mode & FMODE_WRITE) + if (write_sq.count < write_sq.max_active || write_sq.block_size - write_sq.rear_size > 0) + mask |= EPOLLOUT | EPOLLWRNORM; + return mask; + +} + +static inline void sq_init_waitqueue(struct sound_queue *sq) +{ + init_waitqueue_head(&sq->action_queue); + init_waitqueue_head(&sq->open_queue); + init_waitqueue_head(&sq->sync_queue); + sq->busy = 0; +} + +#if 0 /* blocking open() */ +static inline void sq_wake_up(struct sound_queue *sq, struct file *file, + fmode_t mode) +{ + if (file->f_mode & mode) { + sq->busy = 0; /* CHECK: IS THIS OK??? */ + WAKE_UP(sq->open_queue); + } +} +#endif + +static int sq_open2(struct sound_queue *sq, struct file *file, fmode_t mode, + int numbufs, int bufsize) +{ + int rc = 0; + + if (file->f_mode & mode) { + if (sq->busy) { +#if 0 /* blocking open() */ + rc = -EBUSY; + if (file->f_flags & O_NONBLOCK) + return rc; + rc = -EINTR; + if (wait_event_interruptible(sq->open_queue, !sq->busy)) + return rc; + rc = 0; +#else + /* OSS manual says we will return EBUSY regardless + of O_NOBLOCK. + */ + return -EBUSY ; +#endif + } + sq->busy = 1; /* Let's play spot-the-race-condition */ + + /* allocate the default number & size of buffers. + (i.e. specified in _setup() or as module params) + can't be changed at the moment - but _could_ be perhaps + in the setfragments ioctl. + */ + if (( rc = sq_allocate_buffers(sq, numbufs, bufsize))) { +#if 0 /* blocking open() */ + sq_wake_up(sq, file, mode); +#else + sq->busy = 0 ; +#endif + return rc; + } + + sq->non_blocking = file->f_flags & O_NONBLOCK; + } + return rc; +} + +#define write_sq_init_waitqueue() sq_init_waitqueue(&write_sq) +#if 0 /* blocking open() */ +#define write_sq_wake_up(file) sq_wake_up(&write_sq, file, FMODE_WRITE) +#endif +#define write_sq_release_buffers() sq_release_buffers(&write_sq) +#define write_sq_open(file) \ + sq_open2(&write_sq, file, FMODE_WRITE, numWriteBufs, writeBufSize ) + +static int sq_open(struct inode *inode, struct file *file) +{ + int rc; + + mutex_lock(&dmasound_core_mutex); + if (!try_module_get(dmasound.mach.owner)) { + mutex_unlock(&dmasound_core_mutex); + return -ENODEV; + } + + rc = write_sq_open(file); /* checks the f_mode */ + if (rc) + goto out; + if (file->f_mode & FMODE_READ) { + /* TODO: if O_RDWR, release any resources grabbed by write part */ + rc = -ENXIO ; /* I think this is what is required by open(2) */ + goto out; + } + + if (dmasound.mach.sq_open) + dmasound.mach.sq_open(file->f_mode); + + /* CHECK whether this is sensible - in the case that dsp0 could be opened + O_RDONLY and dsp1 could be opened O_WRONLY + */ + + dmasound.minDev = iminor(inode) & 0x0f; + + /* OK. - we should make some attempt at consistency. At least the H'ware + options should be set with a valid mode. We will make it that the LL + driver must supply defaults for hard & soft params. + */ + + if (shared_resource_owner == 0) { + /* you can make this AFMT_U8/mono/8K if you want to mimic old + OSS behaviour - while we still have soft translations ;-) */ + dmasound.soft = dmasound.mach.default_soft ; + dmasound.dsp = dmasound.mach.default_soft ; + dmasound.hard = dmasound.mach.default_hard ; + } + +#ifndef DMASOUND_STRICT_OSS_COMPLIANCE + /* none of the current LL drivers can actually do this "native" at the moment + OSS does not really require us to supply /dev/audio if we can't do it. + */ + if (dmasound.minDev == SND_DEV_AUDIO) { + sound_set_speed(8000); + sound_set_stereo(0); + sound_set_format(AFMT_MU_LAW); + } +#endif + mutex_unlock(&dmasound_core_mutex); + return 0; + out: + module_put(dmasound.mach.owner); + mutex_unlock(&dmasound_core_mutex); + return rc; +} + +static void sq_reset_output(void) +{ + sound_silence(); /* this _must_ stop DMA, we might be about to lose the buffers */ + write_sq.active = 0; + write_sq.count = 0; + write_sq.rear_size = 0; + /* write_sq.front = (write_sq.rear+1) % write_sq.max_count;*/ + write_sq.front = 0 ; + write_sq.rear = -1 ; /* same as for set-up */ + + /* OK - we can unlock the parameters and fragment settings */ + write_sq.locked = 0 ; + write_sq.user_frags = 0 ; + write_sq.user_frag_size = 0 ; +} + +static void sq_reset(void) +{ + sq_reset_output() ; + /* we could consider resetting the shared_resources_owner here... but I + think it is probably still rather non-obvious to application writer + */ + + /* we release everything else though */ + shared_resources_initialised = 0 ; +} + +static int sq_fsync(void) +{ + int rc = 0; + int timeout = 5; + + write_sq.syncing |= 1; + sq_play(); /* there may be an incomplete frame waiting */ + + while (write_sq.active) { + wait_event_interruptible_timeout(write_sq.sync_queue, + !write_sq.active, HZ); + if (signal_pending(current)) { + /* While waiting for audio output to drain, an + * interrupt occurred. Stop audio output immediately + * and clear the queue. */ + sq_reset_output(); + rc = -EINTR; + break; + } + if (!--timeout) { + printk(KERN_WARNING "dmasound: Timeout draining output\n"); + sq_reset_output(); + rc = -EIO; + break; + } + } + + /* flag no sync regardless of whether we had a DSP_POST or not */ + write_sq.syncing = 0 ; + return rc; +} + +static int sq_release(struct inode *inode, struct file *file) +{ + int rc = 0; + + mutex_lock(&dmasound_core_mutex); + + if (file->f_mode & FMODE_WRITE) { + if (write_sq.busy) + rc = sq_fsync(); + + sq_reset_output() ; /* make sure dma is stopped and all is quiet */ + write_sq_release_buffers(); + write_sq.busy = 0; + } + + if (file->f_mode & shared_resource_owner) { /* it's us that has them */ + shared_resource_owner = 0 ; + shared_resources_initialised = 0 ; + dmasound.hard = dmasound.mach.default_hard ; + } + + module_put(dmasound.mach.owner); + +#if 0 /* blocking open() */ + /* Wake up a process waiting for the queue being released. + * Note: There may be several processes waiting for a call + * to open() returning. */ + + /* Iain: hmm I don't understand this next comment ... */ + /* There is probably a DOS atack here. They change the mode flag. */ + /* XXX add check here,*/ + read_sq_wake_up(file); /* checks f_mode */ + write_sq_wake_up(file); /* checks f_mode */ +#endif /* blocking open() */ + + mutex_unlock(&dmasound_core_mutex); + + return rc; +} + +/* here we see if we have a right to modify format, channels, size and so on + if no-one else has claimed it already then we do... + + TODO: We might change this to mask O_RDWR such that only one or the other channel + is the owner - if we have problems. +*/ + +static int shared_resources_are_mine(fmode_t md) +{ + if (shared_resource_owner) + return (shared_resource_owner & md) != 0; + else { + shared_resource_owner = md ; + return 1 ; + } +} + +/* if either queue is locked we must deny the right to change shared params +*/ + +static int queues_are_quiescent(void) +{ + if (write_sq.locked) + return 0 ; + return 1 ; +} + +/* check and set a queue's fragments per user's wishes... + we will check against the pre-defined literals and the actual sizes. + This is a bit fraught - because soft translations can mess with our + buffer requirements *after* this call - OSS says "call setfrags first" +*/ + +/* It is possible to replace all the -EINVAL returns with an override that + just puts the allowable value in. This may be what many OSS apps require +*/ + +static int set_queue_frags(struct sound_queue *sq, int bufs, int size) +{ + if (sq->locked) { +#ifdef DEBUG_DMASOUND +printk("dmasound_core: tried to set_queue_frags on a locked queue\n") ; +#endif + return -EINVAL ; + } + + if ((size < MIN_FRAG_SIZE) || (size > MAX_FRAG_SIZE)) + return -EINVAL ; + size = (1<<size) ; /* now in bytes */ + if (size > sq->bufSize) + return -EINVAL ; /* this might still not work */ + + if (bufs <= 0) + return -EINVAL ; + if (bufs > sq->numBufs) /* the user is allowed say "don't care" with 0x7fff */ + bufs = sq->numBufs ; + + /* there is, currently, no way to specify max_active separately + from max_count. This could be a LL driver issue - I guess + if there is a requirement for these values to be different then + we will have to pass that info. up to this level. + */ + sq->user_frags = + sq->max_active = bufs ; + sq->user_frag_size = size ; + + return 0 ; +} + +static int sq_ioctl(struct file *file, u_int cmd, u_long arg) +{ + int val, result; + u_long fmt; + int data; + int size, nbufs; + audio_buf_info info; + + switch (cmd) { + case SNDCTL_DSP_RESET: + sq_reset(); + return 0; + case SNDCTL_DSP_GETFMTS: + fmt = dmasound.mach.hardware_afmts ; /* this is what OSS says.. */ + return IOCTL_OUT(arg, fmt); + case SNDCTL_DSP_GETBLKSIZE: + /* this should tell the caller about bytes that the app can + read/write - the app doesn't care about our internal buffers. + We force sq_setup() here as per OSS 1.1 (which should + compute the values necessary). + Since there is no mechanism to specify read/write separately, for + fds opened O_RDWR, the write_sq values will, arbitrarily, overwrite + the read_sq ones. + */ + size = 0 ; + if (file->f_mode & FMODE_WRITE) { + if ( !write_sq.locked ) + sq_setup(&write_sq) ; + size = write_sq.user_frag_size ; + } + return IOCTL_OUT(arg, size); + case SNDCTL_DSP_POST: + /* all we are going to do is to tell the LL that any + partial frags can be queued for output. + The LL will have to clear this flag when last output + is queued. + */ + write_sq.syncing |= 0x2 ; + sq_play() ; + return 0 ; + case SNDCTL_DSP_SYNC: + /* This call, effectively, has the same behaviour as SNDCTL_DSP_RESET + except that it waits for output to finish before resetting + everything - read, however, is killed immediately. + */ + result = 0 ; + if (file->f_mode & FMODE_WRITE) { + result = sq_fsync(); + sq_reset_output() ; + } + /* if we are the shared resource owner then release them */ + if (file->f_mode & shared_resource_owner) + shared_resources_initialised = 0 ; + return result ; + case SOUND_PCM_READ_RATE: + return IOCTL_OUT(arg, dmasound.soft.speed); + case SNDCTL_DSP_SPEED: + /* changing this on the fly will have weird effects on the sound. + Where there are rate conversions implemented in soft form - it + will cause the _ctx_xxx() functions to be substituted. + However, there doesn't appear to be any reason to dis-allow it from + a driver pov. + */ + if (shared_resources_are_mine(file->f_mode)) { + IOCTL_IN(arg, data); + data = sound_set_speed(data) ; + shared_resources_initialised = 0 ; + return IOCTL_OUT(arg, data); + } else + return -EINVAL ; + break ; + /* OSS says these next 4 actions are undefined when the device is + busy/active - we will just return -EINVAL. + To be allowed to change one - (a) you have to own the right + (b) the queue(s) must be quiescent + */ + case SNDCTL_DSP_STEREO: + if (shared_resources_are_mine(file->f_mode) && + queues_are_quiescent()) { + IOCTL_IN(arg, data); + shared_resources_initialised = 0 ; + return IOCTL_OUT(arg, sound_set_stereo(data)); + } else + return -EINVAL ; + break ; + case SOUND_PCM_WRITE_CHANNELS: + if (shared_resources_are_mine(file->f_mode) && + queues_are_quiescent()) { + IOCTL_IN(arg, data); + /* the user might ask for 20 channels, we will return 1 or 2 */ + shared_resources_initialised = 0 ; + return IOCTL_OUT(arg, sound_set_stereo(data-1)+1); + } else + return -EINVAL ; + break ; + case SNDCTL_DSP_SETFMT: + if (shared_resources_are_mine(file->f_mode) && + queues_are_quiescent()) { + int format; + IOCTL_IN(arg, data); + shared_resources_initialised = 0 ; + format = sound_set_format(data); + result = IOCTL_OUT(arg, format); + if (result < 0) + return result; + if (format != data && data != AFMT_QUERY) + return -EINVAL; + return 0; + } else + return -EINVAL ; + case SNDCTL_DSP_SUBDIVIDE: + return -EINVAL ; + case SNDCTL_DSP_SETFRAGMENT: + /* we can do this independently for the two queues - with the + proviso that for fds opened O_RDWR we cannot separate the + actions and both queues will be set per the last call. + NOTE: this does *NOT* actually set the queue up - merely + registers our intentions. + */ + IOCTL_IN(arg, data); + result = 0 ; + nbufs = (data >> 16) & 0x7fff ; /* 0x7fff is 'use maximum' */ + size = data & 0xffff; + if (file->f_mode & FMODE_WRITE) { + result = set_queue_frags(&write_sq, nbufs, size) ; + if (result) + return result ; + } + /* NOTE: this return value is irrelevant - OSS specifically says that + the value is 'random' and that the user _must_ check the actual + frags values using SNDCTL_DSP_GETBLKSIZE or similar */ + return IOCTL_OUT(arg, data); + case SNDCTL_DSP_GETOSPACE: + /* + */ + if (file->f_mode & FMODE_WRITE) { + if ( !write_sq.locked ) + sq_setup(&write_sq) ; + info.fragments = write_sq.max_active - write_sq.count; + info.fragstotal = write_sq.max_active; + info.fragsize = write_sq.user_frag_size; + info.bytes = info.fragments * info.fragsize; + if (copy_to_user((void __user *)arg, &info, sizeof(info))) + return -EFAULT; + return 0; + } else + return -EINVAL ; + break ; + case SNDCTL_DSP_GETCAPS: + val = dmasound.mach.capabilities & 0xffffff00; + return IOCTL_OUT(arg,val); + + default: + return mixer_ioctl(file, cmd, arg); + } + return -EINVAL; +} + +static long sq_unlocked_ioctl(struct file *file, u_int cmd, u_long arg) +{ + int ret; + + mutex_lock(&dmasound_core_mutex); + ret = sq_ioctl(file, cmd, arg); + mutex_unlock(&dmasound_core_mutex); + + return ret; +} + +static const struct file_operations sq_fops = +{ + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = sq_write, + .poll = sq_poll, + .unlocked_ioctl = sq_unlocked_ioctl, + .compat_ioctl = compat_ptr_ioctl, + .open = sq_open, + .release = sq_release, +}; + +static int sq_init(void) +{ + const struct file_operations *fops = &sq_fops; + + sq_unit = register_sound_dsp(fops, -1); + if (sq_unit < 0) { + printk(KERN_ERR "dmasound_core: couldn't register fops\n") ; + return sq_unit ; + } + + write_sq_init_waitqueue(); + + /* These parameters will be restored for every clean open() + * in the case of multiple open()s (e.g. dsp0 & dsp1) they + * will be set so long as the shared resources have no owner. + */ + + if (shared_resource_owner == 0) { + dmasound.soft = dmasound.mach.default_soft ; + dmasound.hard = dmasound.mach.default_hard ; + dmasound.dsp = dmasound.mach.default_soft ; + shared_resources_initialised = 0 ; + } + return 0 ; +} + + + /* + * /dev/sndstat + */ + +/* we allow more space for record-enabled because there are extra output lines. + the number here must include the amount we are prepared to give to the low-level + driver. +*/ + +#define STAT_BUFF_LEN 768 + +/* this is how much space we will allow the low-level driver to use + in the stat buffer. Currently, 2 * (80 character line + <NL>). + We do not police this (it is up to the ll driver to be honest). +*/ + +#define LOW_LEVEL_STAT_ALLOC 162 + +static struct { + int busy; + char buf[STAT_BUFF_LEN]; /* state.buf should not overflow! */ + int len, ptr; +} state; + +/* publish this function for use by low-level code, if required */ + +static char *get_afmt_string(int afmt) +{ + switch(afmt) { + case AFMT_MU_LAW: + return "mu-law"; + case AFMT_A_LAW: + return "A-law"; + case AFMT_U8: + return "unsigned 8 bit"; + case AFMT_S8: + return "signed 8 bit"; + case AFMT_S16_BE: + return "signed 16 bit BE"; + case AFMT_U16_BE: + return "unsigned 16 bit BE"; + case AFMT_S16_LE: + return "signed 16 bit LE"; + case AFMT_U16_LE: + return "unsigned 16 bit LE"; + case 0: + return "format not set" ; + default: + break ; + } + return "ERROR: Unsupported AFMT_XXXX code" ; +} + +static int state_open(struct inode *inode, struct file *file) +{ + char *buffer = state.buf; + int len = 0; + int ret; + + mutex_lock(&dmasound_core_mutex); + ret = -EBUSY; + if (state.busy) + goto out; + + ret = -ENODEV; + if (!try_module_get(dmasound.mach.owner)) + goto out; + + state.ptr = 0; + state.busy = 1; + + len += sprintf(buffer+len, "%sDMA sound driver rev %03d :\n", + dmasound.mach.name, (DMASOUND_CORE_REVISION<<4) + + ((dmasound.mach.version>>8) & 0x0f)); + len += sprintf(buffer+len, + "Core driver edition %02d.%02d : %s driver edition %02d.%02d\n", + DMASOUND_CORE_REVISION, DMASOUND_CORE_EDITION, dmasound.mach.name2, + (dmasound.mach.version >> 8), (dmasound.mach.version & 0xff)) ; + + /* call the low-level module to fill in any stat info. that it has + if present. Maximum buffer usage is specified. + */ + + if (dmasound.mach.state_info) + len += dmasound.mach.state_info(buffer+len, + (size_t) LOW_LEVEL_STAT_ALLOC) ; + + /* make usage of the state buffer as deterministic as poss. + exceptional conditions could cause overrun - and this is flagged as + a kernel error. + */ + + /* formats and settings */ + + len += sprintf(buffer+len,"\t\t === Formats & settings ===\n") ; + len += sprintf(buffer+len,"Parameter %20s%20s\n","soft","hard") ; + len += sprintf(buffer+len,"Format :%20s%20s\n", + get_afmt_string(dmasound.soft.format), + get_afmt_string(dmasound.hard.format)); + + len += sprintf(buffer+len,"Samp Rate:%14d s/sec%14d s/sec\n", + dmasound.soft.speed, dmasound.hard.speed); + + len += sprintf(buffer+len,"Channels :%20s%20s\n", + dmasound.soft.stereo ? "stereo" : "mono", + dmasound.hard.stereo ? "stereo" : "mono" ); + + /* sound queue status */ + + len += sprintf(buffer+len,"\t\t === Sound Queue status ===\n"); + len += sprintf(buffer+len,"Allocated:%8s%6s\n","Buffers","Size") ; + len += sprintf(buffer+len,"%9s:%8d%6d\n", + "write", write_sq.numBufs, write_sq.bufSize) ; + len += sprintf(buffer+len, + "Current : MaxFrg FragSiz MaxAct Frnt Rear " + "Cnt RrSize A B S L xruns\n") ; + len += sprintf(buffer+len,"%9s:%7d%8d%7d%5d%5d%4d%7d%2d%2d%2d%2d%7d\n", + "write", write_sq.max_count, write_sq.block_size, + write_sq.max_active, write_sq.front, write_sq.rear, + write_sq.count, write_sq.rear_size, write_sq.active, + write_sq.busy, write_sq.syncing, write_sq.locked, write_sq.xruns) ; +#ifdef DEBUG_DMASOUND +printk("dmasound: stat buffer used %d bytes\n", len) ; +#endif + + if (len >= STAT_BUFF_LEN) + printk(KERN_ERR "dmasound_core: stat buffer overflowed!\n"); + + state.len = len; + ret = 0; +out: + mutex_unlock(&dmasound_core_mutex); + return ret; +} + +static int state_release(struct inode *inode, struct file *file) +{ + mutex_lock(&dmasound_core_mutex); + state.busy = 0; + module_put(dmasound.mach.owner); + mutex_unlock(&dmasound_core_mutex); + return 0; +} + +static ssize_t state_read(struct file *file, char __user *buf, size_t count, + loff_t *ppos) +{ + int n = state.len - state.ptr; + if (n > count) + n = count; + if (n <= 0) + return 0; + if (copy_to_user(buf, &state.buf[state.ptr], n)) + return -EFAULT; + state.ptr += n; + return n; +} + +static const struct file_operations state_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .read = state_read, + .open = state_open, + .release = state_release, +}; + +static int state_init(void) +{ + state_unit = register_sound_special(&state_fops, SND_DEV_STATUS); + if (state_unit < 0) + return state_unit ; + state.busy = 0; + return 0 ; +} + + + /* + * Config & Setup + * + * This function is called by _one_ chipset-specific driver + */ + +int dmasound_init(void) +{ + int res ; + + if (irq_installed) + return -EBUSY; + + /* Set up sound queue, /dev/audio and /dev/dsp. */ + + /* Set default settings. */ + if ((res = sq_init()) < 0) + return res ; + + /* Set up /dev/sndstat. */ + if ((res = state_init()) < 0) + return res ; + + /* Set up /dev/mixer. */ + mixer_init(); + + if (!dmasound.mach.irqinit()) { + printk(KERN_ERR "DMA sound driver: Interrupt initialization failed\n"); + return -ENODEV; + } + irq_installed = 1; + + printk(KERN_INFO "%s DMA sound driver rev %03d installed\n", + dmasound.mach.name, (DMASOUND_CORE_REVISION<<4) + + ((dmasound.mach.version>>8) & 0x0f)); + printk(KERN_INFO + "Core driver edition %02d.%02d : %s driver edition %02d.%02d\n", + DMASOUND_CORE_REVISION, DMASOUND_CORE_EDITION, dmasound.mach.name2, + (dmasound.mach.version >> 8), (dmasound.mach.version & 0xff)) ; + printk(KERN_INFO "Write will use %4d fragments of %7d bytes as default\n", + numWriteBufs, writeBufSize) ; + return 0; +} + +void dmasound_deinit(void) +{ + if (irq_installed) { + sound_silence(); + dmasound.mach.irqcleanup(); + irq_installed = 0; + } + + write_sq_release_buffers(); + + if (mixer_unit >= 0) + unregister_sound_mixer(mixer_unit); + if (state_unit >= 0) + unregister_sound_special(state_unit); + if (sq_unit >= 0) + unregister_sound_dsp(sq_unit); +} + +static int __maybe_unused dmasound_setup(char *str) +{ + int ints[6], size; + + str = get_options(str, ARRAY_SIZE(ints), ints); + + /* check the bootstrap parameter for "dmasound=" */ + + /* FIXME: other than in the most naive of cases there is no sense in these + * buffers being other than powers of two. This is not checked yet. + */ + + switch (ints[0]) { + case 3: + if ((ints[3] < 0) || (ints[3] > MAX_CATCH_RADIUS)) + printk("dmasound_setup: invalid catch radius, using default = %d\n", catchRadius); + else + catchRadius = ints[3]; + fallthrough; + case 2: + if (ints[1] < MIN_BUFFERS) + printk("dmasound_setup: invalid number of buffers, using default = %d\n", numWriteBufs); + else + numWriteBufs = ints[1]; + fallthrough; + case 1: + if ((size = ints[2]) < 256) /* check for small buffer specs */ + size <<= 10 ; + if (size < MIN_BUFSIZE || size > MAX_BUFSIZE) + printk("dmasound_setup: invalid write buffer size, using default = %d\n", writeBufSize); + else + writeBufSize = size; + case 0: + break; + default: + printk("dmasound_setup: invalid number of arguments\n"); + return 0; + } + return 1; +} + +__setup("dmasound=", dmasound_setup); + + /* + * Conversion tables + */ + +#ifdef HAS_8BIT_TABLES +/* 8 bit mu-law */ + +char dmasound_ulaw2dma8[] = { + -126, -122, -118, -114, -110, -106, -102, -98, + -94, -90, -86, -82, -78, -74, -70, -66, + -63, -61, -59, -57, -55, -53, -51, -49, + -47, -45, -43, -41, -39, -37, -35, -33, + -31, -30, -29, -28, -27, -26, -25, -24, + -23, -22, -21, -20, -19, -18, -17, -16, + -16, -15, -15, -14, -14, -13, -13, -12, + -12, -11, -11, -10, -10, -9, -9, -8, + -8, -8, -7, -7, -7, -7, -6, -6, + -6, -6, -5, -5, -5, -5, -4, -4, + -4, -4, -4, -4, -3, -3, -3, -3, + -3, -3, -3, -3, -2, -2, -2, -2, + -2, -2, -2, -2, -2, -2, -2, -2, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, 0, + 125, 121, 117, 113, 109, 105, 101, 97, + 93, 89, 85, 81, 77, 73, 69, 65, + 62, 60, 58, 56, 54, 52, 50, 48, + 46, 44, 42, 40, 38, 36, 34, 32, + 30, 29, 28, 27, 26, 25, 24, 23, + 22, 21, 20, 19, 18, 17, 16, 15, + 15, 14, 14, 13, 13, 12, 12, 11, + 11, 10, 10, 9, 9, 8, 8, 7, + 7, 7, 6, 6, 6, 6, 5, 5, + 5, 5, 4, 4, 4, 4, 3, 3, + 3, 3, 3, 3, 2, 2, 2, 2, + 2, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 +}; + +/* 8 bit A-law */ + +char dmasound_alaw2dma8[] = { + -22, -21, -24, -23, -18, -17, -20, -19, + -30, -29, -32, -31, -26, -25, -28, -27, + -11, -11, -12, -12, -9, -9, -10, -10, + -15, -15, -16, -16, -13, -13, -14, -14, + -86, -82, -94, -90, -70, -66, -78, -74, + -118, -114, -126, -122, -102, -98, -110, -106, + -43, -41, -47, -45, -35, -33, -39, -37, + -59, -57, -63, -61, -51, -49, -55, -53, + -2, -2, -2, -2, -2, -2, -2, -2, + -2, -2, -2, -2, -2, -2, -2, -2, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, + -6, -6, -6, -6, -5, -5, -5, -5, + -8, -8, -8, -8, -7, -7, -7, -7, + -3, -3, -3, -3, -3, -3, -3, -3, + -4, -4, -4, -4, -4, -4, -4, -4, + 21, 20, 23, 22, 17, 16, 19, 18, + 29, 28, 31, 30, 25, 24, 27, 26, + 10, 10, 11, 11, 8, 8, 9, 9, + 14, 14, 15, 15, 12, 12, 13, 13, + 86, 82, 94, 90, 70, 66, 78, 74, + 118, 114, 126, 122, 102, 98, 110, 106, + 43, 41, 47, 45, 35, 33, 39, 37, + 59, 57, 63, 61, 51, 49, 55, 53, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 5, 5, 5, 5, 4, 4, 4, 4, + 7, 7, 7, 7, 6, 6, 6, 6, + 2, 2, 2, 2, 2, 2, 2, 2, + 3, 3, 3, 3, 3, 3, 3, 3 +}; +#endif /* HAS_8BIT_TABLES */ + + /* + * Visible symbols for modules + */ + +EXPORT_SYMBOL(dmasound); +EXPORT_SYMBOL(dmasound_init); +EXPORT_SYMBOL(dmasound_deinit); +EXPORT_SYMBOL(dmasound_write_sq); +EXPORT_SYMBOL(dmasound_catchRadius); +#ifdef HAS_8BIT_TABLES +EXPORT_SYMBOL(dmasound_ulaw2dma8); +EXPORT_SYMBOL(dmasound_alaw2dma8); +#endif diff --git a/sound/oss/dmasound/dmasound_paula.c b/sound/oss/dmasound/dmasound_paula.c new file mode 100644 index 000000000..23cf8284c --- /dev/null +++ b/sound/oss/dmasound/dmasound_paula.c @@ -0,0 +1,739 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * linux/sound/oss/dmasound/dmasound_paula.c + * + * Amiga `Paula' DMA Sound Driver + * + * See linux/sound/oss/dmasound/dmasound_core.c for copyright and credits + * prior to 28/01/2001 + * + * 28/01/2001 [0.1] Iain Sandoe + * - added versioning + * - put in and populated the hardware_afmts field. + * [0.2] - put in SNDCTL_DSP_GETCAPS value. + * [0.3] - put in constraint on state buffer usage. + * [0.4] - put in default hard/soft settings +*/ + + +#include <linux/module.h> +#include <linux/mm.h> +#include <linux/init.h> +#include <linux/ioport.h> +#include <linux/soundcard.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> + +#include <linux/uaccess.h> +#include <asm/setup.h> +#include <asm/amigahw.h> +#include <asm/amigaints.h> +#include <asm/machdep.h> + +#include "dmasound.h" + +#define DMASOUND_PAULA_REVISION 0 +#define DMASOUND_PAULA_EDITION 4 + +#define custom amiga_custom + /* + * The minimum period for audio depends on htotal (for OCS/ECS/AGA) + * (Imported from arch/m68k/amiga/amisound.c) + */ + +extern volatile u_short amiga_audio_min_period; + + + /* + * amiga_mksound() should be able to restore the period after beeping + * (Imported from arch/m68k/amiga/amisound.c) + */ + +extern u_short amiga_audio_period; + + + /* + * Audio DMA masks + */ + +#define AMI_AUDIO_OFF (DMAF_AUD0 | DMAF_AUD1 | DMAF_AUD2 | DMAF_AUD3) +#define AMI_AUDIO_8 (DMAF_SETCLR | DMAF_MASTER | DMAF_AUD0 | DMAF_AUD1) +#define AMI_AUDIO_14 (AMI_AUDIO_8 | DMAF_AUD2 | DMAF_AUD3) + + + /* + * Helper pointers for 16(14)-bit sound + */ + +static int write_sq_block_size_half, write_sq_block_size_quarter; + + +/*** Low level stuff *********************************************************/ + + +static void *AmiAlloc(unsigned int size, gfp_t flags); +static void AmiFree(void *obj, unsigned int size); +static int AmiIrqInit(void); +#ifdef MODULE +static void AmiIrqCleanUp(void); +#endif +static void AmiSilence(void); +static void AmiInit(void); +static int AmiSetFormat(int format); +static int AmiSetVolume(int volume); +static int AmiSetTreble(int treble); +static void AmiPlayNextFrame(int index); +static void AmiPlay(void); +static irqreturn_t AmiInterrupt(int irq, void *dummy); + +#ifdef CONFIG_HEARTBEAT + + /* + * Heartbeat interferes with sound since the 7 kHz low-pass filter and the + * power LED are controlled by the same line. + */ + +static void (*saved_heartbeat)(int) = NULL; + +static inline void disable_heartbeat(void) +{ + if (mach_heartbeat) { + saved_heartbeat = mach_heartbeat; + mach_heartbeat = NULL; + } + AmiSetTreble(dmasound.treble); +} + +static inline void enable_heartbeat(void) +{ + if (saved_heartbeat) + mach_heartbeat = saved_heartbeat; +} +#else /* !CONFIG_HEARTBEAT */ +#define disable_heartbeat() do { } while (0) +#define enable_heartbeat() do { } while (0) +#endif /* !CONFIG_HEARTBEAT */ + + +/*** Mid level stuff *********************************************************/ + +static void AmiMixerInit(void); +static int AmiMixerIoctl(u_int cmd, u_long arg); +static int AmiWriteSqSetup(void); +static int AmiStateInfo(char *buffer, size_t space); + + +/*** Translations ************************************************************/ + +/* ++TeSche: radically changed for new expanding purposes... + * + * These two routines now deal with copying/expanding/translating the samples + * from user space into our buffer at the right frequency. They take care about + * how much data there's actually to read, how much buffer space there is and + * to convert samples into the right frequency/encoding. They will only work on + * complete samples so it may happen they leave some bytes in the input stream + * if the user didn't write a multiple of the current sample size. They both + * return the number of bytes they've used from both streams so you may detect + * such a situation. Luckily all programs should be able to cope with that. + * + * I think I've optimized anything as far as one can do in plain C, all + * variables should fit in registers and the loops are really short. There's + * one loop for every possible situation. Writing a more generalized and thus + * parameterized loop would only produce slower code. Feel free to optimize + * this in assembler if you like. :) + * + * I think these routines belong here because they're not yet really hardware + * independent, especially the fact that the Falcon can play 16bit samples + * only in stereo is hardcoded in both of them! + * + * ++geert: split in even more functions (one per format) + */ + + + /* + * Native format + */ + +static ssize_t ami_ct_s8(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, ssize_t frameLeft) +{ + ssize_t count, used; + + if (!dmasound.soft.stereo) { + void *p = &frame[*frameUsed]; + count = min_t(unsigned long, userCount, frameLeft) & ~1; + used = count; + if (copy_from_user(p, userPtr, count)) + return -EFAULT; + } else { + u_char *left = &frame[*frameUsed>>1]; + u_char *right = left+write_sq_block_size_half; + count = min_t(unsigned long, userCount, frameLeft)>>1 & ~1; + used = count*2; + while (count > 0) { + if (get_user(*left++, userPtr++) + || get_user(*right++, userPtr++)) + return -EFAULT; + count--; + } + } + *frameUsed += used; + return used; +} + + + /* + * Copy and convert 8 bit data + */ + +#define GENERATE_AMI_CT8(funcname, convsample) \ +static ssize_t funcname(const u_char __user *userPtr, size_t userCount, \ + u_char frame[], ssize_t *frameUsed, \ + ssize_t frameLeft) \ +{ \ + ssize_t count, used; \ + \ + if (!dmasound.soft.stereo) { \ + u_char *p = &frame[*frameUsed]; \ + count = min_t(size_t, userCount, frameLeft) & ~1; \ + used = count; \ + while (count > 0) { \ + u_char data; \ + if (get_user(data, userPtr++)) \ + return -EFAULT; \ + *p++ = convsample(data); \ + count--; \ + } \ + } else { \ + u_char *left = &frame[*frameUsed>>1]; \ + u_char *right = left+write_sq_block_size_half; \ + count = min_t(size_t, userCount, frameLeft)>>1 & ~1; \ + used = count*2; \ + while (count > 0) { \ + u_char data; \ + if (get_user(data, userPtr++)) \ + return -EFAULT; \ + *left++ = convsample(data); \ + if (get_user(data, userPtr++)) \ + return -EFAULT; \ + *right++ = convsample(data); \ + count--; \ + } \ + } \ + *frameUsed += used; \ + return used; \ +} + +#define AMI_CT_ULAW(x) (dmasound_ulaw2dma8[(x)]) +#define AMI_CT_ALAW(x) (dmasound_alaw2dma8[(x)]) +#define AMI_CT_U8(x) ((x) ^ 0x80) + +GENERATE_AMI_CT8(ami_ct_ulaw, AMI_CT_ULAW) +GENERATE_AMI_CT8(ami_ct_alaw, AMI_CT_ALAW) +GENERATE_AMI_CT8(ami_ct_u8, AMI_CT_U8) + + + /* + * Copy and convert 16 bit data + */ + +#define GENERATE_AMI_CT_16(funcname, convsample) \ +static ssize_t funcname(const u_char __user *userPtr, size_t userCount, \ + u_char frame[], ssize_t *frameUsed, \ + ssize_t frameLeft) \ +{ \ + const u_short __user *ptr = (const u_short __user *)userPtr; \ + ssize_t count, used; \ + u_short data; \ + \ + if (!dmasound.soft.stereo) { \ + u_char *high = &frame[*frameUsed>>1]; \ + u_char *low = high+write_sq_block_size_half; \ + count = min_t(size_t, userCount, frameLeft)>>1 & ~1; \ + used = count*2; \ + while (count > 0) { \ + if (get_user(data, ptr++)) \ + return -EFAULT; \ + data = convsample(data); \ + *high++ = data>>8; \ + *low++ = (data>>2) & 0x3f; \ + count--; \ + } \ + } else { \ + u_char *lefth = &frame[*frameUsed>>2]; \ + u_char *leftl = lefth+write_sq_block_size_quarter; \ + u_char *righth = lefth+write_sq_block_size_half; \ + u_char *rightl = righth+write_sq_block_size_quarter; \ + count = min_t(size_t, userCount, frameLeft)>>2 & ~1; \ + used = count*4; \ + while (count > 0) { \ + if (get_user(data, ptr++)) \ + return -EFAULT; \ + data = convsample(data); \ + *lefth++ = data>>8; \ + *leftl++ = (data>>2) & 0x3f; \ + if (get_user(data, ptr++)) \ + return -EFAULT; \ + data = convsample(data); \ + *righth++ = data>>8; \ + *rightl++ = (data>>2) & 0x3f; \ + count--; \ + } \ + } \ + *frameUsed += used; \ + return used; \ +} + +#define AMI_CT_S16BE(x) (x) +#define AMI_CT_U16BE(x) ((x) ^ 0x8000) +#define AMI_CT_S16LE(x) (le2be16((x))) +#define AMI_CT_U16LE(x) (le2be16((x)) ^ 0x8000) + +GENERATE_AMI_CT_16(ami_ct_s16be, AMI_CT_S16BE) +GENERATE_AMI_CT_16(ami_ct_u16be, AMI_CT_U16BE) +GENERATE_AMI_CT_16(ami_ct_s16le, AMI_CT_S16LE) +GENERATE_AMI_CT_16(ami_ct_u16le, AMI_CT_U16LE) + + +static TRANS transAmiga = { + .ct_ulaw = ami_ct_ulaw, + .ct_alaw = ami_ct_alaw, + .ct_s8 = ami_ct_s8, + .ct_u8 = ami_ct_u8, + .ct_s16be = ami_ct_s16be, + .ct_u16be = ami_ct_u16be, + .ct_s16le = ami_ct_s16le, + .ct_u16le = ami_ct_u16le, +}; + +/*** Low level stuff *********************************************************/ + +static inline void StopDMA(void) +{ + custom.aud[0].audvol = custom.aud[1].audvol = 0; + custom.aud[2].audvol = custom.aud[3].audvol = 0; + custom.dmacon = AMI_AUDIO_OFF; + enable_heartbeat(); +} + +static void *AmiAlloc(unsigned int size, gfp_t flags) +{ + return amiga_chip_alloc((long)size, "dmasound [Paula]"); +} + +static void AmiFree(void *obj, unsigned int size) +{ + amiga_chip_free (obj); +} + +static int __init AmiIrqInit(void) +{ + /* turn off DMA for audio channels */ + StopDMA(); + + /* Register interrupt handler. */ + if (request_irq(IRQ_AMIGA_AUD0, AmiInterrupt, 0, "DMA sound", + AmiInterrupt)) + return 0; + return 1; +} + +#ifdef MODULE +static void AmiIrqCleanUp(void) +{ + /* turn off DMA for audio channels */ + StopDMA(); + /* release the interrupt */ + free_irq(IRQ_AMIGA_AUD0, AmiInterrupt); +} +#endif /* MODULE */ + +static void AmiSilence(void) +{ + /* turn off DMA for audio channels */ + StopDMA(); +} + + +static void AmiInit(void) +{ + int period, i; + + AmiSilence(); + + if (dmasound.soft.speed) + period = amiga_colorclock/dmasound.soft.speed-1; + else + period = amiga_audio_min_period; + dmasound.hard = dmasound.soft; + dmasound.trans_write = &transAmiga; + + if (period < amiga_audio_min_period) { + /* we would need to squeeze the sound, but we won't do that */ + period = amiga_audio_min_period; + } else if (period > 65535) { + period = 65535; + } + dmasound.hard.speed = amiga_colorclock/(period+1); + + for (i = 0; i < 4; i++) + custom.aud[i].audper = period; + amiga_audio_period = period; +} + + +static int AmiSetFormat(int format) +{ + int size; + + /* Amiga sound DMA supports 8bit and 16bit (pseudo 14 bit) modes */ + + 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_BE: + case AFMT_U16_BE: + case AFMT_S16_LE: + case AFMT_U16_LE: + size = 16; + break; + default: /* :-) */ + size = 8; + format = AFMT_S8; + } + + dmasound.soft.format = format; + dmasound.soft.size = size; + if (dmasound.minDev == SND_DEV_DSP) { + dmasound.dsp.format = format; + dmasound.dsp.size = dmasound.soft.size; + } + AmiInit(); + + return format; +} + + +#define VOLUME_VOXWARE_TO_AMI(v) \ + (((v) < 0) ? 0 : ((v) > 100) ? 64 : ((v) * 64)/100) +#define VOLUME_AMI_TO_VOXWARE(v) ((v)*100/64) + +static int AmiSetVolume(int volume) +{ + dmasound.volume_left = VOLUME_VOXWARE_TO_AMI(volume & 0xff); + custom.aud[0].audvol = dmasound.volume_left; + dmasound.volume_right = VOLUME_VOXWARE_TO_AMI((volume & 0xff00) >> 8); + custom.aud[1].audvol = dmasound.volume_right; + if (dmasound.hard.size == 16) { + if (dmasound.volume_left == 64 && dmasound.volume_right == 64) { + custom.aud[2].audvol = 1; + custom.aud[3].audvol = 1; + } else { + custom.aud[2].audvol = 0; + custom.aud[3].audvol = 0; + } + } + return VOLUME_AMI_TO_VOXWARE(dmasound.volume_left) | + (VOLUME_AMI_TO_VOXWARE(dmasound.volume_right) << 8); +} + +static int AmiSetTreble(int treble) +{ + dmasound.treble = treble; + if (treble < 50) + ciaa.pra &= ~0x02; + else + ciaa.pra |= 0x02; + return treble; +} + + +#define AMI_PLAY_LOADED 1 +#define AMI_PLAY_PLAYING 2 +#define AMI_PLAY_MASK 3 + + +static void AmiPlayNextFrame(int index) +{ + u_char *start, *ch0, *ch1, *ch2, *ch3; + u_long size; + + /* used by AmiPlay() if all doubts whether there really is something + * to be played are already wiped out. + */ + start = write_sq.buffers[write_sq.front]; + size = (write_sq.count == index ? write_sq.rear_size + : write_sq.block_size)>>1; + + if (dmasound.hard.stereo) { + ch0 = start; + ch1 = start+write_sq_block_size_half; + size >>= 1; + } else { + ch0 = start; + ch1 = start; + } + + disable_heartbeat(); + custom.aud[0].audvol = dmasound.volume_left; + custom.aud[1].audvol = dmasound.volume_right; + if (dmasound.hard.size == 8) { + custom.aud[0].audlc = (u_short *)ZTWO_PADDR(ch0); + custom.aud[0].audlen = size; + custom.aud[1].audlc = (u_short *)ZTWO_PADDR(ch1); + custom.aud[1].audlen = size; + custom.dmacon = AMI_AUDIO_8; + } else { + size >>= 1; + custom.aud[0].audlc = (u_short *)ZTWO_PADDR(ch0); + custom.aud[0].audlen = size; + custom.aud[1].audlc = (u_short *)ZTWO_PADDR(ch1); + custom.aud[1].audlen = size; + if (dmasound.volume_left == 64 && dmasound.volume_right == 64) { + /* We can play pseudo 14-bit only with the maximum volume */ + ch3 = ch0+write_sq_block_size_quarter; + ch2 = ch1+write_sq_block_size_quarter; + custom.aud[2].audvol = 1; /* we are being affected by the beeps */ + custom.aud[3].audvol = 1; /* restoring volume here helps a bit */ + custom.aud[2].audlc = (u_short *)ZTWO_PADDR(ch2); + custom.aud[2].audlen = size; + custom.aud[3].audlc = (u_short *)ZTWO_PADDR(ch3); + custom.aud[3].audlen = size; + custom.dmacon = AMI_AUDIO_14; + } else { + custom.aud[2].audvol = 0; + custom.aud[3].audvol = 0; + custom.dmacon = AMI_AUDIO_8; + } + } + write_sq.front = (write_sq.front+1) % write_sq.max_count; + write_sq.active |= AMI_PLAY_LOADED; +} + + +static void AmiPlay(void) +{ + int minframes = 1; + + custom.intena = IF_AUD0; + + if (write_sq.active & AMI_PLAY_LOADED) { + /* There's already a frame loaded */ + custom.intena = IF_SETCLR | IF_AUD0; + return; + } + + if (write_sq.active & AMI_PLAY_PLAYING) + /* Increase threshold: frame 1 is already being played */ + minframes = 2; + + if (write_sq.count < minframes) { + /* Nothing to do */ + custom.intena = IF_SETCLR | IF_AUD0; + return; + } + + if (write_sq.count <= minframes && + write_sq.rear_size < write_sq.block_size && !write_sq.syncing) { + /* hmmm, the only existing frame is not + * yet filled and we're not syncing? + */ + custom.intena = IF_SETCLR | IF_AUD0; + return; + } + + AmiPlayNextFrame(minframes); + + custom.intena = IF_SETCLR | IF_AUD0; +} + + +static irqreturn_t AmiInterrupt(int irq, void *dummy) +{ + int minframes = 1; + + custom.intena = IF_AUD0; + + if (!write_sq.active) { + /* Playing was interrupted and sq_reset() has already cleared + * the sq variables, so better don't do anything here. + */ + WAKE_UP(write_sq.sync_queue); + return IRQ_HANDLED; + } + + if (write_sq.active & AMI_PLAY_PLAYING) { + /* We've just finished a frame */ + write_sq.count--; + WAKE_UP(write_sq.action_queue); + } + + if (write_sq.active & AMI_PLAY_LOADED) + /* Increase threshold: frame 1 is already being played */ + minframes = 2; + + /* Shift the flags */ + write_sq.active = (write_sq.active<<1) & AMI_PLAY_MASK; + + if (!write_sq.active) + /* No frame is playing, disable audio DMA */ + StopDMA(); + + custom.intena = IF_SETCLR | IF_AUD0; + + if (write_sq.count >= minframes) + /* Try to play the next frame */ + AmiPlay(); + + if (!write_sq.active) + /* Nothing to play anymore. + Wake up a process waiting for audio output to drain. */ + WAKE_UP(write_sq.sync_queue); + return IRQ_HANDLED; +} + +/*** Mid level stuff *********************************************************/ + + +/* + * /dev/mixer abstraction + */ + +static void __init AmiMixerInit(void) +{ + dmasound.volume_left = 64; + dmasound.volume_right = 64; + custom.aud[0].audvol = dmasound.volume_left; + custom.aud[3].audvol = 1; /* For pseudo 14bit */ + custom.aud[1].audvol = dmasound.volume_right; + custom.aud[2].audvol = 1; /* For pseudo 14bit */ + dmasound.treble = 50; +} + +static int AmiMixerIoctl(u_int cmd, u_long arg) +{ + int data; + switch (cmd) { + case SOUND_MIXER_READ_DEVMASK: + return IOCTL_OUT(arg, SOUND_MASK_VOLUME | SOUND_MASK_TREBLE); + case SOUND_MIXER_READ_RECMASK: + return IOCTL_OUT(arg, 0); + case SOUND_MIXER_READ_STEREODEVS: + return IOCTL_OUT(arg, SOUND_MASK_VOLUME); + case SOUND_MIXER_READ_VOLUME: + return IOCTL_OUT(arg, + VOLUME_AMI_TO_VOXWARE(dmasound.volume_left) | + VOLUME_AMI_TO_VOXWARE(dmasound.volume_right) << 8); + case SOUND_MIXER_WRITE_VOLUME: + IOCTL_IN(arg, data); + return IOCTL_OUT(arg, dmasound_set_volume(data)); + case SOUND_MIXER_READ_TREBLE: + return IOCTL_OUT(arg, dmasound.treble); + case SOUND_MIXER_WRITE_TREBLE: + IOCTL_IN(arg, data); + return IOCTL_OUT(arg, dmasound_set_treble(data)); + } + return -EINVAL; +} + + +static int AmiWriteSqSetup(void) +{ + write_sq_block_size_half = write_sq.block_size>>1; + write_sq_block_size_quarter = write_sq_block_size_half>>1; + return 0; +} + + +static int AmiStateInfo(char *buffer, size_t space) +{ + int len = 0; + len += sprintf(buffer+len, "\tsound.volume_left = %d [0...64]\n", + dmasound.volume_left); + len += sprintf(buffer+len, "\tsound.volume_right = %d [0...64]\n", + dmasound.volume_right); + if (len >= space) { + printk(KERN_ERR "dmasound_paula: overflowed state buffer alloc.\n") ; + len = space ; + } + return len; +} + + +/*** Machine definitions *****************************************************/ + +static SETTINGS def_hard = { + .format = AFMT_S8, + .stereo = 0, + .size = 8, + .speed = 8000 +} ; + +static SETTINGS def_soft = { + .format = AFMT_U8, + .stereo = 0, + .size = 8, + .speed = 8000 +} ; + +static MACHINE machAmiga = { + .name = "Amiga", + .name2 = "AMIGA", + .owner = THIS_MODULE, + .dma_alloc = AmiAlloc, + .dma_free = AmiFree, + .irqinit = AmiIrqInit, +#ifdef MODULE + .irqcleanup = AmiIrqCleanUp, +#endif /* MODULE */ + .init = AmiInit, + .silence = AmiSilence, + .setFormat = AmiSetFormat, + .setVolume = AmiSetVolume, + .setTreble = AmiSetTreble, + .play = AmiPlay, + .mixer_init = AmiMixerInit, + .mixer_ioctl = AmiMixerIoctl, + .write_sq_setup = AmiWriteSqSetup, + .state_info = AmiStateInfo, + .min_dsp_speed = 8000, + .version = ((DMASOUND_PAULA_REVISION<<8) | DMASOUND_PAULA_EDITION), + .hardware_afmts = (AFMT_S8 | AFMT_S16_BE), /* h'ware-supported formats *only* here */ + .capabilities = DSP_CAP_BATCH /* As per SNDCTL_DSP_GETCAPS */ +}; + + +/*** Config & Setup **********************************************************/ + + +static int __init amiga_audio_probe(struct platform_device *pdev) +{ + dmasound.mach = machAmiga; + dmasound.mach.default_hard = def_hard ; + dmasound.mach.default_soft = def_soft ; + return dmasound_init(); +} + +static int __exit amiga_audio_remove(struct platform_device *pdev) +{ + dmasound_deinit(); + return 0; +} + +static struct platform_driver amiga_audio_driver = { + .remove = __exit_p(amiga_audio_remove), + .driver = { + .name = "amiga-audio", + }, +}; + +module_platform_driver_probe(amiga_audio_driver, amiga_audio_probe); + +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:amiga-audio"); diff --git a/sound/oss/dmasound/dmasound_q40.c b/sound/oss/dmasound/dmasound_q40.c new file mode 100644 index 000000000..e25a78dd1 --- /dev/null +++ b/sound/oss/dmasound/dmasound_q40.c @@ -0,0 +1,639 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * linux/sound/oss/dmasound/dmasound_q40.c + * + * Q40 DMA Sound Driver + * + * See linux/sound/oss/dmasound/dmasound_core.c for copyright and credits + * prior to 28/01/2001 + * + * 28/01/2001 [0.1] Iain Sandoe + * - added versioning + * - put in and populated the hardware_afmts field. + * [0.2] - put in SNDCTL_DSP_GETCAPS value. + * [0.3] - put in default hard/soft settings. + */ + + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/soundcard.h> +#include <linux/interrupt.h> + +#include <linux/uaccess.h> +#include <asm/q40ints.h> +#include <asm/q40_master.h> + +#include "dmasound.h" + +#define DMASOUND_Q40_REVISION 0 +#define DMASOUND_Q40_EDITION 3 + +static int expand_bal; /* Balance factor for expanding (not volume!) */ +static int expand_data; /* Data for expanding */ + + +/*** Low level stuff *********************************************************/ + + +static void *Q40Alloc(unsigned int size, gfp_t flags); +static void Q40Free(void *, unsigned int); +static int Q40IrqInit(void); +#ifdef MODULE +static void Q40IrqCleanUp(void); +#endif +static void Q40Silence(void); +static void Q40Init(void); +static int Q40SetFormat(int format); +static int Q40SetVolume(int volume); +static void Q40PlayNextFrame(int index); +static void Q40Play(void); +static irqreturn_t Q40StereoInterrupt(int irq, void *dummy); +static irqreturn_t Q40MonoInterrupt(int irq, void *dummy); +static void Q40Interrupt(void); + + +/*** Mid level stuff *********************************************************/ + + + +/* userCount, frameUsed, frameLeft == byte counts */ +static ssize_t q40_ct_law(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft) +{ + char *table = dmasound.soft.format == AFMT_MU_LAW ? dmasound_ulaw2dma8: dmasound_alaw2dma8; + ssize_t count, used; + u_char *p = (u_char *) &frame[*frameUsed]; + + used = count = min_t(size_t, userCount, frameLeft); + if (copy_from_user(p,userPtr,count)) + return -EFAULT; + while (count > 0) { + *p = table[*p]+128; + p++; + count--; + } + *frameUsed += used ; + return used; +} + + +static ssize_t q40_ct_s8(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft) +{ + ssize_t count, used; + u_char *p = (u_char *) &frame[*frameUsed]; + + used = count = min_t(size_t, userCount, frameLeft); + if (copy_from_user(p,userPtr,count)) + return -EFAULT; + while (count > 0) { + *p = *p + 128; + p++; + count--; + } + *frameUsed += used; + return used; +} + +static ssize_t q40_ct_u8(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft) +{ + ssize_t count, used; + u_char *p = (u_char *) &frame[*frameUsed]; + + used = count = min_t(size_t, userCount, frameLeft); + if (copy_from_user(p,userPtr,count)) + return -EFAULT; + *frameUsed += used; + return used; +} + + +/* a bit too complicated to optimise right now ..*/ +static ssize_t q40_ctx_law(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft) +{ + unsigned char *table = (unsigned char *) + (dmasound.soft.format == AFMT_MU_LAW ? dmasound_ulaw2dma8: dmasound_alaw2dma8); + unsigned int data = expand_data; + u_char *p = (u_char *) &frame[*frameUsed]; + int bal = expand_bal; + int hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed; + int utotal, ftotal; + + ftotal = frameLeft; + utotal = userCount; + while (frameLeft) { + u_char c; + if (bal < 0) { + if (userCount == 0) + break; + if (get_user(c, userPtr++)) + return -EFAULT; + data = table[c]; + data += 0x80; + userCount--; + bal += hSpeed; + } + *p++ = data; + frameLeft--; + bal -= sSpeed; + } + expand_bal = bal; + expand_data = data; + *frameUsed += (ftotal - frameLeft); + utotal -= userCount; + return utotal; +} + + +static ssize_t q40_ctx_s8(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft) +{ + u_char *p = (u_char *) &frame[*frameUsed]; + unsigned int data = expand_data; + int bal = expand_bal; + int hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed; + int utotal, ftotal; + + + ftotal = frameLeft; + utotal = userCount; + while (frameLeft) { + u_char c; + if (bal < 0) { + if (userCount == 0) + break; + if (get_user(c, userPtr++)) + return -EFAULT; + data = c ; + data += 0x80; + userCount--; + bal += hSpeed; + } + *p++ = data; + frameLeft--; + bal -= sSpeed; + } + expand_bal = bal; + expand_data = data; + *frameUsed += (ftotal - frameLeft); + utotal -= userCount; + return utotal; +} + + +static ssize_t q40_ctx_u8(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft) +{ + u_char *p = (u_char *) &frame[*frameUsed]; + unsigned int data = expand_data; + int bal = expand_bal; + int hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed; + int utotal, ftotal; + + ftotal = frameLeft; + utotal = userCount; + while (frameLeft) { + u_char c; + if (bal < 0) { + if (userCount == 0) + break; + if (get_user(c, userPtr++)) + return -EFAULT; + data = c ; + userCount--; + bal += hSpeed; + } + *p++ = data; + frameLeft--; + bal -= sSpeed; + } + expand_bal = bal; + expand_data = data; + *frameUsed += (ftotal - frameLeft) ; + utotal -= userCount; + return utotal; +} + +/* compressing versions */ +static ssize_t q40_ctc_law(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft) +{ + unsigned char *table = (unsigned char *) + (dmasound.soft.format == AFMT_MU_LAW ? dmasound_ulaw2dma8: dmasound_alaw2dma8); + unsigned int data = expand_data; + u_char *p = (u_char *) &frame[*frameUsed]; + int bal = expand_bal; + int hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed; + int utotal, ftotal; + + ftotal = frameLeft; + utotal = userCount; + while (frameLeft) { + u_char c; + while(bal<0) { + if (userCount == 0) + goto lout; + if (!(bal<(-hSpeed))) { + if (get_user(c, userPtr)) + return -EFAULT; + data = 0x80 + table[c]; + } + userPtr++; + userCount--; + bal += hSpeed; + } + *p++ = data; + frameLeft--; + bal -= sSpeed; + } + lout: + expand_bal = bal; + expand_data = data; + *frameUsed += (ftotal - frameLeft); + utotal -= userCount; + return utotal; +} + + +static ssize_t q40_ctc_s8(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft) +{ + u_char *p = (u_char *) &frame[*frameUsed]; + unsigned int data = expand_data; + int bal = expand_bal; + int hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed; + int utotal, ftotal; + + ftotal = frameLeft; + utotal = userCount; + while (frameLeft) { + u_char c; + while (bal < 0) { + if (userCount == 0) + goto lout; + if (!(bal<(-hSpeed))) { + if (get_user(c, userPtr)) + return -EFAULT; + data = c + 0x80; + } + userPtr++; + userCount--; + bal += hSpeed; + } + *p++ = data; + frameLeft--; + bal -= sSpeed; + } + lout: + expand_bal = bal; + expand_data = data; + *frameUsed += (ftotal - frameLeft); + utotal -= userCount; + return utotal; +} + + +static ssize_t q40_ctc_u8(const u_char __user *userPtr, size_t userCount, + u_char frame[], ssize_t *frameUsed, + ssize_t frameLeft) +{ + u_char *p = (u_char *) &frame[*frameUsed]; + unsigned int data = expand_data; + int bal = expand_bal; + int hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed; + int utotal, ftotal; + + ftotal = frameLeft; + utotal = userCount; + while (frameLeft) { + u_char c; + while (bal < 0) { + if (userCount == 0) + goto lout; + if (!(bal<(-hSpeed))) { + if (get_user(c, userPtr)) + return -EFAULT; + data = c ; + } + userPtr++; + userCount--; + bal += hSpeed; + } + *p++ = data; + frameLeft--; + bal -= sSpeed; + } + lout: + expand_bal = bal; + expand_data = data; + *frameUsed += (ftotal - frameLeft) ; + utotal -= userCount; + return utotal; +} + + +static TRANS transQ40Normal = { + q40_ct_law, q40_ct_law, q40_ct_s8, q40_ct_u8, NULL, NULL, NULL, NULL +}; + +static TRANS transQ40Expanding = { + q40_ctx_law, q40_ctx_law, q40_ctx_s8, q40_ctx_u8, NULL, NULL, NULL, NULL +}; + +static TRANS transQ40Compressing = { + q40_ctc_law, q40_ctc_law, q40_ctc_s8, q40_ctc_u8, NULL, NULL, NULL, NULL +}; + + +/*** Low level stuff *********************************************************/ + +static void *Q40Alloc(unsigned int size, gfp_t flags) +{ + return kmalloc(size, flags); /* change to vmalloc */ +} + +static void Q40Free(void *ptr, unsigned int size) +{ + kfree(ptr); +} + +static int __init Q40IrqInit(void) +{ + /* Register interrupt handler. */ + if (request_irq(Q40_IRQ_SAMPLE, Q40StereoInterrupt, 0, + "DMA sound", Q40Interrupt)) + return 0; + + return(1); +} + + +#ifdef MODULE +static void Q40IrqCleanUp(void) +{ + master_outb(0,SAMPLE_ENABLE_REG); + free_irq(Q40_IRQ_SAMPLE, Q40Interrupt); +} +#endif /* MODULE */ + + +static void Q40Silence(void) +{ + master_outb(0,SAMPLE_ENABLE_REG); + *DAC_LEFT=*DAC_RIGHT=127; +} + +static char *q40_pp; +static unsigned int q40_sc; + +static void Q40PlayNextFrame(int index) +{ + u_char *start; + u_long size; + u_char speed; + int error; + + /* used by Q40Play() if all doubts whether there really is something + * to be played are already wiped out. + */ + start = write_sq.buffers[write_sq.front]; + size = (write_sq.count == index ? write_sq.rear_size : write_sq.block_size); + + q40_pp=start; + q40_sc=size; + + write_sq.front = (write_sq.front+1) % write_sq.max_count; + write_sq.active++; + + speed=(dmasound.hard.speed==10000 ? 0 : 1); + + master_outb( 0,SAMPLE_ENABLE_REG); + free_irq(Q40_IRQ_SAMPLE, Q40Interrupt); + if (dmasound.soft.stereo) + error = request_irq(Q40_IRQ_SAMPLE, Q40StereoInterrupt, 0, + "Q40 sound", Q40Interrupt); + else + error = request_irq(Q40_IRQ_SAMPLE, Q40MonoInterrupt, 0, + "Q40 sound", Q40Interrupt); + if (error && printk_ratelimit()) + pr_err("Couldn't register sound interrupt\n"); + + master_outb( speed, SAMPLE_RATE_REG); + master_outb( 1,SAMPLE_CLEAR_REG); + master_outb( 1,SAMPLE_ENABLE_REG); +} + +static void Q40Play(void) +{ + unsigned long flags; + + if (write_sq.active || write_sq.count<=0 ) { + /* There's already a frame loaded */ + return; + } + + /* nothing in the queue */ + if (write_sq.count <= 1 && write_sq.rear_size < write_sq.block_size && !write_sq.syncing) { + /* hmmm, the only existing frame is not + * yet filled and we're not syncing? + */ + return; + } + spin_lock_irqsave(&dmasound.lock, flags); + Q40PlayNextFrame(1); + spin_unlock_irqrestore(&dmasound.lock, flags); +} + +static irqreturn_t Q40StereoInterrupt(int irq, void *dummy) +{ + spin_lock(&dmasound.lock); + if (q40_sc>1){ + *DAC_LEFT=*q40_pp++; + *DAC_RIGHT=*q40_pp++; + q40_sc -=2; + master_outb(1,SAMPLE_CLEAR_REG); + }else Q40Interrupt(); + spin_unlock(&dmasound.lock); + return IRQ_HANDLED; +} +static irqreturn_t Q40MonoInterrupt(int irq, void *dummy) +{ + spin_lock(&dmasound.lock); + if (q40_sc>0){ + *DAC_LEFT=*q40_pp; + *DAC_RIGHT=*q40_pp++; + q40_sc --; + master_outb(1,SAMPLE_CLEAR_REG); + }else Q40Interrupt(); + spin_unlock(&dmasound.lock); + return IRQ_HANDLED; +} +static void Q40Interrupt(void) +{ + if (!write_sq.active) { + /* playing was interrupted and sq_reset() has already cleared + * the sq variables, so better don't do anything here. + */ + WAKE_UP(write_sq.sync_queue); + master_outb(0,SAMPLE_ENABLE_REG); /* better safe */ + goto exit; + } else write_sq.active=0; + write_sq.count--; + Q40Play(); + + if (q40_sc<2) + { /* there was nothing to play, disable irq */ + master_outb(0,SAMPLE_ENABLE_REG); + *DAC_LEFT=*DAC_RIGHT=127; + } + WAKE_UP(write_sq.action_queue); + + exit: + master_outb(1,SAMPLE_CLEAR_REG); +} + + +static void Q40Init(void) +{ + int i, idx; + const int freq[] = {10000, 20000}; + + /* search a frequency that fits into the allowed error range */ + + idx = -1; + for (i = 0; i < 2; i++) + if ((100 * abs(dmasound.soft.speed - freq[i]) / freq[i]) <= catchRadius) + idx = i; + + dmasound.hard = dmasound.soft; + /*sound.hard.stereo=1;*/ /* no longer true */ + dmasound.hard.size=8; + + if (idx > -1) { + dmasound.soft.speed = freq[idx]; + dmasound.trans_write = &transQ40Normal; + } else + dmasound.trans_write = &transQ40Expanding; + + Q40Silence(); + + if (dmasound.hard.speed > 20200) { + /* squeeze the sound, we do that */ + dmasound.hard.speed = 20000; + dmasound.trans_write = &transQ40Compressing; + } else if (dmasound.hard.speed > 10000) { + dmasound.hard.speed = 20000; + } else { + dmasound.hard.speed = 10000; + } + expand_bal = -dmasound.soft.speed; +} + + +static int Q40SetFormat(int format) +{ + /* Q40 sound supports only 8bit modes */ + + switch (format) { + case AFMT_QUERY: + return(dmasound.soft.format); + case AFMT_MU_LAW: + case AFMT_A_LAW: + case AFMT_S8: + case AFMT_U8: + break; + default: + format = AFMT_S8; + } + + dmasound.soft.format = format; + dmasound.soft.size = 8; + if (dmasound.minDev == SND_DEV_DSP) { + dmasound.dsp.format = format; + dmasound.dsp.size = 8; + } + Q40Init(); + + return(format); +} + +static int Q40SetVolume(int volume) +{ + return 0; +} + + +/*** Machine definitions *****************************************************/ + +static SETTINGS def_hard = { + .format = AFMT_U8, + .stereo = 0, + .size = 8, + .speed = 10000 +} ; + +static SETTINGS def_soft = { + .format = AFMT_U8, + .stereo = 0, + .size = 8, + .speed = 8000 +} ; + +static MACHINE machQ40 = { + .name = "Q40", + .name2 = "Q40", + .owner = THIS_MODULE, + .dma_alloc = Q40Alloc, + .dma_free = Q40Free, + .irqinit = Q40IrqInit, +#ifdef MODULE + .irqcleanup = Q40IrqCleanUp, +#endif /* MODULE */ + .init = Q40Init, + .silence = Q40Silence, + .setFormat = Q40SetFormat, + .setVolume = Q40SetVolume, + .play = Q40Play, + .min_dsp_speed = 10000, + .version = ((DMASOUND_Q40_REVISION<<8) | DMASOUND_Q40_EDITION), + .hardware_afmts = AFMT_U8, /* h'ware-supported formats *only* here */ + .capabilities = DSP_CAP_BATCH /* As per SNDCTL_DSP_GETCAPS */ +}; + + +/*** Config & Setup **********************************************************/ + + +static int __init dmasound_q40_init(void) +{ + if (MACH_IS_Q40) { + dmasound.mach = machQ40; + dmasound.mach.default_hard = def_hard ; + dmasound.mach.default_soft = def_soft ; + return dmasound_init(); + } else + return -ENODEV; +} + +static void __exit dmasound_q40_cleanup(void) +{ + dmasound_deinit(); +} + +module_init(dmasound_q40_init); +module_exit(dmasound_q40_cleanup); + +MODULE_DESCRIPTION("Q40/Q60 sound driver"); +MODULE_LICENSE("GPL"); |