Initial git repository build. I'm not bothering with the full history, even though we have it. We can create a separate "historical" git archive of that later if we want to, and in the meantime it's about 3.2GB when imported into git - space that would just make the early git days unnecessarily complicated, when we don't have a lot of good infrastructure for it. Let it rip!
		
			
				
	
	
		
			1330 lines
		
	
	
	
		
			34 KiB
			
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1330 lines
		
	
	
	
		
			34 KiB
			
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
 	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@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;
 | 
						|
	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, 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);
 | 
						|
 |