PATCH: AC97 updates from 2.4

From: Alan Cox (alan@lxorguk.ukuu.org.uk)
Date: Fri Jul 11 2003 - 13:09:35 EST


This deals with several things
- Codecs that think they are modems but are not
- Abstracting modem detection out of drivers
- Abstracting digital switching out of drivers
- Codecs that have no volume control
- Codec plugins for specific setups
- Codec plugins for things like touchscreen/batmon on AC97
- More codec handlers

The plugin API is intentionally modelled on the other driver_register
type interfaces.

diff -u --new-file --recursive --exclude-from /usr/src/exclude linux-2.5.75/include/linux/ac97_codec.h linux-2.5.75-ac1/include/linux/ac97_codec.h
--- linux-2.5.75/include/linux/ac97_codec.h 2003-07-10 21:04:47.000000000 +0100
+++ linux-2.5.75-ac1/include/linux/ac97_codec.h 2003-07-11 17:17:37.000000000 +0100
@@ -214,6 +214,9 @@
                                     (CODEC)->supported_mixers & (1<<FOO) )
 
 struct ac97_codec {
+ /* Linked list of codecs */
+ struct list_head list;
+
         /* AC97 controller connected with */
         void *private_data;
 
@@ -221,22 +224,37 @@
         int id;
         int dev_mixer;
         int type;
+ u32 model;
+
+ int modem:1;
 
         struct ac97_ops *codec_ops;
 
- /* controller specific lower leverl ac97 accessing routines */
+ /* controller specific lower leverl ac97 accessing routines.
+ must be re-entrant safe */
         u16 (*codec_read) (struct ac97_codec *codec, u8 reg);
         void (*codec_write) (struct ac97_codec *codec, u8 reg, u16 val);
 
         /* Wait for codec-ready. Ok to sleep here. */
         void (*codec_wait) (struct ac97_codec *codec);
 
+ /* callback used by helper drivers for interesting ac97 setups */
+ void (*codec_unregister) (struct ac97_codec *codec);
+
+ struct ac97_driver *driver;
+ void *driver_private; /* Private data for the driver */
+
+ spinlock_t lock;
+
         /* OSS mixer masks */
         int modcnt;
         int supported_mixers;
         int stereo_mixers;
         int record_sources;
 
+ /* Property flags */
+ int flags;
+
         int bit_resolution;
 
         /* OSS mixer interface */
@@ -264,7 +282,14 @@
         /* Amplifier control */
         int (*amplifier)(struct ac97_codec *codec, int on);
         /* Digital mode control */
- int (*digital)(struct ac97_codec *codec, int format);
+ int (*digital)(struct ac97_codec *codec, int slots, int rate, int mode);
+#define AUDIO_DIGITAL 0x8000
+#define AUDIO_PRO 0x4000
+#define AUDIO_DRS 0x2000
+#define AUDIO_CCMASK 0x003F
+
+#define AC97_DELUDED_MODEM 1 /* Audio codec reports its a modem */
+#define AC97_NO_PCM_VOLUME 2 /* Volume control is missing */
 };
 
 extern int ac97_read_proc (char *page_out, char **start, off_t off,
@@ -275,4 +300,19 @@
 extern int ac97_save_state(struct ac97_codec *codec);
 extern int ac97_restore_state(struct ac97_codec *codec);
 
+extern struct ac97_codec *ac97_alloc_codec(void);
+extern void ac97_release_codec(struct ac97_codec *codec);
+
+struct ac97_driver {
+ struct list_head list;
+ char *name;
+ u32 codec_id;
+ u32 codec_mask;
+ int (*probe) (struct ac97_codec *codec, struct ac97_driver *driver);
+ void (*remove) (struct ac97_codec *codec, struct ac97_driver *driver);
+};
+
+extern int ac97_register_driver(struct ac97_driver *driver);
+extern void ac97_unregister_driver(struct ac97_driver *driver);
+
 #endif /* _AC97_CODEC_H_ */
Binary files linux-2.5.75/lib/gen_crc32table and linux-2.5.75-ac1/lib/gen_crc32table differ
diff -u --new-file --recursive --exclude-from /usr/src/exclude linux-2.5.75/sound/oss/ac97_codec.c linux-2.5.75-ac1/sound/oss/ac97_codec.c
--- linux-2.5.75/sound/oss/ac97_codec.c 2003-07-10 21:05:27.000000000 +0100
+++ linux-2.5.75-ac1/sound/oss/ac97_codec.c 2003-07-11 16:28:47.000000000 +0100
@@ -1,4 +1,3 @@
-
 /*
  * ac97_codec.c: Generic AC97 mixer/modem module
  *
@@ -31,6 +30,10 @@
  **************************************************************************
  *
  * History
+ * May 02, 2003 Liam Girdwood <liam.girdwood@wolfsonmicro.com>
+ * Removed non existant WM9700
+ * Added support for WM9705, WM9708, WM9709, WM9710, WM9711
+ * WM9712 and WM9717
  * Mar 28, 2002 Randolph Bentson <bentson@holmsjoen.com>
  * corrections to support WM9707 in ViewPad 1000
  * v0.4 Mar 15 2000 Ollie Lho
@@ -43,7 +46,9 @@
  * Isolated from trident.c to support multiple ac97 codec
  */
 #include <linux/module.h>
+#include <linux/version.h>
 #include <linux/kernel.h>
+#include <linux/slab.h>
 #include <linux/string.h>
 #include <linux/errno.h>
 #include <linux/bitops.h>
@@ -62,9 +67,10 @@
 
 static int ac97_init_mixer(struct ac97_codec *codec);
 
-static int wolfson_init00(struct ac97_codec * codec);
 static int wolfson_init03(struct ac97_codec * codec);
 static int wolfson_init04(struct ac97_codec * codec);
+static int wolfson_init05(struct ac97_codec * codec);
+static int wolfson_init11(struct ac97_codec * codec);
 static int tritech_init(struct ac97_codec * codec);
 static int tritech_maestro_init(struct ac97_codec * codec);
 static int sigmatel_9708_init(struct ac97_codec *codec);
@@ -72,7 +78,10 @@
 static int sigmatel_9744_init(struct ac97_codec *codec);
 static int ad1886_init(struct ac97_codec *codec);
 static int eapd_control(struct ac97_codec *codec, int);
-static int crystal_digital_control(struct ac97_codec *codec, int mode);
+static int crystal_digital_control(struct ac97_codec *codec, int slots, int rate, int mode);
+static int cmedia_init(struct ac97_codec * codec);
+static int cmedia_digital_control(struct ac97_codec *codec, int slots, int rate, int mode);
+static int generic_digital_control(struct ac97_codec *codec, int slots, int rate, int mode);
 
 
 /*
@@ -93,9 +102,11 @@
  
 static struct ac97_ops null_ops = { NULL, NULL, NULL };
 static struct ac97_ops default_ops = { NULL, eapd_control, NULL };
-static struct ac97_ops wolfson_ops00 = { wolfson_init00, NULL, NULL };
+static struct ac97_ops default_digital_ops = { NULL, eapd_control, generic_digital_control};
 static struct ac97_ops wolfson_ops03 = { wolfson_init03, NULL, NULL };
 static struct ac97_ops wolfson_ops04 = { wolfson_init04, NULL, NULL };
+static struct ac97_ops wolfson_ops05 = { wolfson_init05, NULL, NULL };
+static struct ac97_ops wolfson_ops11 = { wolfson_init11, NULL, NULL };
 static struct ac97_ops tritech_ops = { tritech_init, NULL, NULL };
 static struct ac97_ops tritech_m_ops = { tritech_maestro_init, NULL, NULL };
 static struct ac97_ops sigmatel_9708_ops = { sigmatel_9708_init, NULL, NULL };
@@ -103,12 +114,15 @@
 static struct ac97_ops sigmatel_9744_ops = { sigmatel_9744_init, NULL, NULL };
 static struct ac97_ops crystal_digital_ops = { NULL, eapd_control, crystal_digital_control };
 static struct ac97_ops ad1886_ops = { ad1886_init, eapd_control, NULL };
+static struct ac97_ops cmedia_ops = { NULL, eapd_control, NULL};
+static struct ac97_ops cmedia_digital_ops = { cmedia_init, eapd_control, cmedia_digital_control};
 
 /* sorted by vendor/device id */
 static const struct {
         u32 id;
         char *name;
         struct ac97_ops *ops;
+ int flags;
 } ac97_codec_ids[] = {
         {0x41445303, "Analog Devices AD1819", &null_ops},
         {0x41445340, "Analog Devices AD1881", &null_ops},
@@ -120,8 +134,12 @@
         {0x414B4D00, "Asahi Kasei AK4540", &null_ops},
         {0x414B4D01, "Asahi Kasei AK4542", &null_ops},
         {0x414B4D02, "Asahi Kasei AK4543", &null_ops},
+ {0x414C4326, "ALC100P", &null_ops},
         {0x414C4710, "ALC200/200P", &null_ops},
- {0x414C4720, "ALC650", &null_ops},
+ {0x414C4720, "ALC650", &default_digital_ops},
+ {0x434D4941, "CMedia", &cmedia_ops, AC97_NO_PCM_VOLUME },
+ {0x434D4942, "CMedia", &cmedia_ops, AC97_NO_PCM_VOLUME },
+ {0x434D4961, "CMedia", &cmedia_digital_ops, AC97_NO_PCM_VOLUME },
         {0x43525900, "Cirrus Logic CS4297", &default_ops},
         {0x43525903, "Cirrus Logic CS4297", &default_ops},
         {0x43525913, "Cirrus Logic CS4297A rev A", &default_ops},
@@ -132,6 +150,8 @@
         {0x43525931, "Cirrus Logic CS4299 rev A", &crystal_digital_ops},
         {0x43525933, "Cirrus Logic CS4299 rev C", &crystal_digital_ops},
         {0x43525934, "Cirrus Logic CS4299 rev D", &crystal_digital_ops},
+ {0x43585442, "CXT66", &default_ops, AC97_DELUDED_MODEM },
+ {0x44543031, "Diamond Technology DT0893", &default_ops},
         {0x45838308, "ESS Allegro ES1988", &null_ops},
         {0x49434511, "ICE1232", &null_ops}, /* I hope --jk */
         {0x4e534331, "National Semiconductor LM4549", &null_ops},
@@ -143,9 +163,11 @@
         {0x54524106, "TriTech TR28026", &null_ops},
         {0x54524108, "TriTech TR28028", &tritech_ops},
         {0x54524123, "TriTech TR A5", &null_ops},
- {0x574D4C00, "Wolfson WM9700A", &wolfson_ops00},
- {0x574D4C03, "Wolfson WM9703/WM9707", &wolfson_ops03},
+ {0x574D4C03, "Wolfson WM9703/07/08/17", &wolfson_ops03},
         {0x574D4C04, "Wolfson WM9704M/WM9704Q", &wolfson_ops04},
+ {0x574D4C05, "Wolfson WM9705/WM9710", &wolfson_ops05},
+ {0x574D4C09, "Wolfson WM9709", &null_ops},
+ {0x574D4C12, "Wolfson WM9711/9712", &wolfson_ops11},
         {0x83847600, "SigmaTel STAC????", &null_ops},
         {0x83847604, "SigmaTel STAC9701/3/4/5", &null_ops},
         {0x83847605, "SigmaTel STAC9704", &null_ops},
@@ -271,6 +293,10 @@
         [SOUND_MIXER_PHONEIN] = AC97_REC_PHONE
 };
 
+static LIST_HEAD(codecs);
+static LIST_HEAD(codec_drivers);
+static DECLARE_MUTEX(codec_sem);
+
 /* reads the given OSS mixer from the ac97 the caller must have insured that the ac97 knows
    about that given mixer, and should be holding a spinlock for the card */
 static int ac97_read_mixer(struct ac97_codec *codec, int oss_channel)
@@ -445,7 +471,7 @@
 }
 
 /* read or write the recmask, the ac97 can really have left and right recording
- inputs independently set, but OSS doesn't seem to want us to express that to
+ inputs independantly set, but OSS doesn't seem to want us to express that to
    the user. the caller guarantees that we have a supported bit set, and they
    must be holding the card's spinlock */
 static int ac97_recmask_io(struct ac97_codec *codec, int rw, int mask)
