patch-2.4.20 linux-2.4.20/drivers/sound/harmony.c

Next file: linux-2.4.20/drivers/sound/i810_audio.c
Previous file: linux-2.4.20/drivers/sound/forte.c
Back to the patch index
Back to the overall index

diff -urN linux-2.4.19/drivers/sound/harmony.c linux-2.4.20/drivers/sound/harmony.c
@@ -0,0 +1,1295 @@
+/*
+ 	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%lx\n",
+				(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 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_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 dsp\n");
+		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 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 __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 registered\n");
+		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 found\n");
+		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%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, 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 busy\n");
+		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);
+

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)