Path: blob/master/sound/oss/dmasound/dmasound_q40.c
10818 views
/*1* linux/sound/oss/dmasound/dmasound_q40.c2*3* Q40 DMA Sound Driver4*5* See linux/sound/oss/dmasound/dmasound_core.c for copyright and credits6* prior to 28/01/20017*8* 28/01/2001 [0.1] Iain Sandoe9* - added versioning10* - put in and populated the hardware_afmts field.11* [0.2] - put in SNDCTL_DSP_GETCAPS value.12* [0.3] - put in default hard/soft settings.13*/141516#include <linux/module.h>17#include <linux/init.h>18#include <linux/slab.h>19#include <linux/soundcard.h>20#include <linux/interrupt.h>2122#include <asm/uaccess.h>23#include <asm/q40ints.h>24#include <asm/q40_master.h>2526#include "dmasound.h"2728#define DMASOUND_Q40_REVISION 029#define DMASOUND_Q40_EDITION 33031static int expand_bal; /* Balance factor for expanding (not volume!) */32static int expand_data; /* Data for expanding */333435/*** Low level stuff *********************************************************/363738static void *Q40Alloc(unsigned int size, gfp_t flags);39static void Q40Free(void *, unsigned int);40static int Q40IrqInit(void);41#ifdef MODULE42static void Q40IrqCleanUp(void);43#endif44static void Q40Silence(void);45static void Q40Init(void);46static int Q40SetFormat(int format);47static int Q40SetVolume(int volume);48static void Q40PlayNextFrame(int index);49static void Q40Play(void);50static irqreturn_t Q40StereoInterrupt(int irq, void *dummy);51static irqreturn_t Q40MonoInterrupt(int irq, void *dummy);52static void Q40Interrupt(void);535455/*** Mid level stuff *********************************************************/56575859/* userCount, frameUsed, frameLeft == byte counts */60static ssize_t q40_ct_law(const u_char __user *userPtr, size_t userCount,61u_char frame[], ssize_t *frameUsed,62ssize_t frameLeft)63{64char *table = dmasound.soft.format == AFMT_MU_LAW ? dmasound_ulaw2dma8: dmasound_alaw2dma8;65ssize_t count, used;66u_char *p = (u_char *) &frame[*frameUsed];6768used = count = min_t(size_t, userCount, frameLeft);69if (copy_from_user(p,userPtr,count))70return -EFAULT;71while (count > 0) {72*p = table[*p]+128;73p++;74count--;75}76*frameUsed += used ;77return used;78}798081static ssize_t q40_ct_s8(const u_char __user *userPtr, size_t userCount,82u_char frame[], ssize_t *frameUsed,83ssize_t frameLeft)84{85ssize_t count, used;86u_char *p = (u_char *) &frame[*frameUsed];8788used = count = min_t(size_t, userCount, frameLeft);89if (copy_from_user(p,userPtr,count))90return -EFAULT;91while (count > 0) {92*p = *p + 128;93p++;94count--;95}96*frameUsed += used;97return used;98}99100static ssize_t q40_ct_u8(const u_char __user *userPtr, size_t userCount,101u_char frame[], ssize_t *frameUsed,102ssize_t frameLeft)103{104ssize_t count, used;105u_char *p = (u_char *) &frame[*frameUsed];106107used = count = min_t(size_t, userCount, frameLeft);108if (copy_from_user(p,userPtr,count))109return -EFAULT;110*frameUsed += used;111return used;112}113114115/* a bit too complicated to optimise right now ..*/116static ssize_t q40_ctx_law(const u_char __user *userPtr, size_t userCount,117u_char frame[], ssize_t *frameUsed,118ssize_t frameLeft)119{120unsigned char *table = (unsigned char *)121(dmasound.soft.format == AFMT_MU_LAW ? dmasound_ulaw2dma8: dmasound_alaw2dma8);122unsigned int data = expand_data;123u_char *p = (u_char *) &frame[*frameUsed];124int bal = expand_bal;125int hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed;126int utotal, ftotal;127128ftotal = frameLeft;129utotal = userCount;130while (frameLeft) {131u_char c;132if (bal < 0) {133if (userCount == 0)134break;135if (get_user(c, userPtr++))136return -EFAULT;137data = table[c];138data += 0x80;139userCount--;140bal += hSpeed;141}142*p++ = data;143frameLeft--;144bal -= sSpeed;145}146expand_bal = bal;147expand_data = data;148*frameUsed += (ftotal - frameLeft);149utotal -= userCount;150return utotal;151}152153154static ssize_t q40_ctx_s8(const u_char __user *userPtr, size_t userCount,155u_char frame[], ssize_t *frameUsed,156ssize_t frameLeft)157{158u_char *p = (u_char *) &frame[*frameUsed];159unsigned int data = expand_data;160int bal = expand_bal;161int hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed;162int utotal, ftotal;163164165ftotal = frameLeft;166utotal = userCount;167while (frameLeft) {168u_char c;169if (bal < 0) {170if (userCount == 0)171break;172if (get_user(c, userPtr++))173return -EFAULT;174data = c ;175data += 0x80;176userCount--;177bal += hSpeed;178}179*p++ = data;180frameLeft--;181bal -= sSpeed;182}183expand_bal = bal;184expand_data = data;185*frameUsed += (ftotal - frameLeft);186utotal -= userCount;187return utotal;188}189190191static ssize_t q40_ctx_u8(const u_char __user *userPtr, size_t userCount,192u_char frame[], ssize_t *frameUsed,193ssize_t frameLeft)194{195u_char *p = (u_char *) &frame[*frameUsed];196unsigned int data = expand_data;197int bal = expand_bal;198int hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed;199int utotal, ftotal;200201ftotal = frameLeft;202utotal = userCount;203while (frameLeft) {204u_char c;205if (bal < 0) {206if (userCount == 0)207break;208if (get_user(c, userPtr++))209return -EFAULT;210data = c ;211userCount--;212bal += hSpeed;213}214*p++ = data;215frameLeft--;216bal -= sSpeed;217}218expand_bal = bal;219expand_data = data;220*frameUsed += (ftotal - frameLeft) ;221utotal -= userCount;222return utotal;223}224225/* compressing versions */226static ssize_t q40_ctc_law(const u_char __user *userPtr, size_t userCount,227u_char frame[], ssize_t *frameUsed,228ssize_t frameLeft)229{230unsigned char *table = (unsigned char *)231(dmasound.soft.format == AFMT_MU_LAW ? dmasound_ulaw2dma8: dmasound_alaw2dma8);232unsigned int data = expand_data;233u_char *p = (u_char *) &frame[*frameUsed];234int bal = expand_bal;235int hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed;236int utotal, ftotal;237238ftotal = frameLeft;239utotal = userCount;240while (frameLeft) {241u_char c;242while(bal<0) {243if (userCount == 0)244goto lout;245if (!(bal<(-hSpeed))) {246if (get_user(c, userPtr))247return -EFAULT;248data = 0x80 + table[c];249}250userPtr++;251userCount--;252bal += hSpeed;253}254*p++ = data;255frameLeft--;256bal -= sSpeed;257}258lout:259expand_bal = bal;260expand_data = data;261*frameUsed += (ftotal - frameLeft);262utotal -= userCount;263return utotal;264}265266267static ssize_t q40_ctc_s8(const u_char __user *userPtr, size_t userCount,268u_char frame[], ssize_t *frameUsed,269ssize_t frameLeft)270{271u_char *p = (u_char *) &frame[*frameUsed];272unsigned int data = expand_data;273int bal = expand_bal;274int hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed;275int utotal, ftotal;276277ftotal = frameLeft;278utotal = userCount;279while (frameLeft) {280u_char c;281while (bal < 0) {282if (userCount == 0)283goto lout;284if (!(bal<(-hSpeed))) {285if (get_user(c, userPtr))286return -EFAULT;287data = c + 0x80;288}289userPtr++;290userCount--;291bal += hSpeed;292}293*p++ = data;294frameLeft--;295bal -= sSpeed;296}297lout:298expand_bal = bal;299expand_data = data;300*frameUsed += (ftotal - frameLeft);301utotal -= userCount;302return utotal;303}304305306static ssize_t q40_ctc_u8(const u_char __user *userPtr, size_t userCount,307u_char frame[], ssize_t *frameUsed,308ssize_t frameLeft)309{310u_char *p = (u_char *) &frame[*frameUsed];311unsigned int data = expand_data;312int bal = expand_bal;313int hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed;314int utotal, ftotal;315316ftotal = frameLeft;317utotal = userCount;318while (frameLeft) {319u_char c;320while (bal < 0) {321if (userCount == 0)322goto lout;323if (!(bal<(-hSpeed))) {324if (get_user(c, userPtr))325return -EFAULT;326data = c ;327}328userPtr++;329userCount--;330bal += hSpeed;331}332*p++ = data;333frameLeft--;334bal -= sSpeed;335}336lout:337expand_bal = bal;338expand_data = data;339*frameUsed += (ftotal - frameLeft) ;340utotal -= userCount;341return utotal;342}343344345static TRANS transQ40Normal = {346q40_ct_law, q40_ct_law, q40_ct_s8, q40_ct_u8, NULL, NULL, NULL, NULL347};348349static TRANS transQ40Expanding = {350q40_ctx_law, q40_ctx_law, q40_ctx_s8, q40_ctx_u8, NULL, NULL, NULL, NULL351};352353static TRANS transQ40Compressing = {354q40_ctc_law, q40_ctc_law, q40_ctc_s8, q40_ctc_u8, NULL, NULL, NULL, NULL355};356357358/*** Low level stuff *********************************************************/359360static void *Q40Alloc(unsigned int size, gfp_t flags)361{362return kmalloc(size, flags); /* change to vmalloc */363}364365static void Q40Free(void *ptr, unsigned int size)366{367kfree(ptr);368}369370static int __init Q40IrqInit(void)371{372/* Register interrupt handler. */373if (request_irq(Q40_IRQ_SAMPLE, Q40StereoInterrupt, 0,374"DMA sound", Q40Interrupt))375return 0;376377return(1);378}379380381#ifdef MODULE382static void Q40IrqCleanUp(void)383{384master_outb(0,SAMPLE_ENABLE_REG);385free_irq(Q40_IRQ_SAMPLE, Q40Interrupt);386}387#endif /* MODULE */388389390static void Q40Silence(void)391{392master_outb(0,SAMPLE_ENABLE_REG);393*DAC_LEFT=*DAC_RIGHT=127;394}395396static char *q40_pp;397static unsigned int q40_sc;398399static void Q40PlayNextFrame(int index)400{401u_char *start;402u_long size;403u_char speed;404int error;405406/* used by Q40Play() if all doubts whether there really is something407* to be played are already wiped out.408*/409start = write_sq.buffers[write_sq.front];410size = (write_sq.count == index ? write_sq.rear_size : write_sq.block_size);411412q40_pp=start;413q40_sc=size;414415write_sq.front = (write_sq.front+1) % write_sq.max_count;416write_sq.active++;417418speed=(dmasound.hard.speed==10000 ? 0 : 1);419420master_outb( 0,SAMPLE_ENABLE_REG);421free_irq(Q40_IRQ_SAMPLE, Q40Interrupt);422if (dmasound.soft.stereo)423error = request_irq(Q40_IRQ_SAMPLE, Q40StereoInterrupt, 0,424"Q40 sound", Q40Interrupt);425else426error = request_irq(Q40_IRQ_SAMPLE, Q40MonoInterrupt, 0,427"Q40 sound", Q40Interrupt);428if (error && printk_ratelimit())429pr_err("Couldn't register sound interrupt\n");430431master_outb( speed, SAMPLE_RATE_REG);432master_outb( 1,SAMPLE_CLEAR_REG);433master_outb( 1,SAMPLE_ENABLE_REG);434}435436static void Q40Play(void)437{438unsigned long flags;439440if (write_sq.active || write_sq.count<=0 ) {441/* There's already a frame loaded */442return;443}444445/* nothing in the queue */446if (write_sq.count <= 1 && write_sq.rear_size < write_sq.block_size && !write_sq.syncing) {447/* hmmm, the only existing frame is not448* yet filled and we're not syncing?449*/450return;451}452spin_lock_irqsave(&dmasound.lock, flags);453Q40PlayNextFrame(1);454spin_unlock_irqrestore(&dmasound.lock, flags);455}456457static irqreturn_t Q40StereoInterrupt(int irq, void *dummy)458{459spin_lock(&dmasound.lock);460if (q40_sc>1){461*DAC_LEFT=*q40_pp++;462*DAC_RIGHT=*q40_pp++;463q40_sc -=2;464master_outb(1,SAMPLE_CLEAR_REG);465}else Q40Interrupt();466spin_unlock(&dmasound.lock);467return IRQ_HANDLED;468}469static irqreturn_t Q40MonoInterrupt(int irq, void *dummy)470{471spin_lock(&dmasound.lock);472if (q40_sc>0){473*DAC_LEFT=*q40_pp;474*DAC_RIGHT=*q40_pp++;475q40_sc --;476master_outb(1,SAMPLE_CLEAR_REG);477}else Q40Interrupt();478spin_unlock(&dmasound.lock);479return IRQ_HANDLED;480}481static void Q40Interrupt(void)482{483if (!write_sq.active) {484/* playing was interrupted and sq_reset() has already cleared485* the sq variables, so better don't do anything here.486*/487WAKE_UP(write_sq.sync_queue);488master_outb(0,SAMPLE_ENABLE_REG); /* better safe */489goto exit;490} else write_sq.active=0;491write_sq.count--;492Q40Play();493494if (q40_sc<2)495{ /* there was nothing to play, disable irq */496master_outb(0,SAMPLE_ENABLE_REG);497*DAC_LEFT=*DAC_RIGHT=127;498}499WAKE_UP(write_sq.action_queue);500501exit:502master_outb(1,SAMPLE_CLEAR_REG);503}504505506static void Q40Init(void)507{508int i, idx;509const int freq[] = {10000, 20000};510511/* search a frequency that fits into the allowed error range */512513idx = -1;514for (i = 0; i < 2; i++)515if ((100 * abs(dmasound.soft.speed - freq[i]) / freq[i]) <= catchRadius)516idx = i;517518dmasound.hard = dmasound.soft;519/*sound.hard.stereo=1;*/ /* no longer true */520dmasound.hard.size=8;521522if (idx > -1) {523dmasound.soft.speed = freq[idx];524dmasound.trans_write = &transQ40Normal;525} else526dmasound.trans_write = &transQ40Expanding;527528Q40Silence();529530if (dmasound.hard.speed > 20200) {531/* squeeze the sound, we do that */532dmasound.hard.speed = 20000;533dmasound.trans_write = &transQ40Compressing;534} else if (dmasound.hard.speed > 10000) {535dmasound.hard.speed = 20000;536} else {537dmasound.hard.speed = 10000;538}539expand_bal = -dmasound.soft.speed;540}541542543static int Q40SetFormat(int format)544{545/* Q40 sound supports only 8bit modes */546547switch (format) {548case AFMT_QUERY:549return(dmasound.soft.format);550case AFMT_MU_LAW:551case AFMT_A_LAW:552case AFMT_S8:553case AFMT_U8:554break;555default:556format = AFMT_S8;557}558559dmasound.soft.format = format;560dmasound.soft.size = 8;561if (dmasound.minDev == SND_DEV_DSP) {562dmasound.dsp.format = format;563dmasound.dsp.size = 8;564}565Q40Init();566567return(format);568}569570static int Q40SetVolume(int volume)571{572return 0;573}574575576/*** Machine definitions *****************************************************/577578static SETTINGS def_hard = {579.format = AFMT_U8,580.stereo = 0,581.size = 8,582.speed = 10000583} ;584585static SETTINGS def_soft = {586.format = AFMT_U8,587.stereo = 0,588.size = 8,589.speed = 8000590} ;591592static MACHINE machQ40 = {593.name = "Q40",594.name2 = "Q40",595.owner = THIS_MODULE,596.dma_alloc = Q40Alloc,597.dma_free = Q40Free,598.irqinit = Q40IrqInit,599#ifdef MODULE600.irqcleanup = Q40IrqCleanUp,601#endif /* MODULE */602.init = Q40Init,603.silence = Q40Silence,604.setFormat = Q40SetFormat,605.setVolume = Q40SetVolume,606.play = Q40Play,607.min_dsp_speed = 10000,608.version = ((DMASOUND_Q40_REVISION<<8) | DMASOUND_Q40_EDITION),609.hardware_afmts = AFMT_U8, /* h'ware-supported formats *only* here */610.capabilities = DSP_CAP_BATCH /* As per SNDCTL_DSP_GETCAPS */611};612613614/*** Config & Setup **********************************************************/615616617static int __init dmasound_q40_init(void)618{619if (MACH_IS_Q40) {620dmasound.mach = machQ40;621dmasound.mach.default_hard = def_hard ;622dmasound.mach.default_soft = def_soft ;623return dmasound_init();624} else625return -ENODEV;626}627628static void __exit dmasound_q40_cleanup(void)629{630dmasound_deinit();631}632633module_init(dmasound_q40_init);634module_exit(dmasound_q40_cleanup);635636MODULE_DESCRIPTION("Q40/Q60 sound driver");637MODULE_LICENSE("GPL");638639640