@@ -487,8 +513,8 @@
 
         if (cmd == SOUND_MIXER_INFO) {
                 mixer_info info;
- strlcpy(info.id, codec->name, sizeof(info.id));
- strlcpy(info.name, codec->name, sizeof(info.name));
+ strncpy(info.id, codec->name, sizeof(info.id));
+ strncpy(info.name, codec->name, sizeof(info.name));
                 info.modify_counter = codec->modcnt;
                 if (copy_to_user((void *)arg, &info, sizeof(info)))
                         return -EFAULT;
@@ -496,8 +522,8 @@
         }
         if (cmd == SOUND_OLD_MIXER_INFO) {
                 _old_mixer_info info;
- strlcpy(info.id, codec->name, sizeof(info.id));
- strlcpy(info.name, codec->name, sizeof(info.name));
+ strncpy(info.id, codec->name, sizeof(info.id));
+ strncpy(info.name, codec->name, sizeof(info.name));
                 if (copy_to_user((void *)arg, &info, sizeof(info)))
                         return -EFAULT;
                 return 0;
@@ -678,6 +704,75 @@
 }
  
 /**
+ * ac97_check_modem - Check if the Codec is a modem
+ * @codec: codec to check
+ *
+ * Return true if the device is an AC97 1.0 or AC97 2.0 modem
+ */
+
+static int ac97_check_modem(struct ac97_codec *codec)
+{
+ /* Check for an AC97 1.0 soft modem (ID1) */
+ if(codec->codec_read(codec, AC97_RESET) & 2)
+ return 1;
+ /* Check for an AC97 2.x soft modem */
+ codec->codec_write(codec, AC97_EXTENDED_MODEM_ID, 0L);
+ if(codec->codec_read(codec, AC97_EXTENDED_MODEM_ID) & 1)
+ return 1;
+ return 0;
+}
+
+
+/**
+ * ac97_alloc_codec - Allocate an AC97 codec
+ *
+ * Returns a new AC97 codec structure. AC97 codecs may become
+ * refcounted soon so this interface is needed. Returns with
+ * one reference taken.
+ */
+
+struct ac97_codec *ac97_alloc_codec(void)
+{
+ struct ac97_codec *codec = kmalloc(sizeof(struct ac97_codec), GFP_KERNEL);
+ if(!codec)
+ return NULL;
+
+ memset(codec, 0, sizeof(*codec));
+ spin_lock_init(&codec->lock);
+ INIT_LIST_HEAD(&codec->list);
+ return codec;
+}
+
+EXPORT_SYMBOL(ac97_alloc_codec);
+
+/**
+ * ac97_release_codec - Release an AC97 codec
+ * @codec: codec to release
+ *
+ * Release an allocated AC97 codec. This will be refcounted in
+ * time but for the moment is trivial. Calls the unregister
+ * handler if the codec is now defunct.
+ */
+
+void ac97_release_codec(struct ac97_codec *codec)
+{
+ /* Remove from the list first, we don't want to be
+ "rediscovered" */
+ down(&codec_sem);
+ list_del(&codec->list);
+ up(&codec_sem);
+ /*
+ * The driver needs to deal with internal
+ * locking to avoid accidents here.
+ */
+ if(codec->driver)
+ codec->driver->remove(codec, codec->driver);
+ kfree(codec);
+}
+
+EXPORT_SYMBOL(ac97_release_codec);
+
+/**
  * ac97_probe_codec - Initialize and setup AC97-compatible codec
  * @codec: (in/out) Kernel info for a single AC97 codec
  *
@@ -703,10 +798,13 @@
 int ac97_probe_codec(struct ac97_codec *codec)
 {
         u16 id1, id2;
- u16 audio, modem;
+ u16 audio;
         int i;
         char cidbuf[CODEC_ID_BUFSZ];
-
+ u16 f;
+ struct list_head *l;
+ struct ac97_driver *d;
+
         /* probing AC97 codec, AC97 2.0 says that bit 15 of register 0x00 (reset) should
          * be read zero.
          *
@@ -729,13 +827,9 @@
         }
 
         /* probe for Modem Codec */
