harmony.c
上传用户:jlfgdled
上传日期:2013-04-10
资源大小:33168k
文件大小:33k
- /*
- drivers/sound/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@linuxcare.com>
- Copyright 2000-2002 (c) Helge Deller <deller@gmx.de>
- Copyright 2001 (c) Matthieu Delahaye <delahaym@esiee.fr>
- Copyright 2001 (c) Jean-Christophe Vaugeois <vaugeoij@esiee.fr>
-
- 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/ioport.h>
- #include <linux/types.h>
- #include <linux/mm.h>
- #include <linux/pci.h>
- #include <asm/gsc.h>
- #include <asm/io.h>
- #include <asm/pgalloc.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_VOLUME_LEVEL (GAIN_MA_MASK >> GAIN_MA_SHIFT)
- /*
- * 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 {
- int irq;
- struct harmony_hpa *hpa;
- u32 current_gain;
- 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;
- u32 dac_rate; /* 8000 ... 48000 (Hz) */
- int suspended_playing;
- int suspended_recording;
-
- int blocked_playing;
- int blocked_recording;
-
- 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 audio_open, mixer_open;
- int dsp_unit, mixer_unit;
- struct pci_dev *fake_pci_dev; /* The fake pci_dev needed for
- pci_* functions under ccio. */
- };
- 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_consistent; /* Zero if pci_alloc_consistent() fails */
- 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_consistent)
- dma_cache_wback_inv((unsigned long)b.addr+offset,len);
- } while (0)
-
- static int __init harmony_alloc_buffer(struct harmony_buffer *b,
- int buffer_count)
- {
- b->len = buffer_count * HARMONY_BUF_SIZE;
- b->addr = pci_alloc_consistent(harmony.fake_pci_dev,
- b->len, &b->dma_handle);
- if (b->addr && b->dma_handle) {
- b->dma_consistent = 1;
- DPRINTK(KERN_INFO PFX "consistent memory: 0x%lx, played_buf: 0x%lxn",
- (unsigned long)b->dma_handle, (unsigned long)b->addr);
- } else {
- b->dma_consistent = 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 memoryn");
- 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_consistent)
- pci_free_consistent(harmony.fake_pci_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 5125: 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++;
- 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--;
- 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 */
- copy_to_user(buffer+count,
- recorded_buf.addr+(HARMONY_BUF_SIZE*buf_to_read),
- HARMONY_BUF_SIZE);
-
- 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 void harmony_format_auto_detect(const char *buffer, int block_size)
- {
- u8 file_header[24];
- u32 start_string;
-
- if (block_size>24) {
- copy_from_user(file_header, buffer, sizeof(file_header));
- 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);
- return;
- }
- 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;
- return;
- }
- }
- harmony_set_control(HARMONY_DF_8BIT_ULAW, HARMONY_SR_8KHZ, HARMONY_SS_MONO);
- }
- #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;
- if (!harmony.format_initialized)
- harmony_format_auto_detect(buffer, total_count);
-
- 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;
- /* 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 */
- copy_from_user(played_buf.addr + (HARMONY_BUF_SIZE*buf_to_fill) + harmony.play_offset,
- buffer+count, frame_size);
- CHECK_WBACK_INV_OFFSET(played_buf, (HARMONY_BUF_SIZE*buf_to_fill + harmony.play_offset),
- frame_size);
-
- if (!harmony.play_offset)
- 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_LE: /* fall through, but not really supported */
- case AFMT_S16_BE: new_format = HARMONY_DF_16BIT_LINEAR;
- ival = AFMT_S16_BE;
- break;
- default: {
- DPRINTK(KERN_WARNING PFX
- "unsupported sound format 0x%04x requested.n",
- ival);
- return -EINVAL;
- }
- }
- harmony_set_format(new_format);
- } 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 put_user(ival, (int *) arg);
- 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));
- 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));
-
- case SNDCTL_DSP_SYNC:
- return 0;
- }
-
- return -EINVAL;
- }
- /*
- * harmony_interrupt()
- *
- * harmony interruption service routine
- *
- */
- static void 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);
- }
- }
- /*
- * 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.irq, harmony_interrupt, 0 ,"harmony", &harmony)) {
- printk(KERN_ERR PFX "Error requesting irq %d.n", harmony.irq);
- return -EFAULT;
- }
- harmony.dsp_unit = register_sound_dsp(&harmony_audio_fops, -1);
- if (harmony.dsp_unit < 0) {
- printk(KERN_ERR PFX "Error registering dspn");
- free_irq(harmony.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_OGAIN:
- 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_VOLUME:
- left_level = (harmony.current_gain & GAIN_MA_MASK) >> GAIN_MA_SHIFT;
- left_level = to_oss_level(MAX_VOLUME_LEVEL-left_level, MAX_VOLUME_LEVEL);
- return 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;
-
- switch (channel) {
- case SOUND_MIXER_OGAIN:
- 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_VOLUME:
- left_level = to_harmony_level(100-left_level, MAX_VOLUME_LEVEL);
- new_left_level = to_oss_level(MAX_VOLUME_LEVEL-left_level, MAX_VOLUME_LEVEL);
- harmony.current_gain = (harmony.current_gain & ~GAIN_MA_MASK)| (left_level << GAIN_MA_SHIFT);
- harmony_mixer_set_gain();
- return 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;
- if ((recmask & SOUND_MASK_LINE)) {
- 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_HE_MASK) outmask |=SOUND_MASK_PHONEOUT;
- if (harmony.current_gain & GAIN_LE_MASK) outmask |=SOUND_MASK_LINE;
- if (harmony.current_gain & GAIN_SE_MASK) outmask |=SOUND_MASK_SPEAKER;
-
- return outmask;
- }
- static int harmony_mixer_set_outmask(int outmask)
- {
- if (outmask & SOUND_MASK_PHONEOUT)
- harmony.current_gain |= GAIN_HE_MASK;
- else
- harmony.current_gain &= ~GAIN_HE_MASK;
-
- if (outmask & SOUND_MASK_LINE)
- harmony.current_gain |= GAIN_LE_MASK;
- else
- harmony.current_gain &= ~GAIN_LE_MASK;
-
- if (outmask & SOUND_MASK_SPEAKER)
- harmony.current_gain |= GAIN_SE_MASK;
- else
- harmony.current_gain &= ~GAIN_SE_MASK;
-
- harmony_mixer_set_gain();
- return (outmask & (SOUND_MASK_PHONEOUT | SOUND_MASK_LINE | SOUND_MASK_SPEAKER));
- }
- /*
- * 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_IGAIN | SOUND_MASK_OGAIN;
- break;
-
- case MIXER_READ(SOUND_MIXER_RECMASK):
- ret = SOUND_MASK_MIC | SOUND_MASK_LINE;
- break;
- case MIXER_READ(SOUND_MIXER_DEVMASK):
- ret = SOUND_MASK_OGAIN | SOUND_MASK_IGAIN |
- SOUND_MASK_VOLUME;
- break;
- case MIXER_READ(SOUND_MIXER_OUTMASK):
- ret = SOUND_MASK_SPEAKER | SOUND_MASK_LINE |
- SOUND_MASK_PHONEOUT;
- 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_OGAIN):
- case MIXER_WRITE(SOUND_MIXER_IGAIN):
- case MIXER_WRITE(SOUND_MIXER_VOLUME):
- ret = harmony_mixer_set_level(cmd & 0xff, val);
- break;
- case MIXER_READ(SOUND_MIXER_OGAIN):
- case MIXER_READ(SOUND_MIXER_IGAIN):
- case MIXER_READ(SOUND_MIXER_VOLUME):
- 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++;
- return 0;
- }
- static int harmony_mixer_release(struct inode *inode, struct file *file)
- {
- if (!harmony.mixer_open)
- return -EBUSY;
- harmony.mixer_open--;
- 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 Drivern");
- 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 __init
- harmony_driver_callback(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 registeredn");
- return -EBUSY;
- }
- /* Set the HPA of harmony */
- harmony.hpa = (struct harmony_hpa *)dev->hpa;
- harmony.irq = dev->irq;
- if (!harmony.irq) {
- printk(KERN_ERR PFX "no irq foundn");
- return -ENODEV;
- }
- /* 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%02xn", 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 %in",
- id, rev, dev->hpa, harmony.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 busyn");
- harmony.hpa = 0;
- return -EBUSY;
- }
- /* a fake pci_dev is needed for pci_* functions under ccio */
- harmony.fake_pci_dev = ccio_get_fake(dev);
-
- /* 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_callback,
- };
- static int __init init_harmony(void)
- {
- return register_parisc_driver(&harmony_driver);
- }
- static void __exit cleanup_harmony(void)
- {
- free_irq(harmony.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);
- }
- EXPORT_NO_SYMBOLS;
- MODULE_AUTHOR("Alex DeVries <alex@linuxcare.com>");
- MODULE_DESCRIPTION("Harmony sound driver");
- MODULE_LICENSE("GPL");
- module_init(init_harmony);
- module_exit(cleanup_harmony);