forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
639 lines
14 KiB
639 lines
14 KiB
// 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");
|
|
|