linux/sound/pci/cs46xx/cs46xx_lib.c

// SPDX-License-Identifier: GPL-2.0-or-later
/*
 *  Copyright (c) by Jaroslav Kysela <[email protected]>
 *                   Abramo Bagnara <[email protected]>
 *                   Cirrus Logic, Inc.
 *  Routines for control of Cirrus Logic CS461x chips
 *
 *  KNOWN BUGS:
 *    - Sometimes the SPDIF input DSP tasks get's unsynchronized
 *      and the SPDIF get somewhat "distorcionated", or/and left right channel
 *      are swapped. To get around this problem when it happens, mute and unmute 
 *      the SPDIF input mixer control.
 *    - On the Hercules Game Theater XP the amplifier are sometimes turned
 *      off on inadecuate moments which causes distorcions on sound.
 *
 *  TODO:
 *    - Secondary CODEC on some soundcards
 *    - SPDIF input support for other sample rates then 48khz
 *    - Posibility to mix the SPDIF output with analog sources.
 *    - PCM channels for Center and LFE on secondary codec
 *
 *  NOTE: with CONFIG_SND_CS46XX_NEW_DSP unset uses old DSP image (which
 *        is default configuration), no SPDIF, no secondary codec, no
 *        multi channel PCM.  But known to work.
 *
 *  FINALLY: A credit to the developers Tom and Jordan 
 *           at Cirrus for have helping me out with the DSP, however we
 *           still don't have sufficient documentation and technical
 *           references to be able to implement all fancy feutures
 *           supported by the cs46xx DSP's. 
 *           Benny <[email protected]>
 */

#include <linux/delay.h>
#include <linux/pci.h>
#include <linux/pm.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/gameport.h>
#include <linux/mutex.h>
#include <linux/export.h>
#include <linux/module.h>
#include <linux/firmware.h>
#include <linux/vmalloc.h>
#include <linux/io.h>

#include <sound/core.h>
#include <sound/control.h>
#include <sound/info.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include "cs46xx.h"

#include "cs46xx_lib.h"
#include "dsp_spos.h"

static void amp_voyetra(struct snd_cs46xx *chip, int change);

#ifdef CONFIG_SND_CS46XX_NEW_DSP
static const struct snd_pcm_ops snd_cs46xx_playback_rear_ops;
static const struct snd_pcm_ops snd_cs46xx_playback_indirect_rear_ops;
static const struct snd_pcm_ops snd_cs46xx_playback_clfe_ops;
static const struct snd_pcm_ops snd_cs46xx_playback_indirect_clfe_ops;
static const struct snd_pcm_ops snd_cs46xx_playback_iec958_ops;
static const struct snd_pcm_ops snd_cs46xx_playback_indirect_iec958_ops;
#endif

static const struct snd_pcm_ops snd_cs46xx_playback_ops;
static const struct snd_pcm_ops snd_cs46xx_playback_indirect_ops;
static const struct snd_pcm_ops snd_cs46xx_capture_ops;
static const struct snd_pcm_ops snd_cs46xx_capture_indirect_ops;

static unsigned short snd_cs46xx_codec_read(struct snd_cs46xx *chip,
					    unsigned short reg,
					    int codec_index)
{}

static unsigned short snd_cs46xx_ac97_read(struct snd_ac97 * ac97,
					    unsigned short reg)
{}


static void snd_cs46xx_codec_write(struct snd_cs46xx *chip,
				   unsigned short reg,
				   unsigned short val,
				   int codec_index)
{}

static void snd_cs46xx_ac97_write(struct snd_ac97 *ac97,
				   unsigned short reg,
				   unsigned short val)
{}


/*
 *  Chip initialization
 */

int snd_cs46xx_download(struct snd_cs46xx *chip,
			u32 *src,
                        unsigned long offset,
                        unsigned long len)
{}

static inline void memcpy_le32(void *dst, const void *src, unsigned int len)
{}

#ifdef CONFIG_SND_CS46XX_NEW_DSP

static const char *module_names[CS46XX_DSP_MODULES] =;

