diff -Nru linux.original/drivers/sound/Makefile linux/drivers/sound/Makefile --- linux.original/drivers/sound/Makefile Tue Jan 16 21:31:10 2001 +++ linux/drivers/sound/Makefile Wed Jan 17 22:25:49 2001 @@ -108,7 +108,7 @@ # Declare multi-part drivers. list-multi := sound.o gus.o pas2.o sb.o sb_lib.o vidc_mod.o \ - soundcore.o wavefront.o + soundcore.o wavefront.o harmony.o sound-objs := \ dev_table.o soundcard.o sound_syms.o \ @@ -124,6 +124,7 @@ sb_lib-objs := sb_common.o sb_audio.o sb_midi.o sb_mixer.o sb_ess.o vidc_mod-objs := vidc.o vidc_fill.o wavefront-objs := wavfront.o wf_midi.o yss225.o +harmony-objs := harmony_driver.o harmony_sound.o harmony_mixer.o # Extract lists of the multi-part drivers. @@ -189,6 +190,9 @@ wavefront.o: $(wavefront-objs) $(LD) -r -o $@ $(wavefront-objs) +harmony.o: $(harmony-objs) + $(LD) -r -o $@ $(harmony-objs.o) + # Firmware files that need translation # # The translated files are protected by a file that keeps track @@ -332,3 +336,8 @@ ifneq ($(FILES_BOOT_CHANGED),) $(FILES_BOOT_CHANGED): dummy endif + + + + + diff -Nru linux.original/drivers/sound/harmony.h linux/drivers/sound/harmony.h --- linux.original/drivers/sound/harmony.h Thu Jan 1 01:00:00 1970 +++ linux/drivers/sound/harmony.h Wed Jan 17 22:25:34 2001 @@ -0,0 +1,135 @@ +#ifndef _HARMONY_H +#define _HARMONY_H +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include /* for "gsc" irq functions */ +#include + +#include "sound_config.h" +#define MAX_BUFS 3 + +#define CNTL_C 0x80000000 +#define CNTL_ST 0x00000020 +#define CNTL_44100 0x00000015 +#define CNTL_8000 0x00000008 + +#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 1 +#define HARMONY_SS_STEREO 0 + +#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 + +#define HARMONY_BUF_NONE -1 +#define HARMONY_BUF_EMPTY 0 +#define HARMONY_BUF_FILLED 1 +#define HARMONY_BUF_READY_TO_PLAY 2 +#define HARMONY_BUF_PLAYING 3 + +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; +}; + +#define dma_consistent 0 + +#define CHECK_WBACK(addr,len) \ + do { if (!dma_consistent) dma_cache_wback((unsigned long)addr,len); } while (0) +#define CHECK_INV(addr,len) \ + do { if (!dma_consistent) dma_cache_inv((unsigned long)addr,len); } while(0) + +#define CHECK_WBACK_INV(addr,len) \ + do { if (!dma_consistent) dma_cache_wback_inv((unsigned long)addr,len); } while (0) + +struct harmony_dev { + int irq; + int frames_so_far; + struct harmony_hpa *hpa; + int done; + int buf_state[MAX_BUFS+1]; + u32 current_gain; + u8 data_format; + u8 sample_rate; + u8 stereo_select; /* 1 = stereo, 0 = mono */ + /* outstanding_buffers is a count that ensures that the number + of recorded frames is the same as the number of played frames. + In the stable situation, this number is zero. You should never + disable interrupts until this is zero. */ + int outstanding_buffers; +}; + +int harmony_mixer_init(struct harmony_dev *); +int harmony_audio_init(struct harmony_dev *); + + + + +#endif _HARMONY_H + + + + + + + + + + + + diff -Nru linux.original/drivers/sound/harmony_driver.c linux/drivers/sound/harmony_driver.c --- linux.original/drivers/sound/harmony_driver.c Thu Jan 1 01:00:00 1970 +++ linux/drivers/sound/harmony_driver.c Wed Jan 17 22:24:59 2001 @@ -0,0 +1,183 @@ +/* + + drivers/sound/harmony.c + + This is a sound driver for Lasi's Harmony sound chip. This is + unlikely to be used for anything other than a PA-RISC. + + Harmony is found in HP 712s, 715/new, and many other GSC based machines. +Copyright 2000 (c) Linuxcare Canada, Alex deVries + +Bugs: + +1. Doesn't work on 715/old + +This driver doesn't work on 715/old machines, which include the 715/75. The +chip used for this is technically called 'Vivace', which is identical to +Harmony. Should be easy to fix. It has something to do with the way it is +reported by the inventory code. The HPA is not that of Lasi or ASP so the +interrupts aren't registered properly. + +3. gain control ioctls are missing +4. recording is missing + +5. Buffer handling code + +The three buffer system should probably be changed to use N buffers, so we +can load the buffers into memory and then exit. Shouldn't be too hard, and +the performance will be a lot nicer. + +6. Clicks at start and finish + +I'm not sure why, but somehow I've introduced two clicks at the start and +two clicks at the finish of each playing. Shouldn't be too hard to track down. + + +About the rotating buffer +------------------------- + +I'm going to explain how the rotating buffer thing works because I had a hard +time figuring it out for myself. + +At any given time, harmony uses two different buffers. One is the frame +that is currently playing, another is the frame that is ready to be played. + +Obviously you can't be writing into either of these frames while the chip +is playing, or you'll hear clicks, pops and out of order frames. One weekend +of listening to RMS sing The Free Software Song out of order is a good way to +drive this point home. + +So, we use a total of three frames. One is in PLAYING mode, one is is +READY_TO_PLAY mode, and the third is either LOADED or EMPTY. All buffers +start up by being EMTPY. When harmony_audio_write loads up two frames, +interrupts are started and the loop starts. The ISR pops in the first frame, +marking it as PLAYING. The ISR will get called again, and the second +frame will be loaded and marked as READY_TO_PLAY. When the first frame is +done, the ISR will be called again; frame one is marked as EMPTY to reload, +frame two is marked PLAYING, frame three is marked READY_TO_PLAY. + +All the while, harmony_audio_write is looking for frames marked EMPTY, and +filling them and marking them LOADED. + +Yes, this does actually work. + +Let me save you an entire Thursday of listening to "orvalds Linus Linux, I +prounouce as Linus This is" by giving you this rotating buffer code. + +*/ + + + +/* + The source code is splitted into two new files: + harmony_driver.c about hardware detectetion and + harmony_sound.c which manage /dev/dsp + + A new file, harmony_mixer.c, manage /dev/mixer. + + Matthieu Delahaye +*/ + + + + +#include "harmony.h" + + + + +static struct harmony_dev harmony; +static int harmony_driver_callback(struct hp_device *d, struct pa_iodc_driver *dri); + + +/* This is the PDC signature to load up the driver. */ + +static struct pa_iodc_driver harmony_drivers_for[] = { + + {HPHW_FIO, 0x01B, 0, 0x0007B, 0x0, 0x0, + DRIVER_CHECK_HWTYPE + DRIVER_CHECK_SVERSION, + "Lasi Harmony", "Harmony", (void *) harmony_driver_callback}, + {0,0,0,0,0,0,0, + (char *) NULL,(char *) NULL,(void *) NULL} +}; + + +void __init +register_harmony_drivers(void) +{ + register_driver(harmony_drivers_for); +} + +/* This is the callback that's called by the inventory hardware code if +it finds a match to the registered driver. */ + + +static int +harmony_driver_callback(struct hp_device *d, struct pa_iodc_driver *dri) +{ + + u8 id; + u8 rev; + u32 cntl; + int ret; + + /* Set the HPA of harmony */ + + harmony.hpa = d->hpa; + + /* Grab an IRQ from Lasi */ + harmony.irq = busdevice_alloc_irq(d); + + if (!harmony.irq) { + printk(KERN_ERR "Harmony: problem getting irq\n"); + return -1; + } + + request_region(&harmony, 13, "harmony"); + + /* Grab the ID and revision from the device */ + id = gsc_readb((void *) &(harmony.hpa->id)); + + if((id | 1) != 0x15) { + printk(KERN_WARNING "harmony_init: wrong id %02x\n", id); + return -EBUSY; + } + cntl = gsc_readl(&harmony.hpa->cntl); + rev = ((cntl>>20)&0xff); + + printk(KERN_INFO "Lasi Harmony Audio rev. %i at 0x%x, using IRQ %i\n",rev,(unsigned int) d->hpa,harmony.irq); + + + + + + + /* Make sure the control bit isn't set, although I don't think it + ever is. */ + + if(cntl & CNTL_C) { + printk(KERN_WARNING "harmony_init: CNTL busy\n"); + return -EBUSY; + } + + /* Initialize /dev/mixer and /dev/audio */ + + + if((ret=harmony_mixer_init(&harmony))) return ret; + if((ret=harmony_audio_init(&harmony))) return ret; + + + return 0; +} + + + + + + + + + + + + diff -Nru linux.original/drivers/sound/harmony_mixer.c linux/drivers/sound/harmony_mixer.c --- linux.original/drivers/sound/harmony_mixer.c Thu Jan 1 01:00:00 1970 +++ linux/drivers/sound/harmony_mixer.c Wed Jan 17 22:25:09 2001 @@ -0,0 +1,317 @@ +/* + * sound/harmony_mixer.c + * + * The low level mixer driver for the Harmony card. + */ + +/* + * function ioctl inspired from file sb_mixer.c + * Matthieu Delahaye (2001) + */ + +#include "sound_config.h" + +#include "harmony.h" +#include "harmony_mixer.h" + + +struct harmony_dev * harmony_mixer; + + +/* + * This is the only place where the gainctl is written + */ + +static void harmony_mixer_set_gain(void) +{ + int cntl; + while((cntl = gsc_readl(&harmony_mixer->hpa->cntl)) & CNTL_C); +#if 0 + printk(KERN_INFO "Harmony gain is now %x\n",harmony_mixer->current_gain); +#endif + gsc_writel(harmony_mixer->current_gain, &harmony_mixer->hpa->gainctl); + while((cntl = gsc_readl(&harmony_mixer->hpa->cntl)) & CNTL_C); + +} + + +/* + * 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; + + if(channel == SOUND_MIXER_OGAIN) { + left_level=(harmony_mixer->current_gain & GAIN_LO_MASK) >> GAIN_LO_SHIFT; + right_level=(harmony_mixer->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; + } + if(channel == SOUND_MIXER_IGAIN) { + left_level=(harmony_mixer->current_gain & GAIN_LI_MASK) >> GAIN_LI_SHIFT; + right_level=(harmony_mixer->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; + } + if(channel == SOUND_MIXER_VOLUME) { + left_level=(harmony_mixer->current_gain & GAIN_MA_MASK) >> GAIN_MA_SHIFT; + left_level=to_oss_level(MAX_VOLUME_LEVEL-left_level,MAX_VOLUME_LEVEL); + return left_level; + } + return -EINVAL; +} + + +/* + * Some conversions for the same reasons. + * We give back the new real value(s) due to + * the rescale. + */ + +static int harmony_mixer_set_level(int channel, int value) +{ + int left_level; + int right_level; + int new_left_level; + int new_right_level; + + right_level=(value & 0x0000ff00) >> 8; + left_level=value & 0x000000ff; + + if(channel == SOUND_MIXER_OGAIN) { + right_level=to_harmony_level(100-right_level,MAX_OUTPUT_LEVEL); + left_level=to_harmony_level(100-left_level,MAX_OUTPUT_LEVEL); + new_right_level=to_oss_level(MAX_OUTPUT_LEVEL - right_level,MAX_OUTPUT_LEVEL); + new_left_level=to_oss_level(MAX_OUTPUT_LEVEL - left_level,MAX_OUTPUT_LEVEL); + harmony_mixer->current_gain=(harmony_mixer->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; + } + if(channel == 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_mixer->current_gain=(harmony_mixer->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; + + } + if(channel == SOUND_MIXER_VOLUME) { + + left_level=to_harmony_level(100-left_level,MAX_VOLUME_LEVEL); + new_left_level=to_oss_level(MAX_VOLUME_LEVEL-left_level,MAX_VOLUME_LEVEL); + harmony_mixer->current_gain=(harmony_mixer->current_gain & ~GAIN_MA_MASK)| (left_level << GAIN_MA_SHIFT); + harmony_mixer_set_gain(); + return new_left_level; + } + + return -EINVAL; +} + + +/* + * Return the selected input device (mic or line) + */ + +static int harmony_mixer_get_recmask(void) +{ + int current_input_line; + + current_input_line=(harmony_mixer->current_gain & GAIN_IS_MASK) >> GAIN_IS_SHIFT; + if(current_input_line) return SOUND_MASK_MIC; + else return SOUND_MASK_LINE; +} + +/* + * Set the input (only one at time, arbitrary priority to line in) + */ + +static int harmony_mixer_set_recmask(int recmask) +{ + int new_input_line; + int new_input_mask; + + if((recmask & SOUND_MASK_LINE)) { + new_input_line=0; + new_input_mask=SOUND_MASK_LINE; + } else { + new_input_line=1; + new_input_mask=SOUND_MASK_MIC; + } + harmony_mixer->current_gain=((harmony_mixer->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; + + outmask=0; + + if(harmony_mixer->current_gain & GAIN_HE_MASK) outmask |=SOUND_MASK_PHONEOUT; + if(harmony_mixer->current_gain & GAIN_LE_MASK) outmask |=SOUND_MASK_LINE; + if(harmony_mixer->current_gain & GAIN_SE_MASK) outmask |=SOUND_MASK_SPEAKER; + return outmask; +} + + + +static int harmony_mixer_set_outmask(int outmask) +{ + if(outmask & SOUND_MASK_PHONEOUT) + harmony_mixer->current_gain|=GAIN_HE_MASK; + else harmony_mixer->current_gain&=~GAIN_HE_MASK; + if(outmask & SOUND_MASK_LINE) harmony_mixer->current_gain|=GAIN_LE_MASK; + else harmony_mixer->current_gain&=~GAIN_LE_MASK; + if(outmask & SOUND_MASK_SPEAKER) harmony_mixer->current_gain|=GAIN_SE_MASK; + else harmony_mixer->current_gain&=~GAIN_SE_MASK; + harmony_mixer_set_gain(); + return (outmask & (SOUND_MASK_PHONEOUT | SOUND_MASK_LINE | SOUND_MASK_SPEAKER)); + +} + +/* + * This code is inspired from sb_mixer.c + * It determins actions selected by application + */ + +int harmony_mixer_ioctl(struct inode * inode, struct file * file, unsigned int cmd, unsigned long arg) +{ + int val; + int ret; + + if (((cmd >> 8) & 0xff) == 'M') { + if (_SIOC_DIR(cmd) & _SIOC_WRITE) { + if (get_user(val, (int *)arg)) return -EFAULT; + + switch (cmd & 0xff) { + case SOUND_MIXER_RECSRC: + ret = harmony_mixer_set_recmask(val); + break; + + case SOUND_MIXER_OUTSRC: + ret = harmony_mixer_set_outmask(val); + break; + + default: + ret = harmony_mixer_set_level(cmd & 0xff, val); + } + } else { + + switch (cmd & 0xff) { + + case SOUND_MIXER_RECSRC: + ret = harmony_mixer_get_recmask(); + break; + + case SOUND_MIXER_OUTSRC: + ret = harmony_mixer_get_outmask(); + break; + + case SOUND_MIXER_DEVMASK: + ret = HARMONY_MIXER_DEVICES; + break; + + case SOUND_MIXER_STEREODEVS: + ret = HARMONY_STEREO_DEVICES; + break; + + case SOUND_MIXER_RECMASK: + ret = HARMONY_RECORDING_DEVICES; + break; + + case SOUND_MIXER_OUTMASK: + ret = HARMONY_OUTPUT_DEVICES; + break; + + case SOUND_MIXER_CAPS: + ret = HARMONY_CAPS; + break; + + default: + ret = harmony_mixer_get_level(cmd & 0xff); + break; + } + } + return put_user(ret, (int *)arg); + } else + return -EINVAL; +} + +static struct file_operations harmony_mixer_fops = { + owner: THIS_MODULE, + ioctl: harmony_mixer_ioctl, +}; + + +/* + * Mute all the output and reset Harmony. + */ + +void harmony_mixer_reset(void) +{ + harmony_mixer->current_gain=GAIN_TOTAL_SILENCE; + harmony_mixer_set_gain(); + gsc_writel(1, &harmony_mixer->hpa->reset); + udelay(100); + gsc_writel(0, &harmony_mixer->hpa->reset); +} + +int harmony_mixer_init(struct harmony_dev * harmony_device) +{ + + int ret; + harmony_mixer=harmony_device; + + /* Register the device file operations */ + + ret=register_sound_mixer(&harmony_mixer_fops,-1); + if(ret<0) { + printk(KERN_WARNING "Harmony: Error Registering Mixer Driver\n"); + return -EFAULT; + } + + harmony_mixer_reset(); + + return 0; +} + + + + + + + + + + + + + + + + + + + + + diff -Nru linux.original/drivers/sound/harmony_mixer.h linux/drivers/sound/harmony_mixer.h --- linux.original/drivers/sound/harmony_mixer.h Thu Jan 1 01:00:00 1970 +++ linux/drivers/sound/harmony_mixer.h Wed Jan 17 22:25:29 2001 @@ -0,0 +1,58 @@ +/* + * sound/harmony_mixer.h + * + * Definitions for the Harmony mixer + */ +/* + * Copyright (C) by Matthieu Delahaye 2001 + * + * OSS/Free for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL) + * Version 2 (June 1991). See the "COPYING" file distributed with this software + * for more info. + */ + +/* + * 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 GAIN_TOTAL_SILENCE 0x00F00FFF +#define GAIN_DEFAULT 0x0F0FF000 + +#define MAX_OUTPUT_LEVEL (GAIN_RO_MASK >> GAIN_RO_SHIFT) +#define MAX_INPUT_LEVEL (GAIN_RI_MASK >> GAIN_RI_SHIFT) +#define MAX_VOLUME_LEVEL (GAIN_MA_MASK >> GAIN_MA_SHIFT) + + +#define HARMONY_MIXER_DEVICES (SOUND_MASK_OGAIN|SOUND_MASK_IGAIN|SOUND_MASK_VOLUME) +#define HARMONY_RECORDING_DEVICES (SOUND_MASK_MIC | SOUND_MASK_LINE ) +#define HARMONY_OUTPUT_DEVICES (SOUND_MASK_SPEAKER | SOUND_MASK_LINE | SOUND_MASK_PHONEOUT ) +#define HARMONY_CAPS 1 +#define HARMONY_STEREO_DEVICES (SOUND_MASK_IGAIN | SOUND_MASK_OGAIN) + +/* + * Channels Mask in mixer register + */ + + + + + diff -Nru linux.original/drivers/sound/harmony_sound.c linux/drivers/sound/harmony_sound.c --- linux.original/drivers/sound/harmony_sound.c Thu Jan 1 01:00:00 1970 +++ linux/drivers/sound/harmony_sound.c Wed Jan 17 22:25:15 2001 @@ -0,0 +1,603 @@ +/* + + drivers/sound/harmony.c + + This is a sound driver for Lasi's Harmony sound chip. This is + unlikely to be used for anything other than a PA-RISC. + + Harmony is found in HP 712s, 715/new, and many other GSC based machines. +Copyright 2000 (c) Linuxcare Canada, Alex deVries + +Bugs: + +1. Doesn't work on 715/old + +This driver doesn't work on 715/old machines, which include the 715/75. The +chip used for this is technically called 'Vivace', which is identical to +Harmony. Should be easy to fix. It has something to do with the way it is +reported by the inventory code. The HPA is not that of Lasi or ASP so the +interrupts aren't registered properly. + +3. gain control ioctls are missing +4. recording is missing + +5. Buffer handling code + +The three buffer system should probably be changed to use N buffers, so we +can load the buffers into memory and then exit. Shouldn't be too hard, and +the performance will be a lot nicer. + +6. Clicks at start and finish + +I'm not sure why, but somehow I've introduced two clicks at the start and +two clicks at the finish of each playing. Shouldn't be too hard to track down. + + +About the rotating buffer +------------------------- + +I'm going to explain how the rotating buffer thing works because I had a hard +time figuring it out for myself. + +At any given time, harmony uses two different buffers. One is the frame +that is currently playing, another is the frame that is ready to be played. + +Obviously you can't be writing into either of these frames while the chip +is playing, or you'll hear clicks, pops and out of order frames. One weekend +of listening to RMS sing The Free Software Song out of order is a good way to +drive this point home. + +So, we use a total of three frames. One is in PLAYING mode, one is is +READY_TO_PLAY mode, and the third is either LOADED or EMPTY. All buffers +start up by being EMTPY. When harmony_audio_write loads up two frames, +interrupts are started and the loop starts. The ISR pops in the first frame, +marking it as PLAYING. The ISR will get called again, and the second +frame will be loaded and marked as READY_TO_PLAY. When the first frame is +done, the ISR will be called again; frame one is marked as EMPTY to reload, +frame two is marked PLAYING, frame three is marked READY_TO_PLAY. + +All the while, harmony_audio_write is looking for frames marked EMPTY, and +filling them and marking them LOADED. + +Yes, this does actually work. + +Let me save you an entire Thursday of listening to "orvalds Linus Linux, I +prounouce as Linus This is" by giving you this rotating buffer code. + +*/ + + +/* + Changeg struct harmony_dev named harmony to + a pointer (named harmony_audio) to the same structure + + Putted out all access to gain and reset + (harmony_mixer now managed these) + + Matthieu Delahaye + +*/ + + +/* Until we have recording working, this is where we're putting the recording data. */ + +/* The following is required because of our cache incoherence */ +#include "harmony.h" +static char graveyard[4096] __attribute__((aligned(4096))); + +static unsigned char aligned_buf [4096*(MAX_BUFS+1)] __attribute__ ((aligned (4096))); + + + +static int harmony_audio_open(struct inode *inode, struct file *file); +static int harmony_audio_release(struct inode *inode, struct file *file); +static loff_t harmony_audio_llseek(struct file *file, loff_t offset, int whence); +static ssize_t harmony_audio_read(struct file *file, + char *buffer, + size_t count, + loff_t *ppos); +static ssize_t harmony_audio_write(struct file *file, + const char *buffer, + size_t count, + loff_t *ppos); +static unsigned int harmony_audio_poll(struct file *file, + struct poll_table_struct *wait); +static int harmony_audio_ioctl(struct inode *inode, + struct file *file, + unsigned int cmd, + unsigned long arg); +static int harmony_audio_mmap(struct file *file, struct vm_area_struct *vma); + + +static void harmony_interrupt(int irq, void *dev, struct pt_regs *regs); + +static void harmony_set_control(u8 data_format, u8 sample_rate, u8 stereo_select) ; +static void harmony_set_format(u8 data_format); +static void harmony_set_rate(u8 data_rate); +static int harmony_enable_interrupts(void); +static int harmony_disable_interrupts(void); +static int harmony_silence(unsigned char * buffer,int end, int start); + +static struct file_operations harmony_audio_fops = { + owner: THIS_MODULE, + llseek: harmony_audio_llseek, + read: harmony_audio_read, + write: harmony_audio_write, + poll: harmony_audio_poll, + ioctl: harmony_audio_ioctl, + mmap: harmony_audio_mmap, + open: harmony_audio_open, + release: harmony_audio_release, +}; + +static struct harmony_dev * harmony_audio; + +/* This is the PDC signature to load up the driver. */ + + +/* This is the callback that's called by the inventory hardware code if +it finds a match to the registered driver. */ + +static void harmony_update_control(void) { + u32 default_cntl; + + /* Set CNTL */ + default_cntl = ((1 << 31) | /* The C bit */ + (harmony_audio->data_format << 6) | /* Set the data format */ + (harmony_audio->stereo_select << 5) | /* Stereo select */ + (harmony_audio->sample_rate)); /* Set sample rate */ + + /* initialize CNTL */ + gsc_writel( default_cntl, &harmony_audio->hpa->cntl); +} + +static void harmony_set_control(u8 data_format, u8 sample_rate, u8 stereo_select) { + + harmony_audio->sample_rate = sample_rate; + harmony_audio->data_format = data_format; + harmony_audio->stereo_select = stereo_select; + harmony_update_control(); +} +static void harmony_set_rate(u8 data_rate) { + harmony_audio->sample_rate = data_rate; + harmony_update_control(); +} + +static void harmony_set_format(u8 data_format) { + + harmony_audio->data_format = data_format; + harmony_update_control(); +} + +static void harmony_set_stereo(u8 stereo_select) { + + harmony_audio->stereo_select = stereo_select; + harmony_update_control(); +} + +static int harmony_disable_interrupts(void) { + /* Enable interrupts to start playing again */ + gsc_writel(0,&(harmony_audio->hpa->dstatus)); + return 0; +} + +static int harmony_enable_interrupts(void) { + /* Enable interrupts to start playing again */ + gsc_writel(DSTATUS_IE,&(harmony_audio->hpa->dstatus)); + return 0; +} + +/* + * 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 int harmony_silence(unsigned char * buffer,int start, int length) { + + u8 silence_char = 0; + + /* Despite what you hear, silence is different in + different audio formats. */ + + switch (harmony_audio->data_format) { + case HARMONY_DF_16BIT_LINEAR: silence_char = 0; break; + case HARMONY_DF_8BIT_ULAW: silence_char = 0x55; break; + case HARMONY_DF_8BIT_ALAW: silence_char = 0xff; break; + default: silence_char = 0; + } + + memset(aligned_buf+start,silence_char, length); + return 0; +} + + +static int harmony_audio_open(struct inode *inode, struct file *file) +{ + int i; + u32 cntl; + + /* Clear out the state of all the buffers */ + for (i=0;ibuf_state[i] = HARMONY_BUF_EMPTY; + } + harmony_audio->frames_so_far = 0; + harmony_audio->done = 0; + harmony_audio->outstanding_buffers = 0; /* Start off in a balanced mode. */ + harmony_set_control(HARMONY_DF_8BIT_ULAW,HARMONY_SR_8KHZ,HARMONY_SS_STEREO); + + /* Clear out all the buffers and flush to cache */ + harmony_silence(aligned_buf,0,4096*MAX_BUFS); + CHECK_WBACK_INV(aligned_buf,4096*MAX_BUFS); + + while((cntl = gsc_readl(&harmony_audio->hpa->cntl)) & CNTL_C); + + return 0; +} + +/* + * Release (close) the audio device. + */ + +static int harmony_audio_release(struct inode *inode, struct file *file) +{ + + return 0; +} +static loff_t harmony_audio_llseek(struct file *file, loff_t offset, int whence) +{ + printk(KERN_ERR "harmony: llseek\n"); + return -ENODEV; +} + +static ssize_t harmony_audio_read(struct file *file, + char *buffer, + size_t count, + loff_t *ppos) +{ + printk(KERN_ERR "harmony: read\n"); + return -ENODEV; +} + +static int find_free_buffer(void) { + + int i,buf_to_fill_in = -1; + + for (i=MAX_BUFS-1;i>=0;i--) { + if (harmony_audio->buf_state[i] == HARMONY_BUF_EMPTY) { + buf_to_fill_in = i; + } + } + return buf_to_fill_in; +} + + +static ssize_t harmony_audio_write(struct file *file, + const char *buffer, + size_t size_count, + loff_t *ppos) +{ + + u32 status; + int total_count = (int) size_count; + int count = 0; + int frame_size; + int buf_to_fill_in = 0; + long timeout ; + int dstatus,cntl; + + while(count < total_count) { + + /* Wait until we're out of control mode */ + while ((status = gsc_readl(&(harmony_audio->hpa->cntl))) >> 31); + + /* Figure out which buffer to fill in */ + timeout = 0; + buf_to_fill_in = HARMONY_BUF_NONE; + while ((buf_to_fill_in<0) && (timeout<100000)) { + buf_to_fill_in = find_free_buffer(); + if (buf_to_fill_in < 0) { + udelay(10); + timeout++; + } + } + if (timeout==100000) { + return -1; + } + + /* Figure out the size of the frame */ + if ((total_count - count) > 4095) { + frame_size = 4096; + } 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(aligned_buf,(4096*buf_to_fill_in), 4096); + } + + /* Copy the page to an aligned buffer */ + copy_from_user(aligned_buf+(4096*buf_to_fill_in), buffer,frame_size ); + CHECK_WBACK_INV(aligned_buf,4096*MAX_BUFS); + + harmony_audio->buf_state[buf_to_fill_in] = HARMONY_BUF_FILLED; + + harmony_audio->done = 0; + count += frame_size; + + if (frame_size < 4096) { + count = total_count; + harmony_audio->done = 1; + } + + + dstatus = gsc_readl(&harmony_audio->hpa->dstatus); + + cntl = gsc_readl(&harmony_audio->hpa->cntl); + +printk("write: buf: %i frame: %i done: %i cntl: 0x%x dstatus: 0x%x\n",buf_to_fill_in,harmony_audio->frames_so_far,harmony_audio->done,cntl,dstatus); + + + + if (harmony_audio->frames_so_far == 1) { + gsc_writel(__pa(aligned_buf+(4096*0)),&(harmony_audio->hpa->pnxtadd)); + gsc_writel(__pa(graveyard),&(harmony_audio->hpa->rnxtadd)); + harmony_enable_interrupts(); + } + + harmony_audio->frames_so_far++; + } + + return count; +} +static unsigned int harmony_audio_poll(struct file *file, + struct poll_table_struct *wait) +{ + printk(KERN_ERR "harmony: poll\n"); + return -ENODEV; +} + +#define DBGEV printk +#define DBGX printk +#define DBGP + +static int harmony_audio_ioctl(struct inode *inode, + struct file *file, + unsigned int cmd, + unsigned long arg) +{ + + + unsigned long flags; + int ival; + u32 new_format,newrate; + + switch (cmd) { + case OSS_GETVERSION: /* _SIOR ('M', 118, int) */ + DBGX("OSS_GETVERSION\n"); + ival = SOUND_VERSION; + return put_user(ival, (int *) arg); + + case SNDCTL_DSP_GETCAPS: /* _SIOR ('P',15, int) */ + DBGX("SNDCTL_DSP_GETCAPS\n"); + ival = DSP_CAP_DUPLEX | DSP_CAP_BATCH ; + return put_user(ival, (int *) arg); + + case SNDCTL_DSP_GETFMTS: /* _SIOR ('P',11, int) */ + DBGX("SNDCTL_DSP_GETFMTS\n"); + ival = (AFMT_S16_BE | AFMT_MU_LAW | AFMT_A_LAW ); + return put_user(ival, (int *) arg); + break; + + case SOUND_PCM_READ_RATE: /* _SIOR ('P', 2, int) */ +/* + I have no idea what this should be. +*/ + ival = 0; + return put_user(ival, (int *) arg); + + case SNDCTL_DSP_SPEED: + + if (get_user(ival, (int *) arg)) + return -EFAULT; + if (ival) { + switch(ival) { + case 8000: newrate = HARMONY_SR_8KHZ; break; + case 16000: newrate = HARMONY_SR_16KHZ; break; + case 27428: newrate = HARMONY_SR_27KHZ; break; + case 32000: newrate = HARMONY_SR_32KHZ; break; + case 48000: newrate = HARMONY_SR_48KHZ; break; + case 9600: newrate = HARMONY_SR_9KHZ; break; + case 5125: newrate = HARMONY_SR_5KHZ; break; + case 11025: newrate = HARMONY_SR_11KHZ; break; + case 18900: newrate = HARMONY_SR_18KHZ; break; + case 22050: newrate = HARMONY_SR_22KHZ; break; + case 37800: newrate = HARMONY_SR_37KHZ; break; + case 44100: newrate = HARMONY_SR_44KHZ; break; + case 33075: newrate = HARMONY_SR_33KHZ; break; + case 6615: newrate = HARMONY_SR_6KHZ; break; + default: printk("Could not match rate %d\n",ival); newrate = HARMONY_SR_8KHZ; + } + harmony_set_rate(newrate); + } else { + ival = harmony_audio->sample_rate; + } + return put_user(ival,(int*) arg); + + case SNDCTL_DSP_STEREO: /* _SIOWR('P', 3, int) */ + if (get_user(ival, (int *) arg)) + return -EFAULT; + if (ival != 0 && ival != 1) + return -EINVAL; + harmony_set_stereo(ival); + return put_user(ival, (int *) arg); + + case SNDCTL_DSP_GETBLKSIZE: /* _SIOWR('P', 4, int) */ + ival = 4096; + return put_user(ival, (int *) arg); + case SNDCTL_DSP_RESET: + return 0; + + case SNDCTL_DSP_SETFMT: /* _SIOWR('P',5, int) */ + if (get_user(ival, (int *) arg)) { + printk("arg, couldn't get arg\n"); + 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_U16_BE: new_format = HARMONY_DF_16BIT_LINEAR; break; + default: { + printk("Invalid sound format %d\n",ival); + return -EINVAL; + } + } + harmony_set_format(new_format); + } else { + switch (harmony_audio->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); + + default: + DBGP("------ OH NO unknown ioctl 0x%x\n", cmd); + return -EINVAL; + } + DBGP("unimplemented ioctl 0x%x\n", cmd); + return -EINVAL; +} +static int harmony_audio_mmap(struct file *file, struct vm_area_struct *vma) +{ + printk(KERN_ERR "harmony: mmap\n"); + return -ENODEV; +} + +static void harmony_interrupt(int irq, void *dev, struct pt_regs *regs) +{ + u32 dstatus,pcuradd; + struct harmony_hpa * hpa; + int i, buf_to_free = 0, curbuf = MAX_BUFS; + + /* Setup the hpa */ + + hpa = ((struct harmony_dev *)dev)->hpa; + printk(KERN_INFO "Interrupt\n"); + + /* Read dstatus and pcuradd (the current address) */ + dstatus = gsc_readl(&hpa->dstatus); + pcuradd = gsc_readl(&hpa->pcuradd); + + /* Find the last filled buffer */ + curbuf = MAX_BUFS; + for (i=MAX_BUFS-1;i>=0;i--) { + if (harmony_audio->buf_state[i]==HARMONY_BUF_FILLED) { + curbuf = i; + } + } + if (curbuf == MAX_BUFS) { + if (harmony_audio->done) { + } else { + printk("harmony_interrupt: ack, nothing to play yet. No available buffer.\n"); + } + } + + /* Turn off interrupts */ + harmony_disable_interrupts(); + dstatus = gsc_readl(&hpa->dstatus); + + /* Check if this is a request to get the next play buffer */ + if(dstatus & DSTATUS_PN) { + gsc_writel(__pa(aligned_buf+(4096*curbuf)), &hpa->pnxtadd); + harmony_audio->outstanding_buffers++; + + if (!harmony_audio->done) { + + /* Set the volume; we probably don't need to do this. */ + + /* Transition the playing buffer to empty*/ + + buf_to_free = -1; + for (i=0;ibuf_state[i]==HARMONY_BUF_PLAYING) { + buf_to_free = i; + } + } + if (buf_to_free == -1) { + } else { + harmony_audio->buf_state[buf_to_free] = HARMONY_BUF_EMPTY; + } + + /* Transition the buffers that are ready to be played to playing*/ + + buf_to_free = -1; + + for (i=0;ibuf_state[i]==HARMONY_BUF_READY_TO_PLAY) { + buf_to_free = i; + } + } + if (buf_to_free == -1) { + } else { + harmony_audio->buf_state[buf_to_free] = HARMONY_BUF_PLAYING; + } + harmony_audio->buf_state[curbuf] = HARMONY_BUF_READY_TO_PLAY; + + harmony_enable_interrupts(); + } + } + + /* Check if we're being asked to fill in a recording buffer */ + if(dstatus & DSTATUS_RN) { + + /* Not supported yet */ + gsc_writel(__pa(graveyard) , &hpa->rnxtadd); + harmony_audio->outstanding_buffers--; + } + if (harmony_audio->done && (!harmony_audio->outstanding_buffers)) { + harmony_disable_interrupts(); + } +} + + + +int harmony_audio_init(struct harmony_dev * harmony_device) +{ + int ret; + int cntl; + + harmony_audio=harmony_device; + + /* Request that IRQ */ + request_irq(harmony_audio->irq, &harmony_interrupt, 0 ,"harmony", (void *) harmony_audio); + + ret = register_sound_dsp(&harmony_audio_fops, -1); + if (ret < 0) { + printk("Harmony: Error registering\n"); + return -EFAULT; + } + harmony_set_control(HARMONY_DF_8BIT_ULAW,HARMONY_SR_8KHZ,HARMONY_SS_STEREO); + + /* Clear the buffers so you don't end up with crap in the buffers. */ + + harmony_silence(aligned_buf,0,4096*MAX_BUFS); + + /* Make sure this makes it to cache */ + CHECK_WBACK_INV(aligned_buf,4096*MAX_BUFS); + + + /* Wait around until we're out of control mode */ + while((cntl = gsc_readl(&harmony_audio->hpa->cntl)) & CNTL_C); + return 0; + + +} + + +