|  | /* | 
|  | 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.start; | 
|  | harmony.dev = dev; | 
|  |  | 
|  | /* Grab the ID and revision from the device */ | 
|  | id = gsc_readb(&harmony.hpa->id); | 
|  | if ((id | 1) != 0x15) { | 
|  | printk(KERN_WARNING PFX "wrong harmony id 0x%02x\n", id); | 
|  | return -EBUSY; | 
|  | } | 
|  | cntl = gsc_readl(&harmony.hpa->cntl); | 
|  | rev = (cntl>>20) & 0xff; | 
|  |  | 
|  | printk(KERN_INFO "Lasi Harmony Audio driver " HARMONY_VERSION ", " | 
|  | "h/w id %i, rev. %i at 0x%lx, IRQ %i\n", | 
|  | id, rev, dev->hpa.start, harmony.dev->irq); | 
|  |  | 
|  | /* Make sure the control bit isn't set, although I don't think it | 
|  | ever is. */ | 
|  | if (cntl & CNTL_C) { | 
|  | printk(KERN_WARNING PFX "CNTL busy\n"); | 
|  | harmony.hpa = 0; | 
|  | return -EBUSY; | 
|  | } | 
|  |  | 
|  | /* Initialize the memory buffers */ | 
|  | if (harmony_alloc_buffer(&played_buf, MAX_BUFS) || | 
|  | harmony_alloc_buffer(&recorded_buf, MAX_BUFS) || | 
|  | harmony_alloc_buffer(&graveyard, 1) || | 
|  | harmony_alloc_buffer(&silent, 1)) { | 
|  | ret = -EBUSY; | 
|  | goto out_err; | 
|  | } | 
|  |  | 
|  | /* Initialize /dev/mixer and /dev/audio  */ | 
|  | if ((ret=harmony_mixer_init())) | 
|  | goto out_err; | 
|  | if ((ret=harmony_audio_init())) | 
|  | goto out_err; | 
|  |  | 
|  | return 0; | 
|  |  | 
|  | out_err: | 
|  | harmony.hpa = 0; | 
|  | harmony_free_buffer(&played_buf); | 
|  | harmony_free_buffer(&recorded_buf); | 
|  | harmony_free_buffer(&graveyard); | 
|  | harmony_free_buffer(&silent); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  |  | 
|  | static struct parisc_device_id harmony_tbl[] = { | 
|  | /* { HPHW_FIO, HVERSION_REV_ANY_ID, HVERSION_ANY_ID, 0x0007A }, Bushmaster/Flounder */ | 
|  | { HPHW_FIO, HVERSION_REV_ANY_ID, HVERSION_ANY_ID, 0x0007B }, /* 712/715 Audio */ | 
|  | { HPHW_FIO, HVERSION_REV_ANY_ID, HVERSION_ANY_ID, 0x0007E }, /* Pace Audio */ | 
|  | { HPHW_FIO, HVERSION_REV_ANY_ID, HVERSION_ANY_ID, 0x0007F }, /* Outfield / Coral II */ | 
|  | { 0, } | 
|  | }; | 
|  |  | 
|  | MODULE_DEVICE_TABLE(parisc, harmony_tbl); | 
|  |  | 
|  | static struct parisc_driver harmony_driver = { | 
|  | .name		= "Lasi Harmony", | 
|  | .id_table	= harmony_tbl, | 
|  | .probe		= harmony_driver_probe, | 
|  | }; | 
|  |  | 
|  | static int __init init_harmony(void) | 
|  | { | 
|  | return register_parisc_driver(&harmony_driver); | 
|  | } | 
|  |  | 
|  | static void __exit cleanup_harmony(void) | 
|  | { | 
|  | free_irq(harmony.dev->irq, &harmony); | 
|  | unregister_sound_mixer(harmony.mixer_unit); | 
|  | unregister_sound_dsp(harmony.dsp_unit); | 
|  | harmony_free_buffer(&played_buf); | 
|  | harmony_free_buffer(&recorded_buf); | 
|  | harmony_free_buffer(&graveyard); | 
|  | harmony_free_buffer(&silent); | 
|  | unregister_parisc_driver(&harmony_driver); | 
|  | } | 
|  |  | 
|  |  | 
|  | MODULE_AUTHOR("Alex DeVries <alex@onefishtwo.ca>"); | 
|  | MODULE_DESCRIPTION("Harmony sound driver"); | 
|  | MODULE_LICENSE("GPL"); | 
|  |  | 
|  | module_init(init_harmony); | 
|  | module_exit(cleanup_harmony); | 
|  |  |