- codec->codec_write(codec, AC97_EXTENDED_MODEM_ID, 0L);
- modem = codec->codec_read(codec, AC97_EXTENDED_MODEM_ID) & 1;
- modem |= (audio&2);
- audio &= ~2;
-
+ codec->modem = ac97_check_modem(codec);
         codec->name = NULL;
- codec->codec_ops = &null_ops;
+ codec->codec_ops = &default_ops;
 
         id1 = codec->codec_read(codec, AC97_VENDOR_ID1);
         id2 = codec->codec_read(codec, AC97_VENDOR_ID2);
@@ -744,16 +838,51 @@
                         codec->type = ac97_codec_ids[i].id;
                         codec->name = ac97_codec_ids[i].name;
                         codec->codec_ops = ac97_codec_ids[i].ops;
+ codec->flags = ac97_codec_ids[i].flags;
                         break;
                 }
         }
+
+ codec->model = (id1 << 16) | id2;
+
+ f = codec->codec_read(codec, AC97_EXTENDED_STATUS);
+ if(f & 4)
+ codec->codec_ops = &default_digital_ops;
+
+ /* A device which thinks its a modem but isnt */
+ if(codec->flags & AC97_DELUDED_MODEM)
+ codec->modem = 0;
+
         if (codec->name == NULL)
                 codec->name = "Unknown";
         printk(KERN_INFO "ac97_codec: AC97 %s codec, id: %s (%s)\n",
- modem ? "Modem" : (audio ? "Audio" : ""),
+ codec->modem ? "Modem" : (audio ? "Audio" : ""),
                codec_id(id1, id2, cidbuf), codec->name);
 
