640 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * linux/sound/oss/dmasound/dmasound_q40.c4 *5 * Q40 DMA Sound Driver6 *7 * See linux/sound/oss/dmasound/dmasound_core.c for copyright and credits8 * prior to 28/01/20019 *10 * 28/01/2001 [0.1] Iain Sandoe11 * - added versioning12 * - put in and populated the hardware_afmts field.13 * [0.2] - put in SNDCTL_DSP_GETCAPS value.14 * [0.3] - put in default hard/soft settings.15 */16 17 18#include <linux/module.h>19#include <linux/init.h>20#include <linux/slab.h>21#include <linux/soundcard.h>22#include <linux/interrupt.h>23 24#include <linux/uaccess.h>25#include <asm/q40ints.h>26#include <asm/q40_master.h>27 28#include "dmasound.h"29 30#define DMASOUND_Q40_REVISION 031#define DMASOUND_Q40_EDITION 332 33static int expand_bal; /* Balance factor for expanding (not volume!) */34static int expand_data; /* Data for expanding */35 36 37/*** Low level stuff *********************************************************/38 39 40static void *Q40Alloc(unsigned int size, gfp_t flags);41static void Q40Free(void *, unsigned int);42static int Q40IrqInit(void);43#ifdef MODULE44static void Q40IrqCleanUp(void);45#endif46static void Q40Silence(void);47static void Q40Init(void);48static int Q40SetFormat(int format);49static int Q40SetVolume(int volume);50static void Q40PlayNextFrame(int index);51static void Q40Play(void);52static irqreturn_t Q40StereoInterrupt(int irq, void *dummy);53static irqreturn_t Q40MonoInterrupt(int irq, void *dummy);54static void Q40Interrupt(void);55 56 57/*** Mid level stuff *********************************************************/58 59 60 61/* userCount, frameUsed, frameLeft == byte counts */62static ssize_t q40_ct_law(const u_char __user *userPtr, size_t userCount,63 u_char frame[], ssize_t *frameUsed,64 ssize_t frameLeft)65{66 char *table = dmasound.soft.format == AFMT_MU_LAW ? dmasound_ulaw2dma8: dmasound_alaw2dma8;67 ssize_t count, used;68 u_char *p = (u_char *) &frame[*frameUsed];69 70 used = count = min_t(size_t, userCount, frameLeft);71 if (copy_from_user(p,userPtr,count))72 return -EFAULT;73 while (count > 0) {74 *p = table[*p]+128;75 p++;76 count--;77 }78 *frameUsed += used ;79 return used;80}81 82 83static ssize_t q40_ct_s8(const u_char __user *userPtr, size_t userCount,84 u_char frame[], ssize_t *frameUsed,85 ssize_t frameLeft)86{87 ssize_t count, used;88 u_char *p = (u_char *) &frame[*frameUsed];89 90 used = count = min_t(size_t, userCount, frameLeft);91 if (copy_from_user(p,userPtr,count))92 return -EFAULT;93 while (count > 0) {94 *p = *p + 128;95 p++;96 count--;97 }98 *frameUsed += used;99 return used;100}101 102static ssize_t q40_ct_u8(const u_char __user *userPtr, size_t userCount,103 u_char frame[], ssize_t *frameUsed,104 ssize_t frameLeft)105{106 ssize_t count, used;107 u_char *p = (u_char *) &frame[*frameUsed];108 109 used = count = min_t(size_t, userCount, frameLeft);110 if (copy_from_user(p,userPtr,count))111 return -EFAULT;112 *frameUsed += used;113 return used;114}115 116 117/* a bit too complicated to optimise right now ..*/118static ssize_t q40_ctx_law(const u_char __user *userPtr, size_t userCount,119 u_char frame[], ssize_t *frameUsed,120 ssize_t frameLeft)121{122 unsigned char *table = (unsigned char *)123 (dmasound.soft.format == AFMT_MU_LAW ? dmasound_ulaw2dma8: dmasound_alaw2dma8);124 unsigned int data = expand_data;125 u_char *p = (u_char *) &frame[*frameUsed];126 int bal = expand_bal;127 int hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed;128 int utotal, ftotal;129 130 ftotal = frameLeft;131 utotal = userCount;132 while (frameLeft) {133 u_char c;134 if (bal < 0) {135 if (userCount == 0)136 break;137 if (get_user(c, userPtr++))138 return -EFAULT;139 data = table[c];140 data += 0x80;141 userCount--;142 bal += hSpeed;143 }144 *p++ = data;145 frameLeft--;146 bal -= sSpeed;147 }148 expand_bal = bal;149 expand_data = data;150 *frameUsed += (ftotal - frameLeft);151 utotal -= userCount;152 return utotal;153}154 155 156static ssize_t q40_ctx_s8(const u_char __user *userPtr, size_t userCount,157 u_char frame[], ssize_t *frameUsed,158 ssize_t frameLeft)159{160 u_char *p = (u_char *) &frame[*frameUsed];161 unsigned int data = expand_data;162 int bal = expand_bal;163 int hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed;164 int utotal, ftotal;165 166 167 ftotal = frameLeft;168 utotal = userCount;169 while (frameLeft) {170 u_char c;171 if (bal < 0) {172 if (userCount == 0)173 break;174 if (get_user(c, userPtr++))175 return -EFAULT;176 data = c ;177 data += 0x80;178 userCount--;179 bal += hSpeed;180 }181 *p++ = data;182 frameLeft--;183 bal -= sSpeed;184 }185 expand_bal = bal;186 expand_data = data;187 *frameUsed += (ftotal - frameLeft);188 utotal -= userCount;189 return utotal;190}191 192 193static ssize_t q40_ctx_u8(const u_char __user *userPtr, size_t userCount,194 u_char frame[], ssize_t *frameUsed,195 ssize_t frameLeft)196{197 u_char *p = (u_char *) &frame[*frameUsed];198 unsigned int data = expand_data;199 int bal = expand_bal;200 int hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed;201 int utotal, ftotal;202 203 ftotal = frameLeft;204 utotal = userCount;205 while (frameLeft) {206 u_char c;207 if (bal < 0) {208 if (userCount == 0)209 break;210 if (get_user(c, userPtr++))211 return -EFAULT;212 data = c ;213 userCount--;214 bal += hSpeed;215 }216 *p++ = data;217 frameLeft--;218 bal -= sSpeed;219 }220 expand_bal = bal;221 expand_data = data;222 *frameUsed += (ftotal - frameLeft) ;223 utotal -= userCount;224 return utotal;225}226 227/* compressing versions */228static ssize_t q40_ctc_law(const u_char __user *userPtr, size_t userCount,229 u_char frame[], ssize_t *frameUsed,230 ssize_t frameLeft)231{232 unsigned char *table = (unsigned char *)233 (dmasound.soft.format == AFMT_MU_LAW ? dmasound_ulaw2dma8: dmasound_alaw2dma8);234 unsigned int data = expand_data;235 u_char *p = (u_char *) &frame[*frameUsed];236 int bal = expand_bal;237 int hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed;238 int utotal, ftotal;239 240 ftotal = frameLeft;241 utotal = userCount;242 while (frameLeft) {243 u_char c;244 while(bal<0) {245 if (userCount == 0)246 goto lout;247 if (!(bal<(-hSpeed))) {248 if (get_user(c, userPtr))249 return -EFAULT;250 data = 0x80 + table[c];251 }252 userPtr++;253 userCount--;254 bal += hSpeed;255 }256 *p++ = data;257 frameLeft--;258 bal -= sSpeed;259 }260 lout:261 expand_bal = bal;262 expand_data = data;263 *frameUsed += (ftotal - frameLeft);264 utotal -= userCount;265 return utotal;266}267 268 269static ssize_t q40_ctc_s8(const u_char __user *userPtr, size_t userCount,270 u_char frame[], ssize_t *frameUsed,271 ssize_t frameLeft)272{273 u_char *p = (u_char *) &frame[*frameUsed];274 unsigned int data = expand_data;275 int bal = expand_bal;276 int hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed;277 int utotal, ftotal;278 279 ftotal = frameLeft;280 utotal = userCount;281 while (frameLeft) {282 u_char c;283 while (bal < 0) {284 if (userCount == 0)285 goto lout;286 if (!(bal<(-hSpeed))) {287 if (get_user(c, userPtr))288 return -EFAULT;289 data = c + 0x80;290 }291 userPtr++;292 userCount--;293 bal += hSpeed;294 }295 *p++ = data;296 frameLeft--;297 bal -= sSpeed;298 }299 lout:300 expand_bal = bal;301 expand_data = data;302 *frameUsed += (ftotal - frameLeft);303 utotal -= userCount;304 return utotal;305}306 307 308static ssize_t q40_ctc_u8(const u_char __user *userPtr, size_t userCount,309 u_char frame[], ssize_t *frameUsed,310 ssize_t frameLeft)311{312 u_char *p = (u_char *) &frame[*frameUsed];313 unsigned int data = expand_data;314 int bal = expand_bal;315 int hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed;316 int utotal, ftotal;317 318 ftotal = frameLeft;319 utotal = userCount;320 while (frameLeft) {321 u_char c;322 while (bal < 0) {323 if (userCount == 0)324 goto lout;325 if (!(bal<(-hSpeed))) {326 if (get_user(c, userPtr))327 return -EFAULT;328 data = c ;329 }330 userPtr++;331 userCount--;332 bal += hSpeed;333 }334 *p++ = data;335 frameLeft--;336 bal -= sSpeed;337 }338 lout:339 expand_bal = bal;340 expand_data = data;341 *frameUsed += (ftotal - frameLeft) ;342 utotal -= userCount;343 return utotal;344}345 346 347static TRANS transQ40Normal = {348 q40_ct_law, q40_ct_law, q40_ct_s8, q40_ct_u8, NULL, NULL, NULL, NULL349};350 351static TRANS transQ40Expanding = {352 q40_ctx_law, q40_ctx_law, q40_ctx_s8, q40_ctx_u8, NULL, NULL, NULL, NULL353};354 355static TRANS transQ40Compressing = {356 q40_ctc_law, q40_ctc_law, q40_ctc_s8, q40_ctc_u8, NULL, NULL, NULL, NULL357};358 359 360/*** Low level stuff *********************************************************/361 362static void *Q40Alloc(unsigned int size, gfp_t flags)363{364 return kmalloc(size, flags); /* change to vmalloc */365}366 367static void Q40Free(void *ptr, unsigned int size)368{369 kfree(ptr);370}371 372static int __init Q40IrqInit(void)373{374 /* Register interrupt handler. */375 if (request_irq(Q40_IRQ_SAMPLE, Q40StereoInterrupt, 0,376 "DMA sound", Q40Interrupt))377 return 0;378 379 return(1);380}381 382 383#ifdef MODULE384static void Q40IrqCleanUp(void)385{386 master_outb(0,SAMPLE_ENABLE_REG);387 free_irq(Q40_IRQ_SAMPLE, Q40Interrupt);388}389#endif /* MODULE */390 391 392static void Q40Silence(void)393{394 master_outb(0,SAMPLE_ENABLE_REG);395 *DAC_LEFT=*DAC_RIGHT=127;396}397 398static char *q40_pp;399static unsigned int q40_sc;400 401static void Q40PlayNextFrame(int index)402{403 u_char *start;404 u_long size;405 u_char speed;406 int error;407 408 /* used by Q40Play() if all doubts whether there really is something409 * to be played are already wiped out.410 */411 start = write_sq.buffers[write_sq.front];412 size = (write_sq.count == index ? write_sq.rear_size : write_sq.block_size);413 414 q40_pp=start;415 q40_sc=size;416 417 write_sq.front = (write_sq.front+1) % write_sq.max_count;418 write_sq.active++;419 420 speed=(dmasound.hard.speed==10000 ? 0 : 1);421 422 master_outb( 0,SAMPLE_ENABLE_REG);423 free_irq(Q40_IRQ_SAMPLE, Q40Interrupt);424 if (dmasound.soft.stereo)425 error = request_irq(Q40_IRQ_SAMPLE, Q40StereoInterrupt, 0,426 "Q40 sound", Q40Interrupt);427 else428 error = request_irq(Q40_IRQ_SAMPLE, Q40MonoInterrupt, 0,429 "Q40 sound", Q40Interrupt);430 if (error && printk_ratelimit())431 pr_err("Couldn't register sound interrupt\n");432 433 master_outb( speed, SAMPLE_RATE_REG);434 master_outb( 1,SAMPLE_CLEAR_REG);435 master_outb( 1,SAMPLE_ENABLE_REG);436}437 438static void Q40Play(void)439{440 unsigned long flags;441 442 if (write_sq.active || write_sq.count<=0 ) {443 /* There's already a frame loaded */444 return;445 }446 447 /* nothing in the queue */448 if (write_sq.count <= 1 && write_sq.rear_size < write_sq.block_size && !write_sq.syncing) {449 /* hmmm, the only existing frame is not450 * yet filled and we're not syncing?451 */452 return;453 }454 spin_lock_irqsave(&dmasound.lock, flags);455 Q40PlayNextFrame(1);456 spin_unlock_irqrestore(&dmasound.lock, flags);457}458 459static irqreturn_t Q40StereoInterrupt(int irq, void *dummy)460{461 spin_lock(&dmasound.lock);462 if (q40_sc>1){463 *DAC_LEFT=*q40_pp++;464 *DAC_RIGHT=*q40_pp++;465 q40_sc -=2;466 master_outb(1,SAMPLE_CLEAR_REG);467 }else Q40Interrupt();468 spin_unlock(&dmasound.lock);469 return IRQ_HANDLED;470}471static irqreturn_t Q40MonoInterrupt(int irq, void *dummy)472{473 spin_lock(&dmasound.lock);474 if (q40_sc>0){475 *DAC_LEFT=*q40_pp;476 *DAC_RIGHT=*q40_pp++;477 q40_sc --;478 master_outb(1,SAMPLE_CLEAR_REG);479 }else Q40Interrupt();480 spin_unlock(&dmasound.lock);481 return IRQ_HANDLED;482}483static void Q40Interrupt(void)484{485 if (!write_sq.active) {486 /* playing was interrupted and sq_reset() has already cleared487 * the sq variables, so better don't do anything here.488 */489 WAKE_UP(write_sq.sync_queue);490 master_outb(0,SAMPLE_ENABLE_REG); /* better safe */491 goto exit;492 } else write_sq.active=0;493 write_sq.count--;494 Q40Play();495 496 if (q40_sc<2)497 { /* there was nothing to play, disable irq */498 master_outb(0,SAMPLE_ENABLE_REG);499 *DAC_LEFT=*DAC_RIGHT=127;500 }501 WAKE_UP(write_sq.action_queue);502 503 exit:504 master_outb(1,SAMPLE_CLEAR_REG);505}506 507 508static void Q40Init(void)509{510 int i, idx;511 const int freq[] = {10000, 20000};512 513 /* search a frequency that fits into the allowed error range */514 515 idx = -1;516 for (i = 0; i < 2; i++)517 if ((100 * abs(dmasound.soft.speed - freq[i]) / freq[i]) <= catchRadius)518 idx = i;519 520 dmasound.hard = dmasound.soft;521 /*sound.hard.stereo=1;*/ /* no longer true */522 dmasound.hard.size=8;523 524 if (idx > -1) {525 dmasound.soft.speed = freq[idx];526 dmasound.trans_write = &transQ40Normal;527 } else528 dmasound.trans_write = &transQ40Expanding;529 530 Q40Silence();531 532 if (dmasound.hard.speed > 20200) {533 /* squeeze the sound, we do that */534 dmasound.hard.speed = 20000;535 dmasound.trans_write = &transQ40Compressing;536 } else if (dmasound.hard.speed > 10000) {537 dmasound.hard.speed = 20000;538 } else {539 dmasound.hard.speed = 10000;540 }541 expand_bal = -dmasound.soft.speed;542}543 544 545static int Q40SetFormat(int format)546{547 /* Q40 sound supports only 8bit modes */548 549 switch (format) {550 case AFMT_QUERY:551 return(dmasound.soft.format);552 case AFMT_MU_LAW:553 case AFMT_A_LAW:554 case AFMT_S8:555 case AFMT_U8:556 break;557 default:558 format = AFMT_S8;559 }560 561 dmasound.soft.format = format;562 dmasound.soft.size = 8;563 if (dmasound.minDev == SND_DEV_DSP) {564 dmasound.dsp.format = format;565 dmasound.dsp.size = 8;566 }567 Q40Init();568 569 return(format);570}571 572static int Q40SetVolume(int volume)573{574 return 0;575}576 577 578/*** Machine definitions *****************************************************/579 580static SETTINGS def_hard = {581 .format = AFMT_U8,582 .stereo = 0,583 .size = 8,584 .speed = 10000585} ;586 587static SETTINGS def_soft = {588 .format = AFMT_U8,589 .stereo = 0,590 .size = 8,591 .speed = 8000592} ;593 594static MACHINE machQ40 = {595 .name = "Q40",596 .name2 = "Q40",597 .owner = THIS_MODULE,598 .dma_alloc = Q40Alloc,599 .dma_free = Q40Free,600 .irqinit = Q40IrqInit,601#ifdef MODULE602 .irqcleanup = Q40IrqCleanUp,603#endif /* MODULE */604 .init = Q40Init,605 .silence = Q40Silence,606 .setFormat = Q40SetFormat,607 .setVolume = Q40SetVolume,608 .play = Q40Play,609 .min_dsp_speed = 10000,610 .version = ((DMASOUND_Q40_REVISION<<8) | DMASOUND_Q40_EDITION),611 .hardware_afmts = AFMT_U8, /* h'ware-supported formats *only* here */612 .capabilities = DSP_CAP_BATCH /* As per SNDCTL_DSP_GETCAPS */613};614 615 616/*** Config & Setup **********************************************************/617 618 619static int __init dmasound_q40_init(void)620{621 if (MACH_IS_Q40) {622 dmasound.mach = machQ40;623 dmasound.mach.default_hard = def_hard ;624 dmasound.mach.default_soft = def_soft ;625 return dmasound_init();626 } else627 return -ENODEV;628}629 630static void __exit dmasound_q40_cleanup(void)631{632 dmasound_deinit();633}634 635module_init(dmasound_q40_init);636module_exit(dmasound_q40_cleanup);637 638MODULE_DESCRIPTION("Q40/Q60 sound driver");639MODULE_LICENSE("GPL");640