// SPDX-License-Identifier: GPL-2.0-or-later
/*
* HD audio interface patch for Creative CA0132 chip
*
* Copyright (c) 2011, Creative Technology Ltd.
*
* Based on patch_ca0110.c
* Copyright (c) 2008 Takashi Iwai <tiwai@suse.de>
*/
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/mutex.h>
#include <linux/module.h>
#include <linux/firmware.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/io.h>
#include <linux/pci.h>
#include <asm/io.h>
#include <sound/core.h>
#include <sound/hda_codec.h>
#include "hda_local.h"
#include "hda_auto_parser.h"
#include "hda_jack.h"
#include "ca0132_regs.h"
/* Enable this to see controls for tuning purpose. */
/*#define ENABLE_TUNING_CONTROLS*/
#ifdef ENABLE_TUNING_CONTROLS
#include <sound/tlv.h>
#endif
#define FLOAT_ZERO 0x00000000
#define FLOAT_ONE 0x3f800000
#define FLOAT_TWO 0x40000000
#define FLOAT_THREE 0x40400000
#define FLOAT_FIVE 0x40a00000
#define FLOAT_SIX 0x40c00000
#define FLOAT_EIGHT 0x41000000
#define FLOAT_MINUS_5 0xc0a00000
#define UNSOL_TAG_DSP 0x16
#define DSP_DMA_WRITE_BUFLEN_INIT (1UL<<18)
#define DSP_DMA_WRITE_BUFLEN_OVLY (1UL<<15)
#define DMA_TRANSFER_FRAME_SIZE_NWORDS 8
#define DMA_TRANSFER_MAX_FRAME_SIZE_NWORDS 32
#define DMA_OVERLAY_FRAME_SIZE_NWORDS 2
#define MASTERCONTROL 0x80
#define MASTERCONTROL_ALLOC_DMA_CHAN 10
#define MASTERCONTROL_QUERY_SPEAKER_EQ_ADDRESS 60
#define WIDGET_CHIP_CTRL 0x15
#define WIDGET_DSP_CTRL 0x16
#define MEM_CONNID_MICIN1 3
#define MEM_CONNID_MICIN2 5
#define MEM_CONNID_MICOUT1 12
#define MEM_CONNID_MICOUT2 14
#define MEM_CONNID_WUH 10
#define MEM_CONNID_DSP 16
#define MEM_CONNID_DMIC 100
#define SCP_SET 0
#define SCP_GET 1
#define EFX_FILE "ctefx.bin"
#define DESKTOP_EFX_FILE "ctefx-desktop.bin"
#define R3DI_EFX_FILE "ctefx-r3di.bin"
#ifdef CONFIG_SND_HDA_CODEC_CA0132_DSP
MODULE_FIRMWARE(EFX_FILE);
MODULE_FIRMWARE(DESKTOP_EFX_FILE);
MODULE_FIRMWARE(R3DI_EFX_FILE);
#endif
static const char *const dirstr[2] = { "Playback", "Capture" };
#define NUM_OF_OUTPUTS 2
static const char *const out_type_str[2] = { "Speakers", "Headphone" };
enum {
SPEAKER_OUT,
HEADPHONE_OUT,
};
enum {
DIGITAL_MIC,
LINE_MIC_IN
};
/* Strings for Input Source Enum Control */
static const char *const in_src_str[3] = { "Microphone", "Line In", "Front Microphone" };
#define IN_SRC_NUM_OF_INPUTS 3
enum {
REAR_MIC,
REAR_LINE_IN,
FRONT_MIC,
};
enum {
#define VNODE_START_NID 0x80
VNID_SPK = VNODE_START_NID, /* Speaker vnid */
VNID_MIC,
VNID_HP_SEL,
VNID_AMIC1_SEL,
VNID_HP_ASEL,
VNID_AMIC1_ASEL,
VNODE_END_NID,
#define VNODES_COUNT (VNODE_END_NID - VNODE_START_NID)
#define EFFECT_START_NID 0x90
#define OUT_EFFECT_START_NID EFFECT_START_NID
SURROUND = OUT_EFFECT_START_NID,
CRYSTALIZER,
DIALOG_PLUS,
SMART_VOLUME,
X_BASS,
EQUALIZER,
OUT_EFFECT_END_NID,
#define OUT_EFFECTS_COUNT (OUT_EFFECT_END_NID - OUT_EFFECT_START_NID)
#define IN_EFFECT_START_NID OUT_EFFECT_END_NID
ECHO_CANCELLATION = IN_EFFECT_START_NID,
VOICE_FOCUS,
MIC_SVM,
NOISE_REDUCTION,
IN_EFFECT_END_NID,
#define IN_EFFECTS_COUNT (IN_EFFECT_END_NID - IN_EFFECT_START_NID)
VOICEFX = IN_EFFECT_END_NID,
PLAY_ENHANCEMENT,
CRYSTAL_VOICE,
EFFECT_END_NID,
OUTPUT_SOURCE_ENUM,
INPUT_SOURCE_ENUM,
XBASS_XOVER,
EQ_PRESET_ENUM,
SMART_VOLUME_ENUM,
MIC_BOOST_ENUM,
AE5_HEADPHONE_GAIN_ENUM,
AE5_SOUND_FILTER_ENUM,
ZXR_HEADPHONE_GAIN,
SPEAKER_CHANNEL_CFG_ENUM,
SPEAKER_FULL_RANGE_FRONT,
SPEAKER_FULL_RANGE_REAR,
BASS_REDIRECTION,
BASS_REDIRECTION_XOVER,
#define EFFECTS_COUNT (EFFECT_END_NID - EFFECT_START_NID)
};
/* Effects values size*/
#define EFFECT_VALS_MAX_COUNT 12
/*
* Default values for the effect slider controls, they are in order of their
* effect NID's. Surround, Crystalizer, Dialog Plus, Smart Volume, and then
* X-bass.
*/
static const unsigned int effect_slider_defaults[] = {67, 65, 50, 74, 50};
/* Amount of effect level sliders for ca0132_alt controls. */
#define EFFECT_LEVEL_SLIDERS 5
/* Latency introduced by DSP blocks in milliseconds. */
#define DSP_CAPTURE_INIT_LATENCY 0
#define DSP_CRYSTAL_VOICE_LATENCY 124
#define DSP_PLAYBACK_INIT_LATENCY 13
#define DSP_PLAY_ENHANCEMENT_LATENCY 30
#define DSP_SPEAKER_OUT_LATENCY 7
struct ct_effect {
char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
hda_nid_t nid;
int mid; /*effect module ID*/
int reqs[EFFECT_VALS_MAX_COUNT]; /*effect module request*/
int direct; /* 0:output; 1:input*/
int params; /* number of default non-on/off params */
/*effect default values, 1st is on/off. */
unsigned int def_vals[EFFECT_VALS_MAX_COUNT];
};
#define EFX_DIR_OUT 0
#define EFX_DIR_IN 1
static const struct ct_effect ca0132_effects[EFFECTS_COUNT] = {
{ .name = "Surround",
.nid = SURROUND,
.mid = 0x96,
.reqs = {0, 1},
.direct = EFX_DIR_OUT,
.params = 1,
.def_vals = {0x3F800000, 0x3F2B851F}
},
{ .name = "Crystalizer",
.nid = CRYSTALIZER,
.mid = 0x96,
.reqs = {7, 8},
.direct = EFX_DIR_OUT,
.params = 1,
.def_vals = {0x3F800000, 0x3F266666}
},
{ .name = "Dialog Plus",
.nid = DIALOG_PLUS,
.mid = 0x96,
.reqs = {2, 3},
.direct = EFX_DIR_OUT,
.params = 1,
.def_vals = {0x00000000, 0x3F000000}
},
{ .name = "Smart Volume",
.nid = SMART_VOLUME,
.mid = 0x96,
.reqs = {4, 5, 6},
.direct = EFX_DIR_OUT,
.params = 2,
.def_vals = {0x3F800000, 0x3F3D70A4, 0x00000000}
},
{ .name = "X-Bass",
.nid = X_BASS,
.mid = 0x96,
.reqs = {24, 23, 25},
.direct = EFX_DIR_OUT,
.params = 2,
.def_vals = {0x3F800000, 0x42A00000, 0x3F000000}
},
{ .name = "Equalizer",
.nid = EQUALIZER,
.mid = 0x96,
.reqs = {9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20},
.direct = EFX_DIR_OUT,
.params = 11,
.def_vals = {0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000}
},
{ .name = "Echo Cancellation",
.nid = ECHO_CANCELLATION,
.mid = 0x95,
.reqs = {0, 1, 2, 3},
.direct = EFX_DIR_IN,
.params = 3,
.def_vals = {0x00000000, 0x3F3A9692, 0x00000000, 0x00000000}
},
{ .name = "Voice Focus",
.nid = VOICE_FOCUS,
.mid = 0x95,
.reqs = {6, 7, 8, 9},
.direct = EFX_DIR_IN,
.params = 3,
.def_vals = {0x3F800000, 0x3D7DF3B6, 0x41F00000, 0x41F00000}
},
{ .name = "Mic SVM",
.nid = MIC_SVM,
.mid = 0x95,
.reqs = {44, 45},
.direct = EFX_DIR_IN,
.params = 1,
.def_vals = {0x00000000, 0x3F3D70A4}
},
{ .name = "Noise Reduction",
.nid = NOISE_REDUCTION,
.mid = 0x95,
.reqs = {4, 5},
.direct = EFX_DIR_IN,
.params = 1,
.def_vals = {0x3F800000, 0x3F000000}
},
{ .name = "VoiceFX",
.nid = VOICEFX,
.mid = 0x95,
.reqs = {10, 11, 12, 13, 14, 15, 16, 17, 18},
.direct = EFX_DIR_IN,
.params = 8,
.def_vals = {0x00000000, 0x43C80000, 0x44AF0000, 0x44FA0000,
0x3F800000, 0x3F800000, 0x3F800000, 0x00000000,
0x00000000}
}
};
/* Tuning controls */
#ifdef ENABLE_TUNING_CONTROLS
enum {
#define TUNING_CTL_START_NID 0xC0
WEDGE_ANGLE = TUNING_CTL_START_NID,
SVM_LEVEL,
EQUALIZER_BAND_0,
EQUALIZER_BAND_1,
EQUALIZER_BAND_2,
EQUALIZER_BAND_3,
EQUALIZER_BAND_4,
EQUALIZER_BAND_5,
EQUALIZER_BAND_6,
EQUALIZER_BAND_7,
EQUALIZER_BAND_8,
EQUALIZER_BAND_9,
TUNING_CTL_END_NID
#define TUNING_CTLS_COUNT (TUNING_CTL_END_NID - TUNING_CTL_START_NID)
};
struct ct_tuning_ctl {
char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
hda_nid_t parent_nid;
hda_nid_t nid;
int mid; /*effect module ID*/
int req; /*effect module request*/
int direct; /* 0:output; 1:input*/
unsigned int def_val;/*effect default values*/
};
static const struct ct_tuning_ctl ca0132_tuning_ctls[] = {
{ .name = "Wedge Angle",
.parent_nid = VOICE_FOCUS,
.nid = WEDGE_ANGLE,
.mid = 0x95,
.req = 8,
.direct = EFX_DIR_IN,
.def_val = 0x41F00000
},
{ .name = "SVM Level",
.parent_nid = MIC_SVM,
.nid = SVM_LEVEL,
.mid = 0x95,
.req = 45,
.direct = EFX_DIR_IN,
.def_val = 0x3F3D70A4
},
{ .name = "EQ Band0",
.parent_nid = EQUALIZER,
.nid = EQUALIZER_BAND_0,
.mid = 0x96,
.req = 11,
.direct = EFX_DIR_OUT,
.def_val = 0x00000000
},
{ .name = "EQ Band1",
.parent_nid = EQUALIZER,
.nid = EQUALIZER_BAND_1,
.mid = 0x96,
.req = 12,
.direct = EFX_DIR_OUT,
.def_val = 0x00000000
},
{ .name = "EQ Band2",
.parent_nid = EQUALIZER,
.nid = EQUALIZER_BAND_2,
.mid = 0x96,
.req = 13,
.direct = EFX_DIR_OUT,
.def_val = 0x00000000
},
{ .name = "EQ Band3",
.parent_nid = EQUALIZER,
.nid = EQUALIZER_BAND_3,
.mid = 0x96,
.req = 14,
.direct = EFX_DIR_OUT,
.def_val = 0x00000000
},
{ .name = "EQ Band4",
.parent_nid = EQUALIZER,
.nid = EQUALIZER_BAND_4,
.mid = 0x96,
.req = 15,
.direct = EFX_DIR_OUT,
.def_val = 0x00000000
},
{ .name = "EQ Band5",
.parent_nid = EQUALIZER,
.nid = EQUALIZER_BAND_5,
.mid = 0x96,
.req = 16,
.direct = EFX_DIR_OUT,
.def_val = 0x00000000
},
{ .name = "EQ Band6",
.parent_nid = EQUALIZER,
.nid = EQUALIZER_BAND_6,
.mid = 0x96,
.req = 17,
.direct = EFX_DIR_OUT,
.def_val = 0x00000000
},
{ .name = "EQ Band7",
.parent_nid = EQUALIZER,
.nid = EQUALIZER_BAND_7,
.mid = 0x96,
.req = 18,
.direct = EFX_DIR_OUT,
.def_val = 0x00000000
},
{ .name = "EQ Band8",
.parent_nid = EQUALIZER,
.nid = EQUALIZER_BAND_8,
.mid = 0x96,
.req = 19,
.direct = EFX_DIR_OUT,
.def_val = 0x00000000
},
{ .name = "EQ Band9",
.parent_nid = EQUALIZER,
.nid = EQUALIZER_BAND_9,
.mid = 0x96,
.req = 20,
.direct = EFX_DIR_OUT,
.def_val = 0x00000000
}
};
#endif
/* Voice FX Presets */
#define VOICEFX_MAX_PARAM_COUNT 9
struct ct_voicefx {
char *name;
hda_nid_t nid;
int mid;
int reqs[VOICEFX_MAX_PARAM_COUNT]; /*effect module request*/
};
struct ct_voicefx_preset {
char *name; /*preset name*/
unsigned int vals[VOICEFX_MAX_PARAM_COUNT];
};
static const struct ct_voicefx ca0132_voicefx = {
.name = "VoiceFX Capture Switch",
.nid = VOICEFX,
.mid = 0x95,
.reqs = {10, 11, 12, 13, 14, 15, 16, 17, 18}
};
static const struct ct_voicefx_preset ca0132_voicefx_presets[] = {
{ .name = "Neutral",
.vals = { 0x00000000, 0x43C80000, 0x44AF0000,
0x44FA0000, 0x3F800000, 0x3F800000,
0x3F800000, 0x00000000, 0x00000000 }
},
{ .name = "Female2Male",
.vals = { 0x3F800000, 0x43C80000, 0x44AF0000,
0x44FA0000, 0x3F19999A, 0x3F866666,
0x3F800000, 0x00000000, 0x00000000 }
},
{ .name = "Male2Female",
.vals = { 0x3F800000, 0x43C80000, 0x44AF0000,
0x450AC000, 0x4017AE14, 0x3F6B851F,
0x3F800000, 0x00000000, 0x00000000 }
},
{ .name = "ScrappyKid",
.vals = { 0x3F800000, 0x43C80000, 0x44AF0000,
0x44FA0000, 0x40400000, 0x3F28F5C3,
0x3F800000, 0x00000000, 0x00000000 }
},
{ .name = "Elderly",
.vals = { 0x3F800000, 0x44324000, 0x44BB8000,
0x44E10000, 0x3FB33333, 0x3FB9999A,
0x3F800000, 0x3E3A2E43, 0x00000000 }
},
{ .name = "Orc",
.vals = { 0x3F800000, 0x43EA0000, 0x44A52000,
0x45098000, 0x3F266666, 0x3FC00000,
0x3F800000, 0x00000000, 0x00000000 }
},
{ .name = "Elf",
.vals = { 0x3F800000, 0x43C70000, 0x44AE6000,
0x45193000, 0x3F8E147B, 0x3F75C28F,
0x3F800000, 0x00000000, 0x00000000 }
},
{ .name = "Dwarf",
.vals = { 0x3F800000, 0x43930000, 0x44BEE000,
0x45007000, 0x3F451EB8, 0x3F7851EC,
0x3F800000, 0x00000000, 0x00000000 }
},
{ .name = "AlienBrute",
.vals = { 0x3F800000, 0x43BFC5AC, 0x44B28FDF,
0x451F6000, 0x3F266666, 0x3FA7D945,
0x3F800000, 0x3CF5C28F, 0x00000000 }
},
{ .name = "Robot",
.vals = { 0x3F800000, 0x43C80000, 0x44AF0000,
0x44FA0000, 0x3FB2718B, 0x3F800000,
0xBC07010E, 0x00000000, 0x00000000 }
},
{ .name = "Marine",
.vals = { 0x3F800000, 0x43C20000, 0x44906000,
0x44E70000, 0x3F4CCCCD, 0x3F8A3D71,
0x3F0A3D71, 0x00000000, 0x00000000 }
},
{ .name = "Emo",
.vals = { 0x3F800000, 0x43C80000, 0x44AF0000,
0x44FA0000, 0x3F800000, 0x3F800000,
0x3E4CCCCD, 0x00000000, 0x00000000 }
},
{ .name = "DeepVoice",
.vals = { 0x3F800000, 0x43A9C5AC, 0x44AA4FDF,
0x44FFC000, 0x3EDBB56F, 0x3F99C4CA,
0x3F800000, 0x00000000, 0x00000000 }
},
{ .name = "Munchkin",
.vals = { 0x3F800000, 0x43C80000, 0x44AF0000,
0x44FA0000, 0x3F800000, 0x3F1A043C,
0x3F800000, 0x00000000, 0x00000000 }
}
};
/* ca0132 EQ presets, taken from Windows Sound Blaster Z Driver */
#define EQ_PRESET_MAX_PARAM_COUNT 11
struct ct_eq {
char *name;
hda_nid_t nid;
int mid;
int reqs[EQ_PRESET_MAX_PARAM_COUNT]; /*effect module request*/
};
struct ct_eq_preset {
char *name; /*preset name*/
unsigned int vals[EQ_PRESET_MAX_PARAM_COUNT];
};
static const struct ct_eq ca0132_alt_eq_enum = {
.name = "FX: Equalizer Preset Switch",
.nid = EQ_PRESET_ENUM,
.mid = 0x96,
.reqs = {10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20}
};
static const struct ct_eq_preset ca0132_alt_eq_presets[] = {
{ .name = "Flat",
.vals = { 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000 }
},
{ .name = "Acoustic",
.vals = { 0x00000000, 0x00000000, 0x3F8CCCCD,
0x40000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x40000000,
0x40000000, 0x40000000 }
},
{ .name = "Classical",
.vals = { 0x00000000, 0x00000000, 0x40C00000,
0x40C00000, 0x40466666, 0x00000000,
0x00000000, 0x00000000, 0x00000000,
0x40466666, 0x40466666 }
},
{ .name = "Country",
.vals = { 0x00000000, 0xBF99999A, 0x00000000,
0x3FA66666, 0x3FA66666, 0x3F8CCCCD,
0x00000000, 0x00000000, 0x40000000,
0x40466666, 0x40800000 }
},
{ .name = "Dance",
.vals = { 0x00000000, 0xBF99999A, 0x40000000,
0x40466666, 0x40866666, 0xBF99999A,
0xBF99999A, 0x00000000, 0x00000000,
0x40800000, 0x40800000 }
},
{ .name = "Jazz",
.vals = { 0x00000000, 0x00000000, 0x00000000,
0x3F8CCCCD, 0x40800000, 0x40800000,
0x40800000, 0x00000000, 0x3F8CCCCD,
0x40466666, 0x40466666 }
},
{ .name = "New Age",
.vals = { 0x00000000, 0x00000000, 0x40000000,
0x40000000, 0x00000000, 0x00000000,
0x00000000, 0x3F8CCCCD, 0x40000000,
0x40000000, 0x40000000 }
},
{ .name = "Pop",
.vals = { 0x00000000, 0xBFCCCCCD, 0x00000000,
0x40000000, 0x40000000, 0x00000000,
0xBF99999A, 0xBF99999A, 0x00000000,
0x40466666, 0x40C00000 }
},
{ .name = "Rock",
.vals = { 0x00000000, 0xBF99999A, 0xBF99999A,
0x3F8CCCCD, 0x40000000, 0xBF99999A,
0xBF99999A, 0x00000000, 0x00000000,
0x40800000, 0x40800000 }
},
{ .name = "Vocal",
.vals = { 0x00000000, 0xC0000000, 0xBF99999A,
0xBF99999A, 0x00000000, 0x40466666,
0x40800000, 0x40466666, 0x00000000,
0x00000000, 0x3F8CCCCD }
}
};
/*
* DSP reqs for handling full-range speakers/bass redirection. If a speaker is
* set as not being full range, and bass redirection is enabled, all
* frequencies below the crossover frequency are redirected to the LFE
* channel. If the surround configuration has no LFE channel, this can't be
* enabled. X-Bass must be disabled when using these.
*/
enum speaker_range_reqs {
SPEAKER_BASS_REDIRECT = 0x15,
SPEAKER_BASS_REDIRECT_XOVER_FREQ = 0x16,
/* Between 0x16-0x1a are the X-Bass reqs. */
SPEAKER_FULL_RANGE_FRONT_L_R = 0x1a,
SPEAKER_FULL_RANGE_CENTER_LFE = 0x1b,
SPEAKER_FULL_RANGE_REAR_L_R = 0x1c,
SPEAKER_FULL_RANGE_SURROUND_L_R = 0x1d,
SPEAKER_BASS_REDIRECT_SUB_GAIN = 0x1e,
};
/*
* Definitions for the DSP req's to handle speaker tuning. These all belong to
* module ID 0x96, the output effects module.
*/
enum speaker_tuning_reqs {
/*
* Currently, this value is always set to 0.0f. However, on Windows,
* when selecting certain headphone profiles on the new Sound Blaster
* connect software, the QUERY_SPEAKER_EQ_ADDRESS req on mid 0x80 is
* sent. This gets the speaker EQ address area, which is then used to
* send over (presumably) an equalizer profile for the specific
* headphone setup. It is sent using the same method the DSP
* firmware is uploaded with, which I believe is why the 'ctspeq.bin'
* file exists in linux firmware tree but goes unused. It would also
* explain why the QUERY_SPEAKER_EQ_ADDRESS req is defined but unused.
* Once this profile is sent over, SPEAKER_TUNING_USE_SPEAKER_EQ is
* set to 1.0f.
*/
SPEAKER_TUNING_USE_SPEAKER_EQ = 0x1f,
SPEAKER_TUNING_ENABLE_CENTER_EQ = 0x20,
SPEAKER_TUNING_FRONT_LEFT_VOL_LEVEL = 0x21,
SPEAKER_TUNING_FRONT_RIGHT_VOL_LEVEL = 0x22,
SPEAKER_TUNING_CENTER_VOL_LEVEL = 0x23,
SPEAKER_TUNING_LFE_VOL_LEVEL = 0x24,
SPEAKER_TUNING_REAR_LEFT_VOL_LEVEL = 0x25,
SPEAKER_TUNING_REAR_RIGHT_VOL_LEVEL = 0x26,
SPEAKER_TUNING_SURROUND_LEFT_VOL_LEVEL = 0x27,
SPEAKER_TUNING_SURROUND_RIGHT_VOL_LEVEL = 0x28,
/*
* Inversion is used when setting headphone virtualization to line
* out. Not sure why this is, but it's the only place it's ever used.
*/
SPEAKER_TUNING_FRONT_LEFT_INVERT = 0x29,
SPEAKER_TUNING_FRONT_RIGHT_INVERT = 0x2a,
SPEAKER_TUNIN