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.
 
-       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 2004 (c) Stuart Brady <sdbrady@ntlworld.com>
 
                                
 TODO:
@@ -124,9 +125,17 @@ TODO:
 #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
@@ -543,6 +552,7 @@ static ssize_t harmony_audio_write(struct file *file,
        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))
@@ -564,12 +574,16 @@ static ssize_t harmony_audio_write(struct file *file,
                
                
                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 += MAX_BUFS;
+               }
                buf_to_fill %= MAX_BUFS;
-
+               
+               fresh_buffer = (harmony.play_offset == 0);
+               
                /* Figure out the size of the frame */
-               if ((total_count-count) > HARMONY_BUF_SIZE - harmony.play_offset) {
+               if ((total_count-count) >= HARMONY_BUF_SIZE - harmony.play_offset) {
                        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);
        
-               if (!harmony.play_offset)
+               if (fresh_buffer)
                        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;
-                       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);
-                               return -EINVAL;
+                               ival = AFMT_S16_BE;
+                               return put_user(ival, (int *) arg);
                        }
                        }
                        harmony_set_format(new_format);
+                       return 0;
                } else {
                        switch (harmony.data_format) {
                        case HARMONY_DF_8BIT_ULAW:      ival = AFMT_MU_LAW; break;
@@ -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;
                        }
+                       return put_user(ival, (int *) arg);
                }
-               return put_user(ival, (int *) arg);
 
        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);
-               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;
@@ -887,7 +910,7 @@ static int harmony_mixer_get_level(int 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);
@@ -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;
                        
-               case SOUND_MIXER_VOLUME:
+               case SOUND_MIXER_MONITOR:
                        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;
 }
@@ -926,9 +949,11 @@ static int harmony_mixer_set_level(int channel, int value)
 
        right_level = (value & 0x0000ff00) >> 8;
        left_level = value & 0x000000ff;
+       if (right_level > 100) right_level = 100;
+       if (left_level > 100) left_level = 100;
   
        switch (channel) {
-               case SOUND_MIXER_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);
@@ -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;
        
-               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();
-                       return new_left_level;
+                       return (new_left_level << 8) + new_left_level;
        }
 
        return -EINVAL;
@@ -986,11 +1011,15 @@ static int harmony_mixer_set_recmask(int recmask)
 {
        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;
-       } else  {
+       } else {
                new_input_line = 1;
                new_input_mask = SOUND_MASK_MIC;
        }
@@ -1009,9 +1038,9 @@ static int harmony_mixer_get_outmask(void)
 {
        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;
 }
@@ -1019,24 +1048,24 @@ static int harmony_mixer_get_outmask(void)
 
 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 
-               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;
        
-       if (outmask & SOUND_MASK_SPEAKER
-               harmony.current_gain |= GAIN_SE_MASK;
+       if (outmask & MASK_HEADPHONES
+               harmony.current_gain |= GAIN_HE_MASK; 
        else 
-               harmony.current_gain &= ~GAIN_SE_MASK;
+               harmony.current_gain &= ~GAIN_HE_MASK;
        
        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_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):
-               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):
-               ret = SOUND_MASK_SPEAKER | SOUND_MASK_LINE |
-                       SOUND_MASK_PHONEOUT;
+               ret = MASK_INTERNAL | MASK_LINEOUT |
+                       MASK_HEADPHONES;
                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;
        
-       case MIXER_WRITE(SOUND_MIXER_OGAIN):
-       case MIXER_WRITE(SOUND_MIXER_IGAIN):
        case MIXER_WRITE(SOUND_MIXER_VOLUME):
+       case MIXER_WRITE(SOUND_MIXER_IGAIN):
+       case MIXER_WRITE(SOUND_MIXER_MONITOR):
                ret = harmony_mixer_set_level(cmd & 0xff, val);
                break;
 
-       case MIXER_READ(SOUND_MIXER_OGAIN):
-       case MIXER_READ(SOUND_MIXER_IGAIN):
        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;
 
@@ -1201,16 +1230,15 @@ harmony_driver_probe(struct parisc_device *dev)
                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;
        }
 
+       /* 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) {
@@ -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");