11da177e4SLinus Torvalds /* 21da177e4SLinus Torvalds * pcm emulation on emu8000 wavetable 31da177e4SLinus Torvalds * 41da177e4SLinus Torvalds * Copyright (C) 2002 Takashi Iwai <tiwai@suse.de> 51da177e4SLinus Torvalds * 61da177e4SLinus Torvalds * This program is free software; you can redistribute it and/or modify 71da177e4SLinus Torvalds * it under the terms of the GNU General Public License as published by 81da177e4SLinus Torvalds * the Free Software Foundation; either version 2 of the License, or 91da177e4SLinus Torvalds * (at your option) any later version. 101da177e4SLinus Torvalds * 111da177e4SLinus Torvalds * This program is distributed in the hope that it will be useful, 121da177e4SLinus Torvalds * but WITHOUT ANY WARRANTY; without even the implied warranty of 131da177e4SLinus Torvalds * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 141da177e4SLinus Torvalds * GNU General Public License for more details. 151da177e4SLinus Torvalds * 161da177e4SLinus Torvalds * You should have received a copy of the GNU General Public License 171da177e4SLinus Torvalds * along with this program; if not, write to the Free Software 181da177e4SLinus Torvalds * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 191da177e4SLinus Torvalds */ 201da177e4SLinus Torvalds 211da177e4SLinus Torvalds #include "emu8000_local.h" 22174cd4b1SIngo Molnar 23174cd4b1SIngo Molnar #include <linux/sched/signal.h> 241da177e4SLinus Torvalds #include <linux/init.h> 255a0e3ad6STejun Heo #include <linux/slab.h> 261da177e4SLinus Torvalds #include <sound/initval.h> 271da177e4SLinus Torvalds #include <sound/pcm.h> 281da177e4SLinus Torvalds 291da177e4SLinus Torvalds /* 301da177e4SLinus Torvalds * define the following if you want to use this pcm with non-interleaved mode 311da177e4SLinus Torvalds */ 321da177e4SLinus Torvalds /* #define USE_NONINTERLEAVE */ 331da177e4SLinus Torvalds 341da177e4SLinus Torvalds /* NOTE: for using the non-interleaved mode with alsa-lib, you have to set 351da177e4SLinus Torvalds * mmap_emulation flag to 1 in your .asoundrc, such like 361da177e4SLinus Torvalds * 371da177e4SLinus Torvalds * pcm.emu8k { 381da177e4SLinus Torvalds * type plug 391da177e4SLinus Torvalds * slave.pcm { 401da177e4SLinus Torvalds * type hw 411da177e4SLinus Torvalds * card 0 421da177e4SLinus Torvalds * device 1 431da177e4SLinus Torvalds * mmap_emulation 1 441da177e4SLinus Torvalds * } 451da177e4SLinus Torvalds * } 461da177e4SLinus Torvalds * 471da177e4SLinus Torvalds * besides, for the time being, the non-interleaved mode doesn't work well on 481da177e4SLinus Torvalds * alsa-lib... 491da177e4SLinus Torvalds */ 501da177e4SLinus Torvalds 511da177e4SLinus Torvalds 521da177e4SLinus Torvalds struct snd_emu8k_pcm { 53029d64b0STakashi Iwai struct snd_emu8000 *emu; 54029d64b0STakashi Iwai struct snd_pcm_substream *substream; 551da177e4SLinus Torvalds 561da177e4SLinus Torvalds unsigned int allocated_bytes; 57029d64b0STakashi Iwai struct snd_util_memblk *block; 581da177e4SLinus Torvalds unsigned int offset; 591da177e4SLinus Torvalds unsigned int buf_size; 601da177e4SLinus Torvalds unsigned int period_size; 611da177e4SLinus Torvalds unsigned int loop_start[2]; 621da177e4SLinus Torvalds unsigned int pitch; 631da177e4SLinus Torvalds int panning[2]; 641da177e4SLinus Torvalds int last_ptr; 651da177e4SLinus Torvalds int period_pos; 661da177e4SLinus Torvalds int voices; 671da177e4SLinus Torvalds unsigned int dram_opened: 1; 681da177e4SLinus Torvalds unsigned int running: 1; 691da177e4SLinus Torvalds unsigned int timer_running: 1; 701da177e4SLinus Torvalds struct timer_list timer; 711da177e4SLinus Torvalds spinlock_t timer_lock; 721da177e4SLinus Torvalds }; 731da177e4SLinus Torvalds 741da177e4SLinus Torvalds #define LOOP_BLANK_SIZE 8 751da177e4SLinus Torvalds 761da177e4SLinus Torvalds 771da177e4SLinus Torvalds /* 781da177e4SLinus Torvalds * open up channels for the simultaneous data transfer and playback 791da177e4SLinus Torvalds */ 801da177e4SLinus Torvalds static int 81029d64b0STakashi Iwai emu8k_open_dram_for_pcm(struct snd_emu8000 *emu, int channels) 821da177e4SLinus Torvalds { 831da177e4SLinus Torvalds int i; 841da177e4SLinus Torvalds 851da177e4SLinus Torvalds /* reserve up to 2 voices for playback */ 861da177e4SLinus Torvalds snd_emux_lock_voice(emu->emu, 0); 871da177e4SLinus Torvalds if (channels > 1) 881da177e4SLinus Torvalds snd_emux_lock_voice(emu->emu, 1); 891da177e4SLinus Torvalds 901da177e4SLinus Torvalds /* reserve 28 voices for loading */ 911da177e4SLinus Torvalds for (i = channels + 1; i < EMU8000_DRAM_VOICES; i++) { 921da177e4SLinus Torvalds unsigned int mode = EMU8000_RAM_WRITE; 931da177e4SLinus Torvalds snd_emux_lock_voice(emu->emu, i); 941da177e4SLinus Torvalds #ifndef USE_NONINTERLEAVE 951da177e4SLinus Torvalds if (channels > 1 && (i & 1) != 0) 961da177e4SLinus Torvalds mode |= EMU8000_RAM_RIGHT; 971da177e4SLinus Torvalds #endif 981da177e4SLinus Torvalds snd_emu8000_dma_chan(emu, i, mode); 991da177e4SLinus Torvalds } 1001da177e4SLinus Torvalds 1011da177e4SLinus Torvalds /* assign voice 31 and 32 to ROM */ 1021da177e4SLinus Torvalds EMU8000_VTFT_WRITE(emu, 30, 0); 1031da177e4SLinus Torvalds EMU8000_PSST_WRITE(emu, 30, 0x1d8); 1041da177e4SLinus Torvalds EMU8000_CSL_WRITE(emu, 30, 0x1e0); 1051da177e4SLinus Torvalds EMU8000_CCCA_WRITE(emu, 30, 0x1d8); 1061da177e4SLinus Torvalds EMU8000_VTFT_WRITE(emu, 31, 0); 1071da177e4SLinus Torvalds EMU8000_PSST_WRITE(emu, 31, 0x1d8); 1081da177e4SLinus Torvalds EMU8000_CSL_WRITE(emu, 31, 0x1e0); 1091da177e4SLinus Torvalds EMU8000_CCCA_WRITE(emu, 31, 0x1d8); 1101da177e4SLinus Torvalds 1111da177e4SLinus Torvalds return 0; 1121da177e4SLinus Torvalds } 1131da177e4SLinus Torvalds 1141da177e4SLinus Torvalds /* 1151da177e4SLinus Torvalds */ 1161da177e4SLinus Torvalds static void 117029d64b0STakashi Iwai snd_emu8000_write_wait(struct snd_emu8000 *emu, int can_schedule) 1181da177e4SLinus Torvalds { 1191da177e4SLinus Torvalds while ((EMU8000_SMALW_READ(emu) & 0x80000000) != 0) { 1201da177e4SLinus Torvalds if (can_schedule) { 1218433a509SNishanth Aravamudan schedule_timeout_interruptible(1); 1221da177e4SLinus Torvalds if (signal_pending(current)) 1231da177e4SLinus Torvalds break; 1241da177e4SLinus Torvalds } 1251da177e4SLinus Torvalds } 1261da177e4SLinus Torvalds } 1271da177e4SLinus Torvalds 1281da177e4SLinus Torvalds /* 1291da177e4SLinus Torvalds * close all channels 1301da177e4SLinus Torvalds */ 1311da177e4SLinus Torvalds static void 132029d64b0STakashi Iwai emu8k_close_dram(struct snd_emu8000 *emu) 1331da177e4SLinus Torvalds { 1341da177e4SLinus Torvalds int i; 1351da177e4SLinus Torvalds 1361da177e4SLinus Torvalds for (i = 0; i < 2; i++) 1371da177e4SLinus Torvalds snd_emux_unlock_voice(emu->emu, i); 1381da177e4SLinus Torvalds for (; i < EMU8000_DRAM_VOICES; i++) { 1391da177e4SLinus Torvalds snd_emu8000_dma_chan(emu, i, EMU8000_RAM_CLOSE); 1401da177e4SLinus Torvalds snd_emux_unlock_voice(emu->emu, i); 1411da177e4SLinus Torvalds } 1421da177e4SLinus Torvalds } 1431da177e4SLinus Torvalds 1441da177e4SLinus Torvalds /* 1451da177e4SLinus Torvalds * convert Hz to AWE32 rate offset (see emux/soundfont.c) 1461da177e4SLinus Torvalds */ 1471da177e4SLinus Torvalds 1481da177e4SLinus Torvalds #define OFFSET_SAMPLERATE 1011119 /* base = 44100 */ 1491da177e4SLinus Torvalds #define SAMPLERATE_RATIO 4096 1501da177e4SLinus Torvalds 1511da177e4SLinus Torvalds static int calc_rate_offset(int hz) 1521da177e4SLinus Torvalds { 1531da177e4SLinus Torvalds return snd_sf_linear_to_log(hz, OFFSET_SAMPLERATE, SAMPLERATE_RATIO); 1541da177e4SLinus Torvalds } 1551da177e4SLinus Torvalds 1561da177e4SLinus Torvalds 1571da177e4SLinus Torvalds /* 1581da177e4SLinus Torvalds */ 1591da177e4SLinus Torvalds 160029d64b0STakashi Iwai static struct snd_pcm_hardware emu8k_pcm_hw = { 1611da177e4SLinus Torvalds #ifdef USE_NONINTERLEAVE 1621da177e4SLinus Torvalds .info = SNDRV_PCM_INFO_NONINTERLEAVED, 1631da177e4SLinus Torvalds #else 1641da177e4SLinus Torvalds .info = SNDRV_PCM_INFO_INTERLEAVED, 1651da177e4SLinus Torvalds #endif 1661da177e4SLinus Torvalds .formats = SNDRV_PCM_FMTBIT_S16_LE, 1671da177e4SLinus Torvalds .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000, 1681da177e4SLinus Torvalds .rate_min = 4000, 1691da177e4SLinus Torvalds .rate_max = 48000, 1701da177e4SLinus Torvalds .channels_min = 1, 1711da177e4SLinus Torvalds .channels_max = 2, 1721da177e4SLinus Torvalds .buffer_bytes_max = (128*1024), 1731da177e4SLinus Torvalds .period_bytes_min = 1024, 1741da177e4SLinus Torvalds .period_bytes_max = (128*1024), 1751da177e4SLinus Torvalds .periods_min = 2, 1761da177e4SLinus Torvalds .periods_max = 1024, 1771da177e4SLinus Torvalds .fifo_size = 0, 1781da177e4SLinus Torvalds 1791da177e4SLinus Torvalds }; 1801da177e4SLinus Torvalds 1811da177e4SLinus Torvalds /* 1821da177e4SLinus Torvalds * get the current position at the given channel from CCCA register 1831da177e4SLinus Torvalds */ 184029d64b0STakashi Iwai static inline int emu8k_get_curpos(struct snd_emu8k_pcm *rec, int ch) 1851da177e4SLinus Torvalds { 1861da177e4SLinus Torvalds int val = EMU8000_CCCA_READ(rec->emu, ch) & 0xfffffff; 1871da177e4SLinus Torvalds val -= rec->loop_start[ch] - 1; 1881da177e4SLinus Torvalds return val; 1891da177e4SLinus Torvalds } 1901da177e4SLinus Torvalds 1911da177e4SLinus Torvalds 1921da177e4SLinus Torvalds /* 1931da177e4SLinus Torvalds * timer interrupt handler 1941da177e4SLinus Torvalds * check the current position and update the period if necessary. 1951da177e4SLinus Torvalds */ 1961da177e4SLinus Torvalds static void emu8k_pcm_timer_func(unsigned long data) 1971da177e4SLinus Torvalds { 198029d64b0STakashi Iwai struct snd_emu8k_pcm *rec = (struct snd_emu8k_pcm *)data; 1991da177e4SLinus Torvalds int ptr, delta; 2001da177e4SLinus Torvalds 2011da177e4SLinus Torvalds spin_lock(&rec->timer_lock); 2021da177e4SLinus Torvalds /* update the current pointer */ 2031da177e4SLinus Torvalds ptr = emu8k_get_curpos(rec, 0); 2041da177e4SLinus Torvalds if (ptr < rec->last_ptr) 2051da177e4SLinus Torvalds delta = ptr + rec->buf_size - rec->last_ptr; 2061da177e4SLinus Torvalds else 2071da177e4SLinus Torvalds delta = ptr - rec->last_ptr; 2081da177e4SLinus Torvalds rec->period_pos += delta; 2091da177e4SLinus Torvalds rec->last_ptr = ptr; 2101da177e4SLinus Torvalds 2111da177e4SLinus Torvalds /* reprogram timer */ 212f05b4127STakashi Iwai mod_timer(&rec->timer, jiffies + 1); 2131da177e4SLinus Torvalds 2141da177e4SLinus Torvalds /* update period */ 2151da177e4SLinus Torvalds if (rec->period_pos >= (int)rec->period_size) { 2161da177e4SLinus Torvalds rec->period_pos %= rec->period_size; 2171da177e4SLinus Torvalds spin_unlock(&rec->timer_lock); 2181da177e4SLinus Torvalds snd_pcm_period_elapsed(rec->substream); 2191da177e4SLinus Torvalds return; 2201da177e4SLinus Torvalds } 2211da177e4SLinus Torvalds spin_unlock(&rec->timer_lock); 2221da177e4SLinus Torvalds } 2231da177e4SLinus Torvalds 2241da177e4SLinus Torvalds 2251da177e4SLinus Torvalds /* 2261da177e4SLinus Torvalds * open pcm 2271da177e4SLinus Torvalds * creating an instance here 2281da177e4SLinus Torvalds */ 229029d64b0STakashi Iwai static int emu8k_pcm_open(struct snd_pcm_substream *subs) 2301da177e4SLinus Torvalds { 231029d64b0STakashi Iwai struct snd_emu8000 *emu = snd_pcm_substream_chip(subs); 232029d64b0STakashi Iwai struct snd_emu8k_pcm *rec; 233029d64b0STakashi Iwai struct snd_pcm_runtime *runtime = subs->runtime; 2341da177e4SLinus Torvalds 2359e76a76eSTakashi Iwai rec = kzalloc(sizeof(*rec), GFP_KERNEL); 2361da177e4SLinus Torvalds if (! rec) 2371da177e4SLinus Torvalds return -ENOMEM; 2381da177e4SLinus Torvalds 2391da177e4SLinus Torvalds rec->emu = emu; 2401da177e4SLinus Torvalds rec->substream = subs; 2411da177e4SLinus Torvalds runtime->private_data = rec; 2421da177e4SLinus Torvalds 2431da177e4SLinus Torvalds spin_lock_init(&rec->timer_lock); 244f05b4127STakashi Iwai setup_timer(&rec->timer, emu8k_pcm_timer_func, (unsigned long)rec); 2451da177e4SLinus Torvalds 2461da177e4SLinus Torvalds runtime->hw = emu8k_pcm_hw; 2471da177e4SLinus Torvalds runtime->hw.buffer_bytes_max = emu->mem_size - LOOP_BLANK_SIZE * 3; 2481da177e4SLinus Torvalds runtime->hw.period_bytes_max = runtime->hw.buffer_bytes_max / 2; 2491da177e4SLinus Torvalds 2501da177e4SLinus Torvalds /* use timer to update periods.. (specified in msec) */ 2511da177e4SLinus Torvalds snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_TIME, 2521da177e4SLinus Torvalds (1000000 + HZ - 1) / HZ, UINT_MAX); 2531da177e4SLinus Torvalds 2541da177e4SLinus Torvalds return 0; 2551da177e4SLinus Torvalds } 2561da177e4SLinus Torvalds 257029d64b0STakashi Iwai static int emu8k_pcm_close(struct snd_pcm_substream *subs) 2581da177e4SLinus Torvalds { 259029d64b0STakashi Iwai struct snd_emu8k_pcm *rec = subs->runtime->private_data; 2601da177e4SLinus Torvalds kfree(rec); 2611da177e4SLinus Torvalds subs->runtime->private_data = NULL; 2621da177e4SLinus Torvalds return 0; 2631da177e4SLinus Torvalds } 2641da177e4SLinus Torvalds 2651da177e4SLinus Torvalds /* 2661da177e4SLinus Torvalds * calculate pitch target 2671da177e4SLinus Torvalds */ 2681da177e4SLinus Torvalds static int calc_pitch_target(int pitch) 2691da177e4SLinus Torvalds { 2701da177e4SLinus Torvalds int ptarget = 1 << (pitch >> 12); 2711da177e4SLinus Torvalds if (pitch & 0x800) ptarget += (ptarget * 0x102e) / 0x2710; 2721da177e4SLinus Torvalds if (pitch & 0x400) ptarget += (ptarget * 0x764) / 0x2710; 2731da177e4SLinus Torvalds if (pitch & 0x200) ptarget += (ptarget * 0x389) / 0x2710; 2741da177e4SLinus Torvalds ptarget += (ptarget >> 1); 2751da177e4SLinus Torvalds if (ptarget > 0xffff) ptarget = 0xffff; 2761da177e4SLinus Torvalds return ptarget; 2771da177e4SLinus Torvalds } 2781da177e4SLinus Torvalds 2791da177e4SLinus Torvalds /* 2801da177e4SLinus Torvalds * set up the voice 2811da177e4SLinus Torvalds */ 282029d64b0STakashi Iwai static void setup_voice(struct snd_emu8k_pcm *rec, int ch) 2831da177e4SLinus Torvalds { 284029d64b0STakashi Iwai struct snd_emu8000 *hw = rec->emu; 2851da177e4SLinus Torvalds unsigned int temp; 2861da177e4SLinus Torvalds 2871da177e4SLinus Torvalds /* channel to be silent and idle */ 2881da177e4SLinus Torvalds EMU8000_DCYSUSV_WRITE(hw, ch, 0x0080); 2891da177e4SLinus Torvalds EMU8000_VTFT_WRITE(hw, ch, 0x0000FFFF); 2901da177e4SLinus Torvalds EMU8000_CVCF_WRITE(hw, ch, 0x0000FFFF); 2911da177e4SLinus Torvalds EMU8000_PTRX_WRITE(hw, ch, 0); 2921da177e4SLinus Torvalds EMU8000_CPF_WRITE(hw, ch, 0); 2931da177e4SLinus Torvalds 2941da177e4SLinus Torvalds /* pitch offset */ 2951da177e4SLinus Torvalds EMU8000_IP_WRITE(hw, ch, rec->pitch); 2961da177e4SLinus Torvalds /* set envelope parameters */ 2971da177e4SLinus Torvalds EMU8000_ENVVAL_WRITE(hw, ch, 0x8000); 2981da177e4SLinus Torvalds EMU8000_ATKHLD_WRITE(hw, ch, 0x7f7f); 2991da177e4SLinus Torvalds EMU8000_DCYSUS_WRITE(hw, ch, 0x7f7f); 3001da177e4SLinus Torvalds EMU8000_ENVVOL_WRITE(hw, ch, 0x8000); 3011da177e4SLinus Torvalds EMU8000_ATKHLDV_WRITE(hw, ch, 0x7f7f); 3021da177e4SLinus Torvalds /* decay/sustain parameter for volume envelope is used 3031da177e4SLinus Torvalds for triggerg the voice */ 3041da177e4SLinus Torvalds /* modulation envelope heights */ 3051da177e4SLinus Torvalds EMU8000_PEFE_WRITE(hw, ch, 0x0); 3061da177e4SLinus Torvalds /* lfo1/2 delay */ 3071da177e4SLinus Torvalds EMU8000_LFO1VAL_WRITE(hw, ch, 0x8000); 3081da177e4SLinus Torvalds EMU8000_LFO2VAL_WRITE(hw, ch, 0x8000); 3091da177e4SLinus Torvalds /* lfo1 pitch & cutoff shift */ 3101da177e4SLinus Torvalds EMU8000_FMMOD_WRITE(hw, ch, 0); 3111da177e4SLinus Torvalds /* lfo1 volume & freq */ 3121da177e4SLinus Torvalds EMU8000_TREMFRQ_WRITE(hw, ch, 0); 3131da177e4SLinus Torvalds /* lfo2 pitch & freq */ 3141da177e4SLinus Torvalds EMU8000_FM2FRQ2_WRITE(hw, ch, 0); 3151da177e4SLinus Torvalds /* pan & loop start */ 3161da177e4SLinus Torvalds temp = rec->panning[ch]; 3171da177e4SLinus Torvalds temp = (temp <<24) | ((unsigned int)rec->loop_start[ch] - 1); 3181da177e4SLinus Torvalds EMU8000_PSST_WRITE(hw, ch, temp); 3191da177e4SLinus Torvalds /* chorus & loop end (chorus 8bit, MSB) */ 3201da177e4SLinus Torvalds temp = 0; // chorus 3211da177e4SLinus Torvalds temp = (temp << 24) | ((unsigned int)rec->loop_start[ch] + rec->buf_size - 1); 3221da177e4SLinus Torvalds EMU8000_CSL_WRITE(hw, ch, temp); 3231da177e4SLinus Torvalds /* Q & current address (Q 4bit value, MSB) */ 3241da177e4SLinus Torvalds temp = 0; // filterQ 3251da177e4SLinus Torvalds temp = (temp << 28) | ((unsigned int)rec->loop_start[ch] - 1); 3261da177e4SLinus Torvalds EMU8000_CCCA_WRITE(hw, ch, temp); 3271da177e4SLinus Torvalds /* clear unknown registers */ 3281da177e4SLinus Torvalds EMU8000_00A0_WRITE(hw, ch, 0); 3291da177e4SLinus Torvalds EMU8000_0080_WRITE(hw, ch, 0); 3301da177e4SLinus Torvalds } 3311da177e4SLinus Torvalds 3321da177e4SLinus Torvalds /* 3331da177e4SLinus Torvalds * trigger the voice 3341da177e4SLinus Torvalds */ 335029d64b0STakashi Iwai static void start_voice(struct snd_emu8k_pcm *rec, int ch) 3361da177e4SLinus Torvalds { 3371da177e4SLinus Torvalds unsigned long flags; 338029d64b0STakashi Iwai struct snd_emu8000 *hw = rec->emu; 3391da177e4SLinus Torvalds unsigned int temp, aux; 3401da177e4SLinus Torvalds int pt = calc_pitch_target(rec->pitch); 3411da177e4SLinus Torvalds 3421da177e4SLinus Torvalds /* cutoff and volume */ 3431da177e4SLinus Torvalds EMU8000_IFATN_WRITE(hw, ch, 0xff00); 3441da177e4SLinus Torvalds EMU8000_VTFT_WRITE(hw, ch, 0xffff); 3451da177e4SLinus Torvalds EMU8000_CVCF_WRITE(hw, ch, 0xffff); 3461da177e4SLinus Torvalds /* trigger envelope */ 3471da177e4SLinus Torvalds EMU8000_DCYSUSV_WRITE(hw, ch, 0x7f7f); 3481da177e4SLinus Torvalds /* set reverb and pitch target */ 3491da177e4SLinus Torvalds temp = 0; // reverb 3501da177e4SLinus Torvalds if (rec->panning[ch] == 0) 3511da177e4SLinus Torvalds aux = 0xff; 3521da177e4SLinus Torvalds else 3531da177e4SLinus Torvalds aux = (-rec->panning[ch]) & 0xff; 3541da177e4SLinus Torvalds temp = (temp << 8) | (pt << 16) | aux; 3551da177e4SLinus Torvalds EMU8000_PTRX_WRITE(hw, ch, temp); 3561da177e4SLinus Torvalds EMU8000_CPF_WRITE(hw, ch, pt << 16); 3571da177e4SLinus Torvalds 3581da177e4SLinus Torvalds /* start timer */ 3591da177e4SLinus Torvalds spin_lock_irqsave(&rec->timer_lock, flags); 3601da177e4SLinus Torvalds if (! rec->timer_running) { 361f05b4127STakashi Iwai mod_timer(&rec->timer, jiffies + 1); 3621da177e4SLinus Torvalds rec->timer_running = 1; 3631da177e4SLinus Torvalds } 3641da177e4SLinus Torvalds spin_unlock_irqrestore(&rec->timer_lock, flags); 3651da177e4SLinus Torvalds } 3661da177e4SLinus Torvalds 3671da177e4SLinus Torvalds /* 3681da177e4SLinus Torvalds * stop the voice immediately 3691da177e4SLinus Torvalds */ 370029d64b0STakashi Iwai static void stop_voice(struct snd_emu8k_pcm *rec, int ch) 3711da177e4SLinus Torvalds { 3721da177e4SLinus Torvalds unsigned long flags; 373029d64b0STakashi Iwai struct snd_emu8000 *hw = rec->emu; 3741da177e4SLinus Torvalds 3751da177e4SLinus Torvalds EMU8000_DCYSUSV_WRITE(hw, ch, 0x807F); 3761da177e4SLinus Torvalds 3771da177e4SLinus Torvalds /* stop timer */ 3781da177e4SLinus Torvalds spin_lock_irqsave(&rec->timer_lock, flags); 3791da177e4SLinus Torvalds if (rec->timer_running) { 3801da177e4SLinus Torvalds del_timer(&rec->timer); 3811da177e4SLinus Torvalds rec->timer_running = 0; 3821da177e4SLinus Torvalds } 3831da177e4SLinus Torvalds spin_unlock_irqrestore(&rec->timer_lock, flags); 3841da177e4SLinus Torvalds } 3851da177e4SLinus Torvalds 386029d64b0STakashi Iwai static int emu8k_pcm_trigger(struct snd_pcm_substream *subs, int cmd) 3871da177e4SLinus Torvalds { 388029d64b0STakashi Iwai struct snd_emu8k_pcm *rec = subs->runtime->private_data; 3891da177e4SLinus Torvalds int ch; 3901da177e4SLinus Torvalds 3911da177e4SLinus Torvalds switch (cmd) { 3921da177e4SLinus Torvalds case SNDRV_PCM_TRIGGER_START: 3931da177e4SLinus Torvalds for (ch = 0; ch < rec->voices; ch++) 3941da177e4SLinus Torvalds start_voice(rec, ch); 3951da177e4SLinus Torvalds rec->running = 1; 3961da177e4SLinus Torvalds break; 3971da177e4SLinus Torvalds case SNDRV_PCM_TRIGGER_STOP: 3981da177e4SLinus Torvalds rec->running = 0; 3991da177e4SLinus Torvalds for (ch = 0; ch < rec->voices; ch++) 4001da177e4SLinus Torvalds stop_voice(rec, ch); 4011da177e4SLinus Torvalds break; 4021da177e4SLinus Torvalds default: 4031da177e4SLinus Torvalds return -EINVAL; 4041da177e4SLinus Torvalds } 4051da177e4SLinus Torvalds return 0; 4061da177e4SLinus Torvalds } 4071da177e4SLinus Torvalds 4081da177e4SLinus Torvalds 4091da177e4SLinus Torvalds /* 4101da177e4SLinus Torvalds * copy / silence ops 4111da177e4SLinus Torvalds */ 4121da177e4SLinus Torvalds 4131da177e4SLinus Torvalds /* 4141da177e4SLinus Torvalds * this macro should be inserted in the copy/silence loops 4151da177e4SLinus Torvalds * to reduce the latency. without this, the system will hang up 4161da177e4SLinus Torvalds * during the whole loop. 4171da177e4SLinus Torvalds */ 4181da177e4SLinus Torvalds #define CHECK_SCHEDULER() \ 4191da177e4SLinus Torvalds do { \ 4201da177e4SLinus Torvalds cond_resched();\ 4211da177e4SLinus Torvalds if (signal_pending(current))\ 4221da177e4SLinus Torvalds return -EAGAIN;\ 4231da177e4SLinus Torvalds } while (0) 4241da177e4SLinus Torvalds 4254b83eff8STakashi Iwai enum { 4264b83eff8STakashi Iwai COPY_USER, COPY_KERNEL, FILL_SILENCE, 4274b83eff8STakashi Iwai }; 4284b83eff8STakashi Iwai 4294b83eff8STakashi Iwai #define GET_VAL(sval, buf, mode) \ 4304b83eff8STakashi Iwai do { \ 4314b83eff8STakashi Iwai switch (mode) { \ 4324b83eff8STakashi Iwai case FILL_SILENCE: \ 4334b83eff8STakashi Iwai sval = 0; \ 4344b83eff8STakashi Iwai break; \ 4354b83eff8STakashi Iwai case COPY_KERNEL: \ 4364b83eff8STakashi Iwai sval = *buf++; \ 4374b83eff8STakashi Iwai break; \ 4384b83eff8STakashi Iwai default: \ 4394b83eff8STakashi Iwai if (get_user(sval, (unsigned short __user *)buf)) \ 4404b83eff8STakashi Iwai return -EFAULT; \ 4414b83eff8STakashi Iwai buf++; \ 4424b83eff8STakashi Iwai break; \ 4434b83eff8STakashi Iwai } \ 4444b83eff8STakashi Iwai } while (0) 4451da177e4SLinus Torvalds 4461da177e4SLinus Torvalds #ifdef USE_NONINTERLEAVE 4474b83eff8STakashi Iwai 4484b83eff8STakashi Iwai #define LOOP_WRITE(rec, offset, _buf, count, mode) \ 4494b83eff8STakashi Iwai do { \ 4504b83eff8STakashi Iwai struct snd_emu8000 *emu = (rec)->emu; \ 4514b83eff8STakashi Iwai unsigned short *buf = (unsigned short *)(_buf); \ 4524b83eff8STakashi Iwai snd_emu8000_write_wait(emu, 1); \ 4534b83eff8STakashi Iwai EMU8000_SMALW_WRITE(emu, offset); \ 4544b83eff8STakashi Iwai while (count > 0) { \ 4554b83eff8STakashi Iwai unsigned short sval; \ 4564b83eff8STakashi Iwai CHECK_SCHEDULER(); \ 4574b83eff8STakashi Iwai GET_VAL(sval, buf, mode); \ 4584b83eff8STakashi Iwai EMU8000_SMLD_WRITE(emu, sval); \ 4594b83eff8STakashi Iwai count--; \ 4604b83eff8STakashi Iwai } \ 4614b83eff8STakashi Iwai } while (0) 4624b83eff8STakashi Iwai 4631da177e4SLinus Torvalds /* copy one channel block */ 4644b83eff8STakashi Iwai static int emu8k_pcm_copy(struct snd_pcm_substream *subs, 4654b83eff8STakashi Iwai int voice, unsigned long pos, 4664b83eff8STakashi Iwai void __user *src, unsigned long count) 4671da177e4SLinus Torvalds { 4684b83eff8STakashi Iwai struct snd_emu8k_pcm *rec = subs->runtime->private_data; 4694b83eff8STakashi Iwai 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_UESR); 4741da177e4SLinus Torvalds return 0; 4751da177e4SLinus Torvalds } 4761da177e4SLinus Torvalds 4774b83eff8STakashi Iwai static int emu8k_pcm_copy_kernel(struct snd_pcm_substream *subs, 4784b83eff8STakashi Iwai int voice, unsigned long pos, 4794b83eff8STakashi Iwai void *src, unsigned long count) 4801da177e4SLinus Torvalds { 481029d64b0STakashi Iwai struct snd_emu8k_pcm *rec = subs->runtime->private_data; 4821da177e4SLinus Torvalds 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, src, count, COPY_KERNEL); 4874b83eff8STakashi Iwai return 0; 4881da177e4SLinus Torvalds } 4891da177e4SLinus Torvalds 4901da177e4SLinus Torvalds /* make a channel block silence */ 4914b83eff8STakashi Iwai static int emu8k_pcm_silence(struct snd_pcm_substream *subs, 4924b83eff8STakashi Iwai int voice, unsigned long pos, unsigned long count) 4931da177e4SLinus Torvalds { 4944b83eff8STakashi Iwai struct snd_emu8k_pcm *rec = subs->runtime->private_data; 4954b83eff8STakashi Iwai 4964b83eff8STakashi Iwai /* convert to word unit */ 4974b83eff8STakashi Iwai pos = (pos << 1) + rec->loop_start[voice]; 4984b83eff8STakashi Iwai count <<= 1; 4994b83eff8STakashi Iwai LOOP_WRITE(rec, pos, NULL, count, FILL_SILENCE); 5001da177e4SLinus Torvalds return 0; 5011da177e4SLinus Torvalds } 5021da177e4SLinus Torvalds 5031da177e4SLinus Torvalds #else /* interleave */ 5041da177e4SLinus Torvalds 5054b83eff8STakashi Iwai #define LOOP_WRITE(rec, pos, _buf, count, mode) \ 5064b83eff8STakashi Iwai do { \ 5074b83eff8STakashi Iwai struct snd_emu8000 *emu = rec->emu; \ 5084b83eff8STakashi Iwai unsigned short *buf = (unsigned short *)(_buf); \ 5094b83eff8STakashi Iwai snd_emu8000_write_wait(emu, 1); \ 5104b83eff8STakashi Iwai EMU8000_SMALW_WRITE(emu, pos + rec->loop_start[0]); \ 5114b83eff8STakashi Iwai if (rec->voices > 1) \ 5124b83eff8STakashi Iwai EMU8000_SMARW_WRITE(emu, pos + rec->loop_start[1]); \ 5134b83eff8STakashi Iwai while (count > 0) { \ 5144b83eff8STakashi Iwai unsigned short sval; \ 5154b83eff8STakashi Iwai CHECK_SCHEDULER(); \ 5164b83eff8STakashi Iwai GET_VAL(sval, buf, mode); \ 5174b83eff8STakashi Iwai EMU8000_SMLD_WRITE(emu, sval); \ 5184b83eff8STakashi Iwai if (rec->voices > 1) { \ 5194b83eff8STakashi Iwai CHECK_SCHEDULER(); \ 5204b83eff8STakashi Iwai GET_VAL(sval, buf, mode); \ 5214b83eff8STakashi Iwai EMU8000_SMRD_WRITE(emu, sval); \ 5224b83eff8STakashi Iwai } \ 5234b83eff8STakashi Iwai count--; \ 5244b83eff8STakashi Iwai } \ 5254b83eff8STakashi Iwai } while (0) 5264b83eff8STakashi Iwai 5274b83eff8STakashi Iwai 5281da177e4SLinus Torvalds /* 5291da177e4SLinus Torvalds * copy the interleaved data can be done easily by using 5301da177e4SLinus Torvalds * DMA "left" and "right" channels on emu8k engine. 5311da177e4SLinus Torvalds */ 532029d64b0STakashi Iwai static int emu8k_pcm_copy(struct snd_pcm_substream *subs, 5334b83eff8STakashi Iwai int voice, unsigned long pos, 5344b83eff8STakashi Iwai void __user *src, unsigned long count) 5351da177e4SLinus Torvalds { 536029d64b0STakashi Iwai struct snd_emu8k_pcm *rec = subs->runtime->private_data; 5371da177e4SLinus Torvalds 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_USER); 5424b83eff8STakashi Iwai return 0; 5434b83eff8STakashi Iwai } 5441da177e4SLinus Torvalds 5454b83eff8STakashi Iwai static int emu8k_pcm_copy_kernel(struct snd_pcm_substream *subs, 5464b83eff8STakashi Iwai int voice, unsigned long pos, 5474b83eff8STakashi Iwai void *src, unsigned long count) 5484b83eff8STakashi Iwai { 5494b83eff8STakashi Iwai struct snd_emu8k_pcm *rec = subs->runtime->private_data; 5504b83eff8STakashi Iwai 5514b83eff8STakashi Iwai /* convert to frames */ 5524b83eff8STakashi Iwai pos = bytes_to_frames(subs->runtime, pos); 5534b83eff8STakashi Iwai count = bytes_to_frames(subs->runtime, count); 5544b83eff8STakashi Iwai LOOP_WRITE(rec, pos, src, count, COPY_KERNEL); 5551da177e4SLinus Torvalds return 0; 5561da177e4SLinus Torvalds } 5571da177e4SLinus Torvalds 558029d64b0STakashi Iwai static int emu8k_pcm_silence(struct snd_pcm_substream *subs, 5594b83eff8STakashi Iwai int voice, unsigned long pos, unsigned long count) 5601da177e4SLinus Torvalds { 561029d64b0STakashi Iwai struct snd_emu8k_pcm *rec = subs->runtime->private_data; 5621da177e4SLinus Torvalds 5634b83eff8STakashi Iwai /* convert to frames */ 5644b83eff8STakashi Iwai pos = bytes_to_frames(subs->runtime, pos); 5654b83eff8STakashi Iwai count = bytes_to_frames(subs->runtime, count); 5664b83eff8STakashi Iwai LOOP_WRITE(rec, pos, NULL, count, FILL_SILENCE); 5671da177e4SLinus Torvalds return 0; 5681da177e4SLinus Torvalds } 5691da177e4SLinus Torvalds #endif 5701da177e4SLinus Torvalds 5711da177e4SLinus Torvalds 5721da177e4SLinus Torvalds /* 5731da177e4SLinus Torvalds * allocate a memory block 5741da177e4SLinus Torvalds */ 575029d64b0STakashi Iwai static int emu8k_pcm_hw_params(struct snd_pcm_substream *subs, 576029d64b0STakashi Iwai struct snd_pcm_hw_params *hw_params) 5771da177e4SLinus Torvalds { 578029d64b0STakashi Iwai struct snd_emu8k_pcm *rec = subs->runtime->private_data; 5791da177e4SLinus Torvalds 5801da177e4SLinus Torvalds if (rec->block) { 5811da177e4SLinus Torvalds /* reallocation - release the old block */ 5821da177e4SLinus Torvalds snd_util_mem_free(rec->emu->memhdr, rec->block); 5831da177e4SLinus Torvalds rec->block = NULL; 5841da177e4SLinus Torvalds } 5851da177e4SLinus Torvalds 5861da177e4SLinus Torvalds rec->allocated_bytes = params_buffer_bytes(hw_params) + LOOP_BLANK_SIZE * 4; 5871da177e4SLinus Torvalds rec->block = snd_util_mem_alloc(rec->emu->memhdr, rec->allocated_bytes); 5881da177e4SLinus Torvalds if (! rec->block) 5891da177e4SLinus Torvalds return -ENOMEM; 5901da177e4SLinus Torvalds rec->offset = EMU8000_DRAM_OFFSET + (rec->block->offset >> 1); /* in word */ 5911da177e4SLinus Torvalds /* at least dma_bytes must be set for non-interleaved mode */ 5921da177e4SLinus Torvalds subs->dma_buffer.bytes = params_buffer_bytes(hw_params); 5931da177e4SLinus Torvalds 5941da177e4SLinus Torvalds return 0; 5951da177e4SLinus Torvalds } 5961da177e4SLinus Torvalds 5971da177e4SLinus Torvalds /* 5981da177e4SLinus Torvalds * free the memory block 5991da177e4SLinus Torvalds */ 600029d64b0STakashi Iwai static int emu8k_pcm_hw_free(struct snd_pcm_substream *subs) 6011da177e4SLinus Torvalds { 602029d64b0STakashi Iwai struct snd_emu8k_pcm *rec = subs->runtime->private_data; 6031da177e4SLinus Torvalds 6041da177e4SLinus Torvalds if (rec->block) { 6051da177e4SLinus Torvalds int ch; 6061da177e4SLinus Torvalds for (ch = 0; ch < rec->voices; ch++) 6071da177e4SLinus Torvalds stop_voice(rec, ch); // to be sure 6081da177e4SLinus Torvalds if (rec->dram_opened) 6091da177e4SLinus Torvalds emu8k_close_dram(rec->emu); 6101da177e4SLinus Torvalds snd_util_mem_free(rec->emu->memhdr, rec->block); 6111da177e4SLinus Torvalds rec->block = NULL; 6121da177e4SLinus Torvalds } 6131da177e4SLinus Torvalds return 0; 6141da177e4SLinus Torvalds } 6151da177e4SLinus Torvalds 6161da177e4SLinus Torvalds /* 6171da177e4SLinus Torvalds */ 618029d64b0STakashi Iwai static int emu8k_pcm_prepare(struct snd_pcm_substream *subs) 6191da177e4SLinus Torvalds { 620029d64b0STakashi Iwai struct snd_emu8k_pcm *rec = subs->runtime->private_data; 6211da177e4SLinus Torvalds 6221da177e4SLinus Torvalds rec->pitch = 0xe000 + calc_rate_offset(subs->runtime->rate); 6231da177e4SLinus Torvalds rec->last_ptr = 0; 6241da177e4SLinus Torvalds rec->period_pos = 0; 6251da177e4SLinus Torvalds 6261da177e4SLinus Torvalds rec->buf_size = subs->runtime->buffer_size; 6271da177e4SLinus Torvalds rec->period_size = subs->runtime->period_size; 6281da177e4SLinus Torvalds rec->voices = subs->runtime->channels; 6291da177e4SLinus Torvalds rec->loop_start[0] = rec->offset + LOOP_BLANK_SIZE; 6301da177e4SLinus Torvalds if (rec->voices > 1) 6311da177e4SLinus Torvalds rec->loop_start[1] = rec->loop_start[0] + rec->buf_size + LOOP_BLANK_SIZE; 6321da177e4SLinus Torvalds if (rec->voices > 1) { 6331da177e4SLinus Torvalds rec->panning[0] = 0xff; 6341da177e4SLinus Torvalds rec->panning[1] = 0x00; 6351da177e4SLinus Torvalds } else 6361da177e4SLinus Torvalds rec->panning[0] = 0x80; 6371da177e4SLinus Torvalds 6381da177e4SLinus Torvalds if (! rec->dram_opened) { 6391da177e4SLinus Torvalds int err, i, ch; 6401da177e4SLinus Torvalds 6411da177e4SLinus Torvalds snd_emux_terminate_all(rec->emu->emu); 6421da177e4SLinus Torvalds if ((err = emu8k_open_dram_for_pcm(rec->emu, rec->voices)) != 0) 6431da177e4SLinus Torvalds return err; 6441da177e4SLinus Torvalds rec->dram_opened = 1; 6451da177e4SLinus Torvalds 6461da177e4SLinus Torvalds /* clear loop blanks */ 6471da177e4SLinus Torvalds snd_emu8000_write_wait(rec->emu, 0); 6481da177e4SLinus Torvalds EMU8000_SMALW_WRITE(rec->emu, rec->offset); 6491da177e4SLinus Torvalds for (i = 0; i < LOOP_BLANK_SIZE; i++) 6501da177e4SLinus Torvalds EMU8000_SMLD_WRITE(rec->emu, 0); 6511da177e4SLinus Torvalds for (ch = 0; ch < rec->voices; ch++) { 6521da177e4SLinus Torvalds EMU8000_SMALW_WRITE(rec->emu, rec->loop_start[ch] + rec->buf_size); 6531da177e4SLinus Torvalds for (i = 0; i < LOOP_BLANK_SIZE; i++) 6541da177e4SLinus Torvalds EMU8000_SMLD_WRITE(rec->emu, 0); 6551da177e4SLinus Torvalds } 6561da177e4SLinus Torvalds } 6571da177e4SLinus Torvalds 6581da177e4SLinus Torvalds setup_voice(rec, 0); 6591da177e4SLinus Torvalds if (rec->voices > 1) 6601da177e4SLinus Torvalds setup_voice(rec, 1); 6611da177e4SLinus Torvalds return 0; 6621da177e4SLinus Torvalds } 6631da177e4SLinus Torvalds 664029d64b0STakashi Iwai static snd_pcm_uframes_t emu8k_pcm_pointer(struct snd_pcm_substream *subs) 6651da177e4SLinus Torvalds { 666029d64b0STakashi Iwai struct snd_emu8k_pcm *rec = subs->runtime->private_data; 6671da177e4SLinus Torvalds if (rec->running) 6681da177e4SLinus Torvalds return emu8k_get_curpos(rec, 0); 6691da177e4SLinus Torvalds return 0; 6701da177e4SLinus Torvalds } 6711da177e4SLinus Torvalds 6721da177e4SLinus Torvalds 673*84c8f90fSArvind Yadav static const struct snd_pcm_ops emu8k_pcm_ops = { 6741da177e4SLinus Torvalds .open = emu8k_pcm_open, 6751da177e4SLinus Torvalds .close = emu8k_pcm_close, 6761da177e4SLinus Torvalds .ioctl = snd_pcm_lib_ioctl, 6771da177e4SLinus Torvalds .hw_params = emu8k_pcm_hw_params, 6781da177e4SLinus Torvalds .hw_free = emu8k_pcm_hw_free, 6791da177e4SLinus Torvalds .prepare = emu8k_pcm_prepare, 6801da177e4SLinus Torvalds .trigger = emu8k_pcm_trigger, 6811da177e4SLinus Torvalds .pointer = emu8k_pcm_pointer, 6824b83eff8STakashi Iwai .copy_user = emu8k_pcm_copy, 6834b83eff8STakashi Iwai .copy_kernel = emu8k_pcm_copy_kernel, 6844b83eff8STakashi Iwai .fill_silence = emu8k_pcm_silence, 6851da177e4SLinus Torvalds }; 6861da177e4SLinus Torvalds 6871da177e4SLinus Torvalds 688029d64b0STakashi Iwai static void snd_emu8000_pcm_free(struct snd_pcm *pcm) 6891da177e4SLinus Torvalds { 690029d64b0STakashi Iwai struct snd_emu8000 *emu = pcm->private_data; 6911da177e4SLinus Torvalds emu->pcm = NULL; 6921da177e4SLinus Torvalds } 6931da177e4SLinus Torvalds 694029d64b0STakashi Iwai int snd_emu8000_pcm_new(struct snd_card *card, struct snd_emu8000 *emu, int index) 6951da177e4SLinus Torvalds { 696029d64b0STakashi Iwai struct snd_pcm *pcm; 6971da177e4SLinus Torvalds int err; 6981da177e4SLinus Torvalds 6991da177e4SLinus Torvalds if ((err = snd_pcm_new(card, "Emu8000 PCM", index, 1, 0, &pcm)) < 0) 7001da177e4SLinus Torvalds return err; 7011da177e4SLinus Torvalds pcm->private_data = emu; 7021da177e4SLinus Torvalds pcm->private_free = snd_emu8000_pcm_free; 7031da177e4SLinus Torvalds snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &emu8k_pcm_ops); 7041da177e4SLinus Torvalds emu->pcm = pcm; 7051da177e4SLinus Torvalds 7061da177e4SLinus Torvalds snd_device_register(card, pcm); 7071da177e4SLinus Torvalds 7081da177e4SLinus Torvalds return 0; 7091da177e4SLinus Torvalds } 710