diff options
author | Clemens Ladisch <clemens@ladisch.de> | 2007-12-23 19:50:57 +0100 |
---|---|---|
committer | Jaroslav Kysela <perex@perex.cz> | 2008-01-31 17:29:44 +0100 |
commit | d0ce9946c52e7bdf95afb09553775cf28b752254 (patch) | |
tree | 684edc99cfd1def12b87abb5431c6b8f0ea2f716 /sound/pci/oxygen | |
parent | a9b3aa8a0a203b9b62e15c465ba7d4797a6a2c79 (diff) |
[ALSA] add CMI8788 driver
Add the snd-oxygen driver for the C-Media CMI8788 (Oxygen) chip, used on
the Asound A-8788, AuzenTech X-Meridian, Bgears b-Enspirer,
Club3D Theatron DTS, HT-Omega Claro, Razer Barracuda AC-1,
Sondigo Inferno, and TempoTec HIFIER sound cards.
Signed-off-by: Clemens Ladisch <clemens@ladisch.de>
Signed-off-by: Jaroslav Kysela <perex@perex.cz>
Diffstat (limited to 'sound/pci/oxygen')
-rw-r--r-- | sound/pci/oxygen/Makefile | 5 | ||||
-rw-r--r-- | sound/pci/oxygen/oxygen.c | 314 | ||||
-rw-r--r-- | sound/pci/oxygen/oxygen.h | 167 | ||||
-rw-r--r-- | sound/pci/oxygen/oxygen_io.c | 194 | ||||
-rw-r--r-- | sound/pci/oxygen/oxygen_lib.c | 361 | ||||
-rw-r--r-- | sound/pci/oxygen/oxygen_mixer.c | 623 | ||||
-rw-r--r-- | sound/pci/oxygen/oxygen_pcm.c | 726 | ||||
-rw-r--r-- | sound/pci/oxygen/oxygen_regs.h | 246 |
8 files changed, 2636 insertions, 0 deletions
diff --git a/sound/pci/oxygen/Makefile b/sound/pci/oxygen/Makefile new file mode 100644 index 00000000000..99455dd1576 --- /dev/null +++ b/sound/pci/oxygen/Makefile @@ -0,0 +1,5 @@ +snd-oxygen-lib-objs := oxygen_io.o oxygen_lib.o oxygen_mixer.o oxygen_pcm.o +snd-oxygen-objs := oxygen.o + +obj-$(CONFIG_SND_OXYGEN_LIB) += snd-oxygen-lib.o +obj-$(CONFIG_SND_OXYGEN) += snd-oxygen.o diff --git a/sound/pci/oxygen/oxygen.c b/sound/pci/oxygen/oxygen.c new file mode 100644 index 00000000000..bfef5aba0b9 --- /dev/null +++ b/sound/pci/oxygen/oxygen.c @@ -0,0 +1,314 @@ +/* + * C-Media CMI8788 driver for C-Media's reference design and for the X-Meridian + * + * Copyright (c) Clemens Ladisch <clemens@ladisch.de> + * + * + * This driver is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2. + * + * This driver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this driver; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * SPI 0 -> 1st AK4396 (front) + * SPI 1 -> 2nd AK4396 (side) + * SPI 2 -> 3rd AK4396 (center/LFE) + * SPI 3 -> WM8785 + * SPI 4 -> 4th AK4396 (rear) + * + * GPIO 0 -> DFS0 of AK5385 + * GPIO 1 -> DFS1 of AK5385 + */ + +#include <sound/driver.h> +#include <linux/pci.h> +#include <sound/core.h> +#include <sound/initval.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/tlv.h> +#include "oxygen.h" + +MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>"); +MODULE_DESCRIPTION("C-Media CMI8788 driver"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("{{C-Media,CMI8788}}"); + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; +static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "card index"); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string"); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "enable card"); + +static struct pci_device_id oxygen_ids[] __devinitdata = { + { OXYGEN_PCI_SUBID(0x10b0, 0x0216) }, + { OXYGEN_PCI_SUBID(0x10b0, 0x0218) }, + { OXYGEN_PCI_SUBID(0x10b0, 0x0219) }, + { OXYGEN_PCI_SUBID(0x13f6, 0x0001) }, + { OXYGEN_PCI_SUBID(0x13f6, 0x0010) }, + { OXYGEN_PCI_SUBID(0x13f6, 0x8788) }, + { OXYGEN_PCI_SUBID(0x147a, 0xa017) }, + { OXYGEN_PCI_SUBID(0x14c3, 0x1710) }, + { OXYGEN_PCI_SUBID(0x14c3, 0x1711) }, + { OXYGEN_PCI_SUBID(0x1a58, 0x0910) }, + { OXYGEN_PCI_SUBID(0x415a, 0x5431), .driver_data = 1 }, + { OXYGEN_PCI_SUBID(0x7284, 0x9761) }, + { } +}; +MODULE_DEVICE_TABLE(pci, oxygen_ids); + +#define AK4396_WRITE 0x2000 + +/* register 0 */ +#define AK4396_RSTN 0x01 +#define AK4396_DIF_24_MSB 0x04 +/* register 1 */ +#define AK4396_SMUTE 0x01 +#define AK4396_DEM_OFF 0x02 +#define AK4396_DFS_MASK 0x18 +#define AK4396_DFS_NORMAL 0x00 +#define AK4396_DFS_DOUBLE 0x08 +#define AK4396_DFS_QUAD 0x10 + +/* register 0 */ +#define WM8785_OSR_SINGLE 0x000 +#define WM8785_OSR_DOUBLE 0x008 +#define WM8785_OSR_QUAD 0x010 +#define WM8785_FORMAT_LJUST 0x020 +#define WM8785_FORMAT_I2S 0x040 +/* register 1 */ +#define WM8785_WL_16 0x000 +#define WM8785_WL_20 0x001 +#define WM8785_WL_24 0x002 +#define WM8785_WL_32 0x003 + +static void ak4396_write(struct oxygen *chip, unsigned int codec, + u8 reg, u8 value) +{ + /* maps ALSA channel pair number to SPI output */ + static const u8 codec_spi_map[4] = { + 0, 4, 2, 1 + }; + oxygen_write_spi(chip, OXYGEN_SPI_TRIGGER_WRITE | + OXYGEN_SPI_DATA_LENGTH_2 | + (codec_spi_map[codec] << OXYGEN_SPI_CODEC_SHIFT) | + OXYGEN_SPI_MAGIC, + AK4396_WRITE | (reg << 8) | value); +} + +static void wm8785_write(struct oxygen *chip, u8 reg, unsigned int value) +{ + oxygen_write_spi(chip, OXYGEN_SPI_TRIGGER_WRITE | + OXYGEN_SPI_DATA_LENGTH_2 | + (3 << OXYGEN_SPI_CODEC_SHIFT), + (reg << 9) | value); +} + +static void ak4396_init(struct oxygen *chip) +{ + unsigned int i; + + chip->ak4396_reg1 = AK4396_DEM_OFF | AK4396_DFS_NORMAL; + for (i = 0; i < 4; ++i) { + ak4396_write(chip, i, 0, AK4396_DIF_24_MSB | AK4396_RSTN); + ak4396_write(chip, i, 1, chip->ak4396_reg1); + ak4396_write(chip, i, 2, 0); + ak4396_write(chip, i, 3, 0xff); + ak4396_write(chip, i, 4, 0xff); + } + snd_component_add(chip->card, "AK4396"); +} + +static void ak5385_init(struct oxygen *chip) +{ + oxygen_set_bits16(chip, OXYGEN_GPIO_CONTROL, 0x0003); + oxygen_clear_bits16(chip, OXYGEN_GPIO_DATA, 0x0003); + snd_component_add(chip->card, "AK5385"); +} + +static void wm8785_init(struct oxygen *chip) +{ + wm8785_write(chip, 7, 0); + wm8785_write(chip, 0, WM8785_FORMAT_LJUST | WM8785_OSR_SINGLE); + wm8785_write(chip, 1, WM8785_WL_24); + snd_component_add(chip->card, "WM8785"); +} + +static void generic_init(struct oxygen *chip) +{ + ak4396_init(chip); + wm8785_init(chip); +} + +static void meridian_init(struct oxygen *chip) +{ + ak4396_init(chip); + ak5385_init(chip); +} + +static void generic_cleanup(struct oxygen *chip) +{ +} + +static void set_ak4396_params(struct oxygen *chip, + struct snd_pcm_hw_params *params) +{ + unsigned int i; + u8 value; + + value = chip->ak4396_reg1 & ~AK4396_DFS_MASK; + if (params_rate(params) <= 54000) + value |= AK4396_DFS_NORMAL; + else if (params_rate(params) < 120000) + value |= AK4396_DFS_DOUBLE; + else + value |= AK4396_DFS_QUAD; + chip->ak4396_reg1 = value; + for (i = 0; i < 4; ++i) { + ak4396_write(chip, i, 0, AK4396_DIF_24_MSB); + ak4396_write(chip, i, 1, value); + ak4396_write(chip, i, 0, AK4396_DIF_24_MSB | AK4396_RSTN); + } +} + +static void update_ak4396_volume(struct oxygen *chip) +{ + unsigned int i; + + for (i = 0; i < 4; ++i) { + ak4396_write(chip, i, 3, chip->dac_volume[i * 2]); + ak4396_write(chip, i, 4, chip->dac_volume[i * 2 + 1]); + } +} + +static void update_ak4396_mute(struct oxygen *chip) +{ + unsigned int i; + u8 value; + + value = chip->ak4396_reg1 & ~AK4396_SMUTE; + if (chip->dac_mute) + value |= AK4396_SMUTE; + for (i = 0; i < 4; ++i) + ak4396_write(chip, i, 1, value); +} + +static void set_wm8785_params(struct oxygen *chip, + struct snd_pcm_hw_params *params) +{ + unsigned int value; + + wm8785_write(chip, 7, 0); + + value = WM8785_FORMAT_LJUST; + if (params_rate(params) == 96000) + value |= WM8785_OSR_DOUBLE; + else if (params_rate(params) == 192000) + value |= WM8785_OSR_QUAD; + else + value |= WM8785_OSR_SINGLE; + wm8785_write(chip, 0, value); + + if (snd_pcm_format_width(params_format(params)) <= 16) + value = WM8785_WL_16; + else + value = WM8785_WL_24; + wm8785_write(chip, 1, value); +} + +static void set_ak5385_params(struct oxygen *chip, + struct snd_pcm_hw_params *params) +{ + unsigned int value; + + if (params_rate(params) <= 54000) + value = 0; + else if (params_rate(params) <= 108000) + value = 1; + else + value = 2; + oxygen_write16_masked(chip, OXYGEN_GPIO_DATA, value, 0x0003); +} + +static const DECLARE_TLV_DB_LINEAR(ak4396_db_scale, TLV_DB_GAIN_MUTE, 0); + +static const struct oxygen_model model_generic = { + .shortname = "C-Media CMI8788", + .longname = "C-Media Oxygen HD Audio", + .chip = "CMI8788", + .owner = THIS_MODULE, + .init = generic_init, + .cleanup = generic_cleanup, + .set_dac_params = set_ak4396_params, + .set_adc_params = set_wm8785_params, + .update_dac_volume = update_ak4396_volume, + .update_dac_mute = update_ak4396_mute, + .dac_tlv = ak4396_db_scale, +}; +static const struct oxygen_model model_meridian = { + .shortname = "C-Media CMI8788", + .longname = "C-Media Oxygen HD Audio", + .chip = "CMI8788", + .owner = THIS_MODULE, + .init = meridian_init, + .cleanup = generic_cleanup, + .set_dac_params = set_ak4396_params, + .set_adc_params = set_ak5385_params, + .update_dac_volume = update_ak4396_volume, + .update_dac_mute = update_ak4396_mute, + .dac_tlv = ak4396_db_scale, + .record_from_dma_b = 1, +}; + +static int __devinit generic_oxygen_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + static int dev; + const struct oxygen_model *model; + int err; + + if (dev >= SNDRV_CARDS) + return -ENODEV; + if (!enable[dev]) { + ++dev; + return -ENOENT; + } + model = pci_id->driver_data ? &model_meridian : &model_generic; + err = oxygen_pci_probe(pci, index[dev], id[dev], model); + if (err >= 0) + ++dev; + return err; +} + +static struct pci_driver oxygen_driver = { + .name = "CMI8788", + .id_table = oxygen_ids, + .probe = generic_oxygen_probe, + .remove = __devexit_p(oxygen_pci_remove), +}; + +static int __init alsa_card_oxygen_init(void) +{ + return pci_register_driver(&oxygen_driver); +} + +static void __exit alsa_card_oxygen_exit(void) +{ + pci_unregister_driver(&oxygen_driver); +} + +module_init(alsa_card_oxygen_init) +module_exit(alsa_card_oxygen_exit) diff --git a/sound/pci/oxygen/oxygen.h b/sound/pci/oxygen/oxygen.h new file mode 100644 index 00000000000..248f7ed22fd --- /dev/null +++ b/sound/pci/oxygen/oxygen.h @@ -0,0 +1,167 @@ +#ifndef OXYGEN_H_INCLUDED +#define OXYGEN_H_INCLUDED + +#include <linux/mutex.h> +#include <linux/spinlock.h> +#include <linux/workqueue.h> +#include "oxygen_regs.h" + +/* 1 << PCM_x == OXYGEN_CHANNEL_x */ +#define PCM_A 0 +#define PCM_B 1 +#define PCM_C 2 +#define PCM_SPDIF 3 +#define PCM_MULTICH 4 +#define PCM_AC97 5 +#define PCM_COUNT 6 + +#define OXYGEN_PCI_SUBID(sv, sd) \ + .vendor = PCI_VENDOR_ID_CMEDIA, \ + .device = 0x8788, \ + .subvendor = sv, \ + .subdevice = sd + +struct pci_dev; +struct snd_card; +struct snd_pcm_substream; +struct snd_pcm_hw_params; +struct snd_rawmidi; +struct oxygen_model; + +struct oxygen { + unsigned long addr; + spinlock_t reg_lock; + struct mutex mutex; + struct snd_card *card; + struct pci_dev *pci; + struct snd_rawmidi *midi; + int irq; + const struct oxygen_model *model; + unsigned int interrupt_mask; + u8 dac_volume[8]; + u8 dac_mute; + u8 pcm_active; + u8 pcm_running; + u8 dac_routing; + u8 spdif_playback_enable; + u8 ak4396_reg1; + u8 revision; + u8 has_2nd_ac97_codec; + u32 spdif_bits; + u32 spdif_pcm_bits; + struct snd_pcm_substream *streams[PCM_COUNT]; + struct snd_kcontrol *spdif_pcm_ctl; + struct snd_kcontrol *spdif_input_bits_ctl; + struct work_struct spdif_input_bits_work; +}; + +struct oxygen_model { + const char *shortname; + const char *longname; + const char *chip; + struct module *owner; + void (*init)(struct oxygen *chip); + int (*mixer_init)(struct oxygen *chip); + void (*cleanup)(struct oxygen *chip); + void (*set_dac_params)(struct oxygen *chip, + struct snd_pcm_hw_params *params); + void (*set_adc_params)(struct oxygen *chip, + struct snd_pcm_hw_params *params); + void (*update_dac_volume)(struct oxygen *chip); + void (*update_dac_mute)(struct oxygen *chip); + const unsigned int *dac_tlv; + u8 record_from_dma_b; + u8 cd_in_from_video_in; + u8 dac_minimum_volume; +}; + +/* oxygen_lib.c */ + +int oxygen_pci_probe(struct pci_dev *pci, int index, char *id, + const struct oxygen_model *model); +void oxygen_pci_remove(struct pci_dev *pci); + +/* oxygen_mixer.c */ + +int oxygen_mixer_init(struct oxygen *chip); +void oxygen_update_dac_routing(struct oxygen *chip); +void oxygen_update_spdif_source(struct oxygen *chip); + +/* oxygen_pcm.c */ + +int oxygen_pcm_init(struct oxygen *chip); + +/* oxygen_io.c */ + +u8 oxygen_read8(struct oxygen *chip, unsigned int reg); +u16 oxygen_read16(struct oxygen *chip, unsigned int reg); +u32 oxygen_read32(struct oxygen *chip, unsigned int reg); +void oxygen_write8(struct oxygen *chip, unsigned int reg, u8 value); +void oxygen_write16(struct oxygen *chip, unsigned int reg, u16 value); +void oxygen_write32(struct oxygen *chip, unsigned int reg, u32 value); +void oxygen_write8_masked(struct oxygen *chip, unsigned int reg, + u8 value, u8 mask); +void oxygen_write16_masked(struct oxygen *chip, unsigned int reg, + u16 value, u16 mask); +void oxygen_write32_masked(struct oxygen *chip, unsigned int reg, + u32 value, u32 mask); + +u16 oxygen_read_ac97(struct oxygen *chip, unsigned int codec, + unsigned int index); +void oxygen_write_ac97(struct oxygen *chip, unsigned int codec, + unsigned int index, u16 data); +void oxygen_write_ac97_masked(struct oxygen *chip, unsigned int codec, + unsigned int index, u16 data, u16 mask); + +void oxygen_write_spi(struct oxygen *chip, u8 control, unsigned int data); + +static inline void oxygen_set_bits8(struct oxygen *chip, + unsigned int reg, u8 value) +{ + oxygen_write8_masked(chip, reg, value, value); +} + +static inline void oxygen_set_bits16(struct oxygen *chip, + unsigned int reg, u16 value) +{ + oxygen_write16_masked(chip, reg, value, value); +} + +static inline void oxygen_set_bits32(struct oxygen *chip, + unsigned int reg, u32 value) +{ + oxygen_write32_masked(chip, reg, value, value); +} + +static inline void oxygen_clear_bits8(struct oxygen *chip, + unsigned int reg, u8 value) +{ + oxygen_write8_masked(chip, reg, 0, value); +} + +static inline void oxygen_clear_bits16(struct oxygen *chip, + unsigned int reg, u16 value) +{ + oxygen_write16_masked(chip, reg, 0, value); +} + +static inline void oxygen_clear_bits32(struct oxygen *chip, + unsigned int reg, u32 value) +{ + oxygen_write32_masked(chip, reg, 0, value); +} + +static inline void oxygen_ac97_set_bits(struct oxygen *chip, unsigned int codec, + unsigned int index, u16 value) +{ + oxygen_write_ac97_masked(chip, codec, index, value, value); +} + +static inline void oxygen_ac97_clear_bits(struct oxygen *chip, + unsigned int codec, + unsigned int index, u16 value) +{ + oxygen_write_ac97_masked(chip, codec, index, 0, value); +} + +#endif diff --git a/sound/pci/oxygen/oxygen_io.c b/sound/pci/oxygen/oxygen_io.c new file mode 100644 index 00000000000..5f4feeaf8b3 --- /dev/null +++ b/sound/pci/oxygen/oxygen_io.c @@ -0,0 +1,194 @@ +/* + * C-Media CMI8788 driver - helper functions + * + * Copyright (c) Clemens Ladisch <clemens@ladisch.de> + * + * + * This driver is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2. + * + * This driver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this driver; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <sound/driver.h> +#include <linux/delay.h> +#include <linux/sched.h> +#include <sound/core.h> +#include <asm/io.h> +#include "oxygen.h" + +u8 oxygen_read8(struct oxygen *chip, unsigned int reg) +{ + return inb(chip->addr + reg); +} +EXPORT_SYMBOL(oxygen_read8); + +u16 oxygen_read16(struct oxygen *chip, unsigned int reg) +{ + return inw(chip->addr + reg); +} +EXPORT_SYMBOL(oxygen_read16); + +u32 oxygen_read32(struct oxygen *chip, unsigned int reg) +{ + return inl(chip->addr + reg); +} +EXPORT_SYMBOL(oxygen_read32); + +void oxygen_write8(struct oxygen *chip, unsigned int reg, u8 value) +{ + outb(value, chip->addr + reg); +} +EXPORT_SYMBOL(oxygen_write8); + +void oxygen_write16(struct oxygen *chip, unsigned int reg, u16 value) +{ + outw(value, chip->addr + reg); +} +EXPORT_SYMBOL(oxygen_write16); + +void oxygen_write32(struct oxygen *chip, unsigned int reg, u32 value) +{ + outl(value, chip->addr + reg); +} +EXPORT_SYMBOL(oxygen_write32); + +void oxygen_write8_masked(struct oxygen *chip, unsigned int reg, + u8 value, u8 mask) +{ + u8 tmp = inb(chip->addr + reg); + outb((tmp & ~mask) | (value & mask), chip->addr + reg); +} +EXPORT_SYMBOL(oxygen_write8_masked); + +void oxygen_write16_masked(struct oxygen *chip, unsigned int reg, + u16 value, u16 mask) +{ + u16 tmp = inw(chip->addr + reg); + outw((tmp & ~mask) | (value & mask), chip->addr + reg); +} +EXPORT_SYMBOL(oxygen_write16_masked); + +void oxygen_write32_masked(struct oxygen *chip, unsigned int reg, + u32 value, u32 mask) +{ + u32 tmp = inl(chip->addr + reg); + outl((tmp & ~mask) | (value & mask), chip->addr + reg); +} +EXPORT_SYMBOL(oxygen_write32_masked); + +static int oxygen_ac97_wait(struct oxygen *chip, unsigned int mask) +{ + unsigned long timeout = jiffies + msecs_to_jiffies(1); + do { + udelay(5); + cond_resched(); + if (oxygen_read8(chip, OXYGEN_AC97_INTERRUPT_STATUS) & mask) + return 0; + } while (time_after_eq(timeout, jiffies)); + return -EIO; +} + +/* + * About 10% of AC'97 register reads or writes fail to complete, but even those + * where the controller indicates completion aren't guaranteed to have actually + * happened. + * + * It's hard to assign blame to either the controller or the codec because both + * were made by C-Media ... + */ + +void oxygen_write_ac97(struct oxygen *chip, unsigned int codec, + unsigned int index, u16 data) +{ + unsigned int count, succeeded; + u32 reg; + + reg = data; + reg |= index << OXYGEN_AC97_REG_ADDR_SHIFT; + reg |= OXYGEN_AC97_REG_DIR_WRITE; + reg |= codec << OXYGEN_AC97_REG_CODEC_SHIFT; + succeeded = 0; + for (count = 5; count > 0; --count) { + udelay(5); + oxygen_write32(chip, OXYGEN_AC97_REGS, reg); + /* require two "completed" writes, just to be sure */ + if (oxygen_ac97_wait(chip, OXYGEN_AC97_WRITE_COMPLETE) >= 0 && + ++succeeded >= 2) + return; + } + snd_printk(KERN_ERR "AC'97 write timeout\n"); +} +EXPORT_SYMBOL(oxygen_write_ac97); + +u16 oxygen_read_ac97(struct oxygen *chip, unsigned int codec, + unsigned int index) +{ + unsigned int count; + unsigned int last_read = UINT_MAX; + u32 reg; + + reg = index << OXYGEN_AC97_REG_ADDR_SHIFT; + reg |= OXYGEN_AC97_REG_DIR_READ; + reg |= codec << OXYGEN_AC97_REG_CODEC_SHIFT; + for (count = 5; count > 0; --count) { + udelay(5); + oxygen_write32(chip, OXYGEN_AC97_REGS, reg); + udelay(10); + if (oxygen_ac97_wait(chip, OXYGEN_AC97_READ_COMPLETE) >= 0) { + u16 value = oxygen_read16(chip, OXYGEN_AC97_REGS); + /* we require two consecutive reads of the same value */ + if (value == last_read) + return value; + last_read = value; + /* + * Invert the register value bits to make sure that two + * consecutive unsuccessful reads do not return the same + * value. + */ + reg ^= 0xffff; + } + } + snd_printk(KERN_ERR "AC'97 read timeout on codec %u\n", codec); + return 0; +} +EXPORT_SYMBOL(oxygen_read_ac97); + +void oxygen_write_ac97_masked(struct oxygen *chip, unsigned int codec, + unsigned int index, u16 data, u16 mask) +{ + u16 value = oxygen_read_ac97(chip, codec, index); + value &= ~mask; + value |= data & mask; + oxygen_write_ac97(chip, codec, index, value); +} +EXPORT_SYMBOL(oxygen_write_ac97_masked); + +void oxygen_write_spi(struct oxygen *chip, u8 control, unsigned int data) +{ + unsigned int count; + + /* should not need more than 3.84 us (24 * 160 ns) */ + count = 10; + while ((oxygen_read8(chip, OXYGEN_SPI_CONTROL) & OXYGEN_SPI_BUSY) + && count > 0) { + udelay(1); + --count; + } + + spin_lock_irq(&chip->reg_lock); + oxygen_write8(chip, OXYGEN_SPI_DATA1, data); + oxygen_write8(chip, OXYGEN_SPI_DATA2, data >> 8); + if (control & OXYGEN_SPI_DATA_LENGTH_3) + oxygen_write8(chip, OXYGEN_SPI_DATA3, data >> 16); + oxygen_write8(chip, OXYGEN_SPI_CONTROL, control); + spin_unlock_irq(&chip->reg_lock); +} +EXPORT_SYMBOL(oxygen_write_spi); diff --git a/sound/pci/oxygen/oxygen_lib.c b/sound/pci/oxygen/oxygen_lib.c new file mode 100644 index 00000000000..6180cc858e6 --- /dev/null +++ b/sound/pci/oxygen/oxygen_lib.c @@ -0,0 +1,361 @@ +/* + * C-Media CMI8788 driver - main driver module + * + * Copyright (c) Clemens Ladisch <clemens@ladisch.de> + * + * + * This driver is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2. + * + * This driver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this driver; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <sound/driver.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/mutex.h> +#include <linux/pci.h> +#include <sound/ac97_codec.h> +#include <sound/asoundef.h> +#include <sound/core.h> +#include <sound/info.h> +#include <sound/mpu401.h> +#include <sound/pcm.h> +#include "oxygen.h" + +MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>"); +MODULE_DESCRIPTION("C-Media CMI8788 helper library"); +MODULE_LICENSE("GPL"); + + +static irqreturn_t oxygen_interrupt(int dummy, void *dev_id) +{ + struct oxygen *chip = dev_id; + unsigned int status, clear, elapsed_streams, i; + + status = oxygen_read16(chip, OXYGEN_INTERRUPT_STATUS); + if (!status) + return IRQ_NONE; + + spin_lock(&chip->reg_lock); + + clear = status & (OXYGEN_CHANNEL_A | + OXYGEN_CHANNEL_B | + OXYGEN_CHANNEL_C | + OXYGEN_CHANNEL_SPDIF | + OXYGEN_CHANNEL_MULTICH | + OXYGEN_CHANNEL_AC97 | + OXYGEN_INT_SPDIF_IN_CHANGE | + OXYGEN_INT_GPIO); + if (clear) { + if (clear & OXYGEN_INT_SPDIF_IN_CHANGE) + chip->interrupt_mask &= ~OXYGEN_INT_SPDIF_IN_CHANGE; + oxygen_write16(chip, OXYGEN_INTERRUPT_MASK, + chip->interrupt_mask & ~clear); + oxygen_write16(chip, OXYGEN_INTERRUPT_MASK, + chip->interrupt_mask); + } + + elapsed_streams = status & chip->pcm_running; + + spin_unlock(&chip->reg_lock); + + for (i = 0; i < PCM_COUNT; ++i) + if ((elapsed_streams & (1 << i)) && chip->streams[i]) + snd_pcm_period_elapsed(chip->streams[i]); + + if (status & OXYGEN_INT_SPDIF_IN_CHANGE) { + spin_lock(&chip->reg_lock); + i = oxygen_read32(chip, OXYGEN_SPDIF_CONTROL); + if (i & OXYGEN_SPDIF_IN_CHANGE) { + oxygen_write32(chip, OXYGEN_SPDIF_CONTROL, i); + schedule_work(&chip->spdif_input_bits_work); + } + spin_unlock(&chip->reg_lock); + } + + if (status & OXYGEN_INT_GPIO) + ; + + if ((status & OXYGEN_INT_MIDI) && chip->midi) + snd_mpu401_uart_interrupt(0, chip->midi->private_data); + + return IRQ_HANDLED; +} + +static void oxygen_spdif_input_bits_changed(struct work_struct *work) +{ + struct oxygen *chip = container_of(work, struct oxygen, + spdif_input_bits_work); + + spin_lock_irq(&chip->reg_lock); + oxygen_clear_bits32(chip, OXYGEN_SPDIF_CONTROL, OXYGEN_SPDIF_IN_INVERT); + spin_unlock_irq(&chip->reg_lock); + msleep(1); + if (!(oxygen_read32(chip, OXYGEN_SPDIF_CONTROL) + & OXYGEN_SPDIF_IN_VALID)) { + spin_lock_irq(&chip->reg_lock); + oxygen_set_bits32(chip, OXYGEN_SPDIF_CONTROL, + OXYGEN_SPDIF_IN_INVERT); + spin_unlock_irq(&chip->reg_lock); + msleep(1); + if (!(oxygen_read32(chip, OXYGEN_SPDIF_CONTROL) + & OXYGEN_SPDIF_IN_VALID)) { + spin_lock_irq(&chip->reg_lock); + oxygen_clear_bits32(chip, OXYGEN_SPDIF_CONTROL, + OXYGEN_SPDIF_IN_INVERT); + spin_unlock_irq(&chip->reg_lock); + } + } + + if (chip->spdif_input_bits_ctl) { + spin_lock_irq(&chip->reg_lock); + chip->interrupt_mask |= OXYGEN_INT_SPDIF_IN_CHANGE; + oxygen_write16(chip, OXYGEN_INTERRUPT_MASK, + chip->interrupt_mask); + spin_unlock_irq(&chip->reg_lock); + + snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE, + &chip->spdif_input_bits_ctl->id); + } +} + +#ifdef CONFIG_PROC_FS +static void oxygen_proc_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct oxygen *chip = entry->private_data; + int i, j; + + snd_iprintf(buffer, "CMI8788\n\n"); + for (i = 0; i < 0x100; i += 0x10) { + snd_iprintf(buffer, "%02x:", i); + for (j = 0; j < 0x10; ++j) + snd_iprintf(buffer, " %02x", oxygen_read8(chip, i + j)); + snd_iprintf(buffer, "\n"); + } + if (mutex_lock_interruptible(&chip->mutex) < 0) + return; + snd_iprintf(buffer, "\nAC97\n"); + for (i = 0; i < 0x80; i += 0x10) { + snd_iprintf(buffer, "%02x:", i); + for (j = 0; j < 0x10; j += 2) + snd_iprintf(buffer, " %04x", + oxygen_read_ac97(chip, 0, i + j)); + snd_iprintf(buffer, "\n"); + } + mutex_unlock(&chip->mutex); +} + +static void __devinit oxygen_proc_init(struct oxygen *chip) +{ + struct snd_info_entry *entry; + + if (!snd_card_proc_new(chip->card, "cmi8788", &entry)) + snd_info_set_text_ops(entry, chip, oxygen_proc_read); +} +#else +#define oxygen_proc_init(chip) +#endif + +static void __devinit oxygen_init(struct oxygen *chip) +{ + unsigned int i; + + chip->dac_routing = 1; + for (i = 0; i < 8; ++i) + chip->dac_volume[i] = 0xff; + chip->spdif_playback_enable = 1; + chip->spdif_bits = OXYGEN_SPDIF_C | OXYGEN_SPDIF_ORIGINAL | + (IEC958_AES1_CON_PCM_CODER << OXYGEN_SPDIF_CATEGORY_SHIFT); + chip->spdif_pcm_bits = chip->spdif_bits; + + if (oxygen_read8(chip, OXYGEN_REVISION) & OXYGEN_REVISION_2) + chip->revision = 2; + else + chip->revision = 1; + + if (chip->revision == 1) + oxygen_set_bits8(chip, OXYGEN_MISC, OXYGEN_MISC_MAGIC); + + oxygen_set_bits8(chip, OXYGEN_FUNCTION, + OXYGEN_FUNCTION_RESET_CODEC | + OXYGEN_FUNCTION_ENABLE_SPI_4_5); + oxygen_write16(chip, OXYGEN_I2S_MULTICH_FORMAT, 0x010a); + oxygen_write16(chip, OXYGEN_I2S_A_FORMAT, 0x010a); + oxygen_write16(chip, OXYGEN_I2S_B_FORMAT, 0x010a); + oxygen_write16(chip, OXYGEN_I2S_C_FORMAT, 0x010a); + oxygen_set_bits32(chip, OXYGEN_SPDIF_CONTROL, OXYGEN_SPDIF_MAGIC2); + oxygen_write32(chip, OXYGEN_SPDIF_OUTPUT_BITS, chip->spdif_bits); + oxygen_write16(chip, OXYGEN_PLAY_ROUTING, 0x6c00); + oxygen_write8(chip, OXYGEN_REC_ROUTING, 0x10); + oxygen_write8(chip, OXYGEN_ADC_MONITOR, 0x00); + oxygen_write8(chip, OXYGEN_A_MONITOR_ROUTING, 0xe4); + + oxygen_write16(chip, OXYGEN_INTERRUPT_MASK, 0); + oxygen_write16(chip, OXYGEN_DMA_STATUS, 0); + + oxygen_write8(chip, OXYGEN_AC97_INTERRUPT_MASK, 0x00); + oxygen_clear_bits16(chip, OXYGEN_AC97_OUT_CONFIG, + OXYGEN_AC97_OUT_MAGIC3); + oxygen_set_bits16(chip, OXYGEN_AC97_IN_CONFIG, + OXYGEN_AC97_IN_MAGIC3); + oxygen_write_ac97(chip, 0, AC97_RESET, 0); + msleep(1); + oxygen_ac97_set_bits(chip, 0, 0x70, 0x0300); + oxygen_ac97_set_bits(chip, 0, 0x64, 0x8043); + oxygen_ac97_set_bits(chip, 0, 0x62, 0x180f); + oxygen_write_ac97(chip, 0, AC97_MASTER, 0x0000); + oxygen_write_ac97(chip, 0, AC97_PC_BEEP, 0x8000); + oxygen_write_ac97(chip, 0, AC97_MIC, 0x8808); + oxygen_write_ac97(chip, 0, AC97_LINE, 0x0808); + oxygen_write_ac97(chip, 0, AC97_CD, 0x8808); + oxygen_write_ac97(chip, 0, AC97_VIDEO, 0x8808); + oxygen_write_ac97(chip, 0, AC97_AUX, 0x8808); + oxygen_write_ac97(chip, 0, AC97_REC_GAIN, 0x8000); + oxygen_write_ac97(chip, 0, AC97_CENTER_LFE_MASTER, 0x8080); + oxygen_write_ac97(chip, 0, AC97_SURROUND_MASTER, 0x8080); + oxygen_ac97_clear_bits(chip, 0, 0x72, 0x0001); + /* power down unused ADCs and DACs */ + oxygen_ac97_set_bits(chip, 0, AC97_POWERDOWN, + AC97_PD_PR0 | AC97_PD_PR1); + oxygen_ac97_set_bits(chip, 0, AC97_EXTENDED_STATUS, + AC97_EA_PRI | AC97_EA_PRJ | AC97_EA_PRK); +} + +static void oxygen_card_free(struct snd_card *card) +{ + struct oxygen *chip = card->private_data; + + spin_lock_irq(&chip->reg_lock); + chip->interrupt_mask = 0; + chip->pcm_running = 0; + oxygen_write16(chip, OXYGEN_DMA_STATUS, 0); + oxygen_write16(chip, OXYGEN_INTERRUPT_MASK, 0); + spin_unlock_irq(&chip->reg_lock); + if (chip->irq >= 0) { + free_irq(chip->irq, chip); + synchronize_irq(chip->irq); + } + flush_scheduled_work(); + chip->model->cleanup(chip); + mutex_destroy(&chip->mutex); + pci_release_regions(chip->pci); + pci_disable_device(chip->pci); +} + +int __devinit oxygen_pci_probe(struct pci_dev *pci, int index, char *id, + const struct oxygen_model *model) +{ + struct snd_card *card; + struct oxygen *chip; + int err; + + card = snd_card_new(index, id, model->owner, sizeof *chip); + if (!card) + return -ENOMEM; + + chip = card->private_data; + chip->card = card; + chip->pci = pci; + chip->irq = -1; + chip->model = model; + spin_lock_init(&chip->reg_lock); + mutex_init(&chip->mutex); + INIT_WORK(&chip->spdif_input_bits_work, + oxygen_spdif_input_bits_changed); + + err = pci_enable_device(pci); + if (err < 0) + goto err_card; + + err = pci_request_regions(pci, model->chip); + if (err < 0) { + snd_printk(KERN_ERR "cannot reserve PCI resources\n"); + goto err_pci_enable; + } + + if (!(pci_resource_flags(pci, 0) & IORESOURCE_IO) || + pci_resource_len(pci, 0) < 0x100) { + snd_printk(KERN_ERR "invalid PCI I/O range\n"); + err = -ENXIO; + goto err_pci_regions; + } + chip->addr = pci_resource_start(pci, 0); + + pci_set_master(pci); + snd_card_set_dev(card, &pci->dev); + card->private_free = oxygen_card_free; + + oxygen_init(chip); + model->init(chip); + + err = request_irq(pci->irq, oxygen_interrupt, IRQF_SHARED, + model->chip, chip); + if (err < 0) { + snd_printk(KERN_ERR "cannot grab interrupt %d\n", pci->irq); + goto err_card; + } + chip->irq = pci->irq; + + strcpy(card->driver, model->chip); + strcpy(card->shortname, model->shortname); + sprintf(card->longname, "%s (rev %u) at %#lx, irq %i", + model->longname, chip->revision, chip->addr, chip->irq); + strcpy(card->mixername, model->chip); + snd_component_add(card, model->chip); + + err = oxygen_pcm_init(chip); + if (err < 0) + goto err_card; + + err = oxygen_mixer_init(chip); + if (err < 0) + goto err_card; + + if (oxygen_read8(chip, OXYGEN_MISC) & OXYGEN_MISC_MIDI) { + err = snd_mpu401_uart_new(card, 0, MPU401_HW_CMIPCI, + chip->addr + OXYGEN_MPU401, + MPU401_INFO_INTEGRATED, 0, 0, + &chip->midi); + if (err < 0) + goto err_card; + } + + oxygen_proc_init(chip); + + spin_lock_irq(&chip->reg_lock); + chip->interrupt_mask |= OXYGEN_INT_SPDIF_IN_CHANGE; + oxygen_write16(chip, OXYGEN_INTERRUPT_MASK, chip->interrupt_mask); + spin_unlock_irq(&chip->reg_lock); + + err = snd_card_register(card); + if (err < 0) + goto err_card; + + pci_set_drvdata(pci, card); + return 0; + +err_pci_regions: + pci_release_regions(pci); +err_pci_enable: + pci_disable_device(pci); +err_card: + snd_card_free(card); + return err; +} +EXPORT_SYMBOL(oxygen_pci_probe); + +void __devexit oxygen_pci_remove(struct pci_dev *pci) +{ + snd_card_free(pci_get_drvdata(pci)); + pci_set_drvdata(pci, NULL); +} +EXPORT_SYMBOL(oxygen_pci_remove); diff --git a/sound/pci/oxygen/oxygen_mixer.c b/sound/pci/oxygen/oxygen_mixer.c new file mode 100644 |