- return ac97_init_mixer(codec);
+ if(!ac97_init_mixer(codec))
+ return 0;
+
+ /*
+ * Attach last so the caller can override the mixer
+ * callbacks.
+ */
+
+ down(&codec_sem);
+ list_add(&codec->list, &codecs);
+
+ list_for_each(l, &codec_drivers) {
+ d = list_entry(l, struct ac97_driver, list);
+ if ((codec->model ^ d->codec_id) & d->codec_mask)
+ continue;
+ if(d->probe(codec, d) == 0)
+ {
+ codec->driver = d;
+ break;
+ }
+ }
+
+ up(&codec_sem);
+ return 1;
 }
 
 static int ac97_init_mixer(struct ac97_codec *codec)
@@ -772,6 +901,7 @@
         if (!(cap & 0x10))
                 codec->supported_mixers &= ~SOUND_MASK_ALTPCM;
 
+
         /* detect bit resolution */
         codec->codec_write(codec, AC97_MASTER_VOL_STEREO, 0x2020);
         if(codec->codec_read(codec, AC97_MASTER_VOL_STEREO) == 0x2020)
@@ -800,6 +930,15 @@
                 ac97_set_mixer(codec, md->mixer, md->value);
         }
 
+ /*
+ * Volume is MUTE only on this device. We have to initialise
+ * it but its useless beyond that.
+ */
+ if(codec->flags & AC97_NO_PCM_VOLUME)
+ {
+ codec->supported_mixers &= ~SOUND_MASK_PCM;
+ printk(KERN_WARNING "AC97 codec does not have proper volume support.\n");
+ }
         return 1;
 }
 
