diff options
Diffstat (limited to 'sound/oss/harmony.c')
-rw-r--r-- | sound/oss/harmony.c | 1330 |
1 files changed, 0 insertions, 1330 deletions
diff --git a/sound/oss/harmony.c b/sound/oss/harmony.c deleted file mode 100644 index 6601b28..0000000 --- a/sound/oss/harmony.c +++ /dev/null @@ -1,1330 +0,0 @@ -/* - sound/oss/harmony.c - - This is a sound driver for ASP's and Lasi's Harmony sound chip - and is unlikely to be used for anything other than on a HP PA-RISC. - - Harmony is found in HP 712s, 715/new and many other GSC based machines. - On older 715 machines you'll find the technically identical chip - called 'Vivace'. Both Harmony and Vicace are supported by this driver. - - Copyright 2000 (c) Linuxcare Canada, Alex deVries <alex@onefishtwo.ca> - Copyright 2000-2003 (c) Helge Deller <deller@gmx.de> - Copyright 2001 (c) Matthieu Delahaye <delahaym@esiee.fr> - Copyright 2001 (c) Jean-Christophe Vaugeois <vaugeoij@esiee.fr> - Copyright 2004 (c) Stuart Brady <sdbrady@ntlworld.com> - - -TODO: - - fix SNDCTL_DSP_GETOSPACE and SNDCTL_DSP_GETISPACE ioctls to - return the real values - - add private ioctl for selecting line- or microphone input - (only one of them is available at the same time) - - add module parameters - - implement mmap functionality - - implement gain meter ? - - ... -*/ - -#include <linux/delay.h> -#include <linux/errno.h> -#include <linux/init.h> -#include <linux/interrupt.h> -#include <linux/ioport.h> -#include <linux/types.h> -#include <linux/mm.h> -#include <linux/pci.h> - -#include <asm/parisc-device.h> -#include <asm/io.h> - -#include "sound_config.h" - - -#define PFX "harmony: " -#define HARMONY_VERSION "V0.9a" - -#undef DEBUG -#ifdef DEBUG -# define DPRINTK printk -#else -# define DPRINTK(x,...) -#endif - - -#define MAX_BUFS 10 /* maximum number of rotating buffers */ -#define HARMONY_BUF_SIZE 4096 /* needs to be a multiple of PAGE_SIZE (4096)! */ - -#define CNTL_C 0x80000000 -#define CNTL_ST 0x00000020 -#define CNTL_44100 0x00000015 /* HARMONY_SR_44KHZ */ -#define CNTL_8000 0x00000008 /* HARMONY_SR_8KHZ */ - -#define GAINCTL_HE 0x08000000 -#define GAINCTL_LE 0x04000000 -#define GAINCTL_SE 0x02000000 - -#define DSTATUS_PN 0x00000200 -#define DSTATUS_RN 0x00000002 - -#define DSTATUS_IE 0x80000000 - -#define HARMONY_DF_16BIT_LINEAR 0 -#define HARMONY_DF_8BIT_ULAW 1 -#define HARMONY_DF_8BIT_ALAW 2 - -#define HARMONY_SS_MONO 0 -#define HARMONY_SS_STEREO 1 - -#define HARMONY_SR_8KHZ 0x08 -#define HARMONY_SR_16KHZ 0x09 -#define HARMONY_SR_27KHZ 0x0A -#define HARMONY_SR_32KHZ 0x0B -#define HARMONY_SR_48KHZ 0x0E -#define HARMONY_SR_9KHZ 0x0F -#define HARMONY_SR_5KHZ 0x10 -#define HARMONY_SR_11KHZ 0x11 -#define HARMONY_SR_18KHZ 0x12 -#define HARMONY_SR_22KHZ 0x13 -#define HARMONY_SR_37KHZ 0x14 -#define HARMONY_SR_44KHZ 0x15 -#define HARMONY_SR_33KHZ 0x16 -#define HARMONY_SR_6KHZ 0x17 - -/* - * Some magics numbers used to auto-detect file formats - */ - -#define HARMONY_MAGIC_8B_ULAW 1 -#define HARMONY_MAGIC_8B_ALAW 27 -#define HARMONY_MAGIC_16B_LINEAR 3 -#define HARMONY_MAGIC_MONO 1 -#define HARMONY_MAGIC_STEREO 2 - -/* - * Channels Positions in mixer register - */ - -#define GAIN_HE_SHIFT 27 -#define GAIN_HE_MASK ( 1 << GAIN_HE_SHIFT) -#define GAIN_LE_SHIFT 26 -#define GAIN_LE_MASK ( 1 << GAIN_LE_SHIFT) -#define GAIN_SE_SHIFT 25 -#define GAIN_SE_MASK ( 1 << GAIN_SE_SHIFT) -#define GAIN_IS_SHIFT 24 -#define GAIN_IS_MASK ( 1 << GAIN_IS_SHIFT) -#define GAIN_MA_SHIFT 20 -#define GAIN_MA_MASK ( 0x0f << GAIN_MA_SHIFT) -#define GAIN_LI_SHIFT 16 -#define GAIN_LI_MASK ( 0x0f << GAIN_LI_SHIFT) -#define GAIN_RI_SHIFT 12 -#define GAIN_RI_MASK ( 0x0f << GAIN_RI_SHIFT) -#define GAIN_LO_SHIFT 6 -#define GAIN_LO_MASK ( 0x3f << GAIN_LO_SHIFT) -#define GAIN_RO_SHIFT 0 -#define GAIN_RO_MASK ( 0x3f << GAIN_RO_SHIFT) - - -#define MAX_OUTPUT_LEVEL (GAIN_RO_MASK >> GAIN_RO_SHIFT) -#define MAX_INPUT_LEVEL (GAIN_RI_MASK >> GAIN_RI_SHIFT) -#define MAX_MONITOR_LEVEL (GAIN_MA_MASK >> GAIN_MA_SHIFT) - -#define MIXER_INTERNAL SOUND_MIXER_LINE1 -#define MIXER_LINEOUT SOUND_MIXER_LINE2 -#define MIXER_HEADPHONES SOUND_MIXER_LINE3 - -#define MASK_INTERNAL SOUND_MASK_LINE1 -#define MASK_LINEOUT SOUND_MASK_LINE2 -#define MASK_HEADPHONES SOUND_MASK_LINE3 - -/* - * Channels Mask in mixer register - */ - -#define GAIN_TOTAL_SILENCE 0x00F00FFF -#define GAIN_DEFAULT 0x0FF00000 - - -struct harmony_hpa { - u8 unused000; - u8 id; - u8 teleshare_id; - u8 unused003; - u32 reset; - u32 cntl; - u32 gainctl; - u32 pnxtadd; - u32 pcuradd; - u32 rnxtadd; - u32 rcuradd; - u32 dstatus; - u32 ov; - u32 pio; - u32 unused02c; - u32 unused030[3]; - u32 diag; -}; - -struct harmony_dev { - struct harmony_hpa *hpa; - struct parisc_device *dev; - u32 current_gain; - u32 dac_rate; /* 8000 ... 48000 (Hz) */ - u8 data_format; /* HARMONY_DF_xx_BIT_xxx */ - u8 sample_rate; /* HARMONY_SR_xx_KHZ */ - u8 stereo_select; /* HARMONY_SS_MONO or HARMONY_SS_STEREO */ - int format_initialized :1; - int suspended_playing :1; - int suspended_recording :1; - - int blocked_playing :1; - int blocked_recording :1; - int audio_open :1; - int mixer_open :1; - - wait_queue_head_t wq_play, wq_record; - int first_filled_play; /* first buffer containing data (next to play) */ - int nb_filled_play; - int play_offset; - int first_filled_record; - int nb_filled_record; - - int dsp_unit, mixer_unit; -}; - - -static struct harmony_dev harmony; - - -/* - * Dynamic sound buffer allocation and DMA memory - */ - -struct harmony_buffer { - unsigned char *addr; - dma_addr_t dma_handle; - int dma_coherent; /* Zero if dma_alloc_coherent() fails */ - unsigned int len; -}; - -/* - * Harmony memory buffers - */ - -static struct harmony_buffer played_buf, recorded_buf, silent, graveyard; - - -#define CHECK_WBACK_INV_OFFSET(b,offset,len) \ - do { if (!b.dma_coherent) \ - dma_cache_wback_inv((unsigned long)b.addr+offset,len); \ - } while (0) - - -static int __init harmony_alloc_buffer(struct harmony_buffer *b, - unsigned int buffer_count) -{ - b->len = buffer_count * HARMONY_BUF_SIZE; - b->addr = dma_alloc_coherent(&harmony.dev->dev, - b->len, &b->dma_handle, GFP_KERNEL|GFP_DMA); - if (b->addr && b->dma_handle) { - b->dma_coherent = 1; - DPRINTK(KERN_INFO PFX "coherent memory: 0x%lx, played_buf: 0x%lx\n", - (unsigned long)b->dma_handle, (unsigned long)b->addr); - } else { - b->dma_coherent = 0; - /* kmalloc()ed memory will HPMC on ccio machines ! */ - b->addr = kmalloc(b->len, GFP_KERNEL); - if (!b->addr) { - printk(KERN_ERR PFX "couldn't allocate memory\n"); - return -EBUSY; - } - b->dma_handle = __pa(b->addr); - } - return 0; -} - -static void __exit harmony_free_buffer(struct harmony_buffer *b) -{ - if (!b->addr) - return; - - if (b->dma_coherent) - dma_free_coherent(&harmony.dev->dev, - b->len, b->addr, b->dma_handle); - else - kfree(b->addr); - - memset(b, 0, sizeof(*b)); -} - - - -/* - * Low-Level sound-chip programming - */ - -static void __inline__ harmony_wait_CNTL(void) -{ - /* Wait until we're out of control mode */ - while (gsc_readl(&harmony.hpa->cntl) & CNTL_C) - /* wait */ ; -} - - -static void harmony_update_control(void) -{ - u32 default_cntl; - - /* Set CNTL */ - default_cntl = (CNTL_C | /* The C bit */ - (harmony.data_format << 6) | /* Set the data format */ - (harmony.stereo_select << 5) | /* Stereo select */ - (harmony.sample_rate)); /* Set sample rate */ - harmony.format_initialized = 1; - - /* initialize CNTL */ - gsc_writel(default_cntl, &harmony.hpa->cntl); -} - -static void harmony_set_control(u8 data_format, u8 sample_rate, u8 stereo_select) -{ - harmony.sample_rate = sample_rate; - harmony.data_format = data_format; - harmony.stereo_select = stereo_select; - harmony_update_control(); -} - -static void harmony_set_rate(u8 data_rate) -{ - harmony.sample_rate = data_rate; - harmony_update_control(); -} - -static int harmony_detect_rate(int *freq) -{ - int newrate; - switch (*freq) { - case 8000: newrate = HARMONY_SR_8KHZ; break; - case 16000: newrate = HARMONY_SR_16KHZ; break; - case 27428: newrate = HARMONY_SR_27KHZ; break; - case 32000: newrate = HARMONY_SR_32KHZ; break; - case 48000: newrate = HARMONY_SR_48KHZ; break; - case 9600: newrate = HARMONY_SR_9KHZ; break; - case 5512: newrate = HARMONY_SR_5KHZ; break; - case 11025: newrate = HARMONY_SR_11KHZ; break; - case 18900: newrate = HARMONY_SR_18KHZ; break; - case 22050: newrate = HARMONY_SR_22KHZ; break; - case 37800: newrate = HARMONY_SR_37KHZ; break; - case 44100: newrate = HARMONY_SR_44KHZ; break; - case 33075: newrate = HARMONY_SR_33KHZ; break; - case 6615: newrate = HARMONY_SR_6KHZ; break; - default: newrate = HARMONY_SR_8KHZ; - *freq = 8000; break; - } - return newrate; -} - -static void harmony_set_format(u8 data_format) -{ - harmony.data_format = data_format; - harmony_update_control(); -} - -static void harmony_set_stereo(u8 stereo_select) -{ - harmony.stereo_select = stereo_select; - harmony_update_control(); -} - -static void harmony_disable_interrupts(void) -{ - harmony_wait_CNTL(); - gsc_writel(0, &harmony.hpa->dstatus); -} - -static void harmony_enable_interrupts(void) -{ - harmony_wait_CNTL(); - gsc_writel(DSTATUS_IE, &harmony.hpa->dstatus); -} - -/* - * harmony_silence() - * - * This subroutine fills in a buffer starting at location start and - * silences for length bytes. This references the current - * configuration of the audio format. - * - */ - -static void harmony_silence(struct harmony_buffer *buffer, int start, int length) -{ - u8 silence_char; - - /* Despite what you hear, silence is different in - different audio formats. */ - switch (harmony.data_format) { - case HARMONY_DF_8BIT_ULAW: silence_char = 0x55; break; - case HARMONY_DF_8BIT_ALAW: silence_char = 0xff; break; - case HARMONY_DF_16BIT_LINEAR: /* fall through */ - default: silence_char = 0; - } - - memset(buffer->addr+start, silence_char, length); -} - - -static int harmony_audio_open(struct inode *inode, struct file *file) -{ - if (harmony.audio_open) - return -EBUSY; - - harmony.audio_open = 1; - harmony.suspended_playing = harmony.suspended_recording = 1; - harmony.blocked_playing = harmony.blocked_recording = 0; - harmony.first_filled_play = harmony.first_filled_record = 0; - harmony.nb_filled_play = harmony.nb_filled_record = 0; - harmony.play_offset = 0; - init_waitqueue_head(&harmony.wq_play); - init_waitqueue_head(&harmony.wq_record); - - /* Start off in a balanced mode. */ - harmony_set_control(HARMONY_DF_8BIT_ULAW, HARMONY_SR_8KHZ, HARMONY_SS_MONO); - harmony_update_control(); - harmony.format_initialized = 0; - - /* Clear out all the buffers and flush to cache */ - harmony_silence(&played_buf, 0, HARMONY_BUF_SIZE*MAX_BUFS); - CHECK_WBACK_INV_OFFSET(played_buf, 0, HARMONY_BUF_SIZE*MAX_BUFS); - - return 0; -} - -/* - * Release (close) the audio device. - */ - -static int harmony_audio_release(struct inode *inode, struct file *file) -{ - if (!harmony.audio_open) - return -EBUSY; - - harmony.audio_open = 0; - - return 0; -} - -/* - * Read recorded data off the audio device. - */ - -static ssize_t harmony_audio_read(struct file *file, - char *buffer, - size_t size_count, - loff_t *ppos) -{ - int total_count = (int) size_count; - int count = 0; - int buf_to_read; - - while (count<total_count) { - /* Wait until we're out of control mode */ - harmony_wait_CNTL(); - - /* Figure out which buffer to fill in */ - if (harmony.nb_filled_record <= 2) { - harmony.blocked_recording = 1; - if (harmony.suspended_recording) { - harmony.suspended_recording = 0; - harmony_enable_interrupts(); - } - - interruptible_sleep_on(&harmony.wq_record); - harmony.blocked_recording = 0; - } - - if (harmony.nb_filled_record < 2) - return -EBUSY; - - buf_to_read = harmony.first_filled_record; - - /* Copy the page to an aligned buffer */ - if (copy_to_user(buffer+count, recorded_buf.addr + - (HARMONY_BUF_SIZE*buf_to_read), - HARMONY_BUF_SIZE)) { - count = -EFAULT; - break; - } - - harmony.nb_filled_record--; - harmony.first_filled_record++; - harmony.first_filled_record %= MAX_BUFS; - - count += HARMONY_BUF_SIZE; - } - return count; -} - - - - -/* - * Here is the place where we try to recognize file format. - * Sun/NeXT .au files begin with the string .snd - * At offset 12 is specified the encoding. - * At offset 16 is specified speed rate - * At Offset 20 is specified the numbers of voices - */ - -#define four_bytes_to_u32(start) (file_header[start] << 24)|\ - (file_header[start+1] << 16)|\ - (file_header[start+2] << 8)|\ - (file_header[start+3]); - -#define test_rate(tested,real_value,harmony_value) if ((tested)<=(real_value))\ - - -static int harmony_format_auto_detect(const char *buffer, int block_size) -{ - u8 file_header[24]; - u32 start_string; - int ret = 0; - - if (block_size>24) { - if (copy_from_user(file_header, buffer, sizeof(file_header))) - ret = -EFAULT; - - start_string = four_bytes_to_u32(0); - - if ((file_header[4]==0) && (start_string==0x2E736E64)) { - u32 format; - u32 nb_voices; - u32 speed; - - format = four_bytes_to_u32(12); - nb_voices = four_bytes_to_u32(20); - speed = four_bytes_to_u32(16); - - switch (format) { - case HARMONY_MAGIC_8B_ULAW: - harmony.data_format = HARMONY_DF_8BIT_ULAW; - break; - case HARMONY_MAGIC_8B_ALAW: - harmony.data_format = HARMONY_DF_8BIT_ALAW; - break; - case HARMONY_MAGIC_16B_LINEAR: - harmony.data_format = HARMONY_DF_16BIT_LINEAR; - break; - default: - harmony_set_control(HARMONY_DF_16BIT_LINEAR, - HARMONY_SR_44KHZ, HARMONY_SS_STEREO); - goto out; - } - switch (nb_voices) { - case HARMONY_MAGIC_MONO: - harmony.stereo_select = HARMONY_SS_MONO; - break; - case HARMONY_MAGIC_STEREO: - harmony.stereo_select = HARMONY_SS_STEREO; - break; - default: - harmony.stereo_select = HARMONY_SS_MONO; - break; - } - harmony_set_rate(harmony_detect_rate(&speed)); - harmony.dac_rate = speed; - goto out; - } - } - harmony_set_control(HARMONY_DF_8BIT_ULAW, HARMONY_SR_8KHZ, HARMONY_SS_MONO); -out: - return ret; -} -#undef four_bytes_to_u32 - - -static ssize_t harmony_audio_write(struct file *file, - const char *buffer, - size_t size_count, - loff_t *ppos) -{ - int total_count = (int) size_count; - int count = 0; - int frame_size; - int buf_to_fill; - int fresh_buffer; - - if (!harmony.format_initialized) { - if (harmony_format_auto_detect(buffer, total_count)) - return -EFAULT; - } - - while (count<total_count) { - /* Wait until we're out of control mode */ - harmony_wait_CNTL(); - - /* Figure out which buffer to fill in */ - if (harmony.nb_filled_play+2 >= MAX_BUFS && !harmony.play_offset) { - harmony.blocked_playing = 1; - interruptible_sleep_on(&harmony.wq_play); - harmony.blocked_playing = 0; - } - if (harmony.nb_filled_play+2 >= MAX_BUFS && !harmony.play_offset) - return -EBUSY; - - - buf_to_fill = (harmony.first_filled_play+harmony.nb_filled_play); - if (harmony.play_offset) { - buf_to_fill--; - buf_to_fill += MAX_BUFS; - } - buf_to_fill %= MAX_BUFS; - - fresh_buffer = (harmony.play_offset == 0); - - /* Figure out the size of the frame */ - if ((total_count-count) >= HARMONY_BUF_SIZE - harmony.play_offset) { - frame_size = HARMONY_BUF_SIZE - harmony.play_offset; - } else { - frame_size = total_count - count; - /* Clear out the buffer, since there we'll only be - overlaying part of the old buffer with the new one */ - harmony_silence(&played_buf, - HARMONY_BUF_SIZE*buf_to_fill+frame_size+harmony.play_offset, - HARMONY_BUF_SIZE-frame_size-harmony.play_offset); - } - - /* Copy the page to an aligned buffer */ - if (copy_from_user(played_buf.addr +(HARMONY_BUF_SIZE*buf_to_fill) + harmony.play_offset, - buffer+count, frame_size)) - return -EFAULT; - CHECK_WBACK_INV_OFFSET(played_buf, (HARMONY_BUF_SIZE*buf_to_fill + harmony.play_offset), - frame_size); - - if (fresh_buffer) - harmony.nb_filled_play++; - - count += frame_size; - harmony.play_offset += frame_size; - harmony.play_offset %= HARMONY_BUF_SIZE; - if (harmony.suspended_playing && (harmony.nb_filled_play>=4)) - harmony_enable_interrupts(); - } - - return count; -} - -static unsigned int harmony_audio_poll(struct file *file, - struct poll_table_struct *wait) -{ - unsigned int mask = 0; - - if (file->f_mode & FMODE_READ) { - if (!harmony.suspended_recording) - poll_wait(file, &harmony.wq_record, wait); - if (harmony.nb_filled_record) - mask |= POLLIN | POLLRDNORM; - } - - if (file->f_mode & FMODE_WRITE) { - if (!harmony.suspended_playing) - poll_wait(file, &harmony.wq_play, wait); - if (harmony.nb_filled_play) - mask |= POLLOUT | POLLWRNORM; - } - - return mask; -} - -static int harmony_audio_ioctl(struct inode *inode, - struct file *file, - unsigned int cmd, - unsigned long arg) -{ - int ival, new_format; - int frag_size, frag_buf; - struct audio_buf_info info; - - switch (cmd) { - case OSS_GETVERSION: - return put_user(SOUND_VERSION, (int *) arg); - - case SNDCTL_DSP_GETCAPS: - ival = DSP_CAP_DUPLEX; - return put_user(ival, (int *) arg); - - case SNDCTL_DSP_GETFMTS: - ival = (AFMT_S16_BE | AFMT_MU_LAW | AFMT_A_LAW ); - return put_user(ival, (int *) arg); - - case SNDCTL_DSP_SETFMT: - if (get_user(ival, (int *) arg)) - return -EFAULT; - if (ival != AFMT_QUERY) { - switch (ival) { - case AFMT_MU_LAW: new_format = HARMONY_DF_8BIT_ULAW; break; - case AFMT_A_LAW: new_format = HARMONY_DF_8BIT_ALAW; break; - case AFMT_S16_BE: new_format = HARMONY_DF_16BIT_LINEAR; break; - default: { - DPRINTK(KERN_WARNING PFX - "unsupported sound format 0x%04x requested.\n", - ival); - ival = AFMT_S16_BE; - return put_user(ival, (int *) arg); - } - } - harmony_set_format(new_format); - return 0; - } else { - switch (harmony.data_format) { - case HARMONY_DF_8BIT_ULAW: ival = AFMT_MU_LAW; break; - case HARMONY_DF_8BIT_ALAW: ival = AFMT_A_LAW; break; - case HARMONY_DF_16BIT_LINEAR: ival = AFMT_U16_BE; break; - default: ival = 0; - } - return put_user(ival, (int *) arg); - } - - case SOUND_PCM_READ_RATE: - ival = harmony.dac_rate; - return put_user(ival, (int *) arg); - - case SNDCTL_DSP_SPEED: - if (get_user(ival, (int *) arg)) - return -EFAULT; - harmony_set_rate(harmony_detect_rate(&ival)); - harmony.dac_rate = ival; - return put_user(ival, (int*) arg); - - case SNDCTL_DSP_STEREO: - if (get_user(ival, (int *) arg)) - return -EFAULT; - if (ival != 0 && ival != 1) - return -EINVAL; - harmony_set_stereo(ival); - return 0; - - case SNDCTL_DSP_CHANNELS: - if (get_user(ival, (int *) arg)) - return -EFAULT; - if (ival != 1 && ival != 2) { - ival = harmony.stereo_select == HARMONY_SS_MONO ? 1 : 2; - return put_user(ival, (int *) arg); - } - harmony_set_stereo(ival-1); - return 0; - - case SNDCTL_DSP_GETBLKSIZE: - ival = HARMONY_BUF_SIZE; - return put_user(ival, (int *) arg); - - case SNDCTL_DSP_NONBLOCK: - file->f_flags |= O_NONBLOCK; - return 0; - - case SNDCTL_DSP_RESET: - if (!harmony.suspended_recording) { - /* TODO: stop_recording() */ - } - return 0; - - case SNDCTL_DSP_SETFRAGMENT: - if (get_user(ival, (int *)arg)) - return -EFAULT; - frag_size = ival & 0xffff; - frag_buf = (ival>>16) & 0xffff; - /* TODO: We use hardcoded fragment sizes and numbers for now */ - frag_size = 12; /* 4096 == 2^12 */ - frag_buf = MAX_BUFS; - ival = (frag_buf << 16) + frag_size; - return put_user(ival, (int *) arg); - - case SNDCTL_DSP_GETOSPACE: - if (!(file->f_mode & FMODE_WRITE)) - return -EINVAL; - info.fragstotal = MAX_BUFS; - info.fragments = MAX_BUFS - harmony.nb_filled_play; - info.fragsize = HARMONY_BUF_SIZE; - info.bytes = info.fragments * info.fragsize; - return copy_to_user((void *)arg, &info, sizeof(info)) ? -EFAULT : 0; - - case SNDCTL_DSP_GETISPACE: - if (!(file->f_mode & FMODE_READ)) - return -EINVAL; - info.fragstotal = MAX_BUFS; - info.fragments = /*MAX_BUFS-*/ harmony.nb_filled_record; - info.fragsize = HARMONY_BUF_SIZE; - info.bytes = info.fragments * info.fragsize; - return copy_to_user((void *)arg, &info, sizeof(info)) ? -EFAULT : 0; - - case SNDCTL_DSP_SYNC: - return 0; - } - - return -EINVAL; -} - - -/* - * harmony_interrupt() - * - * harmony interruption service routine - * - */ - -static irqreturn_t harmony_interrupt(int irq, void *dev, struct pt_regs *regs) -{ - u32 dstatus; - struct harmony_hpa *hpa; - - /* Setup the hpa */ - hpa = ((struct harmony_dev *)dev)->hpa; - harmony_wait_CNTL(); - - /* Read dstatus and pcuradd (the current address) */ - dstatus = gsc_readl(&hpa->dstatus); - - /* Turn off interrupts */ - harmony_disable_interrupts(); - - /* Check if this is a request to get the next play buffer */ - if (dstatus & DSTATUS_PN) { - if (!harmony.nb_filled_play) { - harmony.suspended_playing = 1; - gsc_writel((unsigned long)silent.dma_handle, &hpa->pnxtadd); - - if (!harmony.suspended_recording) - harmony_enable_interrupts(); - } else { - harmony.suspended_playing = 0; - gsc_writel((unsigned long)played_buf.dma_handle + - (HARMONY_BUF_SIZE*harmony.first_filled_play), - &hpa->pnxtadd); - harmony.first_filled_play++; - harmony.first_filled_play %= MAX_BUFS; - harmony.nb_filled_play--; - - harmony_enable_interrupts(); - } - - if (harmony.blocked_playing) - wake_up_interruptible(&harmony.wq_play); - } - - /* Check if we're being asked to fill in a recording buffer */ - if (dstatus & DSTATUS_RN) { - if((harmony.nb_filled_record+2>=MAX_BUFS) || harmony.suspended_recording) - { - harmony.nb_filled_record = 0; - harmony.first_filled_record = 0; - harmony.suspended_recording = 1; - gsc_writel((unsigned long)graveyard.dma_handle, &hpa->rnxtadd); - if (!harmony.suspended_playing) - harmony_enable_interrupts(); - } else { - int buf_to_fill; - buf_to_fill = (harmony.first_filled_record+harmony.nb_filled_record) % MAX_BUFS; - CHECK_WBACK_INV_OFFSET(recorded_buf, HARMONY_BUF_SIZE*buf_to_fill, HARMONY_BUF_SIZE); - gsc_writel((unsigned long)recorded_buf.dma_handle + - HARMONY_BUF_SIZE*buf_to_fill, - &hpa->rnxtadd); - harmony.nb_filled_record++; - harmony_enable_interrupts(); - } - - if (harmony.blocked_recording && harmony.nb_filled_record>3) - wake_up_interruptible(&harmony.wq_record); - } - return IRQ_HANDLED; -} - -/* - * Sound playing functions - */ - -static struct file_operations harmony_audio_fops = { - .owner = THIS_MODULE, - .llseek = no_llseek, - .read = harmony_audio_read, - .write = harmony_audio_write, - .poll = harmony_audio_poll, - .ioctl = harmony_audio_ioctl, - .open = harmony_audio_open, - .release = harmony_audio_release, -}; - -static int harmony_audio_init(void) -{ - /* Request that IRQ */ - if (request_irq(harmony.dev->irq, harmony_interrupt, 0 ,"harmony", &harmony)) { - printk(KERN_ERR PFX "Error requesting irq %d.\n", harmony.dev->irq); - return -EFAULT; - } - - harmony.dsp_unit = register_sound_dsp(&harmony_audio_fops, -1); - if (harmony.dsp_unit < 0) { - printk(KERN_ERR PFX "Error registering dsp\n"); - free_irq(harmony.dev->irq, &harmony); - return -EFAULT; - } - - /* Clear the buffers so you don't end up with crap in the buffers. */ - harmony_silence(&played_buf, 0, HARMONY_BUF_SIZE*MAX_BUFS); - - /* Make sure this makes it to cache */ - CHECK_WBACK_INV_OFFSET(played_buf, 0, HARMONY_BUF_SIZE*MAX_BUFS); - - /* Clear out the silent buffer and flush to cache */ - harmony_silence(&silent, 0, HARMONY_BUF_SIZE); - CHECK_WBACK_INV_OFFSET(silent, 0, HARMONY_BUF_SIZE); - - harmony.audio_open = 0; - - return 0; -} - - -/* - * mixer functions - */ - -static void harmony_mixer_set_gain(void) -{ - harmony_wait_CNTL(); - gsc_writel(harmony.current_gain, &harmony.hpa->gainctl); -} - -/* - * Read gain of selected channel. - * The OSS rate is from 0 (silent) to 100 -> need some conversions - * - * The harmony gain are attenuation for output and monitor gain. - * is amplifaction for input gain - */ -#define to_harmony_level(level,max) ((level)*max/100) -#define to_oss_level(level,max) ((level)*100/max) - -static int harmony_mixer_get_level(int channel) -{ - int left_level; - int right_level; - - switch (channel) { - case SOUND_MIXER_VOLUME: - left_level = (harmony.current_gain & GAIN_LO_MASK) >> GAIN_LO_SHIFT; - right_level = (harmony.current_gain & GAIN_RO_MASK) >> GAIN_RO_SHIFT; - left_level = to_oss_level(MAX_OUTPUT_LEVEL - left_level, MAX_OUTPUT_LEVEL); - right_level = to_oss_level(MAX_OUTPUT_LEVEL - right_level, MAX_OUTPUT_LEVEL); - return (right_level << 8)+left_level; - - case SOUND_MIXER_IGAIN: - left_level = (harmony.current_gain & GAIN_LI_MASK) >> GAIN_LI_SHIFT; - right_level= (harmony.current_gain & GAIN_RI_MASK) >> GAIN_RI_SHIFT; - left_level = to_oss_level(left_level, MAX_INPUT_LEVEL); - right_level= to_oss_level(right_level, MAX_INPUT_LEVEL); - return (right_level << 8)+left_level; - - case SOUND_MIXER_MONITOR: - left_level = (harmony.current_gain & GAIN_MA_MASK) >> GAIN_MA_SHIFT; - left_level = to_oss_level(MAX_MONITOR_LEVEL-left_level, MAX_MONITOR_LEVEL); - return (left_level << 8)+left_level; - } - return -EINVAL; -} - - - -/* - * Some conversions for the same reasons. - * We give back the new real value(s) due to - * the rescale. - */ - -static int harmony_mixer_set_level(int channel, int value) -{ - int left_level; - int right_level; - int new_left_level; - int new_right_level; - - right_level = (value & 0x0000ff00) >> 8; - left_level = value & 0x000000ff; - if (right_level > 100) right_level = 100; - if (left_level > 100) left_level = 100; - - switch (channel) { - case SOUND_MIXER_VOLUME: - right_level = to_harmony_level(100-right_level, MAX_OUTPUT_LEVEL); - left_level = to_harmony_level(100-left_level, MAX_OUTPUT_LEVEL); - new_right_level = to_oss_level(MAX_OUTPUT_LEVEL - right_level, MAX_OUTPUT_LEVEL); - new_left_level = to_oss_level(MAX_OUTPUT_LEVEL - left_level, MAX_OUTPUT_LEVEL); - harmony.current_gain = (harmony.current_gain & ~(GAIN_LO_MASK | GAIN_RO_MASK)) - | (left_level << GAIN_LO_SHIFT) | (right_level << GAIN_RO_SHIFT); - harmony_mixer_set_gain(); - return (new_right_level << 8) + new_left_level; - - case SOUND_MIXER_IGAIN: - right_level = to_harmony_level(right_level, MAX_INPUT_LEVEL); - left_level = to_harmony_level(left_level, MAX_INPUT_LEVEL); - new_right_level = to_oss_level(right_level, MAX_INPUT_LEVEL); - new_left_level = to_oss_level(left_level, MAX_INPUT_LEVEL); - harmony.current_gain = (harmony.current_gain & ~(GAIN_LI_MASK | GAIN_RI_MASK)) - | (left_level << GAIN_LI_SHIFT) | (right_level << GAIN_RI_SHIFT); - harmony_mixer_set_gain(); - return (new_right_level << 8) + new_left_level; - - case SOUND_MIXER_MONITOR: - left_level = to_harmony_level(100-left_level, MAX_MONITOR_LEVEL); - new_left_level = to_oss_level(MAX_MONITOR_LEVEL-left_level, MAX_MONITOR_LEVEL); - harmony.current_gain = (harmony.current_gain & ~GAIN_MA_MASK) | (left_level << GAIN_MA_SHIFT); - harmony_mixer_set_gain(); - return (new_left_level << 8) + new_left_level; - } - - return -EINVAL; -} - -#undef to_harmony_level -#undef to_oss_level - -/* - * Return the selected input device (mic or line) - */ - -static int harmony_mixer_get_recmask(void) -{ - int current_input_line; - - current_input_line = (harmony.current_gain & GAIN_IS_MASK) - >> GAIN_IS_SHIFT; - if (current_input_line) - return SOUND_MASK_MIC; - - return SOUND_MASK_LINE; -} - -/* - * Set the input (only one at time, arbitrary priority to line in) - */ - -static int harmony_mixer_set_recmask(int recmask) -{ - int new_input_line; - int new_input_mask; - int current_input_line; - - current_input_line = (harmony.current_gain & GAIN_IS_MASK) - >> GAIN_IS_SHIFT; - if ((current_input_line && ((recmask & SOUND_MASK_LINE) || !(recmask & SOUND_MASK_MIC))) || - (!current_input_line && ((recmask & SOUND_MASK_LINE) && !(recmask & SOUND_MASK_MIC)))) { - new_input_line = 0; - new_input_mask = SOUND_MASK_LINE; - } else { - new_input_line = 1; - new_input_mask = SOUND_MASK_MIC; - } - harmony.current_gain = ((harmony.current_gain & ~GAIN_IS_MASK) | - (new_input_line << GAIN_IS_SHIFT )); - harmony_mixer_set_gain(); - return new_input_mask; -} - - -/* - * give the active outlines - */ - -static int harmony_mixer_get_outmask(void) -{ - int outmask = 0; - - if (harmony.current_gain & GAIN_SE_MASK) outmask |= MASK_INTERNAL; - if (harmony.current_gain & GAIN_LE_MASK) outmask |= MASK_LINEOUT; - if (harmony.current_gain & GAIN_HE_MASK) outmask |= MASK_HEADPHONES; - - return outmask; -} - - -static int harmony_mixer_set_outmask(int outmask) -{ - if (outmask & MASK_INTERNAL) - harmony.current_gain |= GAIN_SE_MASK; - else - harmony.current_gain &= ~GAIN_SE_MASK; - - if (outmask & MASK_LINEOUT) - harmony.current_gain |= GAIN_LE_MASK; - else - harmony.current_gain &= ~GAIN_LE_MASK; - - if (outmask & MASK_HEADPHONES) - harmony.current_gain |= GAIN_HE_MASK; - else - harmony.current_gain &= ~GAIN_HE_MASK; - - harmony_mixer_set_gain(); - - return (outmask & (MASK_INTERNAL | MASK_LINEOUT | MASK_HEADPHONES)); -} - -/* - * This code is inspired from sb_mixer.c - */ - -static int harmony_mixer_ioctl(struct inode * inode, struct file * file, - unsigned int cmd, unsigned long arg) -{ - int val; - int ret; - - if (cmd == SOUND_MIXER_INFO) { - mixer_info info; - memset(&info, 0, sizeof(info)); - strncpy(info.id, "harmony", sizeof(info.id)-1); - strncpy(info.name, "Harmony audio", sizeof(info.name)-1); - info.modify_counter = 1; /* ? */ - if (copy_to_user((void *)arg, &info, sizeof(info))) - return -EFAULT; - return 0; - } - - if (cmd == OSS_GETVERSION) - return put_user(SOUND_VERSION, (int *)arg); - - /* read */ - val = 0; - if (_SIOC_DIR(cmd) & _SIOC_WRITE) - if (get_user(val, (int *)arg)) - return -EFAULT; - - switch (cmd) { - case MIXER_READ(SOUND_MIXER_CAPS): - ret = SOUND_CAP_EXCL_INPUT; - break; - case MIXER_READ(SOUND_MIXER_STEREODEVS): - ret = SOUND_MASK_VOLUME | SOUND_MASK_IGAIN; - break; - - case MIXER_READ(SOUND_MIXER_RECMASK): - ret = SOUND_MASK_MIC | SOUND_MASK_LINE; - break; - case MIXER_READ(SOUND_MIXER_DEVMASK): - ret = SOUND_MASK_VOLUME | SOUND_MASK_IGAIN | - SOUND_MASK_MONITOR; - break; - case MIXER_READ(SOUND_MIXER_OUTMASK): - ret = MASK_INTERNAL | MASK_LINEOUT | - MASK_HEADPHONES; - break; - - case MIXER_WRITE(SOUND_MIXER_RECSRC): - ret = harmony_mixer_set_recmask(val); - break; - case MIXER_READ(SOUND_MIXER_RECSRC): - ret = harmony_mixer_get_recmask(); - break; - - case MIXER_WRITE(SOUND_MIXER_OUTSRC): - ret = harmony_mixer_set_outmask(val); - break; - case MIXER_READ(SOUND_MIXER_OUTSRC): - ret = harmony_mixer_get_outmask(); - break; - - case MIXER_WRITE(SOUND_MIXER_VOLUME): - case MIXER_WRITE(SOUND_MIXER_IGAIN): - case MIXER_WRITE(SOUND_MIXER_MONITOR): - ret = harmony_mixer_set_level(cmd & 0xff, val); - break; - - case MIXER_READ(SOUND_MIXER_VOLUME): - case MIXER_READ(SOUND_MIXER_IGAIN): - case MIXER_READ(SOUND_MIXER_MONITOR): - ret = harmony_mixer_get_level(cmd & 0xff); - break; - - default: - return -EINVAL; - } - - if (put_user(ret, (int *)arg)) - return -EFAULT; - return 0; -} - - -static int harmony_mixer_open(struct inode *inode, struct file *file) -{ - if (harmony.mixer_open) - return -EBUSY; - harmony.mixer_open = 1; - return 0; -} - -static int harmony_mixer_release(struct inode *inode, struct file *file) -{ - if (!harmony.mixer_open) - return -EBUSY; - harmony.mixer_open = 0; - return 0; -} - -static struct file_operations harmony_mixer_fops = { - .owner = THIS_MODULE, - .llseek = no_llseek, - .open = harmony_mixer_open, - .release = harmony_mixer_release, - .ioctl = harmony_mixer_ioctl, -}; - - -/* - * Mute all the output and reset Harmony. - */ - -static void __init harmony_mixer_reset(void) -{ - harmony.current_gain = GAIN_TOTAL_SILENCE; - harmony_mixer_set_gain(); - harmony_wait_CNTL(); - gsc_writel(1, &harmony.hpa->reset); - mdelay(50); /* wait 50 ms */ - gsc_writel(0, &harmony.hpa->reset); - harmony.current_gain = GAIN_DEFAULT; - harmony_mixer_set_gain(); -} - -static int __init harmony_mixer_init(void) -{ - /* Register the device file operations */ - harmony.mixer_unit = register_sound_mixer(&harmony_mixer_fops, -1); - if (harmony.mixer_unit < 0) { - printk(KERN_WARNING PFX "Error Registering Mixer Driver\n"); - return -EFAULT; - } - - harmony_mixer_reset(); - harmony.mixer_open = 0; - - return 0; -} - - - -/* - * This is the callback that's called by the inventory hardware code - * if it finds a match to the registered driver. - */ -static int __devinit -harmony_driver_probe(struct parisc_device *dev) -{ - u8 id; - u8 rev; - u32 cntl; - int ret; - - if (harmony.hpa) { - /* We only support one Harmony at this time */ - printk(KERN_ERR PFX "driver already registered\n"); - return -EBUSY; - } - - if (!dev->irq) { - printk(KERN_ERR PFX "no irq found\n"); - return -ENODEV; - } - - /* Set the HPA of harmony */ - harmony.hpa = (struct harmony_hpa *)dev->hpa.start; - harmony.dev = dev; - - /* Grab the ID and revision from the device */ - id = gsc_readb(&harmony.hpa->id); - if ((id | 1) != 0x15) { - printk(KERN_WARNING PFX "wrong harmony id 0x%02x\n", id); - return -EBUSY; - } - cntl = gsc_readl(&harmony.hpa->cntl); - rev = (cntl>>20) & 0xff; - - printk(KERN_INFO "Lasi Harmony Audio driver " HARMONY_VERSION ", " - "h/w id %i, rev. %i at 0x%lx, IRQ %i\n", - id, rev, dev->hpa.start, harmony.dev->irq); - - /* Make sure the control bit isn't set, although I don't think it - ever is. */ - if (cntl & CNTL_C) { - printk(KERN_WARNING PFX "CNTL busy\n"); - harmony.hpa = 0; - return -EBUSY; - } - - /* Initialize the memory buffers */ - if (harmony_alloc_buffer(&played_buf, MAX_BUFS) || - harmony_alloc_buffer(&recorded_buf, MAX_BUFS) || - harmony_alloc_buffer(&graveyard, 1) || - harmony_alloc_buffer(&silent, 1)) { - ret = -EBUSY; - goto out_err; - } - - /* Initialize /dev/mixer and /dev/audio */ - if ((ret=harmony_mixer_init())) - goto out_err; - if ((ret=harmony_audio_init())) - goto out_err; - - return 0; - -out_err: - harmony.hpa = 0; - harmony_free_buffer(&played_buf); - harmony_free_buffer(&recorded_buf); - harmony_free_buffer(&graveyard); - harmony_free_buffer(&silent); - return ret; -} - - -static struct parisc_device_id harmony_tbl[] = { - /* { HPHW_FIO, HVERSION_REV_ANY_ID, HVERSION_ANY_ID, 0x0007A }, Bushmaster/Flounder */ - { HPHW_FIO, HVERSION_REV_ANY_ID, HVERSION_ANY_ID, 0x0007B }, /* 712/715 Audio */ - { HPHW_FIO, HVERSION_REV_ANY_ID, HVERSION_ANY_ID, 0x0007E }, /* Pace Audio */ - { HPHW_FIO, HVERSION_REV_ANY_ID, HVERSION_ANY_ID, 0x0007F }, /* Outfield / Coral II */ - { 0, } -}; - -MODULE_DEVICE_TABLE(parisc, harmony_tbl); - -static struct parisc_driver harmony_driver = { - .name = "Lasi Harmony", - .id_table = harmony_tbl, - .probe = harmony_driver_probe, -}; - -static int __init init_harmony(void) -{ - return register_parisc_driver(&harmony_driver); -} - -static void __exit cleanup_harmony(void) -{ - free_irq(harmony.dev->irq, &harmony); - unregister_sound_mixer(harmony.mixer_unit); - unregister_sound_dsp(harmony.dsp_unit); - harmony_free_buffer(&played_buf); - harmony_free_buffer(&recorded_buf); - harmony_free_buffer(&graveyard); - harmony_free_buffer(&silent); - unregister_parisc_driver(&harmony_driver); -} - - -MODULE_AUTHOR("Alex DeVries <alex@onefishtwo.ca>"); -MODULE_DESCRIPTION("Harmony sound driver"); -MODULE_LICENSE("GPL"); - -module_init(init_harmony); -module_exit(cleanup_harmony); - |