[parisc-linux] Lasi harmony driver with mixer
Matthieu Delahaye
delahaym@esiee.fr
Wed, 17 Jan 2001 23:18:48 +0100
This is a multi-part message in MIME format.
--------------72A8A284674098C68F7C5063
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: 7bit
Matthew Wilcox wrote:
> > +int harmony_mixer_ioctl(struct inode * inode, struct file * file, unsigned int cmd, unsigned long arg)
> > +{
> > + int val;
> > + int ret;
> [...]
>
> it'd be nice if you could rformat this to follow CodingStyle.
>
[...]
here it is the corrected patch according to your remarks
Matthieu Delahaye
ESIEE Team
http://www.esiee.fr/puffin
--------------72A8A284674098C68F7C5063
Content-Type: text/plain; charset=us-ascii;
name="harmony.diff"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
filename="harmony.diff"
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 <linux/config.h>
+#include <linux/delay.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/malloc.h>
+#include <linux/types.h>
+#include <linux/mm.h>
+#include <asm/gsc.h>
+
+#include <asm/pgalloc.h>
+#include <asm/pdc.h>
+#include <asm/page.h>
+#include <asm/segment.h>
+#include <asm/system.h>
+#include <asm/io.h>
+#include <asm/hardware.h>
+
+#include <asm/irq.h> /* for "gsc" irq functions */
+#include <asm/gsc.h>
+
+#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 <alex@linuxcare.com>
+
+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 <alex@linuxcare.com>
+
+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;i<MAX_BUFS;i++) {
+ harmony_audio->buf_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;i<MAX_BUFS;i++) {
+ if (harmony_audio->buf_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;i<MAX_BUFS;i++) {
+ if (harmony_audio->buf_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;
+
+
+}
+
+
+
--------------72A8A284674098C68F7C5063--