@@ -872,54 +1011,75 @@
         return 0;
 }
 
-
-static int wolfson_init00(struct ac97_codec * codec)
+static int cmedia_init(struct ac97_codec *codec)
 {
- /* This initialization is suspect, but not known to be wrong.
- It was copied from the initialization for the WM9704Q, but
- that same sequence is known to fail for the WM9707. Thus
- this warning may help someone with hardware to test
- this code. */
- codec->codec_write(codec, 0x72, 0x0808);
- codec->codec_write(codec, 0x74, 0x0808);
-
- // patch for DVD noise
- codec->codec_write(codec, 0x5a, 0x0200);
-
- // init vol as PCM vol
- codec->codec_write(codec, 0x70,
- codec->codec_read(codec, AC97_PCMOUT_VOL));
-
- codec->codec_write(codec, AC97_SURROUND_MASTER, 0x0000);
+ /* Initialise the CMedia 9739 */
+ /*
+ We could set various options here
+ Register 0x20 bit 0x100 sets mic as center bass
+ Also do multi_channel_ctrl &=~0x3000 |=0x1000
+
+ For now we set up the GPIO and PC beep
+ */
+
+ u16 v;
+
+ /* MIC */
+ codec->codec_write(codec, 0x64, 0x3000);
+ v = codec->codec_read(codec, 0x64);
+ v &= ~0x8000;
+ codec->codec_write(codec, 0x64, v);
+ codec->codec_write(codec, 0x70, 0x0100);
+ codec->codec_write(codec, 0x72, 0x0020);
         return 0;
 }