MODULE_FIRMWARE();
MODULE_FIRMWARE();
MODULE_FIRMWARE();
MODULE_FIRMWARE();
MODULE_FIRMWARE();

static void free_module_desc(struct dsp_module_desc *module)
{}

/* firmware binary format:
 * le32 nsymbols;
 * struct {
 *	le32 address;
 *	char symbol_name[DSP_MAX_SYMBOL_NAME];
 *	le32 symbol_type;
 * } symbols[nsymbols];
 * le32 nsegments;
 * struct {
 *	le32 segment_type;
 *	le32 offset;
 *	le32 size;
 *	le32 data[size];
 * } segments[nsegments];
 */

static int load_firmware(struct snd_cs46xx *chip,
			 struct dsp_module_desc **module_ret,
			 const char *fw_name)
{}

int snd_cs46xx_clear_BA1(struct snd_cs46xx *chip,
                         unsigned long offset,
                         unsigned long len) 
{}

#else /* old DSP image */

struct ba1_struct {
	struct {
		u32 offset;
		u32 size;
	} memory[BA1_MEMORY_COUNT];
	u32 map[BA1_DWORD_SIZE];
};

MODULE_FIRMWARE("cs46xx/ba1");

static int load_firmware(struct snd_cs46xx *chip)
{
	const struct firmware *fw;
	int i, size, err;

	err = request_firmware(&fw, "cs46xx/ba1", &chip->pci->dev);
	if (err < 0)
		return err;
	if (fw->size != sizeof(*chip->ba1)) {
		err = -EINVAL;
		goto error;
	}

	chip->ba1 = vmalloc(sizeof(*chip->ba1));
	if (!chip->ba1) {
		err = -ENOMEM;
		goto error;
	}

	memcpy_le32(chip->ba1, fw->data, sizeof(*chip->ba1));

	/* sanity check */
	size = 0;
	for (i = 0; i < BA1_MEMORY_COUNT; i++)
		size += chip->ba1->memory[i].size;
	if (size > BA1_DWORD_SIZE * 4)
		err = -EINVAL;

 error:
	release_firmware(fw);
	return err;
}

static __maybe_unused int snd_cs46xx_download_image(struct snd_cs46xx *chip)
{
	int idx, err;
	unsigned int offset = 0;
	struct ba1_struct *ba1 = chip->ba1;

	for (idx = 0; idx < BA1_MEMORY_COUNT; idx++) {
		err = snd_cs46xx_download(chip,
					  &ba1->map[offset],
					  ba1->memory[idx].offset,
					  ba1->memory[idx].size);
		if (err < 0)
			return err;
		offset += ba1->memory[idx].size >> 2;
	}	
	return 0;
}
#endif /* CONFIG_SND_CS46XX_NEW_DSP */

/*
 *  Chip reset
 */

static void snd_cs46xx_reset(struct snd_cs46xx *chip)
{}

static int cs46xx_wait_for_fifo(struct snd_cs46xx * chip,int retry_timeout) 
{}

static void snd_cs46xx_clear_serial_FIFOs(struct snd_cs46xx *chip)
{}

static void snd_cs46xx_proc_start(struct snd_cs46xx *chip)
{}

static void snd_cs46xx_proc_stop(struct snd_cs46xx *chip)
{}

/*
 *  Sample rate routines
 */

#define GOF_PER_SEC

static void snd_cs46xx_set_play_sample_rate(struct snd_cs46xx *chip, unsigned int rate)
{}

static void snd_cs46xx_set_capture_sample_rate(struct snd_cs46xx *chip, unsigned int rate)
{}

/*
 *  PCM part
 */

static void snd_cs46xx_pb_trans_copy(struct snd_pcm_substream *substream,
				     struct snd_pcm_indirect *rec, size_t bytes)
{}

static int snd_cs46xx_playback_transfer(struct snd_pcm_substream *substream)
{}

static void snd_cs46xx_cp_trans_copy(struct snd_pcm_substream *substream,
				     struct snd_pcm_indirect *rec, size_t bytes)
{}

