patch-2.4.22 linux-2.4.22/drivers/sound/ac97_codec.c
Next file: linux-2.4.22/drivers/sound/ac97_plugin_ad1980.c
Previous file: linux-2.4.22/drivers/sound/Makefile
Back to the patch index
Back to the overall index
- Lines: 511
- Date:
2003-08-25 04:44:42.000000000 -0700
- Orig file:
linux-2.4.21/drivers/sound/ac97_codec.c
- Orig date:
2003-06-13 07:51:36.000000000 -0700
diff -urN linux-2.4.21/drivers/sound/ac97_codec.c linux-2.4.22/drivers/sound/ac97_codec.c
@@ -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
@@ -45,6 +48,7 @@
#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>
@@ -63,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);
@@ -73,9 +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 mode);
+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);
/*
@@ -96,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 };
@@ -126,8 +134,9 @@
{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 },
@@ -154,15 +163,18 @@
{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},
{0x83847608, "SigmaTel STAC9708", &sigmatel_9708_ops},
{0x83847609, "SigmaTel STAC9721/23", &sigmatel_9721_ops},
{0x83847644, "SigmaTel STAC9744/45", &sigmatel_9744_ops},
+ {0x83847652, "SigmaTel STAC9752/53", &default_ops},
{0x83847656, "SigmaTel STAC9756/57", &sigmatel_9744_ops},
{0x83847666, "SigmaTel STAC9750T", &sigmatel_9744_ops},
{0x83847684, "SigmaTel STAC9783/84?", &null_ops},
@@ -282,6 +294,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)
@@ -709,6 +725,55 @@
/**
+ * 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
*
@@ -737,7 +802,10 @@
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.
*
@@ -762,7 +830,7 @@
/* probe for Modem Codec */
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);
@@ -775,6 +843,12 @@
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)
@@ -786,7 +860,30 @@
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)
@@ -805,8 +902,6 @@
if (!(cap & 0x10))
codec->supported_mixers &= ~SOUND_MASK_ALTPCM;
- if(codec->flags & AC97_NO_PCM_VOLUME)
- codec->supported_mixers &= ~SOUND_MASK_PCM;
/* detect bit resolution */
codec->codec_write(codec, AC97_MASTER_VOL_STEREO, 0x2020);
@@ -836,8 +931,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;
}
@@ -933,53 +1035,52 @@
return 0;
}
-static int wolfson_init00(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);
- 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)
{
@@ -1040,21 +1141,78 @@
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;
@@ -1065,16 +1223,19 @@
* Needs more work.
*/
-static int cmedia_digital_control(struct ac97_codec *codec, int mode)
+static int cmedia_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 = 0x0001; break; /* SPEN off */
- case 1: cv = 0x0009; break; /* 48KHz digital */
+ case 0: cv = 0x0001; break; /* SPEN off */
+ case 48000: cv = 0x0009; break; /* 48KHz digital */
default:
- return -1; /* Not supported yet(eg AC3) */
+ return -EINVAL;
}
codec->codec_write(codec, 0x2A, 0x05c4);
codec->codec_write(codec, 0x6C, cv);
@@ -1226,4 +1387,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");
FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)