-
+
+#define AC97_WM97XX_FMIXER_VOL 0x72
+#define AC97_WM97XX_RMIXER_VOL 0x74
+#define AC97_WM97XX_TEST 0x5a
+#define AC97_WM9704_RPCM_VOL 0x70
+#define AC97_WM9711_OUT3VOL 0x16
 
 static int wolfson_init03(struct ac97_codec * codec)
 {
         /* this is known to work for the ViewSonic ViewPad 1000 */
- codec->codec_write(codec, 0x72, 0x0808);
- codec->codec_write(codec, 0x20, 0x8000);
+ codec->codec_write(codec, AC97_WM97XX_FMIXER_VOL, 0x0808);
+ codec->codec_write(codec, AC97_GENERAL_PURPOSE, 0x8000);
         return 0;
 }
 
-
 static int wolfson_init04(struct ac97_codec * codec)
 {
- codec->codec_write(codec, 0x72, 0x0808);
- codec->codec_write(codec, 0x74, 0x0808);
+ codec->codec_write(codec, AC97_WM97XX_FMIXER_VOL, 0x0808);
+ codec->codec_write(codec, AC97_WM97XX_RMIXER_VOL, 0x0808);
 
         // patch for DVD noise
- codec->codec_write(codec, 0x5a, 0x0200);
+ codec->codec_write(codec, AC97_WM97XX_TEST, 0x0200);
 
         // init vol as PCM vol
- codec->codec_write(codec, 0x70,
+ codec->codec_write(codec, AC97_WM9704_RPCM_VOL,
                 codec->codec_read(codec, AC97_PCMOUT_VOL));
 
+ /* set rear surround volume */
         codec->codec_write(codec, AC97_SURROUND_MASTER, 0x0000);
         return 0;
 }
 
+/* WM9705, WM9710 */
+static int wolfson_init05(struct ac97_codec * codec)
+{
+ /* set front mixer volume */
+ codec->codec_write(codec, AC97_WM97XX_FMIXER_VOL, 0x0808);
+ return 0;
+}
+
+/* WM9711, WM9712 */
+static int wolfson_init11(struct ac97_codec * codec)
+{
+ /* set out3 volume */
+ codec->codec_write(codec, AC97_WM9711_OUT3VOL, 0x0808);
+ return 0;
+}
 
 static int tritech_init(struct ac97_codec * codec)
 {
@@ -980,26 +1140,115 @@
         return 0;
 }
 
+static int generic_digital_control(struct ac97_codec *codec, int slots, int rate, int mode)
+{
+ u16 reg;
+
+ reg = codec->codec_read(codec, AC97_SPDIF_CONTROL);
+
+ switch(rate)
+ {
+ /* Off by default */
+ default:
+ case 0:
+ reg = codec->codec_read(codec, AC97_EXTENDED_STATUS);
+ codec->codec_write(codec, AC97_EXTENDED_STATUS, (reg & ~AC97_EA_SPDIF));
+ if(rate == 0)
+ return 0;
+ return -EINVAL;
+ case 1:
+ reg = (reg & AC97_SC_SPSR_MASK) | AC97_SC_SPSR_48K;
+ break;
+ case 2:
+ reg = (reg & AC97_SC_SPSR_MASK) | AC97_SC_SPSR_44K;
+ break;
+ case 3:
+ reg = (reg & AC97_SC_SPSR_MASK) | AC97_SC_SPSR_32K;
+ break;
+ }
+
+ reg &= ~AC97_SC_CC_MASK;
+ reg |= (mode & AUDIO_CCMASK) << 6;
+
+ if(mode & AUDIO_DIGITAL)
+ reg |= 2;
+ if(mode & AUDIO_PRO)
+ reg |= 1;
+ if(mode & AUDIO_DRS)
+ reg |= 0x4000;
+
+ codec->codec_write(codec, AC97_SPDIF_CONTROL, reg);
+
+ reg = codec->codec_read(codec, AC97_EXTENDED_STATUS);
+ reg &= (AC97_EA_SLOT_MASK);
+ reg |= AC97_EA_VRA | AC97_EA_SPDIF | slots;
+ codec->codec_write(codec, AC97_EXTENDED_STATUS, reg);
+
+ reg = codec->codec_read(codec, AC97_EXTENDED_STATUS);
+ if(!(reg & 0x0400))
+ {
+ codec->codec_write(codec, AC97_EXTENDED_STATUS, reg & ~ AC97_EA_SPDIF);
+ return -EINVAL;
+ }
+ return 0;
+}
+
 /*
- * Crystal digital audio control (CS4299
+ * Crystal digital audio control (CS4299)
  */
  
-static int crystal_digital_control(struct ac97_codec *codec, int mode)
+static int crystal_digital_control(struct ac97_codec *codec, int slots, int rate, int mode)
 {
         u16 cv;
 
- switch(mode)
+ if(mode & AUDIO_DIGITAL)
+ return -EINVAL;
+
+ switch(rate)
         {
                 case 0: cv = 0x0; break; /* SPEN off */
- case 1: cv = 0x8004; break; /* 48KHz digital */
- case 2: cv = 0x8104; break; /* 44.1KHz digital */
+ case 48000: cv = 0x8004; break; /* 48KHz digital */
+ case 44100: cv = 0x8104; break; /* 44.1KHz digital */
+ case 32768: /* 32Khz */
                 default:
- return -1; /* Not supported yet(eg AC3) */
+ return -EINVAL;
         }
         codec->codec_write(codec, 0x68, cv);
         return 0;
 }
 
+/*
+ * CMedia digital audio control
+ * Needs more work.
+ */
+
+static int cmedia_digital_control(struct ac97_codec *codec, int slots, int rate, int mode)
+{
+ u16 cv;
+
+ if(mode & AUDIO_DIGITAL)
+ return -EINVAL;
+
+ switch(rate)
+ {
+ case 0: cv = 0x0001; break; /* SPEN off */
+ case 48000: cv = 0x0009; break; /* 48KHz digital */
+ default:
+ return -EINVAL;
+ }
+ codec->codec_write(codec, 0x2A, 0x05c4);
+ codec->codec_write(codec, 0x6C, cv);
+
+ /* Switch on mix to surround */
+ cv = codec->codec_read(codec, 0x64);
+ cv &= ~0x0200;
+ if(mode)
+ cv |= 0x0200;
+ codec->codec_write(codec, 0x64, cv);
+ return 0;
+}
+
+
 /* copied from drivers/sound/maestro.c */
 #if 0 /* there has been 1 person on the planet with a pt101 that we
         know of. If they care, they can put this back in :) */
