vserver 1.9.3
[linux-2.6.git] / sound / oss / harmony.c
index 22f0eff..2f83ac8 100644 (file)
@@ -8,10 +8,11 @@
        On older 715 machines you'll find the technically identical chip 
        called 'Vivace'. Both Harmony and Vicace are supported by this driver.
 
        On older 715 machines you'll find the technically identical chip 
        called 'Vivace'. Both Harmony and Vicace are supported by this driver.
 
-       Copyright 2000 (c) Linuxcare Canada, Alex deVries <alex@linuxcare.com>
+       Copyright 2000 (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 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:
 
                                
 TODO:
@@ -124,9 +125,17 @@ TODO:
 #define GAIN_RO_MASK    ( 0x3f << GAIN_RO_SHIFT) 
 
 
 #define GAIN_RO_MASK    ( 0x3f << GAIN_RO_SHIFT) 
 
 
-#define MAX_OUTPUT_LEVEL (GAIN_RO_MASK >> GAIN_RO_SHIFT)
-#define MAX_INPUT_LEVEL  (GAIN_RI_MASK >> GAIN_RI_SHIFT)
-#define MAX_VOLUME_LEVEL (GAIN_MA_MASK >> GAIN_MA_SHIFT)
+#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
 
 /*
  * Channels Mask in mixer register
@@ -543,6 +552,7 @@ static ssize_t harmony_audio_write(struct file *file,
        int count = 0;
        int frame_size;
        int buf_to_fill;
        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))
 
        if (!harmony.format_initialized) {
                if (harmony_format_auto_detect(buffer, total_count))
@@ -564,12 +574,16 @@ static ssize_t harmony_audio_write(struct file *file,
                
                
                buf_to_fill = (harmony.first_filled_play+harmony.nb_filled_play); 
                
                
                buf_to_fill = (harmony.first_filled_play+harmony.nb_filled_play); 
-               if (harmony.play_offset)
+               if (harmony.play_offset) {
                        buf_to_fill--;
                        buf_to_fill--;
+                       buf_to_fill += MAX_BUFS;
+               }
                buf_to_fill %= MAX_BUFS;
                buf_to_fill %= MAX_BUFS;
-
+               
+               fresh_buffer = (harmony.play_offset == 0);
+               
                /* Figure out the size of the frame */
                /* Figure out the size of the frame */
-               if ((total_count-count) > HARMONY_BUF_SIZE - harmony.play_offset) {
+               if ((total_count-count) >= HARMONY_BUF_SIZE - harmony.play_offset) {
                        frame_size = HARMONY_BUF_SIZE - harmony.play_offset;
                } else {
                        frame_size = total_count - count;
                        frame_size = HARMONY_BUF_SIZE - harmony.play_offset;
                } else {
                        frame_size = total_count - count;
@@ -587,7 +601,7 @@ static ssize_t harmony_audio_write(struct file *file,
                CHECK_WBACK_INV_OFFSET(played_buf, (HARMONY_BUF_SIZE*buf_to_fill + harmony.play_offset), 
                                frame_size);
        
                CHECK_WBACK_INV_OFFSET(played_buf, (HARMONY_BUF_SIZE*buf_to_fill + harmony.play_offset), 
                                frame_size);
        
-               if (!harmony.play_offset)
+               if (fresh_buffer)
                        harmony.nb_filled_play++;
                
                count += frame_size;
                        harmony.nb_filled_play++;
                
                count += frame_size;
@@ -650,18 +664,17 @@ static int harmony_audio_ioctl(struct inode *inode,
                        switch (ival) {
                        case AFMT_MU_LAW:       new_format = HARMONY_DF_8BIT_ULAW; break;
                        case AFMT_A_LAW:        new_format = HARMONY_DF_8BIT_ALAW; break;
                        switch (ival) {
                        case AFMT_MU_LAW:       new_format = HARMONY_DF_8BIT_ULAW; break;
                        case AFMT_A_LAW:        new_format = HARMONY_DF_8BIT_ALAW; break;
-                       case AFMT_S16_LE:       /* fall through, but not really supported */
-                       case AFMT_S16_BE:       new_format = HARMONY_DF_16BIT_LINEAR;
-                                               ival = AFMT_S16_BE;
-                                               break; 
+                       case AFMT_S16_BE:       new_format = HARMONY_DF_16BIT_LINEAR; break;
                        default: {
                                DPRINTK(KERN_WARNING PFX 
                                        "unsupported sound format 0x%04x requested.\n",
                                        ival);
                        default: {
                                DPRINTK(KERN_WARNING PFX 
                                        "unsupported sound format 0x%04x requested.\n",
                                        ival);
-                               return -EINVAL;
+                               ival = AFMT_S16_BE;
+                               return put_user(ival, (int *) arg);
                        }
                        }
                        harmony_set_format(new_format);
                        }
                        }
                        harmony_set_format(new_format);
+                       return 0;
                } else {
                        switch (harmony.data_format) {
                        case HARMONY_DF_8BIT_ULAW:      ival = AFMT_MU_LAW; break;
                } else {
                        switch (harmony.data_format) {
                        case HARMONY_DF_8BIT_ULAW:      ival = AFMT_MU_LAW; break;
@@ -669,8 +682,8 @@ static int harmony_audio_ioctl(struct inode *inode,
                        case HARMONY_DF_16BIT_LINEAR:   ival = AFMT_U16_BE; break;
                        default: ival = 0;
                        }
                        case HARMONY_DF_16BIT_LINEAR:   ival = AFMT_U16_BE; break;
                        default: ival = 0;
                        }
+                       return put_user(ival, (int *) arg);
                }
                }
-               return put_user(ival, (int *) arg);
 
        case SOUND_PCM_READ_RATE:
                ival = harmony.dac_rate;
 
        case SOUND_PCM_READ_RATE:
                ival = harmony.dac_rate;
@@ -689,7 +702,17 @@ static int harmony_audio_ioctl(struct inode *inode,
                if (ival != 0 && ival != 1)
                        return -EINVAL;
                harmony_set_stereo(ival);
                if (ival != 0 && ival != 1)
                        return -EINVAL;
                harmony_set_stereo(ival);
-               return put_user(ival, (int *) arg);
+               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;
 
        case SNDCTL_DSP_GETBLKSIZE:
                ival = HARMONY_BUF_SIZE;
@@ -887,7 +910,7 @@ static int harmony_mixer_get_level(int channel)
        int right_level;
 
        switch (channel) {
        int right_level;
 
        switch (channel) {
-               case SOUND_MIXER_OGAIN:
+               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);
                        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);
@@ -901,10 +924,10 @@ static int harmony_mixer_get_level(int channel)
                        right_level= to_oss_level(right_level, MAX_INPUT_LEVEL);
                        return (right_level << 8)+left_level;
                        
                        right_level= to_oss_level(right_level, MAX_INPUT_LEVEL);
                        return (right_level << 8)+left_level;
                        
-               case SOUND_MIXER_VOLUME:
+               case SOUND_MIXER_MONITOR:
                        left_level = (harmony.current_gain & GAIN_MA_MASK) >> GAIN_MA_SHIFT;
                        left_level = (harmony.current_gain & GAIN_MA_MASK) >> GAIN_MA_SHIFT;
-                       left_level = to_oss_level(MAX_VOLUME_LEVEL-left_level, MAX_VOLUME_LEVEL);
-                       return left_level;
+                       left_level = to_oss_level(MAX_MONITOR_LEVEL-left_level, MAX_MONITOR_LEVEL);
+                       return (left_level << 8)+left_level;
        }
        return -EINVAL;
 }
        }
        return -EINVAL;
 }
@@ -926,9 +949,11 @@ static int harmony_mixer_set_level(int channel, int value)
 
        right_level = (value & 0x0000ff00) >> 8;
        left_level = value & 0x000000ff;
 
        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) {
   
        switch (channel) {
-               case SOUND_MIXER_OGAIN:
+               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);
                        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);
