#include <linux/errno.h>
#include <linux/bitops.h>
#include <linux/delay.h>
+#include <linux/pci.h>
#include <linux/ac97_codec.h>
#include <asm/uaccess.h>
+#include <linux/mutex.h>
#define CODEC_ID_BUFSZ 14
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 wolfson_init13(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);
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 wolfson_ops13 = { wolfson_init13, 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 };
{0x41445348, "Analog Devices AD1881A", &null_ops},
{0x41445360, "Analog Devices AD1885", &default_ops},
{0x41445361, "Analog Devices AD1886", &ad1886_ops},
+ {0x41445370, "Analog Devices AD1981", &null_ops},
+ {0x41445372, "Analog Devices AD1981A", &null_ops},
+ {0x41445374, "Analog Devices AD1981B", &null_ops},
{0x41445460, "Analog Devices AD1885", &default_ops},
{0x41445461, "Analog Devices AD1886", &ad1886_ops},
{0x414B4D00, "Asahi Kasei AK4540", &null_ops},
{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},
+ {0x43585430, "CXT48", &default_ops, AC97_DELUDED_MODEM },
{0x43585442, "CXT66", &default_ops, AC97_DELUDED_MODEM },
{0x44543031, "Diamond Technology DT0893", &default_ops},
{0x45838308, "ESS Allegro ES1988", &null_ops},
{0x574D4C05, "Wolfson WM9705/WM9710", &wolfson_ops05},
{0x574D4C09, "Wolfson WM9709", &null_ops},
{0x574D4C12, "Wolfson WM9711/9712", &wolfson_ops11},
+ {0x574D4C13, "Wolfson WM9713", &wolfson_ops13, AC97_DEFAULT_POWER_OFF},
{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},
static LIST_HEAD(codecs);
static LIST_HEAD(codec_drivers);
-static DECLARE_MUTEX(codec_sem);
+static DEFINE_MUTEX(codec_mutex);
/* 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 */
strlcpy(info.id, codec->name, sizeof(info.id));
strlcpy(info.name, codec->name, sizeof(info.name));
info.modify_counter = codec->modcnt;
- if (copy_to_user((void *)arg, &info, sizeof(info)))
+ if (copy_to_user((void __user *)arg, &info, sizeof(info)))
return -EFAULT;
return 0;
}
memset(&info, 0, sizeof(info));
strlcpy(info.id, codec->name, sizeof(info.id));
strlcpy(info.name, codec->name, sizeof(info.name));
- if (copy_to_user((void *)arg, &info, sizeof(info)))
+ if (copy_to_user((void __user *)arg, &info, sizeof(info)))
return -EFAULT;
return 0;
}
return -EINVAL;
if (cmd == OSS_GETVERSION)
- return put_user(SOUND_VERSION, (int *)arg);
+ return put_user(SOUND_VERSION, (int __user *)arg);
if (_SIOC_DIR(cmd) == _SIOC_READ) {
switch (_IOC_NR(cmd)) {
val = codec->mixer_state[i];
break;
}
- return put_user(val, (int *)arg);
+ return put_user(val, (int __user *)arg);
}
if (_SIOC_DIR(cmd) == (_SIOC_WRITE|_SIOC_READ)) {
codec->modcnt++;
- if (get_user(val, (int *)arg))
+ if (get_user(val, (int __user *)arg))
return -EFAULT;
switch (_IOC_NR(cmd)) {
{
/* Remove from the list first, we don't want to be
"rediscovered" */
- down(&codec_sem);
+ mutex_lock(&codec_mutex);
list_del(&codec->list);
- up(&codec_sem);
+ mutex_unlock(&codec_mutex);
/*
* The driver needs to deal with internal
* locking to avoid accidents here.
* Currently codec_wait is used to wait for AC97 codec
* reset to complete.
*
+ * Some codecs will power down when a register reset is
+ * performed. We now check for such codecs.
+ *
* Returns 1 (true) on success, or 0 (false) on failure.
*/
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.
- *
- * FIXME: is the following comment outdated? -jgarzik
- * Probing of AC97 in this way is not reliable, it is not even SAFE !!
- */
- codec->codec_write(codec, AC97_RESET, 0L);
-
- /* also according to spec, we wait for codec-ready state */
+ /* wait for codec-ready state */
if (codec->codec_wait)
codec->codec_wait(codec);
else
udelay(10);
- if ((audio = codec->codec_read(codec, AC97_RESET)) & 0x8000) {
- printk(KERN_ERR "ac97_codec: %s ac97 codec not present\n",
- (codec->id & 0x2) ? (codec->id&1 ? "4th" : "Tertiary")
- : (codec->id&1 ? "Secondary": "Primary"));
- return 0;
- }
-
- /* probe for Modem Codec */
- codec->modem = ac97_check_modem(codec);
- codec->name = NULL;
- codec->codec_ops = &default_ops;
-
+ /* will the codec power down if register reset ? */
id1 = codec->codec_read(codec, AC97_VENDOR_ID1);
id2 = codec->codec_read(codec, AC97_VENDOR_ID2);
+ codec->name = NULL;
+ codec->codec_ops = &null_ops;
for (i = 0; i < ARRAY_SIZE(ac97_codec_ids); i++) {
if (ac97_codec_ids[i].id == ((id1 << 16) | id2)) {
codec->type = ac97_codec_ids[i].id;
}
codec->model = (id1 << 16) | id2;
+ if ((codec->flags & AC97_DEFAULT_POWER_OFF) == 0) {
+ /* reset codec and wait for the ready bit before we continue */
+ codec->codec_write(codec, AC97_RESET, 0L);
+ if (codec->codec_wait)
+ codec->codec_wait(codec);
+ else
+ udelay(10);
+ }
+
+ /* probing AC97 codec, AC97 2.0 says that bit 15 of register 0x00 (reset) should
+ * be read zero.
+ *
+ * FIXME: is the following comment outdated? -jgarzik
+ * Probing of AC97 in this way is not reliable, it is not even SAFE !!
+ */
+ if ((audio = codec->codec_read(codec, AC97_RESET)) & 0x8000) {
+ printk(KERN_ERR "ac97_codec: %s ac97 codec not present\n",
+ (codec->id & 0x2) ? (codec->id&1 ? "4th" : "Tertiary")
+ : (codec->id&1 ? "Secondary": "Primary"));
+ return 0;
+ }
+ /* probe for Modem Codec */
+ codec->modem = ac97_check_modem(codec);
+
+ /* enable SPDIF */
f = codec->codec_read(codec, AC97_EXTENDED_STATUS);
- if(f & 4)
+ if((codec->codec_ops == &null_ops) && (f & 4))
codec->codec_ops = &default_digital_ops;
/* A device which thinks its a modem but isnt */
* callbacks.
*/
- down(&codec_sem);
+ mutex_lock(&codec_mutex);
list_add(&codec->list, &codecs);
list_for_each(l, &codec_drivers) {
}
}
- up(&codec_sem);
+ mutex_unlock(&codec_mutex);
return 1;
}
codec->recmask_io = ac97_recmask_io;
codec->mixer_ioctl = ac97_mixer_ioctl;
- /* codec specific initialization for 4-6 channel output or secondary codec stuff */
- if (codec->codec_ops->init != NULL) {
- codec->codec_ops->init(codec);
- }
-
/* initialize mixer channel volumes */
for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) {
struct mixer_defaults *md = &mixer_defaults[i];
ac97_set_mixer(codec, md->mixer, md->value);
}
+ /* codec specific initialization for 4-6 channel output or secondary codec stuff */
+ if (codec->codec_ops->init != NULL) {
+ codec->codec_ops->init(codec);
+ }
+
/*
* Volume is MUTE only on this device. We have to initialise
* it but its useless beyond that.
return 0;
}
+/* WM9713 */
+static int wolfson_init13(struct ac97_codec * codec)
+{
+ codec->codec_write(codec, AC97_RECORD_GAIN, 0x00a0);
+ codec->codec_write(codec, AC97_POWER_CONTROL, 0x0000);
+ codec->codec_write(codec, AC97_EXTENDED_MODEM_ID, 0xDA00);
+ codec->codec_write(codec, AC97_EXTEND_MODEM_STAT, 0x3810);
+ codec->codec_write(codec, AC97_PHONE_VOL, 0x0808);
+ codec->codec_write(codec, AC97_PCBEEP_VOL, 0x0808);
+
+ return 0;
+}
+
static int tritech_init(struct ac97_codec * codec)
{
codec->codec_write(codec, 0x26, 0x0300);
EXPORT_SYMBOL(ac97_set_adc_rate);
-int ac97_save_state(struct ac97_codec *codec)
-{
- return 0;
-}
-
-EXPORT_SYMBOL(ac97_save_state);
-
-int ac97_restore_state(struct ac97_codec *codec)
+static int swap_headphone(int remove_master)
{
- int i;
- unsigned int left, right, val;
-
- for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) {
- if (!supported_mixer(codec, i))
- continue;
+ struct list_head *l;
+ struct ac97_codec *c;
+
+ if (remove_master) {
+ mutex_lock(&codec_mutex);
+ list_for_each(l, &codecs)
+ {
+ c = list_entry(l, struct ac97_codec, list);
+ if (supported_mixer(c, SOUND_MIXER_PHONEOUT))
+ c->supported_mixers &= ~SOUND_MASK_PHONEOUT;
+ }
+ mutex_unlock(&codec_mutex);
+ } else
+ ac97_hw[SOUND_MIXER_PHONEOUT].offset = AC97_MASTER_VOL_STEREO;
- val = codec->mixer_state[i];
- right = val >> 8;
- left = val & 0xff;
- codec->write_mixer(codec, i, left, right);
- }
+ /* Scale values already match */
+ ac97_hw[SOUND_MIXER_VOLUME].offset = AC97_MASTER_VOL_MONO;
return 0;
}
-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)
+static int apply_quirk(int quirk)
{
- 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;
+ switch (quirk) {
+ case AC97_TUNE_NONE:
+ return 0;
+ case AC97_TUNE_HP_ONLY:
+ return swap_headphone(1);
+ case AC97_TUNE_SWAP_HP:
+ return swap_headphone(0);
+ case AC97_TUNE_SWAP_SURROUND:
+ return -ENOSYS; /* not yet implemented */
+ case AC97_TUNE_AD_SHARING:
+ return -ENOSYS; /* not yet implemented */
+ case AC97_TUNE_ALC_JACK:
+ return -ENOSYS; /* not yet implemented */
}
- up(&codec_sem);
- return 0;
+ return -EINVAL;
}
-EXPORT_SYMBOL_GPL(ac97_register_driver);
-
/**
- * ac97_unregister_driver - unregister a codec helper
- * @driver: Driver handler
+ * ac97_tune_hardware - tune up the hardware
+ * @pdev: pci_dev pointer
+ * @quirk: quirk list
+ * @override: explicit quirk value (overrides if not AC97_TUNE_DEFAULT)
+ *
+ * Do some workaround for each pci device, such as renaming of the
+ * headphone (true line-out) control as "Master".
+ * The quirk-list must be terminated with a zero-filled entry.
*
- * Unregister a handler for codecs matching the codec id. The handler
- * remove function is called for all matching codecs.
+ * Returns zero if successful, or a negative error code on failure.
*/
-
-void ac97_unregister_driver(struct ac97_driver *driver)
+
+int ac97_tune_hardware(struct pci_dev *pdev, struct ac97_quirk *quirk, int override)
{
- struct list_head *l;
- struct ac97_codec *c;
-
- down(&codec_sem);
- list_del_init(&driver->list);
+ int result;
- list_for_each(l, &codecs)
- {
- c = list_entry(l, struct ac97_codec, list);
- if (c->driver == driver) {
- driver->remove(c, driver);
- c->driver = NULL;
+ if (!quirk)
+ return -EINVAL;
+
+ if (override != AC97_TUNE_DEFAULT) {
+ result = apply_quirk(override);
+ if (result < 0)
+ printk(KERN_ERR "applying quirk type %d failed (%d)\n", override, result);
+ return result;
+ }
+
+ for (; quirk->vendor; quirk++) {
+ if (quirk->vendor != pdev->subsystem_vendor)
+ continue;
+ if ((! quirk->mask && quirk->device == pdev->subsystem_device) ||
+ quirk->device == (quirk->mask & pdev->subsystem_device)) {
+#ifdef DEBUG
+ printk("ac97 quirk for %s (%04x:%04x)\n", quirk->name, ac97->subsystem_vendor, pdev->subsystem_device);
+#endif
+ result = apply_quirk(quirk->type);
+ if (result < 0)
+ printk(KERN_ERR "applying quirk type %d for %s failed (%d)\n", quirk->type, quirk->name, result);
+ return result;
}
}
-
- up(&codec_sem);
+ return 0;
}
-EXPORT_SYMBOL_GPL(ac97_unregister_driver);
-
+EXPORT_SYMBOL_GPL(ac97_tune_hardware);
+
MODULE_LICENSE("GPL");