| /* |
| * 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 <asm/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"); |