@@ -948,12 +973,12 @@ static int harmony_mixer_set_level(int channel, int value)
                        harmony_mixer_set_gain();
                        return (new_right_level << 8) + new_left_level;
        
                        harmony_mixer_set_gain();
                        return (new_right_level << 8) + new_left_level;
        
-               case SOUND_MIXER_VOLUME:
-                       left_level = to_harmony_level(100-left_level, MAX_VOLUME_LEVEL);
-                       new_left_level = to_oss_level(MAX_VOLUME_LEVEL-left_level, MAX_VOLUME_LEVEL);
-                       harmony.current_gain = (harmony.current_gain & ~GAIN_MA_MASK)| (left_level << GAIN_MA_SHIFT);
+               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();
                        harmony_mixer_set_gain();
-                       return new_left_level;
+                       return (new_left_level << 8) + new_left_level;
        }
 
        return -EINVAL;
        }
 
        return -EINVAL;
@@ -986,11 +1011,15 @@ static int harmony_mixer_set_recmask(int recmask)
 {
        int new_input_line;
        int new_input_mask;
 {
        int new_input_line;
        int new_input_mask;
-
-       if ((recmask & SOUND_MASK_LINE)) {
+       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;
                new_input_line = 0;
                new_input_mask = SOUND_MASK_LINE;
-       } else  {
+       } else {
                new_input_line = 1;
                new_input_mask = SOUND_MASK_MIC;
        }
                new_input_line = 1;
                new_input_mask = SOUND_MASK_MIC;
        }
@@ -1009,9 +1038,9 @@ static int harmony_mixer_get_outmask(void)
 {
        int outmask = 0;
        
 {
        int outmask = 0;
        
-       if (harmony.current_gain & GAIN_HE_MASK) outmask |=SOUND_MASK_PHONEOUT;
-       if (harmony.current_gain & GAIN_LE_MASK) outmask |=SOUND_MASK_LINE;
-       if (harmony.current_gain & GAIN_SE_MASK) outmask |=SOUND_MASK_SPEAKER;
+       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;
 }
        
        return outmask;
 }
@@ -1019,24 +1048,24 @@ static int harmony_mixer_get_outmask(void)
 
 static int harmony_mixer_set_outmask(int outmask)
 {
 
 static int harmony_mixer_set_outmask(int outmask)
 {
-       if (outmask & SOUND_MASK_PHONEOUT
-               harmony.current_gain |= GAIN_HE_MASK; 
+       if (outmask & MASK_INTERNAL
+               harmony.current_gain |= GAIN_SE_MASK;
        else 
        else 
-               harmony.current_gain &= ~GAIN_HE_MASK;
+               harmony.current_gain &= ~GAIN_SE_MASK;
        
        
-       if (outmask & SOUND_MASK_LINE
+       if (outmask & MASK_LINEOUT
                harmony.current_gain |= GAIN_LE_MASK;
        else 
                harmony.current_gain &= ~GAIN_LE_MASK;
        
                harmony.current_gain |= GAIN_LE_MASK;
        else 
                harmony.current_gain &= ~GAIN_LE_MASK;
        
-       if (outmask & SOUND_MASK_SPEAKER
-               harmony.current_gain |= GAIN_SE_MASK;
+       if (outmask & MASK_HEADPHONES
+               harmony.current_gain |= GAIN_HE_MASK; 
        else 
        else 
-               harmony.current_gain &= ~GAIN_SE_MASK;
+               harmony.current_gain &= ~GAIN_HE_MASK;
        
        harmony_mixer_set_gain();
 
        
        harmony_mixer_set_gain();
 
-       return (outmask & (SOUND_MASK_PHONEOUT | SOUND_MASK_LINE | SOUND_MASK_SPEAKER));
+       return (outmask & (MASK_INTERNAL | MASK_LINEOUT | MASK_HEADPHONES));
 }
 
 /*
 }
 
 /*
@@ -1074,19 +1103,19 @@ static int harmony_mixer_ioctl(struct inode * inode, struct file * file,
                ret = SOUND_CAP_EXCL_INPUT;
                break;
        case MIXER_READ(SOUND_MIXER_STEREODEVS):
                ret = SOUND_CAP_EXCL_INPUT;
                break;
        case MIXER_READ(SOUND_MIXER_STEREODEVS):
-               ret = SOUND_MASK_IGAIN | SOUND_MASK_OGAIN;
+               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):
                break;
                
        case MIXER_READ(SOUND_MIXER_RECMASK):
                ret = SOUND_MASK_MIC | SOUND_MASK_LINE;
                break;
        case MIXER_READ(SOUND_MIXER_DEVMASK):
-               ret = SOUND_MASK_OGAIN | SOUND_MASK_IGAIN |
-                       SOUND_MASK_VOLUME;
+               ret = SOUND_MASK_VOLUME | SOUND_MASK_IGAIN |
+                       SOUND_MASK_MONITOR;
                break;
        case MIXER_READ(SOUND_MIXER_OUTMASK):
                break;
        case MIXER_READ(SOUND_MIXER_OUTMASK):
-               ret = SOUND_MASK_SPEAKER | SOUND_MASK_LINE |
-                       SOUND_MASK_PHONEOUT;
+               ret = MASK_INTERNAL | MASK_LINEOUT |
+                       MASK_HEADPHONES;
                break;
                
        case MIXER_WRITE(SOUND_MIXER_RECSRC):
                break;
                
        case MIXER_WRITE(SOUND_MIXER_RECSRC):
@@ -1103,15 +1132,15 @@ static int harmony_mixer_ioctl(struct inode * inode, struct file * file,
                ret = harmony_mixer_get_outmask();
                break;
        
                ret = harmony_mixer_get_outmask();
                break;
        
-       case MIXER_WRITE(SOUND_MIXER_OGAIN):
-       case MIXER_WRITE(SOUND_MIXER_IGAIN):
        case MIXER_WRITE(SOUND_MIXER_VOLUME):
        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;
 
                ret = harmony_mixer_set_level(cmd & 0xff, val);
                break;
 
-       case MIXER_READ(SOUND_MIXER_OGAIN):
-       case MIXER_READ(SOUND_MIXER_IGAIN):
        case MIXER_READ(SOUND_MIXER_VOLUME):
        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;
 
                ret = harmony_mixer_get_level(cmd & 0xff);
                break;
 
@@ -1201,16 +1230,15 @@ harmony_driver_probe(struct parisc_device *dev)
                return -EBUSY;
        }
 
                return -EBUSY;
        }
 
-       harmony.dev = dev;
-
-       /* Set the HPA of harmony */
-       harmony.hpa = (struct harmony_hpa *)dev->hpa;
-
-       if (!harmony.dev->irq) {
+       if (!dev->irq) {
                printk(KERN_ERR PFX "no irq found\n");
                return -ENODEV;
        }
 
                printk(KERN_ERR PFX "no irq found\n");
                return -ENODEV;
        }
 
+       /* Set the HPA of harmony */
+       harmony.hpa = (struct harmony_hpa *)dev->hpa;
+       harmony.dev = dev;
+
        /* Grab the ID and revision from the device */
        id = gsc_readb(&harmony.hpa->id);
        if ((id | 1) != 0x15) {
        /* Grab the ID and revision from the device */
        id = gsc_readb(&harmony.hpa->id);
        if ((id | 1) != 0x15) {
@@ -1293,7 +1321,7 @@ static void __exit cleanup_harmony(void)
 }
 
 
 }
 
 
-MODULE_AUTHOR("Alex DeVries <alex@linuxcare.com>");
+MODULE_AUTHOR("Alex DeVries <alex@onefishtwo.ca>");
 MODULE_DESCRIPTION("Harmony sound driver");
 MODULE_LICENSE("GPL");
 
 MODULE_DESCRIPTION("Harmony sound driver");
 MODULE_LICENSE("GPL");