static int snd_cs46xx_capture_transfer(struct snd_pcm_substream *substream)
{}

static snd_pcm_uframes_t snd_cs46xx_playback_direct_pointer(struct snd_pcm_substream *substream)
{}

static snd_pcm_uframes_t snd_cs46xx_playback_indirect_pointer(struct snd_pcm_substream *substream)
{}

static snd_pcm_uframes_t snd_cs46xx_capture_direct_pointer(struct snd_pcm_substream *substream)
{}

static snd_pcm_uframes_t snd_cs46xx_capture_indirect_pointer(struct snd_pcm_substream *substream)
{}

static int snd_cs46xx_playback_trigger(struct snd_pcm_substream *substream,
				       int cmd)
{}

static int snd_cs46xx_capture_trigger(struct snd_pcm_substream *substream,
				      int cmd)
{}

#ifdef CONFIG_SND_CS46XX_NEW_DSP
static int _cs46xx_adjust_sample_rate (struct snd_cs46xx *chip, struct snd_cs46xx_pcm *cpcm,
				       int sample_rate) 
{}
#endif


static int snd_cs46xx_playback_hw_params(struct snd_pcm_substream *substream,
					 struct snd_pcm_hw_params *hw_params)
{}

static int snd_cs46xx_playback_hw_free(struct snd_pcm_substream *substream)
{}

static int snd_cs46xx_playback_prepare(struct snd_pcm_substream *substream)
{}

static int snd_cs46xx_capture_hw_params(struct snd_pcm_substream *substream,
					struct snd_pcm_hw_params *hw_params)
{}

static int snd_cs46xx_capture_hw_free(struct snd_pcm_substream *substream)
{}

static int snd_cs46xx_capture_prepare(struct snd_pcm_substream *substream)
{}

static irqreturn_t snd_cs46xx_interrupt(int irq, void *dev_id)
{}

static const struct snd_pcm_hardware snd_cs46xx_playback =;

static const struct snd_pcm_hardware snd_cs46xx_capture =;

#ifdef CONFIG_SND_CS46XX_NEW_DSP

static const unsigned int period_sizes[] =;

static const struct snd_pcm_hw_constraint_list hw_constraints_period_sizes =;

#endif

static void snd_cs46xx_pcm_free_substream(struct snd_pcm_runtime *runtime)
{}

static int _cs46xx_playback_open_channel (struct snd_pcm_substream *substream,int pcm_channel_id)
{}

static int snd_cs46xx_playback_open(struct snd_pcm_substream *substream)
{}

#ifdef CONFIG_SND_CS46XX_NEW_DSP
static int snd_cs46xx_playback_open_rear(struct snd_pcm_substream *substream)
{}

static int snd_cs46xx_playback_open_clfe(struct snd_pcm_substream *substream)
{}

static int snd_cs46xx_playback_open_iec958(struct snd_pcm_substream *substream)
{}

static int snd_cs46xx_playback_close(struct snd_pcm_substream *substream);

static int snd_cs46xx_playback_close_iec958(struct snd_pcm_substream *substream)
{}
#endif

static int snd_cs46xx_capture_open(struct snd_pcm_substream *substream)
{}

static int snd_cs46xx_playback_close(struct snd_pcm_substream *substream)
{}

static int snd_cs46xx_capture_close(struct snd_pcm_substream *substream)
{}

#ifdef CONFIG_SND_CS46XX_NEW_DSP
static const struct snd_pcm_ops snd_cs46xx_playback_rear_ops =;

static const struct snd_pcm_ops snd_cs46xx_playback_indirect_rear_ops =;

static const struct snd_pcm_ops snd_cs46xx_playback_clfe_ops =;

static const struct snd_pcm_ops snd_cs46xx_playback_indirect_clfe_ops =;

static const struct snd_pcm_ops snd_cs46xx_playback_iec958_ops =;

static const struct snd_pcm_ops snd_cs46xx_playback_indirect_iec958_ops =;

#endif

