diff options
| -rw-r--r-- | sound/pci/Kconfig | 25 | ||||
| -rw-r--r-- | sound/pci/Makefile | 1 | ||||
| -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 | 
10 files changed, 2662 insertions, 0 deletions
diff --git a/sound/pci/Kconfig b/sound/pci/Kconfig index 45f0f6c2f35..d3be87d1125 100644 --- a/sound/pci/Kconfig +++ b/sound/pci/Kconfig @@ -183,6 +183,31 @@ config SND_CMIPCI  	  To compile this driver as a module, choose M here: the module  	  will be called snd-cmipci. +config SND_OXYGEN_LIB +        tristate +	depends on SND +	select SND_PCM +	select SND_MPU401_UART + +config SND_OXYGEN +	tristate "C-Media 8788 (Oxygen)" +	depends on SND +	select SND_OXYGEN_LIB +	help +	  Say Y here to include support for sound cards based on the +	  C-Media CMI8788 (Oxygen HD Audio) chip: +	   * Asound A-8788 +	   * AuzenTech X-Meridian +	   * Bgears b-Enspirer +	   * Club3D Theatron DTS +	   * HT-Omega Claro +	   * Razer Barracuda AC-1 +	   * Sondigo Inferno +	   * TempoTec HIFIER + +	  To compile this driver as a module, choose M here: the module +	  will be called snd-oxygen. +  config SND_CS4281  	tristate "Cirrus Logic (Sound Fusion) CS4281"  	depends on SND diff --git a/sound/pci/Makefile b/sound/pci/Makefile index 56738da9c14..2d42fd28f4e 100644 --- a/sound/pci/Makefile +++ b/sound/pci/Makefile @@ -68,6 +68,7 @@ obj-$(CONFIG_SND) += \  	korg1212/ \  	mixart/ \  	nm256/ \ +	oxygen/ \  	pcxhr/ \  	riptide/ \  	rme9652/ \ 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 index 00000000000..e252abac004 --- /dev/null +++ b/sound/pci/oxygen/oxygen_mixer.c @@ -0,0 +1,623 @@ +/* + * C-Media CMI8788 driver - mixer code + * + * 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/mutex.h> +#include <sound/ac97_codec.h> +#include <sound/asoundef.h> +#include <sound/control.h> +#include <sound/tlv.h> +#include "oxygen.h" + +static int dac_volume_info(struct snd_kcontrol *ctl, +			   struct snd_ctl_elem_info *info) +{ +	struct oxygen *chip = ctl->private_data; + +	info->type = SNDRV_CTL_ELEM_TYPE_INTEGER; +	info->count = 8; +	info->value.integer.min = chip->model->dac_minimum_volume; +	info->value.integer.max = 0xff; +	return 0; +} + +static int dac_volume_get(struct snd_kcontrol *ctl, +			  struct snd_ctl_elem_value *value) +{ +	struct oxygen *chip = ctl->private_data; +	unsigned int i; + +	mutex_lock(&chip->mutex); +	for (i = 0; i < 8; ++i) +		value->value.integer.value[i] = chip->dac_volume[i]; +	mutex_unlock(&chip->mutex); +	return 0; +} + +static int dac_volume_put(struct snd_kcontrol *ctl, +			  struct snd_ctl_elem_value *value) +{ +	struct oxygen *chip = ctl->private_data; +	unsigned int i; +	int changed; + +	changed = 0; +	mutex_lock(&chip->mutex); +	for (i = 0; i < 8; ++i) +		if (value->value.integer.value[i] != chip->dac_volume[i]) { +			chip->dac_volume[i] = value->value.integer.value[i]; +			changed = 1; +		} +	if (changed) +		chip->model->update_dac_volume(chip); +	mutex_unlock(&chip->mutex); +	return changed; +} + +static int dac_mute_get(struct snd_kcontrol *ctl, +			struct snd_ctl_elem_value *value) +{ +	struct oxygen *chip = ctl->private_data; + +	mutex_lock(&chip->mutex); +	value->value.integer.value[0] = !chip->dac_mute; +	mutex_unlock(&chip->mutex); +	return 0; +} + +static int dac_mute_put(struct snd_kcontrol *ctl, +			  struct snd_ctl_elem_value *value) +{ +	struct oxygen *chip = ctl->private_data; +	int changed; + +	mutex_lock(&chip->mutex); +	changed = !value->value.integer.value[0] != chip->dac_mute; +	if (changed) { +		chip->dac_mute = !value->value.integer.value[0]; +		chip->model->update_dac_mute(chip); +	} +	mutex_unlock(&chip->mutex); +	return changed; +} + +static int upmix_info(struct snd_kcontrol *ctl, struct snd_ctl_elem_info *info) +{ +	static const char *const names[3] = { +		"Front", "Front+Rear", "Front+Rear+Side" +	}; +	info->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; +	info->count = 1; +	info->value.enumerated.items = 3; +	if (info->value.enumerated.item > 2) +		info->value.enumerated.item = 2; +	strcpy(info->value.enumerated.name, names[info->value.enumerated.item]); +	return 0; +} + +static int upmix_get(struct snd_kcontrol *ctl, struct snd_ctl_elem_value *value) +{ +	struct oxygen *chip = ctl->private_data; + +	mutex_lock(&chip->mutex); +	value->value.enumerated.item[0] = chip->dac_routing; +	mutex_unlock(&chip->mutex); +	return 0; +} + +void oxygen_update_dac_routing(struct oxygen *chip) +{ +	/* +	 * hardware channel order: front, side, center/lfe, rear +	 * ALSA channel order:     front, rear, center/lfe, side +	 */ +	static const unsigned int reg_values[3] = { +		0x6c00, 0x2c00, 0x2000 +	}; +	unsigned int reg_value; + +	if ((oxygen_read8(chip, OXYGEN_PLAY_CHANNELS) & +	     OXYGEN_PLAY_CHANNELS_MASK) == OXYGEN_PLAY_CHANNELS_2) +		reg_value = reg_values[chip->dac_routing]; +	else +		reg_value = 0x6c00; +	oxygen_write16_masked(chip, OXYGEN_PLAY_ROUTING, reg_value, 0xff00); +} + +static int upmix_put(struct snd_kcontrol *ctl, struct snd_ctl_elem_value *value) +{ +	struct oxygen *chip = ctl->private_data; +	int changed; + +	mutex_lock(&chip->mutex); +	changed = value->value.enumerated.item[0] != chip->dac_routing; +	if (changed) { +		chip->dac_routing = min(value->value.enumerated.item[0], 2u); +		spin_lock_irq(&chip->reg_lock); +		oxygen_update_dac_routing(chip); +		spin_unlock_irq(&chip->reg_lock); +	} +	mutex_unlock(&chip->mutex); +	return changed; +} + +static int spdif_switch_get(struct snd_kcontrol *ctl, +			    struct snd_ctl_elem_value *value) +{ +	struct oxygen *chip = ctl->private_data; + +	mutex_lock(&chip->mutex); +	value->value.integer.value[0] = chip->spdif_playback_enable; +	mutex_unlock(&chip->mutex); +	return 0; +} + +static unsigned int oxygen_spdif_rate(unsigned int oxygen_rate) +{ +	switch (oxygen_rate) { +	case OXYGEN_RATE_32000: +		return IEC958_AES3_CON_FS_32000 << OXYGEN_SPDIF_CS_RATE_SHIFT; +	case OXYGEN_RATE_44100: +		return IEC958_AES3_CON_FS_44100 << OXYGEN_SPDIF_CS_RATE_SHIFT; +	default: /* OXYGEN_RATE_48000 */ +		return IEC958_AES3_CON_FS_48000 << OXYGEN_SPDIF_CS_RATE_SHIFT; +	case OXYGEN_RATE_64000: +		return 0xb << OXYGEN_SPDIF_CS_RATE_SHIFT; +	case OXYGEN_RATE_88200: +		return 0x8 << OXYGEN_SPDIF_CS_RATE_SHIFT; +	case OXYGEN_RATE_96000: +		return 0xa << OXYGEN_SPDIF_CS_RATE_SHIFT; +	case OXYGEN_RATE_176400: +		return 0xc << OXYGEN_SPDIF_CS_RATE_SHIFT; +	case OXYGEN_RATE_192000: +		return 0xe << OXYGEN_SPDIF_CS_RATE_SHIFT; +	} +} + +void oxygen_update_spdif_source(struct oxygen *chip) +{ +	u32 old_control, new_control; +	u16 old_routing, new_routing; +	unsigned int oxygen_rate; + +	old_control = oxygen_read32(chip, OXYGEN_SPDIF_CONTROL); +	old_routing = oxygen_read16(chip, OXYGEN_PLAY_ROUTING); +	if (chip->pcm_active & (1 << PCM_SPDIF)) { +		new_control = old_control | OXYGEN_SPDIF_OUT_ENABLE; +		new_routing = (old_routing & ~0x00e0) | 0x0000; +		oxygen_rate = (old_control >> OXYGEN_SPDIF_OUT_RATE_SHIFT) +			& OXYGEN_I2S_RATE_MASK; +		/* S/PDIF rate was already set by the caller */ +	} else if ((chip->pcm_active & (1 << PCM_MULTICH)) && +		   chip->spdif_playback_enable) { +		new_routing = (old_routing & ~0x00e0) | 0x0020; +		oxygen_rate = oxygen_read16(chip, OXYGEN_I2S_MULTICH_FORMAT) +			& OXYGEN_I2S_RATE_MASK; +		new_control = (old_control & ~OXYGEN_SPDIF_OUT_RATE_MASK) | +			(oxygen_rate << OXYGEN_SPDIF_OUT_RATE_SHIFT) | +			OXYGEN_SPDIF_OUT_ENABLE; +	} else { +		new_control = old_control & ~OXYGEN_SPDIF_OUT_ENABLE; +		new_routing = old_routing; +		oxygen_rate = OXYGEN_RATE_44100; +	} +	if (old_routing != new_routing) { +		oxygen_write32(chip, OXYGEN_SPDIF_CONTROL, +			       new_control & ~OXYGEN_SPDIF_OUT_ENABLE); +		oxygen_write16(chip, OXYGEN_PLAY_ROUTING, new_routing); +	} +	if (new_control & OXYGEN_SPDIF_OUT_ENABLE) +		oxygen_write32(chip, OXYGEN_SPDIF_OUTPUT_BITS, +			       oxygen_spdif_rate(oxygen_rate) | +			       ((chip->pcm_active & (1 << PCM_SPDIF)) ? +				chip->spdif_pcm_bits : chip->spdif_bits)); +	oxygen_write32(chip, OXYGEN_SPDIF_CONTROL, new_control); +} + +static int spdif_switch_put(struct snd_kcontrol *ctl, +			    struct snd_ctl_elem_value *value) +{ +	struct oxygen *chip = ctl->private_data; +	int changed; + +	mutex_lock(&chip->mutex); +	changed = value->value.integer.value[0] != chip->spdif_playback_enable; +	if (changed) { +		chip->spdif_playback_enable = !!value->value.integer.value[0]; +		spin_lock_irq(&chip->reg_lock); +		oxygen_update_spdif_source(chip); +		spin_unlock_irq(&chip->reg_lock); +	} +	mutex_unlock(&chip->mutex); +	return changed; +} + +static int spdif_info(struct snd_kcontrol *ctl, struct snd_ctl_elem_info *info) +{ +	info->type = SNDRV_CTL_ELEM_TYPE_IEC958; +	info->count = 1; +	return 0; +} + +static void oxygen_to_iec958(u32 bits, struct snd_ctl_elem_value *value) +{ +	value->value.iec958.status[0] = +		bits & (OXYGEN_SPDIF_NONAUDIO | OXYGEN_SPDIF_C | +			OXYGEN_SPDIF_PREEMPHASIS); +	value->value.iec958.status[1] = /* category and original */ +		bits >> OXYGEN_SPDIF_CATEGORY_SHIFT; +} + +static u32 iec958_to_oxygen(struct snd_ctl_elem_value *value) +{ +	u32 bits; + +	bits = value->value.iec958.status[0] & +		(OXYGEN_SPDIF_NONAUDIO | OXYGEN_SPDIF_C | +		 OXYGEN_SPDIF_PREEMPHASIS); +	bits |= value->value.iec958.status[1] << OXYGEN_SPDIF_CATEGORY_SHIFT; +	if (bits & OXYGEN_SPDIF_NONAUDIO) +		bits |= OXYGEN_SPDIF_V; +	return bits; +} + +static inline void write_spdif_bits(struct oxygen *chip, u32 bits) +{ +	oxygen_write32_masked(chip, OXYGEN_SPDIF_OUTPUT_BITS, bits, +			      OXYGEN_SPDIF_NONAUDIO | +			      OXYGEN_SPDIF_C | +			      OXYGEN_SPDIF_PREEMPHASIS | +			      OXYGEN_SPDIF_CATEGORY_MASK | +			      OXYGEN_SPDIF_ORIGINAL | +			      OXYGEN_SPDIF_V); +} + +static int spdif_default_get(struct snd_kcontrol *ctl, +			     struct snd_ctl_elem_value *value) +{ +	struct oxygen *chip = ctl->private_data; + +	mutex_lock(&chip->mutex); +	oxygen_to_iec958(chip->spdif_bits, value); +	mutex_unlock(&chip->mutex); +	return 0; +} + +static int spdif_default_put(struct snd_kcontrol *ctl, +			     struct snd_ctl_elem_value *value) +{ +	struct oxygen *chip = ctl->private_data; +	u32 new_bits; +	int changed; + +	new_bits = iec958_to_oxygen(value); +	mutex_lock(&chip->mutex); +	changed = new_bits != chip->spdif_bits; +	if (changed) { +		chip->spdif_bits = new_bits; +		if (!(chip->pcm_active & (1 << PCM_SPDIF))) +			write_spdif_bits(chip, new_bits); +	} +	mutex_unlock(&chip->mutex); +	return changed; +} + +static int spdif_mask_get(struct snd_kcontrol *ctl, +			  struct snd_ctl_elem_value *value) +{ +	value->value.iec958.status[0] = IEC958_AES0_NONAUDIO | +		IEC958_AES0_CON_NOT_COPYRIGHT | IEC958_AES0_CON_EMPHASIS; +	value->value.iec958.status[1] = +		IEC958_AES1_CON_CATEGORY | IEC958_AES1_CON_ORIGINAL; +	return 0; +} + +static int spdif_pcm_get(struct snd_kcontrol *ctl, +			 struct snd_ctl_elem_value *value) +{ +	struct oxygen *chip = ctl->private_data; + +	mutex_lock(&chip->mutex); +	oxygen_to_iec958(chip->spdif_pcm_bits, value); +	mutex_unlock(&chip->mutex); +	return 0; +} + +static int spdif_pcm_put(struct snd_kcontrol *ctl, +			 struct snd_ctl_elem_value *value) +{ +	struct oxygen *chip = ctl->private_data; +	u32 new_bits; +	int changed; + +	new_bits = iec958_to_oxygen(value); +	mutex_lock(&chip->mutex); +	changed = new_bits != chip->spdif_pcm_bits; +	if (changed) { +		chip->spdif_pcm_bits = new_bits; +		if (chip->pcm_active & (1 << PCM_SPDIF)) +			write_spdif_bits(chip, new_bits); +	} +	mutex_unlock(&chip->mutex); +	return changed; +} + +static int spdif_input_mask_get(struct snd_kcontrol *ctl, +				struct snd_ctl_elem_value *value) +{ +	value->value.iec958.status[0] = 0xff; +	value->value.iec958.status[1] = 0xff; +	value->value.iec958.status[2] = 0xff; +	value->value.iec958.status[3] = 0xff; +	return 0; +} + +static int spdif_input_default_get(struct snd_kcontrol *ctl, +				   struct snd_ctl_elem_value *value) +{ +	struct oxygen *chip = ctl->private_data; +	u32 bits; + +	bits = oxygen_read32(chip, OXYGEN_SPDIF_INPUT_BITS); +	value->value.iec958.status[0] = bits; +	value->value.iec958.status[1] = bits >> 8; +	value->value.iec958.status[2] = bits >> 16; +	value->value.iec958.status[3] = bits >> 24; +	return 0; +} + +static int ac97_switch_get(struct snd_kcontrol *ctl, +			   struct snd_ctl_elem_value *value) +{ +	struct oxygen *chip = ctl->private_data; +	unsigned int index = ctl->private_value & 0xff; +	unsigned int bitnr = (ctl->private_value >> 8) & 0xff; +	int invert = ctl->private_value & (1 << 16); +	u16 reg; + +	mutex_lock(&chip->mutex); +	reg = oxygen_read_ac97(chip, 0, index); +	mutex_unlock(&chip->mutex); +	if (!(reg & (1 << bitnr)) ^ !invert) +		value->value.integer.value[0] = 1; +	else +		value->value.integer.value[0] = 0; +	return 0; +} + +static int ac97_switch_put(struct snd_kcontrol *ctl, +			   struct snd_ctl_elem_value *value) +{ +	struct oxygen *chip = ctl->private_data; +	unsigned int index = ctl->private_value & 0xff; +	unsigned int bitnr = (ctl->private_value >> 8) & 0xff; +	int invert = ctl->private_value & (1 << 16); +	u16 oldreg, newreg; +	int change; + +	mutex_lock(&chip->mutex); +	oldreg = oxygen_read_ac97(chip, 0, index); +	newreg = oldreg; +	if (!value->value.integer.value[0] ^ !invert) +		newreg |= 1 << bitnr; +	else +		newreg &= ~(1 << bitnr); +	change = newreg != oldreg; +	if (change) { +		oxygen_write_ac97(chip, 0, index, newreg); +		if (index == AC97_LINE) +			oxygen_write_ac97_masked(chip, 0, 0x72, +						 !!(newreg & 0x8000), 0x0001); +	} +	mutex_unlock(&chip->mutex); +	return change; +} + +static int ac97_volume_info(struct snd_kcontrol *ctl, +			    struct snd_ctl_elem_info *info) +{ +	info->type = SNDRV_CTL_ELEM_TYPE_INTEGER; +	info->count = 2; +	info->value.integer.min = 0; +	info->value.integer.max = 0x1f; +	return 0; +} + +static int ac97_volume_get(struct snd_kcontrol *ctl, +			   struct snd_ctl_elem_value *value) +{ +	struct oxygen *chip = ctl->private_data; +	unsigned int index = ctl->private_value; +	u16 reg; + +	mutex_lock(&chip->mutex); +	reg = oxygen_read_ac97(chip, 0, index); +	mutex_unlock(&chip->mutex); +	value->value.integer.value[0] = 31 - (reg & 0x1f); +	value->value.integer.value[1] = 31 - ((reg >> 8) & 0x1f); +	return 0; +} + +static int ac97_volume_put(struct snd_kcontrol *ctl, +			   struct snd_ctl_elem_value *value) +{ +	struct oxygen *chip = ctl->private_data; +	unsigned int index = ctl->private_value; +	u16 oldreg, newreg; +	int change; + +	mutex_lock(&chip->mutex); +	oldreg = oxygen_read_ac97(chip, 0, index); +	newreg = oldreg; +	newreg = (newreg & ~0x1f) | +		(31 - (value->value.integer.value[0] & 0x1f)); +	newreg = (newreg & ~0x1f00) | +		((31 - (value->value.integer.value[0] & 0x1f)) << 8); +	change = newreg != oldreg; +	if (change) +		oxygen_write_ac97(chip, 0, index, newreg); +	mutex_unlock(&chip->mutex); +	return change; +} + +#define AC97_SWITCH(xname, index, bitnr, invert) { \ +		.iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ +		.name = xname, \ +		.info = snd_ctl_boolean_mono_info, \ +		.get = ac97_switch_get, \ +		.put = ac97_switch_put, \ +		.private_value = ((invert) << 16) | ((bitnr) << 8) | (index), \ +	} +#define AC97_VOLUME(xname, index) { \ +		.iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ +		.name = xname, \ +		.info = ac97_volume_info, \ +		.get = ac97_volume_get, \ +		.put = ac97_volume_put, \ +		.tlv = { .p = ac97_db_scale, }, \ +		.private_value = (index), \ +	} + +static DECLARE_TLV_DB_SCALE(ac97_db_scale, -3450, 150, 0); + +static const struct snd_kcontrol_new controls[] = { +	{ +		.iface = SNDRV_CTL_ELEM_IFACE_MIXER, +		.name = "PCM Playback Volume", +		.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | +			  SNDRV_CTL_ELEM_ACCESS_TLV_READ, +		.info = dac_volume_info, +		.get = dac_volume_get, +		.put = dac_volume_put, +		.tlv = { +			.p = NULL, /* set later */ +		}, +	}, +	{ +		.iface = SNDRV_CTL_ELEM_IFACE_MIXER, +		.name = "PCM Playback Switch", +		.info = snd_ctl_boolean_mono_info, +		.get = dac_mute_get, +		.put = dac_mute_put, +	}, +	{ +		.iface = SNDRV_CTL_ELEM_IFACE_MIXER, +		.name = "Stereo Upmixing", +		.info = upmix_info, +		.get = upmix_get, +		.put = upmix_put, +	}, +	{ +		.iface = SNDRV_CTL_ELEM_IFACE_MIXER, +		.name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, SWITCH), +		.info = snd_ctl_boolean_mono_info, +		.get = spdif_switch_get, +		.put = spdif_switch_put, +	}, +	{ +		.iface = SNDRV_CTL_ELEM_IFACE_PCM, +		.device = 1, +		.name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT), +		.info = spdif_info, +		.get = spdif_default_get, +		.put = spdif_default_put, +	}, +	{ +		.iface = SNDRV_CTL_ELEM_IFACE_PCM, +		.device = 1, +		.name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, CON_MASK), +		.access = SNDRV_CTL_ELEM_ACCESS_READ, +		.info = spdif_info, +		.get = spdif_mask_get, +	}, +	{ +		.iface = SNDRV_CTL_ELEM_IFACE_PCM, +		.device = 1, +		.name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, PCM_STREAM), +		.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | +			  SNDRV_CTL_ELEM_ACCESS_INACTIVE, +		.info = spdif_info, +		.get = spdif_pcm_get, +		.put = spdif_pcm_put, +	}, +	{ +		.iface = SNDRV_CTL_ELEM_IFACE_PCM, +		.device = 1, +		.name = SNDRV_CTL_NAME_IEC958("", CAPTURE, MASK), +		.access = SNDRV_CTL_ELEM_ACCESS_READ, +		.info = spdif_info, +		.get = spdif_input_mask_get, +	}, +	{ +		.iface = SNDRV_CTL_ELEM_IFACE_PCM, +		.device = 1, +		.name = SNDRV_CTL_NAME_IEC958("", CAPTURE, DEFAULT), +		.access = SNDRV_CTL_ELEM_ACCESS_READ, +		.info = spdif_info, +		.get = spdif_input_default_get, +	}, +	AC97_VOLUME("Mic Capture Volume", AC97_MIC), +	AC97_SWITCH("Mic Capture Switch", AC97_MIC, 15, 1), +	AC97_SWITCH("Mic Boost (+20dB)", AC97_MIC, 6, 0), +	AC97_SWITCH("Line Capture Switch", AC97_LINE, 15, 1), +	AC97_VOLUME("CD Capture Volume", AC97_CD), +	AC97_SWITCH("CD Capture Switch", AC97_CD, 15, 1), +	AC97_VOLUME("Aux Capture Volume", AC97_AUX), +	AC97_SWITCH("Aux Capture Switch", AC97_AUX, 15, 1), +}; + +static void oxygen_any_ctl_free(struct snd_kcontrol *ctl) +{ +	struct oxygen *chip = ctl->private_data; + +	/* I'm too lazy to write a function for each control :-) */ +	chip->spdif_pcm_ctl = NULL; +	chip->spdif_input_bits_ctl = NULL; +} + +int oxygen_mixer_init(struct oxygen *chip) +{ +	unsigned int i; +	struct snd_kcontrol *ctl; +	int err; + +	for (i = 0; i < ARRAY_SIZE(controls); ++i) { +		ctl = snd_ctl_new1(&controls[i], chip); +		if (!ctl) +			return -ENOMEM; +		if (!strcmp(ctl->id.name, "PCM Playback Volume")) +			ctl->tlv.p = chip->model->dac_tlv; +		else if (chip->model->cd_in_from_video_in && +			 !strncmp(ctl->id.name, "CD Capture ", 11)) +			ctl->private_value ^= AC97_CD ^ AC97_VIDEO; +		err = snd_ctl_add(chip->card, ctl); +		if (err < 0) +			return err; +		if (!strcmp(ctl->id.name, +			    SNDRV_CTL_NAME_IEC958("", PLAYBACK, PCM_STREAM))) { +			chip->spdif_pcm_ctl = ctl; +			ctl->private_free = oxygen_any_ctl_free; +		} else if (!strcmp(ctl->id.name, +				 SNDRV_CTL_NAME_IEC958("", CAPTURE, DEFAULT))) { +			chip->spdif_input_bits_ctl = ctl; +			ctl->private_free = oxygen_any_ctl_free; +		} +	} +	return chip->model->mixer_init ? chip->model->mixer_init(chip) : 0; +} diff --git a/sound/pci/oxygen/oxygen_pcm.c b/sound/pci/oxygen/oxygen_pcm.c new file mode 100644 index 00000000000..941399bcab8 --- /dev/null +++ b/sound/pci/oxygen/oxygen_pcm.c @@ -0,0 +1,726 @@ +/* + * C-Media CMI8788 driver - PCM code + * + * 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/pci.h> +#include <sound/control.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include "oxygen.h" + +static struct snd_pcm_hardware oxygen_hardware[PCM_COUNT] = { +	[PCM_A] = { +		.info = SNDRV_PCM_INFO_MMAP | +			SNDRV_PCM_INFO_MMAP_VALID | +			SNDRV_PCM_INFO_INTERLEAVED | +			SNDRV_PCM_INFO_PAUSE | +			SNDRV_PCM_INFO_SYNC_START, +		.formats = SNDRV_PCM_FMTBIT_S16_LE | +			   SNDRV_PCM_FMTBIT_S32_LE, +		.rates = SNDRV_PCM_RATE_44100 | +			 SNDRV_PCM_RATE_48000 | +			 SNDRV_PCM_RATE_96000 | +			 SNDRV_PCM_RATE_192000, +		.rate_min = 44100, +		.rate_max = 192000, +		.channels_min = 2, +		.channels_max = 2, +		.buffer_bytes_max = 256 * 1024, +		.period_bytes_min = 128, +		.period_bytes_max = 128 * 1024, +		.periods_min = 2, +		.periods_max = 2048, +	}, +	[PCM_B] = { +		.info = SNDRV_PCM_INFO_MMAP | +			SNDRV_PCM_INFO_MMAP_VALID | +			SNDRV_PCM_INFO_INTERLEAVED | +			SNDRV_PCM_INFO_PAUSE | +			SNDRV_PCM_INFO_SYNC_START, +		.formats = SNDRV_PCM_FMTBIT_S16_LE | +			   SNDRV_PCM_FMTBIT_S32_LE, +		.rates = SNDRV_PCM_RATE_32000 | +			 SNDRV_PCM_RATE_44100 | +			 SNDRV_PCM_RATE_48000 | +			 SNDRV_PCM_RATE_64000 | +			 SNDRV_PCM_RATE_88200 | +			 SNDRV_PCM_RATE_96000 | +			 SNDRV_PCM_RATE_176400 | +			 SNDRV_PCM_RATE_192000, +		.rate_min = 32000, +		.rate_max = 192000, +		.channels_min = 2, +		.channels_max = 2, +		.buffer_bytes_max = 256 * 1024, +		.period_bytes_min = 128, +		.period_bytes_max = 128 * 1024, +		.periods_min = 2, +		.periods_max = 2048, +	}, +	[PCM_C] = { +		.info = SNDRV_PCM_INFO_MMAP | +			SNDRV_PCM_INFO_MMAP_VALID | +			SNDRV_PCM_INFO_INTERLEAVED | +			SNDRV_PCM_INFO_PAUSE | +			SNDRV_PCM_INFO_SYNC_START, +		.formats = SNDRV_PCM_FMTBIT_S16_LE | +			   SNDRV_PCM_FMTBIT_S32_LE, +		.rates = SNDRV_PCM_RATE_44100 | +			 SNDRV_PCM_RATE_48000 | +			 SNDRV_PCM_RATE_88200 | +			 SNDRV_PCM_RATE_96000, +		.rate_min = 44100, +		.rate_max = 96000, +		.channels_min = 2, +		.channels_max = 2, +		.buffer_bytes_max = 256 * 1024, +		.period_bytes_min = 128, +		.period_bytes_max = 128 * 1024, +		.periods_min = 2, +		.periods_max = 2048, +	}, +	[PCM_SPDIF] = { +		.info = SNDRV_PCM_INFO_MMAP | +			SNDRV_PCM_INFO_MMAP_VALID | +			SNDRV_PCM_INFO_INTERLEAVED | +			SNDRV_PCM_INFO_PAUSE | +			SNDRV_PCM_INFO_SYNC_START, +		.formats = SNDRV_PCM_FMTBIT_S16_LE | +			   SNDRV_PCM_FMTBIT_S32_LE, +		.rates = SNDRV_PCM_RATE_32000 | +			 SNDRV_PCM_RATE_44100 | +			 SNDRV_PCM_RATE_48000 | +			 SNDRV_PCM_RATE_64000 | +			 SNDRV_PCM_RATE_88200 | +			 SNDRV_PCM_RATE_96000 | +			 SNDRV_PCM_RATE_176400 | +			 SNDRV_PCM_RATE_192000, +		.rate_min = 32000, +		.rate_max = 192000, +		.channels_min = 2, +		.channels_max = 2, +		.buffer_bytes_max = 256 * 1024, +		.period_bytes_min = 128, +		.period_bytes_max = 128 * 1024, +		.periods_min = 2, +		.periods_max = 2048, +	}, +	[PCM_MULTICH] = { +		.info = SNDRV_PCM_INFO_MMAP | +			SNDRV_PCM_INFO_MMAP_VALID | +			SNDRV_PCM_INFO_INTERLEAVED | +			SNDRV_PCM_INFO_PAUSE | +			SNDRV_PCM_INFO_SYNC_START, +		.formats = SNDRV_PCM_FMTBIT_S16_LE | +			   SNDRV_PCM_FMTBIT_S32_LE, +		.rates = SNDRV_PCM_RATE_32000 | +			 SNDRV_PCM_RATE_44100 | +			 SNDRV_PCM_RATE_48000 | +			 SNDRV_PCM_RATE_64000 | +			 SNDRV_PCM_RATE_88200 | +			 SNDRV_PCM_RATE_96000 | +			 SNDRV_PCM_RATE_176400 | +			 SNDRV_PCM_RATE_192000, +		.rate_min = 32000, +		.rate_max = 192000, +		.channels_min = 2, +		.channels_max = 8, +		.buffer_bytes_max = 2048 * 1024, +		.period_bytes_min = 128, +		.period_bytes_max = 256 * 1024, +		.periods_min = 2, +		.periods_max = 16384, +	}, +	[PCM_AC97] = { +		.info = SNDRV_PCM_INFO_MMAP | +			SNDRV_PCM_INFO_MMAP_VALID | +			SNDRV_PCM_INFO_INTERLEAVED | +			SNDRV_PCM_INFO_PAUSE | +			SNDRV_PCM_INFO_SYNC_START, +		.formats = SNDRV_PCM_FMTBIT_S16_LE, +		.rates = SNDRV_PCM_RATE_48000, +		.rate_min = 48000, +		.rate_max = 48000, +		.channels_min = 2, +		.channels_max = 2, +		.buffer_bytes_max = 256 * 1024, +		.period_bytes_min = 128, +		.period_bytes_max = 128 * 1024, +		.periods_min = 2, +		.periods_max = 2048, +	}, +}; + +static int oxygen_open(struct snd_pcm_substream *substream, +		       unsigned int channel) +{ +	struct oxygen *chip = snd_pcm_substream_chip(substream); +	struct snd_pcm_runtime *runtime = substream->runtime; +	int err; + +	runtime->private_data = (void *)channel; +	runtime->hw = oxygen_hardware[channel]; +	err = snd_pcm_hw_constraint_step(runtime, 0, +					 SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 32); +	if (err < 0) +		return err; +	err = snd_pcm_hw_constraint_step(runtime, 0, +					 SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 32); +	if (err < 0) +		return err; +	if (runtime->hw.formats & SNDRV_PCM_FMTBIT_S32_LE) { +		err = snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24); +		if (err < 0) +			return err; +	} +	if (runtime->hw.channels_max > 2) { +		err = snd_pcm_hw_constraint_step(runtime, 0, +						 SNDRV_PCM_HW_PARAM_CHANNELS, +						 2); +		if (err < 0) +			return err; +	} +	snd_pcm_set_sync(substream); +	chip->streams[channel] = substream; + +	mutex_lock(&chip->mutex); +	chip->pcm_active |= 1 << channel; +	if (channel == PCM_SPDIF) { +		chip->spdif_pcm_bits = chip->spdif_bits; +		chip->spdif_pcm_ctl->vd[0].access &= +			~SNDRV_CTL_ELEM_ACCESS_INACTIVE; +		snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE | +			       SNDRV_CTL_EVENT_MASK_INFO, +			       &chip->spdif_pcm_ctl->id); +	} +	mutex_unlock(&chip->mutex); + +	return 0; +} + +static int oxygen_rec_a_open(struct snd_pcm_substream *substream) +{ +	return oxygen_open(substream, PCM_A); +} + +static int oxygen_rec_b_open(struct snd_pcm_substream *substream) +{ +	return oxygen_open(substream, PCM_B); +} + +static int oxygen_rec_c_open(struct snd_pcm_substream *substream) +{ +	return oxygen_open(substream, PCM_C); +} + +static int oxygen_spdif_open(struct snd_pcm_substream *substream) +{ +	return oxygen_open(substream, PCM_SPDIF); +} + +static int oxygen_multich_open(struct snd_pcm_substream *substream) +{ +	return oxygen_open(substream, PCM_MULTICH); +} + +static int oxygen_ac97_open(struct snd_pcm_substream *substream) +{ +	return oxygen_open(substream, PCM_AC97); +} + +static int oxygen_close(struct snd_pcm_substream *substream) +{ +	struct oxygen *chip = snd_pcm_substream_chip(substream); +	unsigned int channel = (unsigned int)substream->runtime->private_data; + +	mutex_lock(&chip->mutex); +	chip->pcm_active &= ~(1 << channel); +	if (channel == PCM_SPDIF) { +		chip->spdif_pcm_ctl->vd[0].access |= +			SNDRV_CTL_ELEM_ACCESS_INACTIVE; +		snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE | +			       SNDRV_CTL_EVENT_MASK_INFO, +			       &chip->spdif_pcm_ctl->id); +	} +	if (channel == PCM_SPDIF || channel == PCM_MULTICH) +		oxygen_update_spdif_source(chip); +	mutex_unlock(&chip->mutex); + +	chip->streams[channel] = NULL; +	return 0; +} + +static unsigned int oxygen_format(struct snd_pcm_hw_params *hw_params) +{ +	if (params_format(hw_params) == SNDRV_PCM_FORMAT_S32_LE) +		return OXYGEN_FORMAT_24; +	else +		return OXYGEN_FORMAT_16; +} + +static unsigned int oxygen_rate(struct snd_pcm_hw_params *hw_params) +{ +	switch (params_rate(hw_params)) { +	case 32000: +		return OXYGEN_RATE_32000; +	case 44100: +		return OXYGEN_RATE_44100; +	default: /* 48000 */ +		return OXYGEN_RATE_48000; +	case 64000: +		return OXYGEN_RATE_64000; +	case 88200: +		return OXYGEN_RATE_88200; +	case 96000: +		return OXYGEN_RATE_96000; +	case 176400: +		return OXYGEN_RATE_176400; +	case 192000: +		return OXYGEN_RATE_192000; +	} +} + +static unsigned int oxygen_i2s_magic2(struct snd_pcm_hw_params *hw_params) +{ +	return params_rate(hw_params) <= 96000 ? 0x10 : 0x00; +} + +static unsigned int oxygen_i2s_format(struct snd_pcm_hw_params *hw_params) +{ +	if (params_format(hw_params) == SNDRV_PCM_FORMAT_S32_LE) +		return OXYGEN_I2S_FORMAT_24; +	else +		return OXYGEN_I2S_FORMAT_16; +} + +static unsigned int oxygen_play_channels(struct snd_pcm_hw_params *hw_params) +{ +	switch (params_channels(hw_params)) { +	default: /* 2 */ +		return OXYGEN_PLAY_CHANNELS_2; +	case 4: +		return OXYGEN_PLAY_CHANNELS_4; +	case 6: +		return OXYGEN_PLAY_CHANNELS_6; +	case 8: +		return OXYGEN_PLAY_CHANNELS_8; +	} +} + +static const unsigned int channel_base_registers[PCM_COUNT] = { +	[PCM_A] = OXYGEN_DMA_A_ADDRESS, +	[PCM_B] = OXYGEN_DMA_B_ADDRESS, +	[PCM_C] = OXYGEN_DMA_C_ADDRESS, +	[PCM_SPDIF] = OXYGEN_DMA_SPDIF_ADDRESS, +	[PCM_MULTICH] = OXYGEN_DMA_MULTICH_ADDRESS, +	[PCM_AC97] = OXYGEN_DMA_AC97_ADDRESS, +}; + +static int oxygen_hw_params(struct snd_pcm_substream *substream, +			    struct snd_pcm_hw_params *hw_params) +{ +	struct oxygen *chip = snd_pcm_substream_chip(substream); +	unsigned int channel = (unsigned int)substream->runtime->private_data; +	int err; + +	err = snd_pcm_lib_malloc_pages(substream, +				       params_buffer_bytes(hw_params)); +	if (err < 0) +		return err; + +	oxygen_write32(chip, channel_base_registers[channel], +		       (u32)substream->runtime->dma_addr); +	if (channel == PCM_MULTICH) { +		oxygen_write32(chip, OXYGEN_DMA_MULTICH_COUNT, +			       params_buffer_bytes(hw_params) / 4 - 1); +		oxygen_write32(chip, OXYGEN_DMA_MULTICH_TCOUNT, +			       params_period_bytes(hw_params) / 4 - 1); +	} else { +		oxygen_write16(chip, channel_base_registers[channel] + 4, +			       params_buffer_bytes(hw_params) / 4 - 1); +		oxygen_write16(chip, channel_base_registers[channel] + 6, +			       params_period_bytes(hw_params) / 4 - 1); +	} +	return 0; +} + +static int oxygen_rec_a_hw_params(struct snd_pcm_substream *substream, +				  struct snd_pcm_hw_params *hw_params) +{ +	struct oxygen *chip = snd_pcm_substream_chip(substream); +	int err; + +	err = oxygen_hw_params(substream, hw_params); +	if (err < 0) +		return err; + +	spin_lock_irq(&chip->reg_lock); +	oxygen_write8_masked(chip, OXYGEN_REC_FORMAT, +			     oxygen_format(hw_params) << OXYGEN_REC_FORMAT_A_SHIFT, +			     OXYGEN_REC_FORMAT_A_MASK); +	oxygen_write8_masked(chip, OXYGEN_I2S_A_FORMAT, +			     oxygen_rate(hw_params) | +			     oxygen_i2s_magic2(hw_params) | +			     oxygen_i2s_format(hw_params), +			     OXYGEN_I2S_RATE_MASK | +			     OXYGEN_I2S_MAGIC2_MASK | +			     OXYGEN_I2S_FORMAT_MASK); +	oxygen_clear_bits8(chip, OXYGEN_REC_ROUTING, 0x08); +	spin_unlock_irq(&chip->reg_lock); + +	mutex_lock(&chip->mutex); +	chip->model->set_adc_params(chip, hw_params); +	mutex_unlock(&chip->mutex); +	return 0; +} + +static int oxygen_rec_b_hw_params(struct snd_pcm_substream *substream, +				  struct snd_pcm_hw_params *hw_params) +{ +	struct oxygen *chip = snd_pcm_substream_chip(substream); +	int err; + +	err = oxygen_hw_params(substream, hw_params); +	if (err < 0) +		return err; + +	spin_lock_irq(&chip->reg_lock); +	oxygen_write8_masked(chip, OXYGEN_REC_FORMAT, +			     oxygen_format(hw_params) << OXYGEN_REC_FORMAT_B_SHIFT, +			     OXYGEN_REC_FORMAT_B_MASK); +	oxygen_write8_masked(chip, OXYGEN_I2S_B_FORMAT, +			     oxygen_rate(hw_params) | +			     oxygen_i2s_magic2(hw_params) | +			     oxygen_i2s_format(hw_params), +			     OXYGEN_I2S_RATE_MASK | +			     OXYGEN_I2S_MAGIC2_MASK | +			     OXYGEN_I2S_FORMAT_MASK); +	oxygen_clear_bits8(chip, OXYGEN_REC_ROUTING, 0x10); +	spin_unlock_irq(&chip->reg_lock); + +	mutex_lock(&chip->mutex); +	chip->model->set_adc_params(chip, hw_params); +	mutex_unlock(&chip->mutex); +	return 0; +} + +static int oxygen_rec_c_hw_params(struct snd_pcm_substream *substream, +				  struct snd_pcm_hw_params *hw_params) +{ +	struct oxygen *chip = snd_pcm_substream_chip(substream); +	int err; + +	err = oxygen_hw_params(substream, hw_params); +	if (err < 0) +		return err; + +	spin_lock_irq(&chip->reg_lock); +	oxygen_write8_masked(chip, OXYGEN_REC_FORMAT, +			     oxygen_format(hw_params) << OXYGEN_REC_FORMAT_C_SHIFT, +			     OXYGEN_REC_FORMAT_C_MASK); +	oxygen_clear_bits8(chip, OXYGEN_REC_ROUTING, 0x20); +	spin_unlock_irq(&chip->reg_lock); +	return 0; +} + +static int oxygen_spdif_hw_params(struct snd_pcm_substream *substream, +				  struct snd_pcm_hw_params *hw_params) +{ +	struct oxygen *chip = snd_pcm_substream_chip(substream); +	int err; + +	err = oxygen_hw_params(substream, hw_params); +	if (err < 0) +		return err; + +	spin_lock_irq(&chip->reg_lock); +	oxygen_clear_bits32(chip, OXYGEN_SPDIF_CONTROL, +			    OXYGEN_SPDIF_OUT_ENABLE); +	oxygen_write8_masked(chip, OXYGEN_PLAY_FORMAT, +			     oxygen_format(hw_params) << OXYGEN_SPDIF_FORMAT_SHIFT, +			     OXYGEN_SPDIF_FORMAT_MASK); +	oxygen_write32_masked(chip, OXYGEN_SPDIF_CONTROL, +			      oxygen_rate(hw_params) << OXYGEN_SPDIF_OUT_RATE_SHIFT, +			      OXYGEN_SPDIF_OUT_RATE_MASK); +	oxygen_update_spdif_source(chip); +	spin_unlock_irq(&chip->reg_lock); +	return 0; +} + +static int oxygen_multich_hw_params(struct snd_pcm_substream *substream, +				    struct snd_pcm_hw_params *hw_params) +{ +	struct oxygen *chip = snd_pcm_substream_chip(substream); +	int err; + +	err = oxygen_hw_params(substream, hw_params); +	if (err < 0) +		return err; + +	spin_lock_irq(&chip->reg_lock); +	oxygen_write8_masked(chip, OXYGEN_PLAY_CHANNELS, +			     oxygen_play_channels(hw_params), +			     OXYGEN_PLAY_CHANNELS_MASK); +	oxygen_write8_masked(chip, OXYGEN_PLAY_FORMAT, +			     oxygen_format(hw_params) << OXYGEN_MULTICH_FORMAT_SHIFT, +			     OXYGEN_MULTICH_FORMAT_MASK); +	oxygen_write16_masked(chip, OXYGEN_I2S_MULTICH_FORMAT, +			      oxygen_rate(hw_params) | oxygen_i2s_format(hw_params), +			      OXYGEN_I2S_RATE_MASK | OXYGEN_I2S_FORMAT_MASK); +	oxygen_clear_bits16(chip, OXYGEN_PLAY_ROUTING, 0x001f); +	oxygen_update_dac_routing(chip); +	oxygen_update_spdif_source(chip); +	spin_unlock_irq(&chip->reg_lock); + +	mutex_lock(&chip->mutex); +	chip->model->set_dac_params(chip, hw_params); +	mutex_unlock(&chip->mutex); +	return 0; +} + +static int oxygen_ac97_hw_params(struct snd_pcm_substream *substream, +				 struct snd_pcm_hw_params *hw_params) +{ +	struct oxygen *chip = snd_pcm_substream_chip(substream); +	int err; + +	err = oxygen_hw_params(substream, hw_params); +	if (err < 0) +		return err; + +	spin_lock_irq(&chip->reg_lock); +	oxygen_write8_masked(chip, OXYGEN_PLAY_FORMAT, +			     oxygen_format(hw_params) << OXYGEN_AC97_FORMAT_SHIFT, +			     OXYGEN_AC97_FORMAT_MASK); +	spin_unlock_irq(&chip->reg_lock); +	return 0; +} + +static int oxygen_hw_free(struct snd_pcm_substream *substream) +{ +	struct oxygen *chip = snd_pcm_substream_chip(substream); +	unsigned int channel = (unsigned int)substream->runtime->private_data; + +	spin_lock_irq(&chip->reg_lock); +	chip->interrupt_mask &= ~(1 << channel); +	oxygen_write16(chip, OXYGEN_INTERRUPT_MASK, chip->interrupt_mask); +	spin_unlock_irq(&chip->reg_lock); + +	return snd_pcm_lib_free_pages(substream); +} + +static int oxygen_spdif_hw_free(struct snd_pcm_substream *substream) +{ +	struct oxygen *chip = snd_pcm_substream_chip(substream); + +	spin_lock_irq(&chip->reg_lock); +	oxygen_clear_bits32(chip, OXYGEN_SPDIF_CONTROL, +			    OXYGEN_SPDIF_OUT_ENABLE); +	spin_unlock_irq(&chip->reg_lock); +	return oxygen_hw_free(substream); +} + +static int oxygen_prepare(struct snd_pcm_substream *substream) +{ +	struct oxygen *chip = snd_pcm_substream_chip(substream); +	unsigned int channel = (unsigned int)substream->runtime->private_data; +	unsigned int channel_mask = 1 << channel; + +	spin_lock_irq(&chip->reg_lock); +	oxygen_set_bits8(chip, OXYGEN_DMA_FLUSH, channel_mask); +	oxygen_clear_bits8(chip, OXYGEN_DMA_FLUSH, channel_mask); + +	chip->interrupt_mask |= channel_mask; +	oxygen_write16(chip, OXYGEN_INTERRUPT_MASK, chip->interrupt_mask); +	spin_unlock_irq(&chip->reg_lock); +	return 0; +} + +static int oxygen_trigger(struct snd_pcm_substream *substream, int cmd) +{ +	struct oxygen *chip = snd_pcm_substream_chip(substream); +	struct snd_pcm_substream *s; +	unsigned int mask = 0; +	int running; + +	switch (cmd) { +	case SNDRV_PCM_TRIGGER_STOP: +	case SNDRV_PCM_TRIGGER_PAUSE_PUSH: +		running = 0; +		break; +	case SNDRV_PCM_TRIGGER_START: +	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: +		running = 1; +		break; +	default: +		return -EINVAL; +	} + +	snd_pcm_group_for_each_entry(s, substream) { +		if (snd_pcm_substream_chip(s) == chip) { +			mask |= 1 << (unsigned int)s->runtime->private_data; +			snd_pcm_trigger_done(s, substream); +		} +	} + +	spin_lock(&chip->reg_lock); +	if (running) +		chip->pcm_running |= mask; +	else +		chip->pcm_running &= ~mask; +	oxygen_write8(chip, OXYGEN_DMA_STATUS, chip->pcm_running); +	spin_unlock(&chip->reg_lock); +	return 0; +} + +static snd_pcm_uframes_t oxygen_pointer(struct snd_pcm_substream *substream) +{ +	struct oxygen *chip = snd_pcm_substream_chip(substream); +	struct snd_pcm_runtime *runtime = substream->runtime; +	unsigned int channel = (unsigned int)runtime->private_data; +	u32 curr_addr; + +	/* no spinlock, this read should be atomic */ +	curr_addr = oxygen_read32(chip, channel_base_registers[channel]); +	return bytes_to_frames(runtime, curr_addr - (u32)runtime->dma_addr); +} + +static struct snd_pcm_ops oxygen_rec_a_ops = { +	.open      = oxygen_rec_a_open, +	.close     = oxygen_close, +	.ioctl     = snd_pcm_lib_ioctl, +	.hw_params = oxygen_rec_a_hw_params, +	.hw_free   = oxygen_hw_free, +	.prepare   = oxygen_prepare, +	.trigger   = oxygen_trigger, +	.pointer   = oxygen_pointer, +}; + +static struct snd_pcm_ops oxygen_rec_b_ops = { +	.open      = oxygen_rec_b_open, +	.close     = oxygen_close, +	.ioctl     = snd_pcm_lib_ioctl, +	.hw_params = oxygen_rec_b_hw_params, +	.hw_free   = oxygen_hw_free, +	.prepare   = oxygen_prepare, +	.trigger   = oxygen_trigger, +	.pointer   = oxygen_pointer, +}; + +static struct snd_pcm_ops oxygen_rec_c_ops = { +	.open      = oxygen_rec_c_open, +	.close     = oxygen_close, +	.ioctl     = snd_pcm_lib_ioctl, +	.hw_params = oxygen_rec_c_hw_params, +	.hw_free   = oxygen_hw_free, +	.prepare   = oxygen_prepare, +	.trigger   = oxygen_trigger, +	.pointer   = oxygen_pointer, +}; + +static struct snd_pcm_ops oxygen_spdif_ops = { +	.open      = oxygen_spdif_open, +	.close     = oxygen_close, +	.ioctl     = snd_pcm_lib_ioctl, +	.hw_params = oxygen_spdif_hw_params, +	.hw_free   = oxygen_spdif_hw_free, +	.prepare   = oxygen_prepare, +	.trigger   = oxygen_trigger, +	.pointer   = oxygen_pointer, +}; + +static struct snd_pcm_ops oxygen_multich_ops = { +	.open      = oxygen_multich_open, +	.close     = oxygen_close, +	.ioctl     = snd_pcm_lib_ioctl, +	.hw_params = oxygen_multich_hw_params, +	.hw_free   = oxygen_hw_free, +	.prepare   = oxygen_prepare, +	.trigger   = oxygen_trigger, +	.pointer   = oxygen_pointer, +}; + +static struct snd_pcm_ops oxygen_ac97_ops = { +	.open      = oxygen_ac97_open, +	.close     = oxygen_close, +	.ioctl     = snd_pcm_lib_ioctl, +	.hw_params = oxygen_ac97_hw_params, +	.hw_free   = oxygen_hw_free, +	.prepare   = oxygen_prepare, +	.trigger   = oxygen_trigger, +	.pointer   = oxygen_pointer, +}; + +static void oxygen_pcm_free(struct snd_pcm *pcm) +{ +	snd_pcm_lib_preallocate_free_for_all(pcm); +} + +int __devinit oxygen_pcm_init(struct oxygen *chip) +{ +	struct snd_pcm *pcm; +	int err; + +	err = snd_pcm_new(chip->card, "Analog", 0, 1, 1, &pcm); +	if (err < 0) +		return err; +	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &oxygen_multich_ops); +	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, +			chip->model->record_from_dma_b ? +			&oxygen_rec_b_ops : &oxygen_rec_a_ops); +	pcm->private_data = chip; +	pcm->private_free = oxygen_pcm_free; +	strcpy(pcm->name, "Analog"); +	snd_pcm_lib_preallocate_pages(pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream, +				      SNDRV_DMA_TYPE_DEV, +				      snd_dma_pci_data(chip->pci), +				      512 * 1024, 2048 * 1024); +	snd_pcm_lib_preallocate_pages(pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream, +				      SNDRV_DMA_TYPE_DEV, +				      snd_dma_pci_data(chip->pci), +				      128 * 1024, 256 * 1024); + +	err = snd_pcm_new(chip->card, "Digital", 1, 1, 1, &pcm); +	if (err < 0) +		return err; +	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &oxygen_spdif_ops); +	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &oxygen_rec_c_ops); +	pcm->private_data = chip; +	pcm->private_free = oxygen_pcm_free; +	strcpy(pcm->name, "Digital"); +	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, +					      snd_dma_pci_data(chip->pci), +					      128 * 1024, 256 * 1024); + +	if (chip->has_2nd_ac97_codec) { +		err = snd_pcm_new(chip->card, "AC97", 2, 1, 0, &pcm); +		if (err < 0) +			return err; +		snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, +				&oxygen_ac97_ops); +		pcm->private_data = chip; +		pcm->private_free = oxygen_pcm_free; +		strcpy(pcm->name, "Front Panel"); +		snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, +						      snd_dma_pci_data(chip->pci), +						      128 * 1024, 256 * 1024); +	} +	return 0; +} diff --git a/sound/pci/oxygen/oxygen_regs.h b/sound/pci/oxygen/oxygen_regs.h new file mode 100644 index 00000000000..7a4726d2b2c --- /dev/null +++ b/sound/pci/oxygen/oxygen_regs.h @@ -0,0 +1,246 @@ +#ifndef OXYGEN_REGS_H_INCLUDED +#define OXYGEN_REGS_H_INCLUDED + +/* recording channel A */ +#define OXYGEN_DMA_A_ADDRESS		0x00	/* 32-bit base address */ +#define OXYGEN_DMA_A_COUNT		0x04	/* buffer counter (dwords) */ +#define OXYGEN_DMA_A_TCOUNT		0x06	/* interrupt counter (dwords) */ + +/* recording channel B */ +#define OXYGEN_DMA_B_ADDRESS		0x08 +#define OXYGEN_DMA_B_COUNT		0x0c +#define OXYGEN_DMA_B_TCOUNT		0x0e + +/* recording channel C */ +#define OXYGEN_DMA_C_ADDRESS		0x10 +#define OXYGEN_DMA_C_COUNT		0x14 +#define OXYGEN_DMA_C_TCOUNT		0x16 + +/* SPDIF playback channel */ +#define OXYGEN_DMA_SPDIF_ADDRESS	0x18 +#define OXYGEN_DMA_SPDIF_COUNT		0x1c +#define OXYGEN_DMA_SPDIF_TCOUNT		0x1e + +/* multichannel playback channel */ +#define OXYGEN_DMA_MULTICH_ADDRESS	0x20 +#define OXYGEN_DMA_MULTICH_COUNT	0x24	/* 32 bits */ +#define OXYGEN_DMA_MULTICH_TCOUNT	0x28	/* 32 bits */ + +/* AC'97 (front panel) playback channel */ +#define OXYGEN_DMA_AC97_ADDRESS		0x30 +#define OXYGEN_DMA_AC97_COUNT		0x34 +#define OXYGEN_DMA_AC97_TCOUNT		0x36 + +/* all registers 0x00..0x36 return current position on read */ + +#define OXYGEN_DMA_STATUS		0x40	/* 1 = running, 0 = stop */ +#define  OXYGEN_CHANNEL_A		0x01 +#define  OXYGEN_CHANNEL_B		0x02 +#define  OXYGEN_CHANNEL_C		0x04 +#define  OXYGEN_CHANNEL_SPDIF		0x08 +#define  OXYGEN_CHANNEL_MULTICH		0x10 +#define  OXYGEN_CHANNEL_AC97		0x20 + +#define OXYGEN_DMA_RESET		0x42 +/* OXYGEN_CHANNEL_* */ + +#define OXYGEN_PLAY_CHANNELS		0x43 +#define  OXYGEN_PLAY_CHANNELS_MASK	0x03 +#define  OXYGEN_PLAY_CHANNELS_2		0x00 +#define  OXYGEN_PLAY_CHANNELS_4		0x01 +#define  OXYGEN_PLAY_CHANNELS_6		0x02 +#define  OXYGEN_PLAY_CHANNELS_8		0x03 + +#define OXYGEN_INTERRUPT_MASK		0x44 +/* OXYGEN_CHANNEL_* */ +#define  OXYGEN_INT_SPDIF_IN_CHANGE	0x0100 +#define  OXYGEN_INT_GPIO		0x0800 + +#define OXYGEN_INTERRUPT_STATUS		0x46 +/* OXYGEN_CHANNEL_* amd OXYGEN_INT_* */ +#define  OXYGEN_INT_MIDI		0x1000 + +#define OXYGEN_MISC			0x48 +#define  OXYGEN_MISC_MAGIC		0x20 +#define  OXYGEN_MISC_MIDI		0x40 + +#define OXYGEN_REC_FORMAT		0x4a +#define  OXYGEN_REC_FORMAT_A_MASK	0x03 +#define  OXYGEN_REC_FORMAT_A_SHIFT	0 +#define  OXYGEN_REC_FORMAT_B_MASK	0x0c +#define  OXYGEN_REC_FORMAT_B_SHIFT	2 +#define  OXYGEN_REC_FORMAT_C_MASK	0x30 +#define  OXYGEN_REC_FORMAT_C_SHIFT	4 +#define  OXYGEN_FORMAT_16		0x00 +#define  OXYGEN_FORMAT_24		0x01 +#define  OXYGEN_FORMAT_32		0x02 + +#define OXYGEN_PLAY_FORMAT		0x4b +#define  OXYGEN_SPDIF_FORMAT_MASK	0x03 +#define  OXYGEN_SPDIF_FORMAT_SHIFT	0 +#define  OXYGEN_MULTICH_FORMAT_MASK	0x0c +#define  OXYGEN_MULTICH_FORMAT_SHIFT	2 +#define  OXYGEN_AC97_FORMAT_MASK	0x30 +#define  OXYGEN_AC97_FORMAT_SHIFT	4 +/* OXYGEN_FORMAT_* */ + +#define OXYGEN_REC_CHANNELS		0x4c +#define  OXYGEN_REC_A_CHANNELS_MASK	0x07 +#define  OXYGEN_REC_CHANNELS_2		0x00 +#define  OXYGEN_REC_CHANNELS_4		0x01 +#define  OXYGEN_REC_CHANNELS_6		0x03	/* or 0x02 */ +#define  OXYGEN_REC_CHANNELS_8		0x04 + +#define OXYGEN_FUNCTION			0x50 +#define  OXYGEN_FUNCTION_RESET_CODEC	0x02 +#define  OXYGEN_FUNCTION_ENABLE_SPI_4_5	0x80 + +#define OXYGEN_I2S_MULTICH_FORMAT	0x60 +#define  OXYGEN_I2S_RATE_MASK		0x0007 +#define  OXYGEN_RATE_32000		0x0000 +#define  OXYGEN_RATE_44100		0x0001 +#define  OXYGEN_RATE_48000		0x0002 +#define  OXYGEN_RATE_64000		0x0003 +#define  OXYGEN_RATE_88200		0x0004 +#define  OXYGEN_RATE_96000		0x0005 +#define  OXYGEN_RATE_176400		0x0006 +#define  OXYGEN_RATE_192000		0x0007 +#define  OXYGEN_I2S_MAGIC1_MASK		0x0008 +#define  OXYGEN_I2S_MAGIC2_MASK		0x0030 +#define  OXYGEN_I2S_FORMAT_MASK		0x00c0 +#define  OXYGEN_I2S_FORMAT_16		0x0000 +#define  OXYGEN_I2S_FORMAT_20		0x0040 +#define  OXYGEN_I2S_FORMAT_24		0x0080 +#define  OXYGEN_I2S_FORMAT_32		0x00c0 + +#define OXYGEN_I2S_A_FORMAT		0x62 +#define OXYGEN_I2S_B_FORMAT		0x64 +#define OXYGEN_I2S_C_FORMAT		0x66 +/* OXYGEN_I2S_RATE_* and OXYGEN_I2S_FORMAT_* */ + +#define OXYGEN_SPDIF_CONTROL		0x70 +#define  OXYGEN_SPDIF_OUT_ENABLE	0x00000002 +#define  OXYGEN_SPDIF_LOOPBACK		0x00000004 +#define  OXYGEN_SPDIF_MAGIC2		0x00000020 +#define  OXYGEN_SPDIF_MAGIC3		0x00000040 +#define  OXYGEN_SPDIF_IN_VALID		0x00001000 +#define  OXYGEN_SPDIF_IN_CHANGE		0x00008000	/* r/wc */ +#define  OXYGEN_SPDIF_IN_INVERT		0x00010000	/* ? */ +#define  OXYGEN_SPDIF_OUT_RATE_MASK	0x07000000 +#define  OXYGEN_SPDIF_OUT_RATE_SHIFT	24 +/* OXYGEN_RATE_* << OXYGEN_SPDIF_OUT_RATE_SHIFT */ + +#define OXYGEN_SPDIF_OUTPUT_BITS	0x74 +#define  OXYGEN_SPDIF_NONAUDIO		0x00000002 +#define  OXYGEN_SPDIF_C			0x00000004 +#define  OXYGEN_SPDIF_PREEMPHASIS	0x00000008 +#define  OXYGEN_SPDIF_CATEGORY_MASK	0x000007f0 +#define  OXYGEN_SPDIF_CATEGORY_SHIFT	4 +#define  OXYGEN_SPDIF_ORIGINAL		0x00000800 +#define  OXYGEN_SPDIF_CS_RATE_MASK	0x0000f000 +#define  OXYGEN_SPDIF_CS_RATE_SHIFT	12 +#define  OXYGEN_SPDIF_V			0x00010000	/* 0 = valid */ + +#define OXYGEN_SPDIF_INPUT_BITS		0x78 +/* 32 bits, IEC958_AES_* */ + +#define OXYGEN_2WIRE_CONTROL		0x90 +#define  OXYGEN_2WIRE_DIR_MASK		0x01 +#define  OXYGEN_2WIRE_DIR_WRITE		0x00	/* ? */ +#define  OXYGEN_2WIRE_DIR_READ		0x01	/* ? */ +#define  OXYGEN_2WIRE_ADDRESS_MASK	0xfe	/* slave device address */ +#define  OXYGEN_2WIRE_ADDRESS_SHIFT	1 + +#define OXYGEN_2WIRE_MAP		0x91	/* address, 8 bits */ +#define OXYGEN_2WIRE_DATA		0x92	/* data, 16 bits */ + +#define OXYGEN_2WIRE_BUS_STATUS		0x94 +#define  OXYGEN_2WIRE_BUSY		0x01 + +#define OXYGEN_SPI_CONTROL		0x98 +#define  OXYGEN_SPI_BUSY		0x01	/* read */ +#define  OXYGEN_SPI_TRIGGER_WRITE	0x01	/* write */ +#define  OXYGEN_SPI_DATA_LENGTH_MASK	0x02 +#define  OXYGEN_SPI_DATA_LENGTH_2	0x00 +#define  OXYGEN_SPI_DATA_LENGTH_3	0x02 +#define  OXYGEN_SPI_CODEC_MASK		0x70	/* 0..5 */ +#define  OXYGEN_SPI_CODEC_SHIFT		4 +#define  OXYGEN_SPI_MAGIC		0x80 + +#define OXYGEN_SPI_DATA1		0x99 +#define OXYGEN_SPI_DATA2		0x9a +#define OXYGEN_SPI_DATA3		0x9b + +#define OXYGEN_MPU401			0xa0 + +#define OXYGEN_GPI_DATA			0xa4 + +#define OXYGEN_GPI_INTERRUPT_MASK	0xa5 + +#define OXYGEN_GPIO_DATA		0xa6 + +#define OXYGEN_GPIO_CONTROL		0xa8 +/* 0: input, 1: output */ + +#define OXYGEN_GPIO_INTERRUPT_MASK	0xaa + +#define OXYGEN_DEVICE_SENSE		0xac	/* ? */ + +#define OXYGEN_PLAY_ROUTING		0xc0 +#define  OXYGEN_PLAY_DAC0_SOURCE_MASK	0x0300 +#define  OXYGEN_PLAY_DAC1_SOURCE_MASK	0x0700 +#define  OXYGEN_PLAY_DAC2_SOURCE_MASK	0x3000 +#define  OXYGEN_PLAY_DAC3_SOURCE_MASK	0x7000 + +#define OXYGEN_REC_ROUTING		0xc2 + +#define OXYGEN_ADC_MONITOR		0xc3 +#define  OXYGEN_ADC_MONITOR_MULTICH	0x01 +#define  OXYGEN_ADC_MONITOR_AC97	0x04 +#define  OXYGEN_ADC_MONITOR_SPDIF	0x10 + +#define OXYGEN_A_MONITOR_ROUTING	0xc4 + +#define OXYGEN_AC97_CONTROL		0xd0 +#define  OXYGEN_AC97_RESET1		0x0001 +#define  OXYGEN_AC97_RESET1_BUSY	0x0002 +#define  OXYGEN_AC97_RESET2		0x0008 +#define  OXYGEN_AC97_CODEC_0		0x0010 +#define  OXYGEN_AC97_CODEC_1		0x0020 + +#define OXYGEN_AC97_INTERRUPT_MASK	0xd2 + +#define OXYGEN_AC97_INTERRUPT_STATUS	0xd3 +#define  OXYGEN_AC97_READ_COMPLETE	0x01 +#define  OXYGEN_AC97_WRITE_COMPLETE	0x02 + +#define OXYGEN_AC97_OUT_CONFIG		0xd4 +#define  OXYGEN_AC97_OUT_MAGIC1		0x00000011 +#define  OXYGEN_AC97_OUT_MAGIC2		0x00000033 +#define  OXYGEN_AC97_OUT_MAGIC3		0x0000ff00 + +#define OXYGEN_AC97_IN_CONFIG		0xd8 +#define  OXYGEN_AC97_IN_MAGIC1		0x00000011 +#define  OXYGEN_AC97_IN_MAGIC2		0x00000033 +#define  OXYGEN_AC97_IN_MAGIC3		0x00000300 + +#define OXYGEN_AC97_REGS		0xdc +#define  OXYGEN_AC97_REG_DATA_MASK	0x0000ffff +#define  OXYGEN_AC97_REG_ADDR_MASK	0x007f0000 +#define  OXYGEN_AC97_REG_ADDR_SHIFT	16 +#define  OXYGEN_AC97_REG_DIR_MASK	0x00800000 +#define  OXYGEN_AC97_REG_DIR_WRITE	0x00000000 +#define  OXYGEN_AC97_REG_DIR_READ	0x00800000 +#define  OXYGEN_AC97_REG_CODEC_MASK	0x01000000 +#define  OXYGEN_AC97_REG_CODEC_SHIFT	24 + +#define OXYGEN_DMA_FLUSH		0xe1 +/* OXYGEN_CHANNEL_* */ + +#define OXYGEN_CODEC_VERSION		0xe4 + +#define OXYGEN_REVISION			0xe6 +#define  OXYGEN_REVISION_2		0x08	/* bit flag */ +#define  OXYGEN_REVISION_8787		0x14	/* all 8 bits */ + +#endif  |