11a59d1b8SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later 21da177e4SLinus Torvalds /* 31da177e4SLinus Torvalds * pcm emulation on emu8000 wavetable 41da177e4SLinus Torvalds * 51da177e4SLinus Torvalds * Copyright (C) 2002 Takashi Iwai <tiwai@suse.de> 61da177e4SLinus Torvalds */ 71da177e4SLinus Torvalds 81da177e4SLinus Torvalds #include "emu8000_local.h" 9174cd4b1SIngo Molnar 10174cd4b1SIngo Molnar #include <linux/sched/signal.h> 111da177e4SLinus Torvalds #include <linux/init.h> 125a0e3ad6STejun Heo #include <linux/slab.h> 131da177e4SLinus Torvalds #include <sound/initval.h> 141da177e4SLinus Torvalds #include <sound/pcm.h> 151da177e4SLinus Torvalds 161da177e4SLinus Torvalds /* 171da177e4SLinus Torvalds * define the following if you want to use this pcm with non-interleaved mode 181da177e4SLinus Torvalds */ 191da177e4SLinus Torvalds /* #define USE_NONINTERLEAVE */ 201da177e4SLinus Torvalds 211da177e4SLinus Torvalds /* NOTE: for using the non-interleaved mode with alsa-lib, you have to set 221da177e4SLinus Torvalds * mmap_emulation flag to 1 in your .asoundrc, such like 231da177e4SLinus Torvalds * 241da177e4SLinus Torvalds * pcm.emu8k { 251da177e4SLinus Torvalds * type plug 261da177e4SLinus Torvalds * slave.pcm { 271da177e4SLinus Torvalds * type hw 281da177e4SLinus Torvalds * card 0 291da177e4SLinus Torvalds * device 1 301da177e4SLinus Torvalds * mmap_emulation 1 311da177e4SLinus Torvalds * } 321da177e4SLinus Torvalds * } 331da177e4SLinus Torvalds * 341da177e4SLinus Torvalds * besides, for the time being, the non-interleaved mode doesn't work well on 351da177e4SLinus Torvalds * alsa-lib... 361da177e4SLinus Torvalds */ 371da177e4SLinus Torvalds 381da177e4SLinus Torvalds 391da177e4SLinus Torvalds struct snd_emu8k_pcm { 40029d64b0STakashi Iwai struct snd_emu8000 *emu; 41029d64b0STakashi Iwai struct snd_pcm_substream *substream; 421da177e4SLinus Torvalds 431da177e4SLinus Torvalds unsigned int allocated_bytes; 44029d64b0STakashi Iwai struct snd_util_memblk *block; 451da177e4SLinus Torvalds unsigned int offset; 461da177e4SLinus Torvalds unsigned int buf_size; 471da177e4SLinus Torvalds unsigned int period_size; 481da177e4SLinus Torvalds unsigned int loop_start[2]; 491da177e4SLinus Torvalds unsigned int pitch; 501da177e4SLinus Torvalds int panning[2]; 511da177e4SLinus Torvalds int last_ptr; 521da177e4SLinus Torvalds int period_pos; 531da177e4SLinus Torvalds int voices; 541da177e4SLinus Torvalds unsigned int dram_opened: 1; 551da177e4SLinus Torvalds unsigned int running: 1; 561da177e4SLinus Torvalds unsigned int timer_running: 1; 571da177e4SLinus Torvalds struct timer_list timer; 581da177e4SLinus Torvalds spinlock_t timer_lock; 591da177e4SLinus Torvalds }; 601da177e4SLinus Torvalds 611da177e4SLinus Torvalds #define LOOP_BLANK_SIZE 8 621da177e4SLinus Torvalds 631da177e4SLinus Torvalds 641da177e4SLinus Torvalds /* 651da177e4SLinus Torvalds * open up channels for the simultaneous data transfer and playback 661da177e4SLinus Torvalds */ 671da177e4SLinus Torvalds static int 68029d64b0STakashi Iwai emu8k_open_dram_for_pcm(struct snd_emu8000 *emu, int channels) 691da177e4SLinus Torvalds { 701da177e4SLinus Torvalds int i; 711da177e4SLinus Torvalds 721da177e4SLinus Torvalds /* reserve up to 2 voices for playback */ 731da177e4SLinus Torvalds snd_emux_lock_voice(emu->emu, 0); 741da177e4SLinus Torvalds if (channels > 1) 751da177e4SLinus Torvalds snd_emux_lock_voice(emu->emu, 1); 761da177e4SLinus Torvalds 771da177e4SLinus Torvalds /* reserve 28 voices for loading */ 781da177e4SLinus Torvalds for (i = channels + 1; i < EMU8000_DRAM_VOICES; i++) { 791da177e4SLinus Torvalds unsigned int mode = EMU8000_RAM_WRITE; 801da177e4SLinus Torvalds snd_emux_lock_voice(emu->emu, i); 811da177e4SLinus Torvalds #ifndef USE_NONINTERLEAVE 821da177e4SLinus Torvalds if (channels > 1 && (i & 1) != 0) 831da177e4SLinus Torvalds mode |= EMU8000_RAM_RIGHT; 841da177e4SLinus Torvalds #endif 851da177e4SLinus Torvalds snd_emu8000_dma_chan(emu, i, mode); 861da177e4SLinus Torvalds } 871da177e4SLinus Torvalds 881da177e4SLinus Torvalds /* assign voice 31 and 32 to ROM */ 891da177e4SLinus Torvalds EMU8000_VTFT_WRITE(emu, 30, 0); 901da177e4SLinus Torvalds EMU8000_PSST_WRITE(emu, 30, 0x1d8); 911da177e4SLinus Torvalds EMU8000_CSL_WRITE(emu, 30, 0x1e0); 921da177e4SLinus Torvalds EMU8000_CCCA_WRITE(emu, 30, 0x1d8); 931da177e4SLinus Torvalds EMU8000_VTFT_WRITE(emu, 31, 0); 941da177e4SLinus Torvalds EMU8000_PSST_WRITE(emu, 31, 0x1d8); 951da177e4SLinus Torvalds EMU8000_CSL_WRITE(emu, 31, 0x1e0); 961da177e4SLinus Torvalds EMU8000_CCCA_WRITE(emu, 31, 0x1d8); 971da177e4SLinus Torvalds 981da177e4SLinus Torvalds return 0; 991da177e4SLinus Torvalds } 1001da177e4SLinus Torvalds 1011da177e4SLinus Torvalds /* 1021da177e4SLinus Torvalds */ 1031da177e4SLinus Torvalds static void 104029d64b0STakashi Iwai snd_emu8000_write_wait(struct snd_emu8000 *emu, int can_schedule) 1051da177e4SLinus Torvalds { 1061da177e4SLinus Torvalds while ((EMU8000_SMALW_READ(emu) & 0x80000000) != 0) { 1071da177e4SLinus Torvalds if (can_schedule) { 1088433a509SNishanth Aravamudan schedule_timeout_interruptible(1); 1091da177e4SLinus Torvalds if (signal_pending(current)) 1101da177e4SLinus Torvalds break; 1111da177e4SLinus Torvalds } 1121da177e4SLinus Torvalds } 1131da177e4SLinus Torvalds } 1141da177e4SLinus Torvalds 1151da177e4SLinus Torvalds /* 1161da177e4SLinus Torvalds * close all channels 1171da177e4SLinus Torvalds */ 1181da177e4SLinus Torvalds static void 119029d64b0STakashi Iwai emu8k_close_dram(struct snd_emu8000 *emu) 1201da177e4SLinus Torvalds { 1211da177e4SLinus Torvalds int i; 1221da177e4SLinus Torvalds 1231da177e4SLinus Torvalds for (i = 0; i < 2; i++) 1241da177e4SLinus Torvalds snd_emux_unlock_voice(emu->emu, i); 1251da177e4SLinus Torvalds for (; i < EMU8000_DRAM_VOICES; i++) { 1261da177e4SLinus Torvalds snd_emu8000_dma_chan(emu, i, EMU8000_RAM_CLOSE); 1271da177e4SLinus Torvalds snd_emux_unlock_voice(emu->emu, i); 1281da177e4SLinus Torvalds } 1291da177e4SLinus Torvalds } 1301da177e4SLinus Torvalds 1311da177e4SLinus Torvalds /* 1321da177e4SLinus Torvalds * convert Hz to AWE32 rate offset (see emux/soundfont.c) 1331da177e4SLinus Torvalds */ 1341da177e4SLinus Torvalds 1351da177e4SLinus Torvalds #define OFFSET_SAMPLERATE 1011119 /* base = 44100 */ 1361da177e4SLinus Torvalds #define SAMPLERATE_RATIO 4096 1371da177e4SLinus Torvalds 1381da177e4SLinus Torvalds static int calc_rate_offset(int hz) 1391da177e4SLinus Torvalds { 1401da177e4SLinus Torvalds return snd_sf_linear_to_log(hz, OFFSET_SAMPLERATE, SAMPLERATE_RATIO); 1411da177e4SLinus Torvalds } 1421da177e4SLinus Torvalds 1431da177e4SLinus Torvalds 1441da177e4SLinus Torvalds /* 1451da177e4SLinus Torvalds */ 1461da177e4SLinus Torvalds 147aec54654SBhumika Goyal static const struct snd_pcm_hardware emu8k_pcm_hw = { 1481da177e4SLinus Torvalds #ifdef USE_NONINTERLEAVE 1491da177e4SLinus Torvalds .info = SNDRV_PCM_INFO_NONINTERLEAVED, 1501da177e4SLinus Torvalds #else 1511da177e4SLinus Torvalds .info = SNDRV_PCM_INFO_INTERLEAVED, 1521da177e4SLinus Torvalds #endif 1531da177e4SLinus Torvalds .formats = SNDRV_PCM_FMTBIT_S16_LE, 1541da177e4SLinus Torvalds .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000, 1551da177e4SLinus Torvalds .rate_min = 4000, 1561da177e4SLinus Torvalds .rate_max = 48000, 1571da177e4SLinus Torvalds .channels_min = 1, 1581da177e4SLinus Torvalds .channels_max = 2, 1591da177e4SLinus Torvalds .buffer_bytes_max = (128*1024), 1601da177e4SLinus Torvalds .period_bytes_min = 1024, 1611da177e4SLinus Torvalds .period_bytes_max = (128*1024), 1621da177e4SLinus Torvalds .periods_min = 2, 1631da177e4SLinus Torvalds .periods_max = 1024, 1641da177e4SLinus Torvalds .fifo_size = 0, 1651da177e4SLinus Torvalds 1661da177e4SLinus Torvalds }; 1671da177e4SLinus Torvalds 1681da177e4SLinus Torvalds /* 1691da177e4SLinus Torvalds * get the current position at the given channel from CCCA register 1701da177e4SLinus Torvalds */ 171029d64b0STakashi Iwai static inline int emu8k_get_curpos(struct snd_emu8k_pcm *rec, int ch) 1721da177e4SLinus Torvalds { 1731da177e4SLinus Torvalds int val = EMU8000_CCCA_READ(rec->emu, ch) & 0xfffffff; 1741da177e4SLinus Torvalds val -= rec->loop_start[ch] - 1; 1751da177e4SLinus Torvalds return val; 1761da177e4SLinus Torvalds } 1771da177e4SLinus Torvalds 1781da177e4SLinus Torvalds 1791da177e4SLinus Torvalds /* 1801da177e4SLinus Torvalds * timer interrupt handler 1811da177e4SLinus Torvalds * check the current position and update the period if necessary. 1821da177e4SLinus Torvalds */ 1834f928246SKees Cook static void emu8k_pcm_timer_func(struct timer_list *t) 1841da177e4SLinus Torvalds { 1854f928246SKees Cook struct snd_emu8k_pcm *rec = from_timer(rec, t, timer); 1861da177e4SLinus Torvalds int ptr, delta; 1871da177e4SLinus Torvalds 1881da177e4SLinus Torvalds spin_lock(&rec->timer_lock); 1891da177e4SLinus Torvalds /* update the current pointer */ 1901da177e4SLinus Torvalds ptr = emu8k_get_curpos(rec, 0); 1911da177e4SLinus Torvalds if (ptr < rec->last_ptr) 1921da177e4SLinus Torvalds delta = ptr + rec->buf_size - rec->last_ptr; 1931da177e4SLinus Torvalds else 1941da177e4SLinus Torvalds delta = ptr - rec->last_ptr; 1951da177e4SLinus Torvalds rec->period_pos += delta; 1961da177e4SLinus Torvalds rec->last_ptr = ptr; 1971da177e4SLinus Torvalds 1981da177e4SLinus Torvalds /* reprogram timer */ 199f05b4127STakashi Iwai mod_timer(&rec->timer, jiffies + 1); 2001da177e4SLinus Torvalds 2011da177e4SLinus Torvalds /* update period */ 2021da177e4SLinus Torvalds if (rec->period_pos >= (int)rec->period_size) { 2031da177e4SLinus Torvalds rec->period_pos %= rec->period_size; 2041da177e4SLinus Torvalds spin_unlock(&rec->timer_lock); 2051da177e4SLinus Torvalds snd_pcm_period_elapsed(rec->substream); 2061da177e4SLinus Torvalds return; 2071da177e4SLinus Torvalds } 2081da177e4SLinus Torvalds spin_unlock(&rec->timer_lock); 2091da177e4SLinus Torvalds } 2101da177e4SLinus Torvalds 2111da177e4SLinus Torvalds 2121da177e4SLinus Torvalds /* 2131da177e4SLinus Torvalds * open pcm 2141da177e4SLinus Torvalds * creating an instance here 2151da177e4SLinus Torvalds */ 216029d64b0STakashi Iwai static int emu8k_pcm_open(struct snd_pcm_substream *subs) 2171da177e4SLinus Torvalds { 218029d64b0STakashi Iwai struct snd_emu8000 *emu = snd_pcm_substream_chip(subs); 219029d64b0STakashi Iwai struct snd_emu8k_pcm *rec; 220029d64b0STakashi Iwai struct snd_pcm_runtime *runtime = subs->runtime; 2211da177e4SLinus Torvalds 2229e76a76eSTakashi Iwai rec = kzalloc(sizeof(*rec), GFP_KERNEL); 2231da177e4SLinus Torvalds if (! rec) 2241da177e4SLinus Torvalds return -ENOMEM; 2251da177e4SLinus Torvalds 2261da177e4SLinus Torvalds rec->emu = emu; 2271da177e4SLinus Torvalds rec->substream = subs; 2281da177e4SLinus Torvalds runtime->private_data = rec; 2291da177e4SLinus Torvalds 2301da177e4SLinus Torvalds spin_lock_init(&rec->timer_lock); 2314f928246SKees Cook timer_setup(&rec->timer, emu8k_pcm_timer_func, 0); 2321da177e4SLinus Torvalds 2331da177e4SLinus Torvalds runtime->hw = emu8k_pcm_hw; 2341da177e4SLinus Torvalds runtime->hw.buffer_bytes_max = emu->mem_size - LOOP_BLANK_SIZE * 3; 2351da177e4SLinus Torvalds runtime->hw.period_bytes_max = runtime->hw.buffer_bytes_max / 2; 2361da177e4SLinus Torvalds 2371da177e4SLinus Torvalds /* use timer to update periods.. (specified in msec) */ 2381da177e4SLinus Torvalds snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_TIME, 2391da177e4SLinus Torvalds (1000000 + HZ - 1) / HZ, UINT_MAX); 2401da177e4SLinus Torvalds 2411da177e4SLinus Torvalds return 0; 2421da177e4SLinus Torvalds } 2431da177e4SLinus Torvalds 244029d64b0STakashi Iwai static int emu8k_pcm_close(struct snd_pcm_substream *subs) 2451da177e4SLinus Torvalds { 246029d64b0STakashi Iwai struct snd_emu8k_pcm *rec = subs->runtime->private_data; 2471da177e4SLinus Torvalds kfree(rec); 2481da177e4SLinus Torvalds subs->runtime->private_data = NULL; 2491da177e4SLinus Torvalds return 0; 2501da177e4SLinus Torvalds } 2511da177e4SLinus Torvalds 2521da177e4SLinus Torvalds /* 2531da177e4SLinus Torvalds * calculate pitch target 2541da177e4SLinus Torvalds */ 2551da177e4SLinus Torvalds static int calc_pitch_target(int pitch) 2561da177e4SLinus Torvalds { 2571da177e4SLinus Torvalds int ptarget = 1 << (pitch >> 12); 2581da177e4SLinus Torvalds if (pitch & 0x800) ptarget += (ptarget * 0x102e) / 0x2710; 2591da177e4SLinus Torvalds if (pitch & 0x400) ptarget += (ptarget * 0x764) / 0x2710; 2601da177e4SLinus Torvalds if (pitch & 0x200) ptarget += (ptarget * 0x389) / 0x2710; 2611da177e4SLinus Torvalds ptarget += (ptarget >> 1); 2621da177e4SLinus Torvalds if (ptarget > 0xffff) ptarget = 0xffff; 2631da177e4SLinus Torvalds return ptarget; 2641da177e4SLinus Torvalds } 2651da177e4SLinus Torvalds 2661da177e4SLinus Torvalds /* 2671da177e4SLinus Torvalds * set up the voice 2681da177e4SLinus Torvalds */ 269029d64b0STakashi Iwai static void setup_voice(struct snd_emu8k_pcm *rec, int ch) 2701da177e4SLinus Torvalds { 271029d64b0STakashi Iwai struct snd_emu8000 *hw = rec->emu; 2721da177e4SLinus Torvalds unsigned int temp; 2731da177e4SLinus Torvalds 2741da177e4SLinus Torvalds /* channel to be silent and idle */ 2751da177e4SLinus Torvalds EMU8000_DCYSUSV_WRITE(hw, ch, 0x0080); 2761da177e4SLinus Torvalds EMU8000_VTFT_WRITE(hw, ch, 0x0000FFFF); 2771da177e4SLinus Torvalds EMU8000_CVCF_WRITE(hw, ch, 0x0000FFFF); 2781da177e4SLinus Torvalds EMU8000_PTRX_WRITE(hw, ch, 0); 2791da177e4SLinus Torvalds EMU8000_CPF_WRITE(hw, ch, 0); 2801da177e4SLinus Torvalds 2811da177e4SLinus Torvalds /* pitch offset */ 2821da177e4SLinus Torvalds EMU8000_IP_WRITE(hw, ch, rec->pitch); 2831da177e4SLinus Torvalds /* set envelope parameters */ 2841da177e4SLinus Torvalds EMU8000_ENVVAL_WRITE(hw, ch, 0x8000); 2851da177e4SLinus Torvalds EMU8000_ATKHLD_WRITE(hw, ch, 0x7f7f); 2861da177e4SLinus Torvalds EMU8000_DCYSUS_WRITE(hw, ch, 0x7f7f); 2871da177e4SLinus Torvalds EMU8000_ENVVOL_WRITE(hw, ch, 0x8000); 2881da177e4SLinus Torvalds EMU8000_ATKHLDV_WRITE(hw, ch, 0x7f7f); 2891da177e4SLinus Torvalds /* decay/sustain parameter for volume envelope is used 2901da177e4SLinus Torvalds for triggerg the voice */ 2911da177e4SLinus Torvalds /* modulation envelope heights */ 2921da177e4SLinus Torvalds EMU8000_PEFE_WRITE(hw, ch, 0x0); 2931da177e4SLinus Torvalds /* lfo1/2 delay */ 2941da177e4SLinus Torvalds EMU8000_LFO1VAL_WRITE(hw, ch, 0x8000); 2951da177e4SLinus Torvalds EMU8000_LFO2VAL_WRITE(hw, ch, 0x8000); 2961da177e4SLinus Torvalds /* lfo1 pitch & cutoff shift */ 2971da177e4SLinus Torvalds EMU8000_FMMOD_WRITE(hw, ch, 0); 2981da177e4SLinus Torvalds /* lfo1 volume & freq */ 2991da177e4SLinus Torvalds EMU8000_TREMFRQ_WRITE(hw, ch, 0); 3001da177e4SLinus Torvalds /* lfo2 pitch & freq */ 3011da177e4SLinus Torvalds EMU8000_FM2FRQ2_WRITE(hw, ch, 0); 3021da177e4SLinus Torvalds /* pan & loop start */ 3031da177e4SLinus Torvalds temp = rec->panning[ch]; 3041da177e4SLinus Torvalds temp = (temp <<24) | ((unsigned int)rec->loop_start[ch] - 1); 3051da177e4SLinus Torvalds EMU8000_PSST_WRITE(hw, ch, temp); 3061da177e4SLinus Torvalds /* chorus & loop end (chorus 8bit, MSB) */ 3071da177e4SLinus Torvalds temp = 0; // chorus 3081da177e4SLinus Torvalds temp = (temp << 24) | ((unsigned int)rec->loop_start[ch] + rec->buf_size - 1); 3091da177e4SLinus Torvalds EMU8000_CSL_WRITE(hw, ch, temp); 3101da177e4SLinus Torvalds /* Q & current address (Q 4bit value, MSB) */ 3111da177e4SLinus Torvalds temp = 0; // filterQ 3121da177e4SLinus Torvalds temp = (temp << 28) | ((unsigned int)rec->loop_start[ch] - 1); 3131da177e4SLinus Torvalds EMU8000_CCCA_WRITE(hw, ch, temp); 3141da177e4SLinus Torvalds /* clear unknown registers */ 3151da177e4SLinus Torvalds EMU8000_00A0_WRITE(hw, ch, 0); 3161da177e4SLinus Torvalds EMU8000_0080_WRITE(hw, ch, 0); 3171da177e4SLinus Torvalds } 3181da177e4SLinus Torvalds 3191da177e4SLinus Torvalds /* 3201da177e4SLinus Torvalds * trigger the voice 3211da177e4SLinus Torvalds */ 322029d64b0STakashi Iwai static void start_voice(struct snd_emu8k_pcm *rec, int ch) 3231da177e4SLinus Torvalds { 3241da177e4SLinus Torvalds unsigned long flags; 325029d64b0STakashi Iwai struct snd_emu8000 *hw = rec->emu; 3261da177e4SLinus Torvalds unsigned int temp, aux; 3271da177e4SLinus Torvalds int pt = calc_pitch_target(rec->pitch); 3281da177e4SLinus Torvalds 3291da177e4SLinus Torvalds /* cutoff and volume */ 3301da177e4SLinus Torvalds EMU8000_IFATN_WRITE(hw, ch, 0xff00); 3311da177e4SLinus Torvalds EMU8000_VTFT_WRITE(hw, ch, 0xffff); 3321da177e4SLinus Torvalds EMU8000_CVCF_WRITE(hw, ch, 0xffff); 3331da177e4SLinus Torvalds /* trigger envelope */ 3341da177e4SLinus Torvalds EMU8000_DCYSUSV_WRITE(hw, ch, 0x7f7f); 3351da177e4SLinus Torvalds /* set reverb and pitch target */ 3361da177e4SLinus Torvalds temp = 0; // reverb 3371da177e4SLinus Torvalds if (rec->panning[ch] == 0) 3381da177e4SLinus Torvalds aux = 0xff; 3391da177e4SLinus Torvalds else 3401da177e4SLinus Torvalds aux = (-rec->panning[ch]) & 0xff; 3411da177e4SLinus Torvalds temp = (temp << 8) | (pt << 16) | aux; 3421da177e4SLinus Torvalds EMU8000_PTRX_WRITE(hw, ch, temp); 3431da177e4SLinus Torvalds EMU8000_CPF_WRITE(hw, ch, pt << 16); 3441da177e4SLinus Torvalds 3451da177e4SLinus Torvalds /* start timer */ 3461da177e4SLinus Torvalds spin_lock_irqsave(&rec->timer_lock, flags); 3471da177e4SLinus Torvalds if (! rec->timer_running) { 348f05b4127STakashi Iwai mod_timer(&rec->timer, jiffies + 1); 3491da177e4SLinus Torvalds rec->timer_running = 1; 3501da177e4SLinus Torvalds } 3511da177e4SLinus Torvalds spin_unlock_irqrestore(&rec->timer_lock, flags); 3521da177e4SLinus Torvalds } 3531da177e4SLinus Torvalds 3541da177e4SLinus Torvalds /* 3551da177e4SLinus Torvalds * stop the voice immediately 3561da177e4SLinus Torvalds */ 357029d64b0STakashi Iwai static void stop_voice(struct snd_emu8k_pcm *rec, int ch) 3581da177e4SLinus Torvalds { 3591da177e4SLinus Torvalds unsigned long flags; 360029d64b0STakashi Iwai struct snd_emu8000 *hw = rec->emu; 3611da177e4SLinus Torvalds 3621da177e4SLinus Torvalds EMU8000_DCYSUSV_WRITE(hw, ch, 0x807F); 3631da177e4SLinus Torvalds 3641da177e4SLinus Torvalds /* stop timer */ 3651da177e4SLinus Torvalds spin_lock_irqsave(&rec->timer_lock, flags); 3661da177e4SLinus Torvalds if (rec->timer_running) { 3671da177e4SLinus Torvalds del_timer(&rec->timer); 3681da177e4SLinus Torvalds rec->timer_running = 0; 3691da177e4SLinus Torvalds } 3701da177e4SLinus Torvalds spin_unlock_irqrestore(&rec->timer_lock, flags); 3711da177e4SLinus Torvalds } 3721da177e4SLinus Torvalds 373029d64b0STakashi Iwai static int emu8k_pcm_trigger(struct snd_pcm_substream *subs, int cmd) 3741da177e4SLinus Torvalds { 375029d64b0STakashi Iwai struct snd_emu8k_pcm *rec = subs->runtime->private_data; 3761da177e4SLinus Torvalds int ch; 3771da177e4SLinus Torvalds 3781da177e4SLinus Torvalds switch (cmd) { 3791da177e4SLinus Torvalds case SNDRV_PCM_TRIGGER_START: 3801da177e4SLinus Torvalds for (ch = 0; ch < rec->voices; ch++) 3811da177e4SLinus Torvalds start_voice(rec, ch); 3821da177e4SLinus Torvalds rec->running = 1; 3831da177e4SLinus Torvalds break; 3841da177e4SLinus Torvalds case SNDRV_PCM_TRIGGER_STOP: 3851da177e4SLinus Torvalds rec->running = 0; 3861da177e4SLinus Torvalds for (ch = 0; ch < rec->voices; ch++) 3871da177e4SLinus Torvalds stop_voice(rec, ch); 3881da177e4SLinus Torvalds break; 3891da177e4SLinus Torvalds default: 3901da177e4SLinus Torvalds return -EINVAL; 3911da177e4SLinus Torvalds } 3921da177e4SLinus Torvalds return 0; 3931da177e4SLinus Torvalds } 3941da177e4SLinus Torvalds 3951da177e4SLinus Torvalds 3961da177e4SLinus Torvalds /* 3971da177e4SLinus Torvalds * copy / silence ops 3981da177e4SLinus Torvalds */ 3991da177e4SLinus Torvalds 4001da177e4SLinus Torvalds /* 4011da177e4SLinus Torvalds * this macro should be inserted in the copy/silence loops 4021da177e4SLinus Torvalds * to reduce the latency. without this, the system will hang up 4031da177e4SLinus Torvalds * during the whole loop. 4041da177e4SLinus Torvalds */ 4051da177e4SLinus Torvalds #define CHECK_SCHEDULER() \ 4061da177e4SLinus Torvalds do { \ 4071da177e4SLinus Torvalds cond_resched();\ 4081da177e4SLinus Torvalds if (signal_pending(current))\ 4091da177e4SLinus Torvalds return -EAGAIN;\ 4101da177e4SLinus Torvalds } while (0) 4111da177e4SLinus Torvalds 4124b83eff8STakashi Iwai enum { 4134b83eff8STakashi Iwai COPY_USER, COPY_KERNEL, FILL_SILENCE, 4144b83eff8STakashi Iwai }; 4154b83eff8STakashi Iwai 4164b83eff8STakashi Iwai #define GET_VAL(sval, buf, mode) \ 4174b83eff8STakashi Iwai do { \ 4184b83eff8STakashi Iwai switch (mode) { \ 4194b83eff8STakashi Iwai case FILL_SILENCE: \ 4204b83eff8STakashi Iwai sval = 0; \ 4214b83eff8STakashi Iwai break; \ 4224b83eff8STakashi Iwai case COPY_KERNEL: \ 4234b83eff8STakashi Iwai sval = *buf++; \ 4244b83eff8STakashi Iwai break; \ 4254b83eff8STakashi Iwai default: \ 4264b83eff8STakashi Iwai if (get_user(sval, (unsigned short __user *)buf)) \ 4274b83eff8STakashi Iwai return -EFAULT; \ 4284b83eff8STakashi Iwai buf++; \ 4294b83eff8STakashi Iwai break; \ 4304b83eff8STakashi Iwai } \ 4314b83eff8STakashi Iwai } while (0) 4321da177e4SLinus Torvalds 4331da177e4SLinus Torvalds #ifdef USE_NONINTERLEAVE 4344b83eff8STakashi Iwai 4354b83eff8STakashi Iwai #define LOOP_WRITE(rec, offset, _buf, count, mode) \ 4364b83eff8STakashi Iwai do { \ 4374b83eff8STakashi Iwai struct snd_emu8000 *emu = (rec)->emu; \ 438f4caf899STakashi Iwai unsigned short *buf = (__force unsigned short *)(_buf); \ 4394b83eff8STakashi Iwai snd_emu8000_write_wait(emu, 1); \ 4404b83eff8STakashi Iwai EMU8000_SMALW_WRITE(emu, offset); \ 4414b83eff8STakashi Iwai while (count > 0) { \ 4424b83eff8STakashi Iwai unsigned short sval; \ 4434b83eff8STakashi Iwai CHECK_SCHEDULER(); \ 4444b83eff8STakashi Iwai GET_VAL(sval, buf, mode); \ 4454b83eff8STakashi Iwai EMU8000_SMLD_WRITE(emu, sval); \ 4464b83eff8STakashi Iwai count--; \ 4474b83eff8STakashi Iwai } \ 4484b83eff8STakashi Iwai } while (0) 4494b83eff8STakashi Iwai 4501da177e4SLinus Torvalds /* copy one channel block */ 4514b83eff8STakashi Iwai static int emu8k_pcm_copy(struct snd_pcm_substream *subs, 4524b83eff8STakashi Iwai int voice, unsigned long pos, 4534b83eff8STakashi Iwai void __user *src, unsigned long count) 4541da177e4SLinus Torvalds { 4554b83eff8STakashi Iwai struct snd_emu8k_pcm *rec = subs->runtime->private_data; 4564b83eff8STakashi Iwai 4574b83eff8STakashi Iwai /* convert to word unit */ 4584b83eff8STakashi Iwai pos = (pos << 1) + rec->loop_start[voice]; 4594b83eff8STakashi Iwai count <<= 1; 460789b7f43STakashi Iwai LOOP_WRITE(rec, pos, src, count, COPY_USER); 4611da177e4SLinus Torvalds return 0; 4621da177e4SLinus Torvalds } 4631da177e4SLinus Torvalds 4644b83eff8STakashi Iwai static int emu8k_pcm_copy_kernel(struct snd_pcm_substream *subs, 4654b83eff8STakashi Iwai int voice, unsigned long pos, 4664b83eff8STakashi Iwai void *src, unsigned long count) 4671da177e4SLinus Torvalds { 468029d64b0STakashi Iwai struct snd_emu8k_pcm *rec = subs->runtime->private_data; 4691da177e4SLinus Torvalds 4704b83eff8STakashi Iwai /* convert to word unit */ 4714b83eff8STakashi Iwai pos = (pos << 1) + rec->loop_start[voice]; 4724b83eff8STakashi Iwai count <<= 1; 4734b83eff8STakashi Iwai LOOP_WRITE(rec, pos, src, count, COPY_KERNEL); 4744b83eff8STakashi Iwai return 0; 4751da177e4SLinus Torvalds } 4761da177e4SLinus Torvalds 4771da177e4SLinus Torvalds /* make a channel block silence */ 4784b83eff8STakashi Iwai static int emu8k_pcm_silence(struct snd_pcm_substream *subs, 4794b83eff8STakashi Iwai int voice, unsigned long pos, unsigned long count) 4801da177e4SLinus Torvalds { 4814b83eff8STakashi Iwai struct snd_emu8k_pcm *rec = subs->runtime->private_data; 4824b83eff8STakashi Iwai 4834b83eff8STakashi Iwai /* convert to word unit */ 4844b83eff8STakashi Iwai pos = (pos << 1) + rec->loop_start[voice]; 4854b83eff8STakashi Iwai count <<= 1; 4864b83eff8STakashi Iwai LOOP_WRITE(rec, pos, NULL, count, FILL_SILENCE); 4871da177e4SLinus Torvalds return 0; 4881da177e4SLinus Torvalds } 4891da177e4SLinus Torvalds 4901da177e4SLinus Torvalds #else /* interleave */ 4911da177e4SLinus Torvalds 4924b83eff8STakashi Iwai #define LOOP_WRITE(rec, pos, _buf, count, mode) \ 4934b83eff8STakashi Iwai do { \ 4944b83eff8STakashi Iwai struct snd_emu8000 *emu = rec->emu; \ 495f4caf899STakashi Iwai unsigned short *buf = (__force unsigned short *)(_buf); \ 4964b83eff8STakashi Iwai snd_emu8000_write_wait(emu, 1); \ 4974b83eff8STakashi Iwai EMU8000_SMALW_WRITE(emu, pos + rec->loop_start[0]); \ 4984b83eff8STakashi Iwai if (rec->voices > 1) \ 4994b83eff8STakashi Iwai EMU8000_SMARW_WRITE(emu, pos + rec->loop_start[1]); \ 5004b83eff8STakashi Iwai while (count > 0) { \ 5014b83eff8STakashi Iwai unsigned short sval; \ 5024b83eff8STakashi Iwai CHECK_SCHEDULER(); \ 5034b83eff8STakashi Iwai GET_VAL(sval, buf, mode); \ 5044b83eff8STakashi Iwai EMU8000_SMLD_WRITE(emu, sval); \ 5054b83eff8STakashi Iwai if (rec->voices > 1) { \ 5064b83eff8STakashi Iwai CHECK_SCHEDULER(); \ 5074b83eff8STakashi Iwai GET_VAL(sval, buf, mode); \ 5084b83eff8STakashi Iwai EMU8000_SMRD_WRITE(emu, sval); \ 5094b83eff8STakashi Iwai } \ 5104b83eff8STakashi Iwai count--; \ 5114b83eff8STakashi Iwai } \ 5124b83eff8STakashi Iwai } while (0) 5134b83eff8STakashi Iwai 5144b83eff8STakashi Iwai 5151da177e4SLinus Torvalds /* 5161da177e4SLinus Torvalds * copy the interleaved data can be done easily by using 5171da177e4SLinus Torvalds * DMA "left" and "right" channels on emu8k engine. 5181da177e4SLinus Torvalds */ 519029d64b0STakashi Iwai static int emu8k_pcm_copy(struct snd_pcm_substream *subs, 5204b83eff8STakashi Iwai int voice, unsigned long pos, 5214b83eff8STakashi Iwai void __user *src, unsigned long count) 5221da177e4SLinus Torvalds { 523029d64b0STakashi Iwai struct snd_emu8k_pcm *rec = subs->runtime->private_data; 5241da177e4SLinus Torvalds 5254b83eff8STakashi Iwai /* convert to frames */ 5264b83eff8STakashi Iwai pos = bytes_to_frames(subs->runtime, pos); 5274b83eff8STakashi Iwai count = bytes_to_frames(subs->runtime, count); 5284b83eff8STakashi Iwai LOOP_WRITE(rec, pos, src, count, COPY_USER); 5294b83eff8STakashi Iwai return 0; 5304b83eff8STakashi Iwai } 5311da177e4SLinus Torvalds 5324b83eff8STakashi Iwai static int emu8k_pcm_copy_kernel(struct snd_pcm_substream *subs, 5334b83eff8STakashi Iwai int voice, unsigned long pos, 5344b83eff8STakashi Iwai void *src, unsigned long count) 5354b83eff8STakashi Iwai { 5364b83eff8STakashi Iwai struct snd_emu8k_pcm *rec = subs->runtime->private_data; 5374b83eff8STakashi Iwai 5384b83eff8STakashi Iwai /* convert to frames */ 5394b83eff8STakashi Iwai pos = bytes_to_frames(subs->runtime, pos); 5404b83eff8STakashi Iwai count = bytes_to_frames(subs->runtime, count); 5414b83eff8STakashi Iwai LOOP_WRITE(rec, pos, src, count, COPY_KERNEL); 5421da177e4SLinus Torvalds return 0; 5431da177e4SLinus Torvalds } 5441da177e4SLinus Torvalds 545029d64b0STakashi Iwai static int emu8k_pcm_silence(struct snd_pcm_substream *subs, 5464b83eff8STakashi Iwai int voice, unsigned long pos, unsigned long count) 5471da177e4SLinus Torvalds { 548029d64b0STakashi Iwai struct snd_emu8k_pcm *rec = subs->runtime->private_data; 5491da177e4SLinus Torvalds 5504b83eff8STakashi Iwai /* convert to frames */ 5514b83eff8STakashi Iwai pos = bytes_to_frames(subs->runtime, pos); 5524b83eff8STakashi Iwai count = bytes_to_frames(subs->runtime, count); 5534b83eff8STakashi Iwai LOOP_WRITE(rec, pos, NULL, count, FILL_SILENCE); 5541da177e4SLinus Torvalds return 0; 5551da177e4SLinus Torvalds } 5561da177e4SLinus Torvalds #endif 5571da177e4SLinus Torvalds 5581da177e4SLinus Torvalds 5591da177e4SLinus Torvalds /* 5601da177e4SLinus Torvalds * allocate a memory block 5611da177e4SLinus Torvalds */ 562029d64b0STakashi Iwai static int emu8k_pcm_hw_params(struct snd_pcm_substream *subs, 563029d64b0STakashi Iwai struct snd_pcm_hw_params *hw_params) 5641da177e4SLinus Torvalds { 565029d64b0STakashi Iwai struct snd_emu8k_pcm *rec = subs->runtime->private_data; 5661da177e4SLinus Torvalds 5671da177e4SLinus Torvalds if (rec->block) { 5681da177e4SLinus Torvalds /* reallocation - release the old block */ 5691da177e4SLinus Torvalds snd_util_mem_free(rec->emu->memhdr, rec->block); 5701da177e4SLinus Torvalds rec->block = NULL; 5711da177e4SLinus Torvalds } 5721da177e4SLinus Torvalds 5731da177e4SLinus Torvalds rec->allocated_bytes = params_buffer_bytes(hw_params) + LOOP_BLANK_SIZE * 4; 5741da177e4SLinus Torvalds rec->block = snd_util_mem_alloc(rec->emu->memhdr, rec->allocated_bytes); 5751da177e4SLinus Torvalds if (! rec->block) 5761da177e4SLinus Torvalds return -ENOMEM; 5771da177e4SLinus Torvalds rec->offset = EMU8000_DRAM_OFFSET + (rec->block->offset >> 1); /* in word */ 5781da177e4SLinus Torvalds /* at least dma_bytes must be set for non-interleaved mode */ 5791da177e4SLinus Torvalds subs->dma_buffer.bytes = params_buffer_bytes(hw_params); 5801da177e4SLinus Torvalds 5811da177e4SLinus Torvalds return 0; 5821da177e4SLinus Torvalds } 5831da177e4SLinus Torvalds 5841da177e4SLinus Torvalds /* 5851da177e4SLinus Torvalds * free the memory block 5861da177e4SLinus Torvalds */ 587029d64b0STakashi Iwai static int emu8k_pcm_hw_free(struct snd_pcm_substream *subs) 5881da177e4SLinus Torvalds { 589029d64b0STakashi Iwai struct snd_emu8k_pcm *rec = subs->runtime->private_data; 5901da177e4SLinus Torvalds 5911da177e4SLinus Torvalds if (rec->block) { 5921da177e4SLinus Torvalds int ch; 5931da177e4SLinus Torvalds for (ch = 0; ch < rec->voices; ch++) 5941da177e4SLinus Torvalds stop_voice(rec, ch); // to be sure 5951da177e4SLinus Torvalds if (rec->dram_opened) 5961da177e4SLinus Torvalds emu8k_close_dram(rec->emu); 5971da177e4SLinus Torvalds snd_util_mem_free(rec->emu->memhdr, rec->block); 5981da177e4SLinus Torvalds rec->block = NULL; 5991da177e4SLinus Torvalds } 6001da177e4SLinus Torvalds return 0; 6011da177e4SLinus Torvalds } 6021da177e4SLinus Torvalds 6031da177e4SLinus Torvalds /* 6041da177e4SLinus Torvalds */ 605029d64b0STakashi Iwai static int emu8k_pcm_prepare(struct snd_pcm_substream *subs) 6061da177e4SLinus Torvalds { 607029d64b0STakashi Iwai struct snd_emu8k_pcm *rec = subs->runtime->private_data; 6081da177e4SLinus Torvalds 6091da177e4SLinus Torvalds rec->pitch = 0xe000 + calc_rate_offset(subs->runtime->rate); 6101da177e4SLinus Torvalds rec->last_ptr = 0; 6111da177e4SLinus Torvalds rec->period_pos = 0; 6121da177e4SLinus Torvalds 6131da177e4SLinus Torvalds rec->buf_size = subs->runtime->buffer_size; 6141da177e4SLinus Torvalds rec->period_size = subs->runtime->period_size; 6151da177e4SLinus Torvalds rec->voices = subs->runtime->channels; 6161da177e4SLinus Torvalds rec->loop_start[0] = rec->offset + LOOP_BLANK_SIZE; 6171da177e4SLinus Torvalds if (rec->voices > 1) 6181da177e4SLinus Torvalds rec->loop_start[1] = rec->loop_start[0] + rec->buf_size + LOOP_BLANK_SIZE; 6191da177e4SLinus Torvalds if (rec->voices > 1) { 6201da177e4SLinus Torvalds rec->panning[0] = 0xff; 6211da177e4SLinus Torvalds rec->panning[1] = 0x00; 6221da177e4SLinus Torvalds } else 6231da177e4SLinus Torvalds rec->panning[0] = 0x80; 6241da177e4SLinus Torvalds 6251da177e4SLinus Torvalds if (! rec->dram_opened) { 6261da177e4SLinus Torvalds int err, i, ch; 6271da177e4SLinus Torvalds 6281da177e4SLinus Torvalds snd_emux_terminate_all(rec->emu->emu); 629*10dc8ad5STakashi Iwai err = emu8k_open_dram_for_pcm(rec->emu, rec->voices); 630*10dc8ad5STakashi Iwai if (err) 6311da177e4SLinus Torvalds return err; 6321da177e4SLinus Torvalds rec->dram_opened = 1; 6331da177e4SLinus Torvalds 6341da177e4SLinus Torvalds /* clear loop blanks */ 6351da177e4SLinus Torvalds snd_emu8000_write_wait(rec->emu, 0); 6361da177e4SLinus Torvalds EMU8000_SMALW_WRITE(rec->emu, rec->offset); 6371da177e4SLinus Torvalds for (i = 0; i < LOOP_BLANK_SIZE; i++) 6381da177e4SLinus Torvalds EMU8000_SMLD_WRITE(rec->emu, 0); 6391da177e4SLinus Torvalds for (ch = 0; ch < rec->voices; ch++) { 6401da177e4SLinus Torvalds EMU8000_SMALW_WRITE(rec->emu, rec->loop_start[ch] + rec->buf_size); 6411da177e4SLinus Torvalds for (i = 0; i < LOOP_BLANK_SIZE; i++) 6421da177e4SLinus Torvalds EMU8000_SMLD_WRITE(rec->emu, 0); 6431da177e4SLinus Torvalds } 6441da177e4SLinus Torvalds } 6451da177e4SLinus Torvalds 6461da177e4SLinus Torvalds setup_voice(rec, 0); 6471da177e4SLinus Torvalds if (rec->voices > 1) 6481da177e4SLinus Torvalds setup_voice(rec, 1); 6491da177e4SLinus Torvalds return 0; 6501da177e4SLinus Torvalds } 6511da177e4SLinus Torvalds 652029d64b0STakashi Iwai static snd_pcm_uframes_t emu8k_pcm_pointer(struct snd_pcm_substream *subs) 6531da177e4SLinus Torvalds { 654029d64b0STakashi Iwai struct snd_emu8k_pcm *rec = subs->runtime->private_data; 6551da177e4SLinus Torvalds if (rec->running) 6561da177e4SLinus Torvalds return emu8k_get_curpos(rec, 0); 6571da177e4SLinus Torvalds return 0; 6581da177e4SLinus Torvalds } 6591da177e4SLinus Torvalds 6601da177e4SLinus Torvalds 66184c8f90fSArvind Yadav static const struct snd_pcm_ops emu8k_pcm_ops = { 6621da177e4SLinus Torvalds .open = emu8k_pcm_open, 6631da177e4SLinus Torvalds .close = emu8k_pcm_close, 6641da177e4SLinus Torvalds .hw_params = emu8k_pcm_hw_params, 6651da177e4SLinus Torvalds .hw_free = emu8k_pcm_hw_free, 6661da177e4SLinus Torvalds .prepare = emu8k_pcm_prepare, 6671da177e4SLinus Torvalds .trigger = emu8k_pcm_trigger, 6681da177e4SLinus Torvalds .pointer = emu8k_pcm_pointer, 6694b83eff8STakashi Iwai .copy_user = emu8k_pcm_copy, 6704b83eff8STakashi Iwai .copy_kernel = emu8k_pcm_copy_kernel, 6714b83eff8STakashi Iwai .fill_silence = emu8k_pcm_silence, 6721da177e4SLinus Torvalds }; 6731da177e4SLinus Torvalds 6741da177e4SLinus Torvalds 675029d64b0STakashi Iwai static void snd_emu8000_pcm_free(struct snd_pcm *pcm) 6761da177e4SLinus Torvalds { 677029d64b0STakashi Iwai struct snd_emu8000 *emu = pcm->private_data; 6781da177e4SLinus Torvalds emu->pcm = NULL; 6791da177e4SLinus Torvalds } 6801da177e4SLinus Torvalds 681029d64b0STakashi Iwai int snd_emu8000_pcm_new(struct snd_card *card, struct snd_emu8000 *emu, int index) 6821da177e4SLinus Torvalds { 683029d64b0STakashi Iwai struct snd_pcm *pcm; 6841da177e4SLinus Torvalds int err; 6851da177e4SLinus Torvalds 686*10dc8ad5STakashi Iwai err = snd_pcm_new(card, "Emu8000 PCM", index, 1, 0, &pcm); 687*10dc8ad5STakashi Iwai if (err < 0) 6881da177e4SLinus Torvalds return err; 6891da177e4SLinus Torvalds pcm->private_data = emu; 6901da177e4SLinus Torvalds pcm->private_free = snd_emu8000_pcm_free; 6911da177e4SLinus Torvalds snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &emu8k_pcm_ops); 6921da177e4SLinus Torvalds emu->pcm = pcm; 6931da177e4SLinus Torvalds 6941da177e4SLinus Torvalds snd_device_register(card, pcm); 6951da177e4SLinus Torvalds 6961da177e4SLinus Torvalds return 0; 6971da177e4SLinus Torvalds } 698