static const struct snd_pcm_ops snd_cs46xx_playback_ops =;

static const struct snd_pcm_ops snd_cs46xx_playback_indirect_ops =;

static const struct snd_pcm_ops snd_cs46xx_capture_ops =;

static const struct snd_pcm_ops snd_cs46xx_capture_indirect_ops =;

#ifdef CONFIG_SND_CS46XX_NEW_DSP
#define MAX_PLAYBACK_CHANNELS
#else
#define MAX_PLAYBACK_CHANNELS
#endif

int snd_cs46xx_pcm(struct snd_cs46xx *chip, int device)
{}


#ifdef CONFIG_SND_CS46XX_NEW_DSP
int snd_cs46xx_pcm_rear(struct snd_cs46xx *chip, int device)
{}

int snd_cs46xx_pcm_center_lfe(struct snd_cs46xx *chip, int device)
{}

int snd_cs46xx_pcm_iec958(struct snd_cs46xx *chip, int device)
{}
#endif

/*
 *  Mixer routines
 */
static void snd_cs46xx_mixer_free_ac97(struct snd_ac97 *ac97)
{}

static int snd_cs46xx_vol_info(struct snd_kcontrol *kcontrol, 
			       struct snd_ctl_elem_info *uinfo)
{}

static int snd_cs46xx_vol_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{}

static int snd_cs46xx_vol_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{}

#ifdef CONFIG_SND_CS46XX_NEW_DSP

static int snd_cs46xx_vol_dac_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{}

static int snd_cs46xx_vol_dac_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{}

#if 0
static int snd_cs46xx_vol_iec958_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
	struct snd_cs46xx *chip = snd_kcontrol_chip(kcontrol);

	ucontrol->value.integer.value[0] = chip->dsp_spos_instance->spdif_input_volume_left;
	ucontrol->value.integer.value[1] = chip->dsp_spos_instance->spdif_input_volume_right;
	return 0;
}

static int snd_cs46xx_vol_iec958_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
	struct snd_cs46xx *chip = snd_kcontrol_chip(kcontrol);
	int change = 0;

	if (chip->dsp_spos_instance->spdif_input_volume_left  != ucontrol->value.integer.value[0] ||
	    chip->dsp_spos_instance->spdif_input_volume_right!= ucontrol->value.integer.value[1]) {
		cs46xx_dsp_set_iec958_volume (chip,
					      ucontrol->value.integer.value[0],
					      ucontrol->value.integer.value[1]);
		change = 1;
	}

	return change;
}
#endif

#define snd_mixer_boolean_info

static int snd_cs46xx_iec958_get(struct snd_kcontrol *kcontrol, 
                                 struct snd_ctl_elem_value *ucontrol)
{}

static int snd_cs46xx_iec958_put(struct snd_kcontrol *kcontrol, 
                                  struct snd_ctl_elem_value *ucontrol)
{}

static int snd_cs46xx_adc_capture_get(struct snd_kcontrol *kcontrol, 
                                      struct snd_ctl_elem_value *ucontrol)
{}

static int snd_cs46xx_adc_capture_put(struct snd_kcontrol *kcontrol, 
                                      struct snd_ctl_elem_value *ucontrol)
{}

static int snd_cs46xx_pcm_capture_get(struct snd_kcontrol *kcontrol, 
                                      struct snd_ctl_elem_value *ucontrol)
{}


static int snd_cs46xx_pcm_capture_put(struct snd_kcontrol *kcontrol, 
                                      struct snd_ctl_elem_value *ucontrol)
{}

static int snd_herc_spdif_select_get(struct snd_kcontrol *kcontrol, 
                                     struct snd_ctl_elem_value *ucontrol)
{}

/*
 *	Game Theatre XP card - EGPIO[0] is used to select SPDIF input optical or coaxial.
 */ 
static int snd_herc_spdif_select_put(struct snd_kcontrol *kcontrol, 
                                       struct snd_ctl_elem_value *ucontrol)
{}


static int snd_cs46xx_spdif_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
{}