@@ -1137,4 +1386,67 @@
 
 EXPORT_SYMBOL(ac97_restore_state);
 
+/**
+ * ac97_register_driver - register a codec helper
+ * @driver: Driver handler
+ *
+ * Register a handler for codecs matching the codec id. The handler
+ * attach function is called for all present codecs and will be
+ * called when new codecs are discovered.
+ */
+
+int ac97_register_driver(struct ac97_driver *driver)
+{
+ struct list_head *l;
+ struct ac97_codec *c;
+
+ down(&codec_sem);
+ INIT_LIST_HEAD(&driver->list);
+ list_add(&driver->list, &codec_drivers);
+
+ list_for_each(l, &codecs)
+ {
+ c = list_entry(l, struct ac97_codec, list);
+ if(c->driver != NULL || ((c->model ^ driver->codec_id) & driver->codec_mask))
+ continue;
+ if(driver->probe(c, driver))
+ continue;
+ c->driver = driver;
+ }
+ up(&codec_sem);
+ return 0;
+}
+
+EXPORT_SYMBOL_GPL(ac97_register_driver);
+
+/**
+ * ac97_unregister_driver - unregister a codec helper
+ * @driver: Driver handler
+ *
+ * Register a handler for codecs matching the codec id. The handler
+ * attach function is called for all present codecs and will be
+ * called when new codecs are discovered.
+ */
+
+void ac97_unregister_driver(struct ac97_driver *driver)
+{
+ struct list_head *l;
+ struct ac97_codec *c;
+
+ down(&codec_sem);
+ list_del_init(&driver->list);
+
+ list_for_each(l, &codecs)
+ {
+ c = list_entry(l, struct ac97_codec, list);
+ if(c->driver == driver)
+ driver->remove(c, driver);
+ c->driver = NULL;
+ }
+
+ up(&codec_sem);
+}
+
+EXPORT_SYMBOL_GPL(ac97_unregister_driver);
+
 MODULE_LICENSE("GPL");
-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/



This archive was generated by hypermail 2b29 : Tue Jul 15 2003 - 22:00:40 EST