static int snd_cs46xx_spdif_default_get(struct snd_kcontrol *kcontrol,
					struct snd_ctl_elem_value *ucontrol)
{}

static int snd_cs46xx_spdif_default_put(struct snd_kcontrol *kcontrol,
					struct snd_ctl_elem_value *ucontrol)
{}

static int snd_cs46xx_spdif_mask_get(struct snd_kcontrol *kcontrol,
				     struct snd_ctl_elem_value *ucontrol)
{}

static int snd_cs46xx_spdif_stream_get(struct snd_kcontrol *kcontrol,
                                         struct snd_ctl_elem_value *ucontrol)
{}

static int snd_cs46xx_spdif_stream_put(struct snd_kcontrol *kcontrol,
                                        struct snd_ctl_elem_value *ucontrol)
{}

#endif /* CONFIG_SND_CS46XX_NEW_DSP */


static const struct snd_kcontrol_new snd_cs46xx_controls[] =;

#ifdef CONFIG_SND_CS46XX_NEW_DSP
/* set primary cs4294 codec into Extended Audio Mode */
static int snd_cs46xx_front_dup_get(struct snd_kcontrol *kcontrol, 
				    struct snd_ctl_elem_value *ucontrol)
{}

static int snd_cs46xx_front_dup_put(struct snd_kcontrol *kcontrol, 
				    struct snd_ctl_elem_value *ucontrol)
{}

static const struct snd_kcontrol_new snd_cs46xx_front_dup_ctl =;
#endif

#ifdef CONFIG_SND_CS46XX_NEW_DSP
/* Only available on the Hercules Game Theater XP soundcard */
static const struct snd_kcontrol_new snd_hercules_controls[] =;


static void snd_cs46xx_codec_reset (struct snd_ac97 * ac97)
{}
#endif

static int cs46xx_detect_codec(struct snd_cs46xx *chip, int codec)
{}

int snd_cs46xx_mixer(struct snd_cs46xx *chip, int spdif_device)
{}

/*
 *  RawMIDI interface
 */

static void snd_cs46xx_midi_reset(struct snd_cs46xx *chip)
{}

static int snd_cs46xx_midi_input_open(struct snd_rawmidi_substream *substream)
{}

static int snd_cs46xx_midi_input_close(struct snd_rawmidi_substream *substream)
{}

static int snd_cs46xx_midi_output_open(struct snd_rawmidi_substream *substream)
{}

static int snd_cs46xx_midi_output_close(struct snd_rawmidi_substream *substream)
{}

static void snd_cs46xx_midi_input_trigger(struct snd_rawmidi_substream *substream, int up)
{}

static void snd_cs46xx_midi_output_trigger(struct snd_rawmidi_substream *substream, int up)
{}

static const struct snd_rawmidi_ops snd_cs46xx_midi_output =;

static const struct snd_rawmidi_ops snd_cs46xx_midi_input =;

int snd_cs46xx_midi(struct snd_cs46xx *chip, int device)
{}


/*
 * gameport interface
 */

#if IS_REACHABLE(CONFIG_GAMEPORT)

static void snd_cs46xx_gameport_trigger(struct gameport *gameport)
{}

static unsigned char snd_cs46xx_gameport_read(struct gameport *gameport)
{}

static int snd_cs46xx_gameport_cooked_read(struct gameport *gameport, int *axes, int *buttons)
{}

static int snd_cs46xx_gameport_open(struct gameport *gameport, int mode)
{}

int snd_cs46xx_gameport(struct snd_cs46xx *chip)
{}

static inline void snd_cs46xx_remove_gameport(struct snd_cs46xx *chip)
{}
#else
int snd_cs46xx_gameport(struct snd_cs46xx *chip) { return -ENOSYS; }
static inline void snd_cs46xx_remove_gameport(struct snd_cs46xx *chip) { }
#endif /* CONFIG_GAMEPORT */

#ifdef CONFIG_SND_PROC_FS
/*
 *  proc interface
 */

static ssize_t snd_cs46xx_io_read(struct snd_info_entry *entry,
				  void *file_private_data,
				  struct file *file, char __user *buf,
				  size_t count, loff_t pos)
{}

static const struct snd_info_entry_ops snd_cs46xx_proc_io_ops =;

static int snd_cs46xx_proc_init(struct snd_card *card, struct snd_cs46xx *chip)
{}

static int snd_cs46xx_proc_done(struct snd_cs46xx *chip)
{}
#else /* !CONFIG_SND_PROC_FS */
#define snd_cs46xx_proc_init
#define snd_cs46xx_proc_done
#endif

/*
 * stop the h/w
 */
static void snd_cs46xx_hw_stop(struct snd_cs46xx *chip)
{}


static void snd_cs46xx_free(struct snd_card *card)
{}

/*
 *  initialize chip
 */
static int snd_cs46xx_chip_init(struct snd_cs46xx *chip)
{}

/*
 *  start and load DSP 
 */

static void cs46xx_enable_stream_irqs(struct snd_cs46xx *chip)
{}

int snd_cs46xx_start_dsp(struct snd_cs46xx *chip)
{}


/*
 *	AMP control - null AMP
 */
 
static void amp_none(struct snd_cs46xx *chip, int change)
{}

#ifdef CONFIG_SND_CS46XX_NEW_DSP
static int voyetra_setup_eapd_slot(struct snd_cs46xx *chip)
{}
#endif

/*
 *	Crystal EAPD mode
 */
 
static void amp_voyetra(struct snd_cs46xx *chip, int change)
{}

static void hercules_init(struct snd_cs46xx *chip) 
{}


/*
 *	Game Theatre XP card - EGPIO[2] is used to enable the external amp.
 */ 
static void amp_hercules(struct snd_cs46xx *chip, int change)
{}

static void voyetra_mixer_init (struct snd_cs46xx *chip)
{}

static void hercules_mixer_init (struct snd_cs46xx *chip)
{}


#if 0
/*
 *	Untested
 */
 
static void amp_voyetra_4294(struct snd_cs46xx *chip, int change)
{
	chip->amplifier += change;

	if (chip->amplifier) {
		/* Switch the GPIO pins 7 and 8 to open drain */
		snd_cs46xx_codec_write(chip, 0x4C,
				       snd_cs46xx_codec_read(chip, 0x4C) & 0xFE7F);
		snd_cs46xx_codec_write(chip, 0x4E,
				       snd_cs46xx_codec_read(chip, 0x4E) | 0x0180);
		/* Now wake the AMP (this might be backwards) */
		snd_cs46xx_codec_write(chip, 0x54,
				       snd_cs46xx_codec_read(chip, 0x54) & ~0x0180);
	} else {
		snd_cs46xx_codec_write(chip, 0x54,
				       snd_cs46xx_codec_read(chip, 0x54) | 0x0180);
	}
}
#endif


/*
 *	Handle the CLKRUN on a thinkpad. We must disable CLKRUN support
 *	whenever we need to beat on the chip.
 *
 *	The original idea and code for this hack comes from David Kaiser at
 *	Linuxcare. Perhaps one day Crystal will document their chips well
 *	enough to make them useful.
 */
 
static void clkrun_hack(struct snd_cs46xx *chip, int change)
{}

	
/*
 * detect intel piix4
 */
static void clkrun_init(struct snd_cs46xx *chip)
{}


/*
 * Card subid table
 */
 
struct cs_card_type
{};

static struct cs_card_type cards[] =;


/*
 * APM support
 */
#ifdef CONFIG_PM_SLEEP
static const unsigned int saved_regs[] =;

static int snd_cs46xx_suspend(struct device *dev)
{}

static int snd_cs46xx_resume(struct device *dev)
{}

SIMPLE_DEV_PM_OPS(snd_cs46xx_pm, snd_cs46xx_suspend, snd_cs46xx_resume);
#endif /* CONFIG_PM_SLEEP */


/*
 */

int snd_cs46xx_create(struct snd_card *card,
		      struct pci_dev *pci,
		      int external_amp, int thinkpad)
{}