[alsa-devel] [PATCH v4 00/13] ALSA: hda/ca0132: Patch Series for Recon3Di and Sound Blaster Z Support
This patchset adds support for the Sound Blaster Z and the Recon3Di.
In order to figure out how to get these cards to work, I made a program called QemuHDADump[1], which uses the trace function of qemu to see interactions with the memory mapped pci BAR space of the card being used in the virtual machine. With this, I obtain the CORB buffer location to get the command verbs, and then dump them each time the buffer rolls over. This program may be useful for fixing other HDA related driver issues where there is no documentation for the device.
So far, I have been able to get all features supported on the Sound Blaster Z and the Recon3Di. All output and input effects work, all inputs and outputs work, and just about anything else I can think of. I have also added new controls in order to select the new inputs and outputs, as well as controls to change the effect levels and presets.
I have also added the ability to use firmware taken from the Windows drivers of both the Sound Blaster Z and Recon3Di. I am trying to get into contact with Creative to get permission to redistribute these along with the current file included with the Chromebook, but they have not been very responsive. Luckily, the cards work with the Chromebook firmware just fine, although I believe there has to be a reason they have different firmware in Windows. I will not link to the firmwares here, but if you look up my thread on Creative Labs forums, you will find the link to download the firmwares there.
I am willing to help get the other non-working cards such as the ZxR and the newer AE-5 working too, but I will need someone willing to run QemuHDADump in a virtual machine in order to get the commands.
So, in summary: -This patchset makes the cards work better than they did before (they really didn't work before)
-This patchset leaves the original chromebook related stuff alone.
Thanks.
[1] https://github.com/Conmanx360/QemuHDADump
Bugs: ------------------------------------------------------------------------------- Recon3Di: (Reported by Mariusz Ceier) ******************* -Occasionally switching between rear and front mic breaks the input until computer is shutdown or put to sleep.
-Surround Sound works, but is inconsistent. Sometimes, just updating the volume fixes it, and sometimes, it requires a restart.
Sound Blaster Z: ******************* -none that I'm aware of.
Version changes: ------------------------------------------------------------------------------- v1: ******************* -Massive patch formatting failure, please ignore v1.
v2: ******************* -Fixed patch formatting failure.
v3: ******************* -Fixed mem_base unmap, instead of checking for QUIRK_SBZ on exit, have it check if the area is mapped, and if it is, unmap it. Also make it unmap after all other commands are finished.
-Change notification of failure to map mem_base from codec_dbg to codec_warn, and use codec_info to tell the user that their card might have been incorrectly identified as a Sound Blaster Z.
-Remove commented out commands in sbz_exit_chip function, only reintroduce them when their functions are defined.
v4: ******************* -Split patch into smaller pieces.
-Added const to alt_out_presets array.
-Fixed command that was commented out and only put it in when it was actually used.
Connor McAdams (13): ALSA: hda/ca0132: R3Di and SBZ quirk entires + alt firmware loading ALSA: hda/ca0132: Add pincfg for SBZ + R3Di, add fp hp auto-detect ALSA: hda/ca0132: Add PCI region2 iomap for SBZ ALSA: hda/ca0132: Add extra exit functions for R3Di and SBZ ALSA: hda/ca0132: add extra init functions for r3di + sbz ALSA: hda/ca0132: update core functions for sbz + r3di ALSA: hda/ca0132: add dsp setup related commands for the sbz ALSA: hda/ca0132: Add dsp setup + gpio functions for r3di ALSA: hda/ca0132: add the ability to set src_id on scp commands ALSA: hda/ca0132: add alt_select_in/out for R3Di + SBZ ALSA: hda/ca0132: Add DSP Volume set and New mixers for SBZ + R3Di ALSA: hda/ca0132: add ca0132_alt_set_vipsource ALSA: hda/ca0132: Add new control changes for SBZ + R3Di
sound/pci/hda/patch_ca0132.c | 3057 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 2941 insertions(+), 116 deletions(-)
This patch adds PCI quirk ID's for the Sound Blaster Z and Recon3Di. Only the currently tested ID's have been added.
This patch also adds the ability to load alternative firmwares for each card, the firmwares can be obtained from within the Windows driver. The Recon3Di uses "ctefx-r3di.bin" and the Sound Blaster Z uses "ctefx-sbz.bin". If the alternative firmware for the given quirk is not found, the original ctefx.bin will be used. This has been confirmed to work for both the R3Di and the SBZ.
This patch also makes the character array *dirstr a const.
Signed-off-by: Connor McAdams conmanx360@gmail.com --- sound/pci/hda/patch_ca0132.c | 61 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 55 insertions(+), 6 deletions(-)
diff --git a/sound/pci/hda/patch_ca0132.c b/sound/pci/hda/patch_ca0132.c index 768ea86..8346100 100644 --- a/sound/pci/hda/patch_ca0132.c +++ b/sound/pci/hda/patch_ca0132.c @@ -72,12 +72,16 @@ #define SCP_GET 1
#define EFX_FILE "ctefx.bin" +#define SBZ_EFX_FILE "ctefx-sbz.bin" +#define R3DI_EFX_FILE "ctefx-r3di.bin"
#ifdef CONFIG_SND_HDA_CODEC_CA0132_DSP MODULE_FIRMWARE(EFX_FILE); +MODULE_FIRMWARE(SBZ_EFX_FILE); +MODULE_FIRMWARE(R3DI_EFX_FILE); #endif
-static char *dirstr[2] = { "Playback", "Capture" }; +static const char *dirstr[2] = { "Playback", "Capture" };
enum { SPEAKER_OUT, @@ -734,6 +738,7 @@ struct ca0132_spec { unsigned int scp_resp_header; unsigned int scp_resp_data[4]; unsigned int scp_resp_count; + bool alt_firmware_present;
/* mixer and effects related */ unsigned char dmic_ctl; @@ -762,6 +767,8 @@ struct ca0132_spec { enum { QUIRK_NONE, QUIRK_ALIENWARE, + QUIRK_SBZ, + QUIRK_R3DI, };
static const struct hda_pintbl alienware_pincfgs[] = { @@ -782,6 +789,10 @@ static const struct snd_pci_quirk ca0132_quirks[] = { SND_PCI_QUIRK(0x1028, 0x0685, "Alienware 15 2015", QUIRK_ALIENWARE), SND_PCI_QUIRK(0x1028, 0x0688, "Alienware 17 2015", QUIRK_ALIENWARE), SND_PCI_QUIRK(0x1028, 0x0708, "Alienware 15 R2 2016", QUIRK_ALIENWARE), + SND_PCI_QUIRK(0x1102, 0x0010, "Sound Blaster Z", QUIRK_SBZ), + SND_PCI_QUIRK(0x1102, 0x0023, "Sound Blaster Z", QUIRK_SBZ), + SND_PCI_QUIRK(0x1458, 0xA016, "Recon3Di", QUIRK_R3DI), + SND_PCI_QUIRK(0x1458, 0xA036, "Recon3Di", QUIRK_R3DI), {} };
@@ -3207,7 +3218,7 @@ static int ca0132_select_out(struct hda_codec *codec) pin_ctl & ~PIN_HP); /* enable speaker node */ pin_ctl = snd_hda_codec_read(codec, spec->out_pins[0], 0, - AC_VERB_GET_PIN_WIDGET_CONTROL, 0); + AC_VERB_GET_PIN_WIDGET_CONTROL, 0); snd_hda_set_pin_ctl(codec, spec->out_pins[0], pin_ctl | PIN_OUT); } else { @@ -4370,11 +4381,49 @@ static void ca0132_set_dsp_msr(struct hda_codec *codec, bool is96k) static bool ca0132_download_dsp_images(struct hda_codec *codec) { bool dsp_loaded = false; + struct ca0132_spec *spec = codec->spec; const struct dsp_image_seg *dsp_os_image; const struct firmware *fw_entry; - - if (request_firmware(&fw_entry, EFX_FILE, codec->card->dev) != 0) - return false; + /* + * Alternate firmwares for different variants. The Recon3Di apparently + * can use the default firmware, but I'll leave the option in case + * it needs it again. + */ + switch (spec->quirk) { + case QUIRK_SBZ: + if (request_firmware(&fw_entry, SBZ_EFX_FILE, + codec->card->dev) != 0) { + codec_dbg(codec, "SBZ alt firmware not detected. "); + spec->alt_firmware_present = false; + } else { + codec_dbg(codec, "Sound Blaster Z firmware selected."); + spec->alt_firmware_present = true; + } + break; + case QUIRK_R3DI: + if (request_firmware(&fw_entry, R3DI_EFX_FILE, + codec->card->dev) != 0) { + codec_dbg(codec, "Recon3Di alt firmware not detected."); + spec->alt_firmware_present = false; + } else { + codec_dbg(codec, "Recon3Di firmware selected."); + spec->alt_firmware_present = true; + } + break; + default: + spec->alt_firmware_present = false; + break; + } + /* + * Use default ctefx.bin if no alt firmware is detected, or if none + * exists for your particular codec. + */ + if (!spec->alt_firmware_present) { + codec_dbg(codec, "Default firmware selected."); + if (request_firmware(&fw_entry, EFX_FILE, + codec->card->dev) != 0) + return false; + }
dsp_os_image = (struct dsp_image_seg *)(fw_entry->data); if (dspload_image(codec, dsp_os_image, 0, 0, true, 0)) { @@ -4476,7 +4525,7 @@ static struct hda_verb ca0132_base_exit_verbs[] = { {} };
-/* Other verbs tables. Sends after DSP download. */ +/* Other verbs tables. Sends after DSP download. */ static struct hda_verb ca0132_init_verbs0[] = { /* chip init verbs */ {0x15, 0x70D, 0xF0},
This patch adds an unsolicited response tag for the front headphone panel which uses the same hp_callback as the rear headphone detection.
This patch also adds pincfgs for the R3Di and SBZ which were taken from the Windows driver. The pins are also defined in the function ca0132_config. Both the R3Di and SBZ are also given a max out channel value of 6 to handle 5.1 surround sound in later patches.
Signed-off-by: Connor McAdams conmanx360@gmail.com --- sound/pci/hda/patch_ca0132.c | 111 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 108 insertions(+), 3 deletions(-)
diff --git a/sound/pci/hda/patch_ca0132.c b/sound/pci/hda/patch_ca0132.c index 8346100..02238fe 100644 --- a/sound/pci/hda/patch_ca0132.c +++ b/sound/pci/hda/patch_ca0132.c @@ -723,6 +723,7 @@ struct ca0132_spec { hda_nid_t shared_mic_nid; hda_nid_t shared_out_nid; hda_nid_t unsol_tag_hp; + hda_nid_t unsol_tag_front_hp; /* for desktop ca0132 codecs */ hda_nid_t unsol_tag_amic1;
/* chip access */ @@ -785,6 +786,36 @@ static const struct hda_pintbl alienware_pincfgs[] = { {} };
+/* Sound Blaster Z pin configs taken from Windows Driver */ +static const struct hda_pintbl sbz_pincfgs[] = { + { 0x0b, 0x01017010 }, /* Port G -- Lineout FRONT L/R */ + { 0x0c, 0x014510f0 }, /* SPDIF Out 1 */ + { 0x0d, 0x014510f0 }, /* Digital Out */ + { 0x0e, 0x01c510f0 }, /* SPDIF In */ + { 0x0f, 0x0221701f }, /* Port A -- BackPanel HP */ + { 0x10, 0x01017012 }, /* Port D -- Center/LFE or FP Hp */ + { 0x11, 0x01017014 }, /* Port B -- LineMicIn2 / Rear L/R */ + { 0x12, 0x01a170f0 }, /* Port C -- LineIn1 */ + { 0x13, 0x908700f0 }, /* What U Hear In*/ + { 0x18, 0x50d000f0 }, /* N/A */ + {} +}; + +/* Recon3D integrated pin configs taken from Windows Driver */ +static const struct hda_pintbl r3di_pincfgs[] = { + { 0x0b, 0x01014110 }, /* Port G -- Lineout FRONT L/R */ + { 0x0c, 0x014510f0 }, /* SPDIF Out 1 */ + { 0x0d, 0x014510f0 }, /* Digital Out */ + { 0x0e, 0x41c520f0 }, /* SPDIF In */ + { 0x0f, 0x0221401f }, /* Port A -- BackPanel HP */ + { 0x10, 0x01016011 }, /* Port D -- Center/LFE or FP Hp */ + { 0x11, 0x01011014 }, /* Port B -- LineMicIn2 / Rear L/R */ + { 0x12, 0x02a090f0 }, /* Port C -- LineIn1 */ + { 0x13, 0x908700f0 }, /* What U Hear In*/ + { 0x18, 0x500000f0 }, /* N/A */ + {} +}; + static const struct snd_pci_quirk ca0132_quirks[] = { SND_PCI_QUIRK(0x1028, 0x0685, "Alienware 15 2015", QUIRK_ALIENWARE), SND_PCI_QUIRK(0x1028, 0x0688, "Alienware 17 2015", QUIRK_ALIENWARE), @@ -4503,6 +4534,10 @@ static void ca0132_init_unsol(struct hda_codec *codec) amic_callback); snd_hda_jack_detect_enable_callback(codec, UNSOL_TAG_DSP, ca0132_process_dsp_response); + /* Front headphone jack detection */ + if (spec->quirk == QUIRK_SBZ || spec->quirk == QUIRK_R3DI) + snd_hda_jack_detect_enable_callback(codec, + spec->unsol_tag_front_hp, hp_callback); }
/* @@ -4684,9 +4719,14 @@ static void ca0132_config(struct hda_codec *codec)
spec->multiout.dac_nids = spec->dacs; spec->multiout.num_dacs = 3; - spec->multiout.max_channels = 2;
- if (spec->quirk == QUIRK_ALIENWARE) { + if (spec->quirk == QUIRK_NONE || spec->quirk == QUIRK_ALIENWARE) + spec->multiout.max_channels = 2; + else + spec->multiout.max_channels = 6; + + switch (spec->quirk) { + case QUIRK_ALIENWARE: codec_dbg(codec, "ca0132_config: QUIRK_ALIENWARE applied.\n"); snd_hda_apply_pincfgs(codec, alienware_pincfgs);
@@ -4706,7 +4746,71 @@ static void ca0132_config(struct hda_codec *codec) spec->input_pins[2] = 0x13; spec->shared_mic_nid = 0x7; spec->unsol_tag_amic1 = 0x11; - } else { + break; + case QUIRK_SBZ: + codec_dbg(codec, "%s: QUIRK_SBZ applied.\n", __func__); + snd_hda_apply_pincfgs(codec, sbz_pincfgs); + + spec->num_outputs = 2; + spec->out_pins[0] = 0x0B; /* Line out */ + spec->out_pins[1] = 0x0F; /* Rear headphone out */ + spec->out_pins[2] = 0x10; /* Front Headphone / Center/LFE*/ + spec->out_pins[3] = 0x11; /* Rear surround */ + spec->shared_out_nid = 0x2; + spec->unsol_tag_hp = spec->out_pins[1]; + spec->unsol_tag_front_hp = spec->out_pins[2]; + + spec->adcs[0] = 0x7; /* Rear Mic / Line-in */ + spec->adcs[1] = 0x8; /* Front Mic, but only if no DSP */ + spec->adcs[2] = 0xa; /* what u hear */ + + spec->num_inputs = 2; + spec->input_pins[0] = 0x12; /* Rear Mic / Line-in */ + spec->input_pins[1] = 0x13; /* What U Hear */ + spec->shared_mic_nid = 0x7; + spec->unsol_tag_amic1 = spec->input_pins[0]; + + /* SPDIF I/O */ + spec->dig_out = 0x05; + spec->multiout.dig_out_nid = spec->dig_out; + cfg->dig_out_pins[0] = 0x0c; + cfg->dig_outs = 1; + cfg->dig_out_type[0] = HDA_PCM_TYPE_SPDIF; + spec->dig_in = 0x09; + cfg->dig_in_pin = 0x0e; + cfg->dig_in_type = HDA_PCM_TYPE_SPDIF; + break; + case QUIRK_R3DI: + codec_dbg(codec, "%s: QUIRK_R3DI applied.\n", __func__); + snd_hda_apply_pincfgs(codec, r3di_pincfgs); + + spec->num_outputs = 2; + spec->out_pins[0] = 0x0B; /* Line out */ + spec->out_pins[1] = 0x0F; /* Rear headphone out */ + spec->out_pins[2] = 0x10; /* Front Headphone / Center/LFE*/ + spec->out_pins[3] = 0x11; /* Rear surround */ + spec->shared_out_nid = 0x2; + spec->unsol_tag_hp = spec->out_pins[1]; + spec->unsol_tag_front_hp = spec->out_pins[2]; + + spec->adcs[0] = 0x07; /* Rear Mic / Line-in */ + spec->adcs[1] = 0x08; /* Front Mic, but only if no DSP */ + spec->adcs[2] = 0x0a; /* what u hear */ + + spec->num_inputs = 2; + spec->input_pins[0] = 0x12; /* Rear Mic / Line-in */ + spec->input_pins[1] = 0x13; /* What U Hear */ + spec->shared_mic_nid = 0x7; + spec->unsol_tag_amic1 = spec->input_pins[0]; + + /* SPDIF I/O */ + spec->dig_out = 0x05; + spec->multiout.dig_out_nid = spec->dig_out; + cfg->dig_out_pins[0] = 0x0c; + cfg->dig_outs = 1; + cfg->dig_out_type[0] = HDA_PCM_TYPE_SPDIF; + break; + default: spec->num_outputs = 2; spec->out_pins[0] = 0x0b; /* speaker out */ spec->out_pins[1] = 0x10; /* headphone out */ @@ -4733,6 +4837,7 @@ static void ca0132_config(struct hda_codec *codec) spec->dig_in = 0x09; cfg->dig_in_pin = 0x0e; cfg->dig_in_type = HDA_PCM_TYPE_SPDIF; + break; } }
This patch adds iomapping for the region2 section of memory on the SBZ. This memory region is used in later patches for setting inputs and outputs. If the mapping fails, the quirk is changed back to QUIRK_NONE to avoid attempts to write to uninitialized memory.
It also adds a new exit sequence to unmap the iomem for the SBZ.
Signed-off-by: Connor McAdams conmanx360@gmail.com --- sound/pci/hda/patch_ca0132.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+)
diff --git a/sound/pci/hda/patch_ca0132.c b/sound/pci/hda/patch_ca0132.c index 02238fe..78d2c26 100644 --- a/sound/pci/hda/patch_ca0132.c +++ b/sound/pci/hda/patch_ca0132.c @@ -29,6 +29,9 @@ #include <linux/firmware.h> #include <linux/kernel.h> #include <sound/core.h> +#include <linux/types.h> +#include <linux/io.h> +#include <linux/pci.h> #include "hda_codec.h" #include "hda_local.h" #include "hda_auto_parser.h" @@ -760,6 +763,11 @@ struct ca0132_spec { #ifdef ENABLE_TUNING_CONTROLS long cur_ctl_vals[TUNING_CTLS_COUNT]; #endif + /* + * Sound Blaster Z PCI region 2 iomem, used for input and output + * switching, and other unknown commands. + */ + void __iomem *mem_base; };
/* @@ -4696,6 +4704,8 @@ static void ca0132_free(struct hda_codec *codec) snd_hda_sequence_write(codec, spec->base_exit_verbs); ca0132_exit_chip(codec); snd_hda_power_down(codec); + if (spec->mem_base) + iounmap(spec->mem_base); kfree(spec->spec_init_verbs); kfree(codec->spec); } @@ -4911,6 +4921,15 @@ static int patch_ca0132(struct hda_codec *codec) else spec->quirk = QUIRK_NONE;
+ /* Setup BAR Region 2 for Sound Blaster Z */ + if (spec->quirk == QUIRK_SBZ) { + spec->mem_base = pci_iomap(codec->bus->pci, 2, 0xC20); + if (spec->mem_base == NULL) { + codec_warn(codec, "pci_iomap failed!"); + codec_info(codec, "perhaps this is not an SBZ?"); + spec->quirk = QUIRK_NONE; + } + } spec->dsp_state = DSP_DOWNLOAD_INIT; spec->num_mixers = 1; spec->mixers[0] = ca0132_mixer;
This patch adds extra functions for shutdown on the Sound Blaster Z and Recon3Di. The Recon3Di only has one specific functions, which sets the GPIO data pins to 0 to prevent a popping noise.
The Sound Blaster Z exit sequence was taken from Windows. Without this exit function, the card will not reload properly unless the PC has been shutdown to clear the onboard memory. There are commented out functions currently in the sbz_exit_chip function that are added in a later patch.
Also, a reboot notify function has been added, to make sure these functions are ran before a reboot. This helps when using the card through VFIO in a virtual machine, to make sure the card reloads the DSP properly.
Signed-off-by: Connor McAdams conmanx360@gmail.com --- sound/pci/hda/patch_ca0132.c | 131 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 129 insertions(+), 2 deletions(-)
diff --git a/sound/pci/hda/patch_ca0132.c b/sound/pci/hda/patch_ca0132.c index 78d2c26..5cda7a5 100644 --- a/sound/pci/hda/patch_ca0132.c +++ b/sound/pci/hda/patch_ca0132.c @@ -4641,6 +4641,115 @@ static void ca0132_init_chip(struct hda_codec *codec) #endif }
+/* + * Recon3Di exit specific commands. + */ +/* prevents popping noise on shutdown */ +static void r3di_gpio_shutdown(struct hda_codec *codec) +{ + snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DATA, 0x00); +} + +/* + * Sound Blaster Z exit specific commands. + */ +static void sbz_region2_exit(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + unsigned int i; + + for (i = 0; i < 4; i++) + writeb(0x0, spec->mem_base + 0x100); + for (i = 0; i < 8; i++) + writeb(0xb3, spec->mem_base + 0x304); + /* + * I believe these are GPIO, with the right most hex digit being the + * gpio pin, and the second digit being on or off. We see this more in + * the input/output select functions. + */ + writew(0x0000, spec->mem_base + 0x320); + writew(0x0001, spec->mem_base + 0x320); + writew(0x0104, spec->mem_base + 0x320); + writew(0x0005, spec->mem_base + 0x320); + writew(0x0007, spec->mem_base + 0x320); +} + +static void sbz_set_pin_ctl_default(struct hda_codec *codec) +{ + hda_nid_t pins[5] = {0x0B, 0x0C, 0x0E, 0x12, 0x13}; + unsigned int i; + + snd_hda_codec_write(codec, 0x11, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40); + + for (i = 0; i < 5; i++) + snd_hda_codec_write(codec, pins[i], 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, 0x00); +} + +static void sbz_clear_unsolicited(struct hda_codec *codec) +{ + hda_nid_t pins[7] = {0x0B, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13}; + unsigned int i; + + for (i = 0; i < 7; i++) { + snd_hda_codec_write(codec, pins[i], 0, + AC_VERB_SET_UNSOLICITED_ENABLE, 0x00); + } +} + +/* On shutdown, sends commands in sets of three */ +static void sbz_gpio_shutdown_commands(struct hda_codec *codec, int dir, + int mask, int data) +{ + if (dir >= 0) + snd_hda_codec_write(codec, 0x01, 0, + AC_VERB_SET_GPIO_DIRECTION, dir); + if (mask >= 0) + snd_hda_codec_write(codec, 0x01, 0, + AC_VERB_SET_GPIO_MASK, mask); + + if (data >= 0) + snd_hda_codec_write(codec, 0x01, 0, + AC_VERB_SET_GPIO_DATA, data); +} + +static void sbz_exit_chip(struct hda_codec *codec) +{ + + /* Mess with GPIO */ + sbz_gpio_shutdown_commands(codec, 0x07, 0x07, -1); + sbz_gpio_shutdown_commands(codec, 0x07, 0x07, 0x05); + sbz_gpio_shutdown_commands(codec, 0x07, 0x07, 0x01); + + + chipio_set_conn_rate(codec, 0x41, SR_192_000); + chipio_set_conn_rate(codec, 0x91, SR_192_000); + + chipio_write(codec, 0x18a020, 0x00000083); + + sbz_gpio_shutdown_commands(codec, 0x07, 0x07, 0x03); + sbz_gpio_shutdown_commands(codec, 0x07, 0x07, 0x07); + sbz_gpio_shutdown_commands(codec, 0x07, 0x07, 0x06); + + + chipio_set_control_param(codec, 0x0D, 0x24); + + sbz_clear_unsolicited(codec); + sbz_set_pin_ctl_default(codec); + + snd_hda_codec_write(codec, 0x0B, 0, + AC_VERB_SET_EAPD_BTLENABLE, 0x00); + + if (dspload_is_loaded(codec)) + dsp_reset(codec); + + snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0, + VENDOR_CHIPIO_CT_EXTENSIONS_ENABLE, 0x00); + + sbz_region2_exit(codec); +} + static void ca0132_exit_chip(struct hda_codec *codec) { /* put any chip cleanup stuffs here. */ @@ -4701,8 +4810,20 @@ static void ca0132_free(struct hda_codec *codec)
cancel_delayed_work_sync(&spec->unsol_hp_work); snd_hda_power_up(codec); - snd_hda_sequence_write(codec, spec->base_exit_verbs); - ca0132_exit_chip(codec); + switch (spec->quirk) { + case QUIRK_SBZ: + sbz_exit_chip(codec); + break; + case QUIRK_R3DI: + r3di_gpio_shutdown(codec); + snd_hda_sequence_write(codec, spec->base_exit_verbs); + ca0132_exit_chip(codec); + break; + default: + snd_hda_sequence_write(codec, spec->base_exit_verbs); + ca0132_exit_chip(codec); + break; + } snd_hda_power_down(codec); if (spec->mem_base) iounmap(spec->mem_base); @@ -4710,12 +4831,18 @@ static void ca0132_free(struct hda_codec *codec) kfree(codec->spec); }
+static void ca0132_reboot_notify(struct hda_codec *codec) +{ + codec->patch_ops.free(codec); +} + static const struct hda_codec_ops ca0132_patch_ops = { .build_controls = ca0132_build_controls, .build_pcms = ca0132_build_pcms, .init = ca0132_init, .free = ca0132_free, .unsol_event = snd_hda_jack_unsol_event, + .reboot_notify = ca0132_reboot_notify, };
static void ca0132_config(struct hda_codec *codec)
This patch adds extra init functions for the Sound Blaster Z and Recon3Di. It also adds more checks to make sure that the DSP isn't downloaded twice on startup, by checking if the dsp_state is already set to DSP_DOWNLOADED. It also adds the ability to re-download the DSP on a resume.
It also changes the init verbs table to apply to all codecs, and takes the two specific end verbs and puts them into a separate function in ca0132_init instead.
GPIO functions are also added.
Signed-off-by: Connor McAdams conmanx360@gmail.com --- sound/pci/hda/patch_ca0132.c | 273 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 265 insertions(+), 8 deletions(-)
diff --git a/sound/pci/hda/patch_ca0132.c b/sound/pci/hda/patch_ca0132.c index 5cda7a5..5002311 100644 --- a/sound/pci/hda/patch_ca0132.c +++ b/sound/pci/hda/patch_ca0132.c @@ -710,6 +710,7 @@ struct ca0132_spec { const struct hda_verb *base_init_verbs; const struct hda_verb *base_exit_verbs; const struct hda_verb *chip_init_verbs; + const struct hda_verb *sbz_init_verbs; struct hda_verb *spec_init_verbs; struct auto_pin_cfg autocfg;
@@ -743,6 +744,7 @@ struct ca0132_spec { unsigned int scp_resp_data[4]; unsigned int scp_resp_count; bool alt_firmware_present; + bool dsp_reload;
/* mixer and effects related */ unsigned char dmic_ctl; @@ -2740,6 +2742,59 @@ static bool dspload_wait_loaded(struct hda_codec *codec) }
/* + * Setup GPIO for the other variants of Core3D. + */ + +/* + * Sets up the GPIO pins so that they are discoverable. If this isn't done, + * the card shows as having no GPIO pins. + */ +static void ca0132_gpio_init(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + + switch (spec->quirk) { + case QUIRK_SBZ: + snd_hda_codec_write(codec, 0x01, 0, 0x793, 0x00); + snd_hda_codec_write(codec, 0x01, 0, 0x794, 0x53); + snd_hda_codec_write(codec, 0x01, 0, 0x790, 0x23); + break; + case QUIRK_R3DI: + snd_hda_codec_write(codec, 0x01, 0, 0x793, 0x00); + snd_hda_codec_write(codec, 0x01, 0, 0x794, 0x5B); + break; + } + +} + +/* Sets the GPIO for audio output. */ +static void ca0132_gpio_setup(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + + switch (spec->quirk) { + case QUIRK_SBZ: + snd_hda_codec_write(codec, 0x01, 0, + AC_VERB_SET_GPIO_DIRECTION, 0x07); + snd_hda_codec_write(codec, 0x01, 0, + AC_VERB_SET_GPIO_MASK, 0x07); + snd_hda_codec_write(codec, 0x01, 0, + AC_VERB_SET_GPIO_DATA, 0x04); + snd_hda_codec_write(codec, 0x01, 0, + AC_VERB_SET_GPIO_DATA, 0x06); + break; + case QUIRK_R3DI: + snd_hda_codec_write(codec, 0x01, 0, + AC_VERB_SET_GPIO_DIRECTION, 0x1E); + snd_hda_codec_write(codec, 0x01, 0, + AC_VERB_SET_GPIO_MASK, 0x1F); + snd_hda_codec_write(codec, 0x01, 0, + AC_VERB_SET_GPIO_DATA, 0x0C); + break; + } +} + +/* * PCM callbacks */ static int ca0132_playback_pcm_prepare(struct hda_pcm_stream *hinfo, @@ -4490,11 +4545,14 @@ static void ca0132_download_dsp(struct hda_codec *codec) return; /* don't retry failures */
chipio_enable_clocks(codec); - spec->dsp_state = DSP_DOWNLOADING; - if (!ca0132_download_dsp_images(codec)) - spec->dsp_state = DSP_DOWNLOAD_FAILED; - else - spec->dsp_state = DSP_DOWNLOADED; + if (spec->dsp_state != DSP_DOWNLOADED) { + spec->dsp_state = DSP_DOWNLOADING; + + if (!ca0132_download_dsp_images(codec)) + spec->dsp_state = DSP_DOWNLOAD_FAILED; + else + spec->dsp_state = DSP_DOWNLOADED; + }
if (spec->dsp_state == DSP_DOWNLOADED) ca0132_set_dsp_msr(codec, true); @@ -4569,6 +4627,7 @@ static struct hda_verb ca0132_base_exit_verbs[] = { };
/* Other verbs tables. Sends after DSP download. */ + static struct hda_verb ca0132_init_verbs0[] = { /* chip init verbs */ {0x15, 0x70D, 0xF0}, @@ -4598,8 +4657,27 @@ static struct hda_verb ca0132_init_verbs0[] = { {0x15, 0x546, 0xC9}, {0x15, 0x53B, 0xCE}, {0x15, 0x5E8, 0xC9}, - {0x15, 0x717, 0x0D}, - {0x15, 0x718, 0x20}, + {} +}; + +/* Extra init verbs for SBZ */ +static struct hda_verb sbz_init_verbs[] = { + {0x15, 0x70D, 0x20}, + {0x15, 0x70E, 0x19}, + {0x15, 0x707, 0x00}, + {0x15, 0x539, 0xCE}, + {0x15, 0x546, 0xC9}, + {0x15, 0x70D, 0xB7}, + {0x15, 0x70E, 0x09}, + {0x15, 0x707, 0x10}, + {0x15, 0x70D, 0xAF}, + {0x15, 0x70E, 0x09}, + {0x15, 0x707, 0x01}, + {0x15, 0x707, 0x05}, + {0x15, 0x70D, 0x73}, + {0x15, 0x70E, 0x09}, + {0x15, 0x707, 0x14}, + {0x15, 0x6FF, 0xC4}, {} };
@@ -4758,16 +4836,166 @@ static void ca0132_exit_chip(struct hda_codec *codec) dsp_reset(codec); }
+/* + * This is for the extra volume verbs 0x797 (left) and 0x798 (right). These add + * extra precision for decibel values. If you had the dB value in floating point + * you would take the value after the decimal point, multiply by 64, and divide + * by 2. So for 8.59, it's (59 * 64) / 100. Useful if someone wanted to + * implement fixed point or floating point dB volumes. For now, I'll set them + * to 0 just incase a value has lingered from a boot into Windows. + */ +static void ca0132_alt_vol_setup(struct hda_codec *codec) +{ + snd_hda_codec_write(codec, 0x02, 0, 0x797, 0x00); + snd_hda_codec_write(codec, 0x02, 0, 0x798, 0x00); + snd_hda_codec_write(codec, 0x03, 0, 0x797, 0x00); + snd_hda_codec_write(codec, 0x03, 0, 0x798, 0x00); + snd_hda_codec_write(codec, 0x04, 0, 0x797, 0x00); + snd_hda_codec_write(codec, 0x04, 0, 0x798, 0x00); + snd_hda_codec_write(codec, 0x07, 0, 0x797, 0x00); + snd_hda_codec_write(codec, 0x07, 0, 0x798, 0x00); +} + +/* + * Extra commands that don't really fit anywhere else. + */ +static void sbz_pre_dsp_setup(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + + writel(0x00820680, spec->mem_base + 0x01C); + writel(0x00820680, spec->mem_base + 0x01C); + + snd_hda_codec_write(codec, 0x15, 0, 0xd00, 0xfc); + snd_hda_codec_write(codec, 0x15, 0, 0xd00, 0xfd); + snd_hda_codec_write(codec, 0x15, 0, 0xd00, 0xfe); + snd_hda_codec_write(codec, 0x15, 0, 0xd00, 0xff); + + chipio_write(codec, 0x18b0a4, 0x000000c2); + + snd_hda_codec_write(codec, 0x11, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, 0x44); +} + +/* + * Extra commands that don't really fit anywhere else. + */ +static void r3di_pre_dsp_setup(struct hda_codec *codec) +{ + chipio_write(codec, 0x18b0a4, 0x000000c2); + + snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0, + VENDOR_CHIPIO_8051_ADDRESS_LOW, 0x1E); + snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0, + VENDOR_CHIPIO_8051_ADDRESS_HIGH, 0x1C); + snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0, + VENDOR_CHIPIO_8051_DATA_WRITE, 0x5B); + + snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0, + VENDOR_CHIPIO_8051_ADDRESS_LOW, 0x20); + snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0, + VENDOR_CHIPIO_8051_ADDRESS_HIGH, 0x19); + snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0, + VENDOR_CHIPIO_8051_DATA_WRITE, 0x00); + snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0, + VENDOR_CHIPIO_8051_DATA_WRITE, 0x40); + + snd_hda_codec_write(codec, 0x11, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, 0x04); +} + + +/* + * These are sent before the DSP is downloaded. Not sure + * what they do, or if they're necessary. Could possibly + * be removed. Figure they're better to leave in. + */ +static void sbz_region2_startup(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + + writel(0x00000000, spec->mem_base + 0x400); + writel(0x00000000, spec->mem_base + 0x408); + writel(0x00000000, spec->mem_base + 0x40C); + writel(0x00880680, spec->mem_base + 0x01C); + writel(0x00000083, spec->mem_base + 0xC0C); + writel(0x00000030, spec->mem_base + 0xC00); + writel(0x00000000, spec->mem_base + 0xC04); + writel(0x00000003, spec->mem_base + 0xC0C); + writel(0x00000003, spec->mem_base + 0xC0C); + writel(0x00000003, spec->mem_base + 0xC0C); + writel(0x00000003, spec->mem_base + 0xC0C); + writel(0x000000C1, spec->mem_base + 0xC08); + writel(0x000000F1, spec->mem_base + 0xC08); + writel(0x00000001, spec->mem_base + 0xC08); + writel(0x000000C7, spec->mem_base + 0xC08); + writel(0x000000C1, spec->mem_base + 0xC08); + writel(0x00000080, spec->mem_base + 0xC04); +} + +/* + * Extra init functions for alternative ca0132 codecs. Done + * here so they don't clutter up the main ca0132_init function + * anymore than they have to. + */ +static void ca0132_alt_init(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + + ca0132_alt_vol_setup(codec); + + switch (spec->quirk) { + case QUIRK_SBZ: + codec_dbg(codec, "SBZ alt_init"); + ca0132_gpio_init(codec); + sbz_pre_dsp_setup(codec); + snd_hda_sequence_write(codec, spec->chip_init_verbs); + snd_hda_sequence_write(codec, spec->sbz_init_verbs); + break; + case QUIRK_R3DI: + codec_dbg(codec, "R3DI alt_init"); + ca0132_gpio_init(codec); + ca0132_gpio_setup(codec); + r3di_pre_dsp_setup(codec); + snd_hda_sequence_write(codec, spec->chip_init_verbs); + snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0, 0x6FF, 0xC4); + break; + } +} + static int ca0132_init(struct hda_codec *codec) { struct ca0132_spec *spec = codec->spec; struct auto_pin_cfg *cfg = &spec->autocfg; int i; + bool dsp_loaded; + + /* + * If the DSP is already downloaded, and init has been entered again, + * there's only two reasons for it. One, the codec has awaken from a + * suspended state, and in that case dspload_is_loaded will return + * false, and the init will be ran again. The other reason it gets + * re entered is on startup for some reason it triggers a suspend and + * resume state. In this case, it will check if the DSP is downloaded, + * and not run the init function again. For codecs using alt_functions, + * it will check if the DSP is loaded properly. + */ + if (spec->dsp_state == DSP_DOWNLOADED) { + dsp_loaded = dspload_is_loaded(codec); + if (!dsp_loaded) { + spec->dsp_reload = true; + spec->dsp_state = DSP_DOWNLOAD_INIT; + } else + return 0; + }
if (spec->dsp_state != DSP_DOWNLOAD_FAILED) spec->dsp_state = DSP_DOWNLOAD_INIT; spec->curr_chip_addx = INVALID_CHIP_ADDRESS;
+ if (spec->quirk == QUIRK_SBZ) + sbz_region2_startup(codec); + snd_hda_power_up_pm(codec);
ca0132_init_unsol(codec); @@ -4775,8 +5003,16 @@ static int ca0132_init(struct hda_codec *codec) ca0132_init_params(codec); ca0132_init_flags(codec); snd_hda_sequence_write(codec, spec->base_init_verbs); + + if (spec->quirk != QUIRK_NONE) + ca0132_alt_init(codec); + ca0132_download_dsp(codec); ca0132_refresh_widget_caps(codec); + + if (spec->quirk == QUIRK_SBZ) + writew(0x0107, spec->mem_base + 0x320); + ca0132_setup_defaults(codec); ca0132_init_analog_mic2(codec); ca0132_init_dmic(codec); @@ -4791,7 +5027,17 @@ static int ca0132_init(struct hda_codec *codec)
init_input(codec, cfg->dig_in_pin, spec->dig_in);
- snd_hda_sequence_write(codec, spec->chip_init_verbs); + if (spec->quirk == QUIRK_ALIENWARE || spec->quirk == QUIRK_NONE) { + snd_hda_sequence_write(codec, spec->chip_init_verbs); + snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0, + VENDOR_CHIPIO_PARAM_EX_ID_SET, 0x0D); + snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0, + VENDOR_CHIPIO_PARAM_EX_VALUE_SET, 0x20); + } + + if (spec->quirk == QUIRK_SBZ) + ca0132_gpio_setup(codec); + snd_hda_sequence_write(codec, spec->spec_init_verbs);
ca0132_select_out(codec); @@ -4799,6 +5045,15 @@ static int ca0132_init(struct hda_codec *codec)
snd_hda_jack_report_sync(codec);
+ /* + * Re set the PlayEnhancement switch on a resume event, because the + * controls will not be reloaded. + */ + if (spec->dsp_reload) { + spec->dsp_reload = false; + ca0132_pe_switch_set(codec); + } + snd_hda_power_down_pm(codec);
return 0; @@ -4985,6 +5240,8 @@ static int ca0132_prepare_verbs(struct hda_codec *codec) struct ca0132_spec *spec = codec->spec;
spec->chip_init_verbs = ca0132_init_verbs0; + if (spec->quirk == QUIRK_SBZ) + spec->sbz_init_verbs = sbz_init_verbs; spec->spec_init_verbs = kzalloc(sizeof(struct hda_verb) * NUM_SPEC_VERBS, GFP_KERNEL); if (!spec->spec_init_verbs) return -ENOMEM;
This patch updates core functions to accommodate the Sound Blaster Z and Recon3Di by changing which functions they use. It also adds the ability to enable/disable streams.
Signed-off-by: Connor McAdams conmanx360@gmail.com --- sound/pci/hda/patch_ca0132.c | 176 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 153 insertions(+), 23 deletions(-)
diff --git a/sound/pci/hda/patch_ca0132.c b/sound/pci/hda/patch_ca0132.c index 5002311..31c9d42 100644 --- a/sound/pci/hda/patch_ca0132.c +++ b/sound/pci/hda/patch_ca0132.c @@ -770,6 +770,13 @@ struct ca0132_spec { * switching, and other unknown commands. */ void __iomem *mem_base; + + /* + * Whether or not to use the alt functions like alt_select_out, + * alt_select_in, etc. Only used on desktop codecs for now, because of + * surround sound support. + */ + bool use_alt_functions; };
/* @@ -1110,6 +1117,42 @@ static void chipio_set_control_param(struct hda_codec *codec, }
/* + * Set chip parameters through the chip I/O widget. NO MUTEX. + */ +static void chipio_set_control_param_no_mutex(struct hda_codec *codec, + enum control_param_id param_id, int param_val) +{ + int val; + + if ((param_id < 32) && (param_val < 8)) { + val = (param_val << 5) | (param_id); + snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0, + VENDOR_CHIPIO_PARAM_SET, val); + } else { + if (chipio_send(codec, VENDOR_CHIPIO_STATUS, 0) == 0) { + snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0, + VENDOR_CHIPIO_PARAM_EX_ID_SET, + param_id); + snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0, + VENDOR_CHIPIO_PARAM_EX_VALUE_SET, + param_val); + } + } +} + +/* + * Enable/Disable audio stream. + */ +static void chipio_set_stream_control(struct hda_codec *codec, + int streamid, int enable) +{ + chipio_set_control_param_no_mutex(codec, + CONTROL_PARAM_STREAM_ID, streamid); + chipio_set_control_param_no_mutex(codec, + CONTROL_PARAM_STREAM_CONTROL, enable); +} + +/* * Set sampling rate of the connection point. */ static void chipio_set_conn_rate(struct hda_codec *codec, @@ -2627,14 +2670,16 @@ static int dspxfr_image(struct hda_codec *codec, */ static void dspload_post_setup(struct hda_codec *codec) { + struct ca0132_spec *spec = codec->spec; codec_dbg(codec, "---- dspload_post_setup ------\n"); + if (!spec->use_alt_functions) { + /*set DSP speaker to 2.0 configuration*/ + chipio_write(codec, XRAM_XRAM_INST_OFFSET(0x18), 0x08080080); + chipio_write(codec, XRAM_XRAM_INST_OFFSET(0x19), 0x3f800000);
- /*set DSP speaker to 2.0 configuration*/ - chipio_write(codec, XRAM_XRAM_INST_OFFSET(0x18), 0x08080080); - chipio_write(codec, XRAM_XRAM_INST_OFFSET(0x19), 0x3f800000); - - /*update write pointer*/ - chipio_write(codec, XRAM_XRAM_INST_OFFSET(0x29), 0x00000002); + /*update write pointer*/ + chipio_write(codec, XRAM_XRAM_INST_OFFSET(0x29), 0x00000002); + } }
/** @@ -3523,7 +3568,7 @@ static int ca0132_voicefx_set(struct hda_codec *codec, int enable) static int ca0132_effects_set(struct hda_codec *codec, hda_nid_t nid, long val) { struct ca0132_spec *spec = codec->spec; - unsigned int on; + unsigned int on, tmp; int num_fx = OUT_EFFECTS_COUNT + IN_EFFECTS_COUNT; int err = 0; int idx = nid - EFFECT_START_NID; @@ -3547,6 +3592,39 @@ static int ca0132_effects_set(struct hda_codec *codec, hda_nid_t nid, long val) /* Voice Focus applies to 2-ch Mic, Digital Mic */ if ((nid == VOICE_FOCUS) && (spec->cur_mic_type != DIGITAL_MIC)) val = 0; + + /* If Voice Focus on SBZ, set to two channel. */ + if ((nid == VOICE_FOCUS) && (spec->quirk == QUIRK_SBZ)) { + if (spec->effects_switch[CRYSTAL_VOICE - + EFFECT_START_NID]) { + + if (spec->effects_switch[VOICE_FOCUS - + EFFECT_START_NID]) { + tmp = FLOAT_TWO; + val = 1; + } else + tmp = FLOAT_ONE; + + dspio_set_uint_param(codec, 0x80, 0x00, tmp); + } + } + /* + * For SBZ noise reduction, there's an extra command + * to module ID 0x47. No clue why. + */ + if ((nid == NOISE_REDUCTION) && (spec->quirk == QUIRK_SBZ)) { + if (spec->effects_switch[CRYSTAL_VOICE - + EFFECT_START_NID]) { + if (spec->effects_switch[NOISE_REDUCTION - + EFFECT_START_NID]) + tmp = FLOAT_ONE; + else + tmp = FLOAT_ZERO; + } else + tmp = FLOAT_ZERO; + + dspio_set_uint_param(codec, 0x47, 0x00, tmp); + } }
codec_dbg(codec, "ca0132_effect_set: nid=0x%x, val=%ld\n", @@ -4181,12 +4259,16 @@ static int ca0132_build_pcms(struct hda_codec *codec) info->stream[SNDRV_PCM_STREAM_CAPTURE].substreams = 1; info->stream[SNDRV_PCM_STREAM_CAPTURE].nid = spec->adcs[0];
- info = snd_hda_codec_pcm_new(codec, "CA0132 Analog Mic-In2"); - if (!info) - return -ENOMEM; - info->stream[SNDRV_PCM_STREAM_CAPTURE] = ca0132_pcm_analog_capture; - info->stream[SNDRV_PCM_STREAM_CAPTURE].substreams = 1; - info->stream[SNDRV_PCM_STREAM_CAPTURE].nid = spec->adcs[1]; + /* With the DSP enabled, desktops don't use this ADC. */ + if (spec->use_alt_functions) { + info = snd_hda_codec_pcm_new(codec, "CA0132 Analog Mic-In2"); + if (!info) + return -ENOMEM; + info->stream[SNDRV_PCM_STREAM_CAPTURE] = + ca0132_pcm_analog_capture; + info->stream[SNDRV_PCM_STREAM_CAPTURE].substreams = 1; + info->stream[SNDRV_PCM_STREAM_CAPTURE].nid = spec->adcs[1]; + }
info = snd_hda_codec_pcm_new(codec, "CA0132 What U Hear"); if (!info) @@ -4441,12 +4523,32 @@ static void ca0132_setup_defaults(struct hda_codec *codec) */ static void ca0132_init_flags(struct hda_codec *codec) { - chipio_set_control_flag(codec, CONTROL_FLAG_IDLE_ENABLE, 0); - chipio_set_control_flag(codec, CONTROL_FLAG_PORT_A_COMMON_MODE, 0); - chipio_set_control_flag(codec, CONTROL_FLAG_PORT_D_COMMON_MODE, 0); - chipio_set_control_flag(codec, CONTROL_FLAG_PORT_A_10KOHM_LOAD, 0); - chipio_set_control_flag(codec, CONTROL_FLAG_PORT_D_10KOHM_LOAD, 0); - chipio_set_control_flag(codec, CONTROL_FLAG_ADC_C_HIGH_PASS, 1); + struct ca0132_spec *spec = codec->spec; + + if (spec->use_alt_functions) { + chipio_set_control_flag(codec, CONTROL_FLAG_DSP_96KHZ, 1); + chipio_set_control_flag(codec, CONTROL_FLAG_DAC_96KHZ, 1); + chipio_set_control_flag(codec, CONTROL_FLAG_ADC_B_96KHZ, 1); + chipio_set_control_flag(codec, CONTROL_FLAG_ADC_C_96KHZ, 1); + chipio_set_control_flag(codec, CONTROL_FLAG_SRC_RATE_96KHZ, 1); + chipio_set_control_flag(codec, CONTROL_FLAG_IDLE_ENABLE, 0); + chipio_set_control_flag(codec, CONTROL_FLAG_SPDIF2OUT, 0); + chipio_set_control_flag(codec, + CONTROL_FLAG_PORT_D_10KOHM_LOAD, 0); + chipio_set_control_flag(codec, + CONTROL_FLAG_PORT_A_10KOHM_LOAD, 1); + } else { + chipio_set_control_flag(codec, CONTROL_FLAG_IDLE_ENABLE, 0); + chipio_set_control_flag(codec, + CONTROL_FLAG_PORT_A_COMMON_MODE, 0); + chipio_set_control_flag(codec, + CONTROL_FLAG_PORT_D_COMMON_MODE, 0); + chipio_set_control_flag(codec, + CONTROL_FLAG_PORT_A_10KOHM_LOAD, 0); + chipio_set_control_flag(codec, + CONTROL_FLAG_PORT_D_10KOHM_LOAD, 0); + chipio_set_control_flag(codec, CONTROL_FLAG_ADC_C_HIGH_PASS, 1); + } }
/* @@ -4454,6 +4556,16 @@ static void ca0132_init_flags(struct hda_codec *codec) */ static void ca0132_init_params(struct hda_codec *codec) { + struct ca0132_spec *spec = codec->spec; + + if (spec->use_alt_functions) { + chipio_set_conn_rate(codec, MEM_CONNID_WUH, SR_48_000); + chipio_set_conn_rate(codec, 0x0B, SR_48_000); + chipio_set_control_param(codec, CONTROL_PARAM_SPDIF1_SOURCE, 0); + chipio_set_control_param(codec, 0, 0); + chipio_set_control_param(codec, CONTROL_PARAM_VIP_SOURCE, 0); + } + chipio_set_control_param(codec, CONTROL_PARAM_PORTA_160OHM_GAIN, 6); chipio_set_control_param(codec, CONTROL_PARAM_PORTD_160OHM_GAIN, 6); } @@ -4554,7 +4666,8 @@ static void ca0132_download_dsp(struct hda_codec *codec) spec->dsp_state = DSP_DOWNLOADED; }
- if (spec->dsp_state == DSP_DOWNLOADED) + /* For codecs using alt functions, this is already done earlier */ + if (spec->dsp_state == DSP_DOWNLOADED && (!spec->use_alt_functions)) ca0132_set_dsp_msr(codec, true); }
@@ -4601,7 +4714,7 @@ static void ca0132_init_unsol(struct hda_codec *codec) snd_hda_jack_detect_enable_callback(codec, UNSOL_TAG_DSP, ca0132_process_dsp_response); /* Front headphone jack detection */ - if (spec->quirk == QUIRK_SBZ || spec->quirk == QUIRK_R3DI) + if (spec->use_alt_functions) snd_hda_jack_detect_enable_callback(codec, spec->unsol_tag_front_hp, hp_callback); } @@ -4794,12 +4907,16 @@ static void sbz_gpio_shutdown_commands(struct hda_codec *codec, int dir,
static void sbz_exit_chip(struct hda_codec *codec) { + chipio_set_stream_control(codec, 0x03, 0); + chipio_set_stream_control(codec, 0x04, 0);
/* Mess with GPIO */ sbz_gpio_shutdown_commands(codec, 0x07, 0x07, -1); sbz_gpio_shutdown_commands(codec, 0x07, 0x07, 0x05); sbz_gpio_shutdown_commands(codec, 0x07, 0x07, 0x01);
+ chipio_set_stream_control(codec, 0x14, 0); + chipio_set_stream_control(codec, 0x0C, 0);
chipio_set_conn_rate(codec, 0x41, SR_192_000); chipio_set_conn_rate(codec, 0x91, SR_192_000); @@ -4810,6 +4927,7 @@ static void sbz_exit_chip(struct hda_codec *codec) sbz_gpio_shutdown_commands(codec, 0x07, 0x07, 0x07); sbz_gpio_shutdown_commands(codec, 0x07, 0x07, 0x06);
+ chipio_set_stream_control(codec, 0x0C, 0);
chipio_set_control_param(codec, 0x0D, 0x24);
@@ -5027,7 +5145,7 @@ static int ca0132_init(struct hda_codec *codec)
init_input(codec, cfg->dig_in_pin, spec->dig_in);
- if (spec->quirk == QUIRK_ALIENWARE || spec->quirk == QUIRK_NONE) { + if (!spec->use_alt_functions) { snd_hda_sequence_write(codec, spec->chip_init_verbs); snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0, VENDOR_CHIPIO_PARAM_EX_ID_SET, 0x0D); @@ -5112,7 +5230,7 @@ static void ca0132_config(struct hda_codec *codec) spec->multiout.dac_nids = spec->dacs; spec->multiout.num_dacs = 3;
- if (spec->quirk == QUIRK_NONE || spec->quirk == QUIRK_ALIENWARE) + if (!spec->use_alt_functions) spec->multiout.max_channels = 2; else spec->multiout.max_channels = 6; @@ -5314,10 +5432,22 @@ static int patch_ca0132(struct hda_codec *codec) spec->quirk = QUIRK_NONE; } } + spec->dsp_state = DSP_DOWNLOAD_INIT; spec->num_mixers = 1; spec->mixers[0] = ca0132_mixer;
+ /* Setup whether or not to use alt functions */ + switch (spec->quirk) { + case QUIRK_SBZ: + case QUIRK_R3DI: + spec->use_alt_functions = true; + break; + default: + spec->use_alt_functions = false; + break; + } + spec->base_init_verbs = ca0132_base_init_verbs; spec->base_exit_verbs = ca0132_base_exit_verbs;
Add dsp setup related functions for the Sound Blaster Z, along with other helper functions.
Also, add sbz_dsp_startup_check, which fixes a bug where the card sometimes starts up and has no sound.
Signed-off-by: Connor McAdams conmanx360@gmail.com --- sound/pci/hda/patch_ca0132.c | 341 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 335 insertions(+), 6 deletions(-)
diff --git a/sound/pci/hda/patch_ca0132.c b/sound/pci/hda/patch_ca0132.c index 31c9d42..37609bd 100644 --- a/sound/pci/hda/patch_ca0132.c +++ b/sound/pci/hda/patch_ca0132.c @@ -45,6 +45,7 @@ #define FLOAT_ZERO 0x00000000 #define FLOAT_ONE 0x3f800000 #define FLOAT_TWO 0x40000000 +#define FLOAT_THREE 0x40400000 #define FLOAT_MINUS_5 0xc0a00000
#define UNSOL_TAG_DSP 0x16 @@ -744,6 +745,7 @@ struct ca0132_spec { unsigned int scp_resp_data[4]; unsigned int scp_resp_count; bool alt_firmware_present; + bool startup_check_entered; bool dsp_reload;
/* mixer and effects related */ @@ -1024,6 +1026,29 @@ static int chipio_write(struct hda_codec *codec, }
/* + * Write given value to the given address through the chip I/O widget. + * not protected by the Mutex + */ +static int chipio_write_no_mutex(struct hda_codec *codec, + unsigned int chip_addx, const unsigned int data) +{ + int err; + + + /* write the address, and if successful proceed to write data */ + err = chipio_write_address(codec, chip_addx); + if (err < 0) + goto exit; + + err = chipio_write_data(codec, data); + if (err < 0) + goto exit; + +exit: + return err; +} + +/* * Write multiple values to the given address through the chip I/O widget. * protected by the Mutex */ @@ -1139,6 +1164,32 @@ static void chipio_set_control_param_no_mutex(struct hda_codec *codec, } } } +/* + * Connect stream to a source point, and then connect + * that source point to a destination point. + */ +static void chipio_set_stream_source_dest(struct hda_codec *codec, + int streamid, int source_point, int dest_point) +{ + chipio_set_control_param_no_mutex(codec, + CONTROL_PARAM_STREAM_ID, streamid); + chipio_set_control_param_no_mutex(codec, + CONTROL_PARAM_STREAM_SOURCE_CONN_POINT, source_point); + chipio_set_control_param_no_mutex(codec, + CONTROL_PARAM_STREAM_DEST_CONN_POINT, dest_point); +} + +/* + * Set number of channels in the selected stream. + */ +static void chipio_set_stream_channels(struct hda_codec *codec, + int streamid, unsigned int channels) +{ + chipio_set_control_param_no_mutex(codec, + CONTROL_PARAM_STREAM_ID, streamid); + chipio_set_control_param_no_mutex(codec, + CONTROL_PARAM_STREAMS_CHANNELS, channels); +}
/* * Enable/Disable audio stream. @@ -1152,6 +1203,19 @@ static void chipio_set_stream_control(struct hda_codec *codec, CONTROL_PARAM_STREAM_CONTROL, enable); }
+ +/* + * Set sampling rate of the connection point. NO MUTEX. + */ +static void chipio_set_conn_rate_no_mutex(struct hda_codec *codec, + int connid, enum ca0132_sample_rate rate) +{ + chipio_set_control_param_no_mutex(codec, + CONTROL_PARAM_CONN_POINT_ID, connid); + chipio_set_control_param_no_mutex(codec, + CONTROL_PARAM_CONN_POINT_SAMPLE_RATE, rate); +} + /* * Set sampling rate of the connection point. */ @@ -4475,6 +4539,123 @@ static void ca0132_refresh_widget_caps(struct hda_codec *codec) }
/* + * Initialize Sound Blaster Z analog microphones. + */ +static void sbz_init_analog_mics(struct hda_codec *codec) +{ + unsigned int tmp; + + /* Mic 1 Setup */ + chipio_set_conn_rate(codec, MEM_CONNID_MICIN1, SR_96_000); + chipio_set_conn_rate(codec, MEM_CONNID_MICOUT1, SR_96_000); + tmp = FLOAT_THREE; + dspio_set_uint_param(codec, 0x80, 0x00, tmp); + + /* Mic 2 Setup, even though it isn't connected on SBZ */ + chipio_set_conn_rate(codec, MEM_CONNID_MICIN2, SR_96_000); + chipio_set_conn_rate(codec, MEM_CONNID_MICOUT2, SR_96_000); + tmp = FLOAT_ZERO; + dspio_set_uint_param(codec, 0x80, 0x01, tmp); + +} + +/* + * Sets the source of stream 0x14 to connpointID 0x48, and the destination + * connpointID to 0x91. If this isn't done, the destination is 0x71, and + * you get no sound. I'm guessing this has to do with the Sound Blaster Z + * having an updated DAC, which changes the destination to that DAC. + */ +static void sbz_connect_streams(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + + mutex_lock(&spec->chipio_mutex); + + codec_dbg(codec, "Connect Streams entered, mutex locked and loaded.\n"); + + chipio_set_stream_channels(codec, 0x0C, 6); + chipio_set_stream_control(codec, 0x0C, 1); + + /* This value is 0x43 for 96khz, and 0x83 for 192khz. */ + chipio_write_no_mutex(codec, 0x18a020, 0x00000043); + + /* Setup stream 0x14 with it's source and destination points */ + chipio_set_stream_source_dest(codec, 0x14, 0x48, 0x91); + chipio_set_conn_rate_no_mutex(codec, 0x48, SR_96_000); + chipio_set_conn_rate_no_mutex(codec, 0x91, SR_96_000); + chipio_set_stream_channels(codec, 0x14, 2); + chipio_set_stream_control(codec, 0x14, 1); + + codec_dbg(codec, "Connect Streams exited, mutex released.\n"); + + mutex_unlock(&spec->chipio_mutex); + +} + +/* + * Write data through ChipIO to setup proper stream destinations. + * Not sure how it exactly works, but it seems to direct data + * to different destinations. Example is f8 to c0, e0 to c0. + * All I know is, if you don't set these, you get no sound. + */ +static void sbz_chipio_startup_data(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + + mutex_lock(&spec->chipio_mutex); + codec_dbg(codec, "Startup Data entered, mutex locked and loaded.\n"); + + /* These control audio output */ + chipio_write_no_mutex(codec, 0x190060, 0x0001f8c0); + chipio_write_no_mutex(codec, 0x190064, 0x0001f9c1); + chipio_write_no_mutex(codec, 0x190068, 0x0001fac6); + chipio_write_no_mutex(codec, 0x19006c, 0x0001fbc7); + /* Signal to update I think */ + chipio_write_no_mutex(codec, 0x19042c, 0x00000001); + + chipio_set_stream_channels(codec, 0x0C, 6); + chipio_set_stream_control(codec, 0x0C, 1); + /* No clue what these control */ + chipio_write_no_mutex(codec, 0x190030, 0x0001e0c0); + chipio_write_no_mutex(codec, 0x190034, 0x0001e1c1); + chipio_write_no_mutex(codec, 0x190038, 0x0001e4c2); + chipio_write_no_mutex(codec, 0x19003c, 0x0001e5c3); + chipio_write_no_mutex(codec, 0x190040, 0x0001e2c4); + chipio_write_no_mutex(codec, 0x190044, 0x0001e3c5); + chipio_write_no_mutex(codec, 0x190048, 0x0001e8c6); + chipio_write_no_mutex(codec, 0x19004c, 0x0001e9c7); + chipio_write_no_mutex(codec, 0x190050, 0x0001ecc8); + chipio_write_no_mutex(codec, 0x190054, 0x0001edc9); + chipio_write_no_mutex(codec, 0x190058, 0x0001eaca); + chipio_write_no_mutex(codec, 0x19005c, 0x0001ebcb); + + chipio_write_no_mutex(codec, 0x19042c, 0x00000001); + + codec_dbg(codec, "Startup Data exited, mutex released.\n"); + mutex_unlock(&spec->chipio_mutex); +} + +static void sbz_dsp_initial_mic_setup(struct hda_codec *codec) +{ + unsigned int tmp; + + chipio_set_stream_control(codec, 0x03, 0); + chipio_set_stream_control(codec, 0x04, 0); + + chipio_set_conn_rate(codec, MEM_CONNID_MICIN1, SR_96_000); + chipio_set_conn_rate(codec, MEM_CONNID_MICOUT1, SR_96_000); + + tmp = FLOAT_THREE; + dspio_set_uint_param(codec, 0x80, 0x00, tmp); + + chipio_set_stream_control(codec, 0x03, 1); + chipio_set_stream_control(codec, 0x04, 1); + + chipio_write(codec, 0x18b098, 0x0000000c); + chipio_write(codec, 0x18b09C, 0x0000000c); +} + +/* * Setup default parameters for DSP */ static void ca0132_setup_defaults(struct hda_codec *codec) @@ -4519,6 +4700,83 @@ static void ca0132_setup_defaults(struct hda_codec *codec) }
/* + * Setup default parameters for the Sound Blaster Z DSP. A lot more going on + * than the Chromebook setup. + */ +static void sbz_setup_defaults(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + unsigned int tmp, stream_format; + int num_fx; + int idx, i; + + if (spec->dsp_state != DSP_DOWNLOADED) + return; + + + sbz_init_analog_mics(codec); + + sbz_connect_streams(codec); + + sbz_chipio_startup_data(codec); + + chipio_set_stream_control(codec, 0x03, 1); + chipio_set_stream_control(codec, 0x04, 1); + + /* + * Sets internal input loopback to off, used to have a switch to + * enable input loopback, but turned out to be way too buggy. + */ + tmp = FLOAT_ONE; + dspio_set_uint_param(codec, 0x37, 0x08, tmp); + dspio_set_uint_param(codec, 0x37, 0x10, tmp); + + /*remove DSP headroom*/ + tmp = FLOAT_ZERO; + dspio_set_uint_param(codec, 0x96, 0x3C, tmp); + + /* set WUH source */ + tmp = FLOAT_TWO; + dspio_set_uint_param(codec, 0x31, 0x00, tmp); + chipio_set_conn_rate(codec, MEM_CONNID_WUH, SR_48_000); + + /* Set speaker source? */ + dspio_set_uint_param(codec, 0x32, 0x00, tmp); + + sbz_dsp_initial_mic_setup(codec); + + + /* out, in effects + voicefx */ + num_fx = OUT_EFFECTS_COUNT + IN_EFFECTS_COUNT + 1; + for (idx = 0; idx < num_fx; idx++) { + for (i = 0; i <= ca0132_effects[idx].params; i++) { + dspio_set_uint_param(codec, + ca0132_effects[idx].mid, + ca0132_effects[idx].reqs[i], + ca0132_effects[idx].def_vals[i]); + } + } + + /* + * Have to make a stream to bind the sound output to, otherwise + * you'll get dead audio. Before I did this, it would bind to an + * audio input, and would never work + */ + stream_format = snd_hdac_calc_stream_format(48000, 2, + SNDRV_PCM_FORMAT_S32_LE, 32, 0); + + snd_hda_codec_setup_stream(codec, spec->dacs[0], spec->dsp_stream_id, + 0, stream_format); + + snd_hda_codec_cleanup_stream(codec, spec->dacs[0]); + + snd_hda_codec_setup_stream(codec, spec->dacs[0], spec->dsp_stream_id, + 0, stream_format); + + snd_hda_codec_cleanup_stream(codec, spec->dacs[0]); +} + +/* * Initialization of flags in chip */ static void ca0132_init_flags(struct hda_codec *codec) @@ -4955,6 +5213,71 @@ static void ca0132_exit_chip(struct hda_codec *codec) }
/* + * This fixes a problem that was hard to reproduce. Very rarely, I would + * boot up, and there would be no sound, but the DSP indicated it had loaded + * properly. I did a few memory dumps to see if anything was different, and + * there were a few areas of memory uninitialized with a1a2a3a4. This function + * checks if those areas are uninitialized, and if they are, it'll attempt to + * reload the card 3 times. Usually it fixes by the second. + */ +static void sbz_dsp_startup_check(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + unsigned int dsp_data_check[4]; + unsigned int cur_address = 0x390; + unsigned int i; + unsigned int failure = 0; + unsigned int reload = 3; + + if (spec->startup_check_entered) + return; + + spec->startup_check_entered = true; + + for (i = 0; i < 4; i++) { + chipio_read(codec, cur_address, &dsp_data_check[i]); + cur_address += 0x4; + } + for (i = 0; i < 4; i++) { + if (dsp_data_check[i] == 0xa1a2a3a4) + failure = 1; + } + + codec_dbg(codec, "Startup Check: %d ", failure); + if (failure) + codec_info(codec, "DSP not initialized properly. Attempting to fix."); + /* + * While the failure condition is true, and we haven't reached our + * three reload limit, continue trying to reload the driver and + * fix the issue. + */ + while (failure && (reload != 0)) { + codec_info(codec, "Reloading... Tries left: %d", reload); + sbz_exit_chip(codec); + spec->dsp_state = DSP_DOWNLOAD_INIT; + codec->patch_ops.init(codec); + failure = 0; + for (i = 0; i < 4; i++) { + chipio_read(codec, cur_address, &dsp_data_check[i]); + cur_address += 0x4; + } + for (i = 0; i < 4; i++) { + if (dsp_data_check[i] == 0xa1a2a3a4) + failure = 1; + } + reload--; + } + + if (!failure && reload < 3) + codec_info(codec, "DSP fixed."); + + if (!failure) + return; + + codec_info(codec, "DSP failed to initialize properly. Either try a full shutdown or a suspend to clear the internal memory."); +} + +/* * This is for the extra volume verbs 0x797 (left) and 0x798 (right). These add * extra precision for decibel values. If you had the dB value in floating point * you would take the value after the decimal point, multiply by 64, and divide @@ -5103,8 +5426,11 @@ static int ca0132_init(struct hda_codec *codec) if (!dsp_loaded) { spec->dsp_reload = true; spec->dsp_state = DSP_DOWNLOAD_INIT; - } else + } else { + if (spec->quirk == QUIRK_SBZ) + sbz_dsp_startup_check(codec); return 0; + } }
if (spec->dsp_state != DSP_DOWNLOAD_FAILED) @@ -5117,7 +5443,6 @@ static int ca0132_init(struct hda_codec *codec) snd_hda_power_up_pm(codec);
ca0132_init_unsol(codec); - ca0132_init_params(codec); ca0132_init_flags(codec); snd_hda_sequence_write(codec, spec->base_init_verbs); @@ -5131,9 +5456,11 @@ static int ca0132_init(struct hda_codec *codec) if (spec->quirk == QUIRK_SBZ) writew(0x0107, spec->mem_base + 0x320);
- ca0132_setup_defaults(codec); - ca0132_init_analog_mic2(codec); - ca0132_init_dmic(codec); + if (spec->quirk != QUIRK_SBZ) { + ca0132_setup_defaults(codec); + ca0132_init_analog_mic2(codec); + ca0132_init_dmic(codec); + }
for (i = 0; i < spec->num_outputs; i++) init_output(codec, spec->out_pins[i], spec->dacs[0]); @@ -5153,8 +5480,10 @@ static int ca0132_init(struct hda_codec *codec) VENDOR_CHIPIO_PARAM_EX_VALUE_SET, 0x20); }
- if (spec->quirk == QUIRK_SBZ) + if (spec->quirk == QUIRK_SBZ) { ca0132_gpio_setup(codec); + sbz_setup_defaults(codec); + }
snd_hda_sequence_write(codec, spec->spec_init_verbs);
Adds dsp setup functions for Recon3Di as well as the GPIO functions specific to it.
Signed-off-by: Connor McAdams conmanx360@gmail.com --- sound/pci/hda/patch_ca0132.c | 149 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 148 insertions(+), 1 deletion(-)
diff --git a/sound/pci/hda/patch_ca0132.c b/sound/pci/hda/patch_ca0132.c index 37609bd..7a9d505 100644 --- a/sound/pci/hda/patch_ca0132.c +++ b/sound/pci/hda/patch_ca0132.c @@ -2904,6 +2904,76 @@ static void ca0132_gpio_setup(struct hda_codec *codec) }
/* + * GPIO control functions for the Recon3D integrated. + */ + +enum r3di_gpio_bit { + /* Bit 1 - Switch between front/rear mic. 0 = rear, 1 = front */ + R3DI_MIC_SELECT_BIT = 1, + /* Bit 2 - Switch between headphone/line out. 0 = Headphone, 1 = Line */ + R3DI_OUT_SELECT_BIT = 2, + /* + * I dunno what this actually does, but it stays on until the dsp + * is downloaded. + */ + R3DI_GPIO_DSP_DOWNLOADING = 3, + /* + * Same as above, no clue what it does, but it comes on after the dsp + * is downloaded. + */ + R3DI_GPIO_DSP_DOWNLOADED = 4 +}; + +enum r3di_mic_select { + /* Set GPIO bit 1 to 0 for rear mic */ + R3DI_REAR_MIC = 0, + /* Set GPIO bit 1 to 1 for front microphone*/ + R3DI_FRONT_MIC = 1 +}; + +enum r3di_out_select { + /* Set GPIO bit 2 to 0 for headphone */ + R3DI_HEADPHONE_OUT = 0, + /* Set GPIO bit 2 to 1 for speaker */ + R3DI_LINE_OUT = 1 +}; +enum r3di_dsp_status { + /* Set GPIO bit 3 to 1 until DSP is downloaded */ + R3DI_DSP_DOWNLOADING = 0, + /* Set GPIO bit 4 to 1 once DSP is downloaded */ + R3DI_DSP_DOWNLOADED = 1 +}; + +static void r3di_gpio_dsp_status_set(struct hda_codec *codec, + enum r3di_dsp_status dsp_status) +{ + unsigned int cur_gpio; + + /* Get the current GPIO Data setup */ + cur_gpio = snd_hda_codec_read(codec, 0x01, 0, AC_VERB_GET_GPIO_DATA, 0); + + switch (dsp_status) { + case R3DI_DSP_DOWNLOADING: + cur_gpio |= (1 << R3DI_GPIO_DSP_DOWNLOADING); + snd_hda_codec_write(codec, codec->core.afg, 0, + AC_VERB_SET_GPIO_DATA, cur_gpio); + break; + case R3DI_DSP_DOWNLOADED: + /* Set DOWNLOADING bit to 0. */ + cur_gpio &= ~(1 << R3DI_GPIO_DSP_DOWNLOADING); + + snd_hda_codec_write(codec, codec->core.afg, 0, + AC_VERB_SET_GPIO_DATA, cur_gpio); + + cur_gpio |= (1 << R3DI_GPIO_DSP_DOWNLOADED); + break; + } + + snd_hda_codec_write(codec, codec->core.afg, 0, + AC_VERB_SET_GPIO_DATA, cur_gpio); +} + +/* * PCM callbacks */ static int ca0132_playback_pcm_prepare(struct hda_pcm_stream *hinfo, @@ -4539,6 +4609,30 @@ static void ca0132_refresh_widget_caps(struct hda_codec *codec) }
/* + * Recon3Di r3di_setup_defaults sub functions. + */ + +static void r3di_dsp_initial_mic_setup(struct hda_codec *codec) +{ + unsigned int tmp; + + /* Mic 1 Setup */ + chipio_set_conn_rate(codec, MEM_CONNID_MICIN1, SR_96_000); + chipio_set_conn_rate(codec, MEM_CONNID_MICOUT1, SR_96_000); + /* This ConnPointID is unique to Recon3Di. Haven't seen it elsewhere */ + chipio_set_conn_rate(codec, 0x0F, SR_96_000); + tmp = FLOAT_ONE; + dspio_set_uint_param(codec, 0x80, 0x00, tmp); + + /* Mic 2 Setup, even though it isn't connected on SBZ */ + chipio_set_conn_rate(codec, MEM_CONNID_MICIN2, SR_96_000); + chipio_set_conn_rate(codec, MEM_CONNID_MICOUT2, SR_96_000); + chipio_set_conn_rate(codec, 0x0F, SR_96_000); + tmp = FLOAT_ZERO; + dspio_set_uint_param(codec, 0x80, 0x01, tmp); +} + +/* * Initialize Sound Blaster Z analog microphones. */ static void sbz_init_analog_mics(struct hda_codec *codec) @@ -4700,6 +4794,50 @@ static void ca0132_setup_defaults(struct hda_codec *codec) }
/* + * Setup default parameters for Recon3Di DSP. + */ + +static void r3di_setup_defaults(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + unsigned int tmp; + int num_fx; + int idx, i; + + if (spec->dsp_state != DSP_DOWNLOADED) + return; + + + r3di_dsp_initial_mic_setup(codec); + + /*remove DSP headroom*/ + tmp = FLOAT_ZERO; + dspio_set_uint_param(codec, 0x96, 0x3C, tmp); + + /* set WUH source */ + tmp = FLOAT_TWO; + dspio_set_uint_param(codec, 0x31, 0x00, tmp); + chipio_set_conn_rate(codec, MEM_CONNID_WUH, SR_48_000); + + /* Set speaker source? */ + dspio_set_uint_param(codec, 0x32, 0x00, tmp); + + r3di_gpio_dsp_status_set(codec, R3DI_DSP_DOWNLOADED); + + /* Setup effect defaults */ + num_fx = OUT_EFFECTS_COUNT + IN_EFFECTS_COUNT + 1; + for (idx = 0; idx < num_fx; idx++) { + for (i = 0; i <= ca0132_effects[idx].params; i++) { + dspio_set_uint_param(codec, + ca0132_effects[idx].mid, + ca0132_effects[idx].reqs[i], + ca0132_effects[idx].def_vals[i]); + } + } + +} + +/* * Setup default parameters for the Sound Blaster Z DSP. A lot more going on * than the Chromebook setup. */ @@ -5397,6 +5535,7 @@ static void ca0132_alt_init(struct hda_codec *codec) codec_dbg(codec, "R3DI alt_init"); ca0132_gpio_init(codec); ca0132_gpio_setup(codec); + r3di_gpio_dsp_status_set(codec, R3DI_DSP_DOWNLOADING); r3di_pre_dsp_setup(codec); snd_hda_sequence_write(codec, spec->chip_init_verbs); snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0, 0x6FF, 0xC4); @@ -5445,21 +5584,29 @@ static int ca0132_init(struct hda_codec *codec) ca0132_init_unsol(codec); ca0132_init_params(codec); ca0132_init_flags(codec); + snd_hda_sequence_write(codec, spec->base_init_verbs);
if (spec->quirk != QUIRK_NONE) ca0132_alt_init(codec);
ca0132_download_dsp(codec); + ca0132_refresh_widget_caps(codec);
if (spec->quirk == QUIRK_SBZ) writew(0x0107, spec->mem_base + 0x320);
- if (spec->quirk != QUIRK_SBZ) { + switch (spec->quirk) { + case QUIRK_R3DI: + r3di_setup_defaults(codec); + break; + case QUIRK_NONE: + case QUIRK_ALIENWARE: ca0132_setup_defaults(codec); ca0132_init_analog_mic2(codec); ca0132_init_dmic(codec); + break; }
for (i = 0; i < spec->num_outputs; i++)
This patch adds the ability to change the src_id on scp commands, which is used in the dsp setup of the Recon3Di and the Sound Blaster Z. It also makes sure to maintain backwards compatibility with the older dspio_set_uint_param function, and sets it's src to the default 0x20.
Signed-off-by: Connor McAdams conmanx360@gmail.com --- sound/pci/hda/patch_ca0132.c | 86 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 74 insertions(+), 12 deletions(-)
diff --git a/sound/pci/hda/patch_ca0132.c b/sound/pci/hda/patch_ca0132.c index 7a9d505..878bbf7 100644 --- a/sound/pci/hda/patch_ca0132.c +++ b/sound/pci/hda/patch_ca0132.c @@ -1579,8 +1579,8 @@ static int dspio_send_scp_message(struct hda_codec *codec, * Returns zero or a negative error code. */ static int dspio_scp(struct hda_codec *codec, - int mod_id, int req, int dir, void *data, unsigned int len, - void *reply, unsigned int *reply_len) + int mod_id, int src_id, int req, int dir, const void *data, + unsigned int len, void *reply, unsigned int *reply_len) { int status = 0; struct scp_msg scp_send, scp_reply; @@ -1604,7 +1604,7 @@ static int dspio_scp(struct hda_codec *codec, return -EINVAL; }
- scp_send.hdr = make_scp_header(mod_id, 0x20, (dir == SCP_GET), req, + scp_send.hdr = make_scp_header(mod_id, src_id, (dir == SCP_GET), req, 0, 0, 0, len/sizeof(unsigned int)); if (data != NULL && len > 0) { len = min((unsigned int)(sizeof(scp_send.data)), len); @@ -1661,15 +1661,24 @@ static int dspio_scp(struct hda_codec *codec, * Set DSP parameters */ static int dspio_set_param(struct hda_codec *codec, int mod_id, - int req, void *data, unsigned int len) + int src_id, int req, const void *data, unsigned int len) { - return dspio_scp(codec, mod_id, req, SCP_SET, data, len, NULL, NULL); + return dspio_scp(codec, mod_id, src_id, req, SCP_SET, data, len, NULL, + NULL); }
static int dspio_set_uint_param(struct hda_codec *codec, int mod_id, - int req, unsigned int data) + int req, const unsigned int data) { - return dspio_set_param(codec, mod_id, req, &data, sizeof(unsigned int)); + return dspio_set_param(codec, mod_id, 0x20, req, &data, + sizeof(unsigned int)); +} + +static int dspio_set_uint_param_no_source(struct hda_codec *codec, int mod_id, + int req, const unsigned int data) +{ + return dspio_set_param(codec, mod_id, 0x00, req, &data, + sizeof(unsigned int)); }
/* @@ -1681,8 +1690,9 @@ static int dspio_alloc_dma_chan(struct hda_codec *codec, unsigned int *dma_chan) unsigned int size = sizeof(dma_chan);
codec_dbg(codec, " dspio_alloc_dma_chan() -- begin\n"); - status = dspio_scp(codec, MASTERCONTROL, MASTERCONTROL_ALLOC_DMA_CHAN, - SCP_GET, NULL, 0, dma_chan, &size); + status = dspio_scp(codec, MASTERCONTROL, 0x20, + MASTERCONTROL_ALLOC_DMA_CHAN, SCP_GET, NULL, 0, + dma_chan, &size);
if (status < 0) { codec_dbg(codec, "dspio_alloc_dma_chan: SCP Failed\n"); @@ -1711,8 +1721,9 @@ static int dspio_free_dma_chan(struct hda_codec *codec, unsigned int dma_chan) codec_dbg(codec, " dspio_free_dma_chan() -- begin\n"); codec_dbg(codec, "dspio_free_dma_chan: chan=%d\n", dma_chan);
- status = dspio_scp(codec, MASTERCONTROL, MASTERCONTROL_ALLOC_DMA_CHAN, - SCP_SET, &dma_chan, sizeof(dma_chan), NULL, &dummy); + status = dspio_scp(codec, MASTERCONTROL, 0x20, + MASTERCONTROL_ALLOC_DMA_CHAN, SCP_SET, &dma_chan, + sizeof(dma_chan), NULL, &dummy);
if (status < 0) { codec_dbg(codec, "dspio_free_dma_chan: SCP Failed\n"); @@ -3226,7 +3237,7 @@ static int tuning_ctl_set(struct hda_codec *codec, hda_nid_t nid, break;
snd_hda_power_up(codec); - dspio_set_param(codec, ca0132_tuning_ctls[i].mid, + dspio_set_param(codec, ca0132_tuning_ctls[i].mid, 0x20, ca0132_tuning_ctls[i].req, &(lookup[idx]), sizeof(unsigned int)); snd_hda_power_down(codec); @@ -4612,6 +4623,27 @@ static void ca0132_refresh_widget_caps(struct hda_codec *codec) * Recon3Di r3di_setup_defaults sub functions. */
+static void r3di_dsp_scp_startup(struct hda_codec *codec) +{ + unsigned int tmp; + + tmp = 0x00000000; + dspio_set_uint_param_no_source(codec, 0x80, 0x0A, tmp); + + tmp = 0x00000001; + dspio_set_uint_param_no_source(codec, 0x80, 0x0B, tmp); + + tmp = 0x00000004; + dspio_set_uint_param_no_source(codec, 0x80, 0x0C, tmp); + + tmp = 0x00000005; + dspio_set_uint_param_no_source(codec, 0x80, 0x0C, tmp); + + tmp = 0x00000000; + dspio_set_uint_param_no_source(codec, 0x80, 0x0C, tmp); + +} + static void r3di_dsp_initial_mic_setup(struct hda_codec *codec) { unsigned int tmp; @@ -4729,6 +4761,34 @@ static void sbz_chipio_startup_data(struct hda_codec *codec) mutex_unlock(&spec->chipio_mutex); }
+/* + * Sound Blaster Z uses these after DSP is loaded. Weird SCP commands + * without a 0x20 source like normal. + */ +static void sbz_dsp_scp_startup(struct hda_codec *codec) +{ + unsigned int tmp; + + tmp = 0x00000003; + dspio_set_uint_param_no_source(codec, 0x80, 0x0C, tmp); + + tmp = 0x00000000; + dspio_set_uint_param_no_source(codec, 0x80, 0x0A, tmp); + + tmp = 0x00000001; + dspio_set_uint_param_no_source(codec, 0x80, 0x0B, tmp); + + tmp = 0x00000004; + dspio_set_uint_param_no_source(codec, 0x80, 0x0C, tmp); + + tmp = 0x00000005; + dspio_set_uint_param_no_source(codec, 0x80, 0x0C, tmp); + + tmp = 0x00000000; + dspio_set_uint_param_no_source(codec, 0x80, 0x0C, tmp); + +} + static void sbz_dsp_initial_mic_setup(struct hda_codec *codec) { unsigned int tmp; @@ -4807,6 +4867,7 @@ static void r3di_setup_defaults(struct hda_codec *codec) if (spec->dsp_state != DSP_DOWNLOADED) return;
+ r3di_dsp_scp_startup(codec);
r3di_dsp_initial_mic_setup(codec);
@@ -4851,6 +4912,7 @@ static void sbz_setup_defaults(struct hda_codec *codec) if (spec->dsp_state != DSP_DOWNLOADED) return;
+ sbz_dsp_scp_startup(codec);
sbz_init_analog_mics(codec);
Add functions ca0132_alt_select_out and ca0132_alt_select_in for switching outputs and inputs for r3di and sbz. Also, add enumerated controls for selecting output and input source.
Signed-off-by: Connor McAdams conmanx360@gmail.com --- sound/pci/hda/patch_ca0132.c | 625 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 607 insertions(+), 18 deletions(-)
diff --git a/sound/pci/hda/patch_ca0132.c b/sound/pci/hda/patch_ca0132.c index 878bbf7..2a42faf 100644 --- a/sound/pci/hda/patch_ca0132.c +++ b/sound/pci/hda/patch_ca0132.c @@ -46,6 +46,7 @@ #define FLOAT_ONE 0x3f800000 #define FLOAT_TWO 0x40000000 #define FLOAT_THREE 0x40400000 +#define FLOAT_EIGHT 0x41000000 #define FLOAT_MINUS_5 0xc0a00000
#define UNSOL_TAG_DSP 0x16 @@ -87,9 +88,11 @@ MODULE_FIRMWARE(R3DI_EFX_FILE);
static const char *dirstr[2] = { "Playback", "Capture" };
+#define NUM_OF_OUTPUTS 3 enum { SPEAKER_OUT, - HEADPHONE_OUT + HEADPHONE_OUT, + SURROUND_OUT };
enum { @@ -97,6 +100,15 @@ enum { LINE_MIC_IN };
+/* Strings for Input Source Enum Control */ +static const char *in_src_str[3] = {"Rear Mic", "Line", "Front Mic" }; +#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 */ @@ -130,7 +142,9 @@ enum { VOICEFX = IN_EFFECT_END_NID, PLAY_ENHANCEMENT, CRYSTAL_VOICE, - EFFECT_END_NID + EFFECT_END_NID, + OUTPUT_SOURCE_ENUM, + INPUT_SOURCE_ENUM #define EFFECTS_COUNT (EFFECT_END_NID - EFFECT_START_NID) };
@@ -480,6 +494,49 @@ static struct ct_voicefx_preset ca0132_voicefx_presets[] = { } };
+/* DSP command sequences for ca0132_alt_select_out */ +#define ALT_OUT_SET_MAX_COMMANDS 9 /* Max number of commands in sequence */ +struct ca0132_alt_out_set { + char *name; /*preset name*/ + unsigned char commands; + unsigned int mids[ALT_OUT_SET_MAX_COMMANDS]; + unsigned int reqs[ALT_OUT_SET_MAX_COMMANDS]; + unsigned int vals[ALT_OUT_SET_MAX_COMMANDS]; +}; + +static const struct ca0132_alt_out_set alt_out_presets[] = { + { .name = "Line Out", + .commands = 7, + .mids = { 0x96, 0x96, 0x96, 0x8F, + 0x96, 0x96, 0x96 }, + .reqs = { 0x19, 0x17, 0x18, 0x01, + 0x1F, 0x15, 0x3A }, + .vals = { 0x3F000000, 0x42A00000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, + 0x00000000 } + }, + { .name = "Headphone", + .commands = 7, + .mids = { 0x96, 0x96, 0x96, 0x8F, + 0x96, 0x96, 0x96 }, + .reqs = { 0x19, 0x17, 0x18, 0x01, + 0x1F, 0x15, 0x3A }, + .vals = { 0x3F000000, 0x42A00000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, + 0x00000000 } + }, + { .name = "Surround", + .commands = 8, + .mids = { 0x96, 0x8F, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x96 }, + .reqs = { 0x18, 0x01, 0x1F, 0x15, + 0x3A, 0x1A, 0x1B, 0x1C }, + .vals = { 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000 } + } +}; + enum hda_cmd_vendor_io { /* for DspIO node */ VENDOR_DSPIO_SCP_WRITE_DATA_LOW = 0x000, @@ -759,6 +816,9 @@ struct ca0132_spec { long effects_switch[EFFECTS_COUNT]; long voicefx_val; long cur_mic_boost; + /* ca0132_alt control related values */ + unsigned char in_enum_val; + unsigned char out_enum_val;
struct hda_codec *codec; struct delayed_work unsol_hp_work; @@ -2955,6 +3015,47 @@ enum r3di_dsp_status { R3DI_DSP_DOWNLOADED = 1 };
+ +static void r3di_gpio_mic_set(struct hda_codec *codec, + enum r3di_mic_select cur_mic) +{ + unsigned int cur_gpio; + + /* Get the current GPIO Data setup */ + cur_gpio = snd_hda_codec_read(codec, 0x01, 0, AC_VERB_GET_GPIO_DATA, 0); + + switch (cur_mic) { + case R3DI_REAR_MIC: + cur_gpio &= ~(1 << R3DI_MIC_SELECT_BIT); + break; + case R3DI_FRONT_MIC: + cur_gpio |= (1 << R3DI_MIC_SELECT_BIT); + break; + } + snd_hda_codec_write(codec, codec->core.afg, 0, + AC_VERB_SET_GPIO_DATA, cur_gpio); +} + +static void r3di_gpio_out_set(struct hda_codec *codec, + enum r3di_out_select cur_out) +{ + unsigned int cur_gpio; + + /* Get the current GPIO Data setup */ + cur_gpio = snd_hda_codec_read(codec, 0x01, 0, AC_VERB_GET_GPIO_DATA, 0); + + switch (cur_out) { + case R3DI_HEADPHONE_OUT: + cur_gpio &= ~(1 << R3DI_OUT_SELECT_BIT); + break; + case R3DI_LINE_OUT: + cur_gpio |= (1 << R3DI_OUT_SELECT_BIT); + break; + } + snd_hda_codec_write(codec, codec->core.afg, 0, + AC_VERB_SET_GPIO_DATA, cur_gpio); +} + static void r3di_gpio_dsp_status_set(struct hda_codec *codec, enum r3di_dsp_status dsp_status) { @@ -3546,13 +3647,209 @@ static int ca0132_select_out(struct hda_codec *codec) return err < 0 ? err : 0; }
+/* + * This function behaves similarly to the ca0132_select_out funciton above, + * except with a few differences. It adds the ability to select the current + * output with an enumerated control "output source" if the auto detect + * mute switch is set to off. If the auto detect mute switch is enabled, it + * will detect either headphone or lineout(SPEAKER_OUT) from jack detection. + * It also adds the ability to auto-detect the front headphone port. The only + * way to select surround is to disable auto detect, and set Surround with the + * enumerated control. + */ +static int ca0132_alt_select_out(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + unsigned int pin_ctl; + int jack_present; + int auto_jack; + unsigned int i; + unsigned int tmp; + int err; + /* Default Headphone is rear headphone */ + hda_nid_t headphone_nid = spec->out_pins[1]; + + codec_dbg(codec, "%s\n", __func__); + + snd_hda_power_up_pm(codec); + + auto_jack = spec->vnode_lswitch[VNID_HP_ASEL - VNODE_START_NID]; + + /* + * If headphone rear or front is plugged in, set to headphone. + * If neither is plugged in, set to rear line out. Only if + * hp/speaker auto detect is enabled. + */ + if (auto_jack) { + jack_present = snd_hda_jack_detect(codec, spec->unsol_tag_hp) || + snd_hda_jack_detect(codec, spec->unsol_tag_front_hp); + + if (jack_present) + spec->cur_out_type = HEADPHONE_OUT; + else + spec->cur_out_type = SPEAKER_OUT; + } else + spec->cur_out_type = spec->out_enum_val; + + /* Begin DSP output switch */ + tmp = FLOAT_ONE; + err = dspio_set_uint_param(codec, 0x96, 0x3A, tmp); + if (err < 0) + goto exit; + + switch (spec->cur_out_type) { + case SPEAKER_OUT: + codec_dbg(codec, "%s speaker\n", __func__); + /*speaker out config*/ + switch (spec->quirk) { + case QUIRK_SBZ: + writew(0x0007, spec->mem_base + 0x320); + writew(0x0104, spec->mem_base + 0x320); + writew(0x0101, spec->mem_base + 0x320); + chipio_set_control_param(codec, 0x0D, 0x18); + break; + case QUIRK_R3DI: + chipio_set_control_param(codec, 0x0D, 0x24); + r3di_gpio_out_set(codec, R3DI_LINE_OUT); + break; + } + + /* disable headphone node */ + pin_ctl = snd_hda_codec_read(codec, spec->out_pins[1], 0, + AC_VERB_GET_PIN_WIDGET_CONTROL, 0); + snd_hda_set_pin_ctl(codec, spec->out_pins[1], + pin_ctl & ~PIN_HP); + /* enable line-out node */ + pin_ctl = snd_hda_codec_read(codec, spec->out_pins[0], 0, + AC_VERB_GET_PIN_WIDGET_CONTROL, 0); + snd_hda_set_pin_ctl(codec, spec->out_pins[0], + pin_ctl | PIN_OUT); + /* Enable EAPD */ + snd_hda_codec_write(codec, spec->out_pins[0], 0, + AC_VERB_SET_EAPD_BTLENABLE, 0x01); + + /* If PlayEnhancement is enabled, set different source */ + if (spec->effects_switch[PLAY_ENHANCEMENT - EFFECT_START_NID]) + dspio_set_uint_param(codec, 0x80, 0x04, FLOAT_ONE); + else + dspio_set_uint_param(codec, 0x80, 0x04, FLOAT_EIGHT); + break; + case HEADPHONE_OUT: + codec_dbg(codec, "%s hp\n", __func__); + /* Headphone out config*/ + switch (spec->quirk) { + case QUIRK_SBZ: + writew(0x0107, spec->mem_base + 0x320); + writew(0x0104, spec->mem_base + 0x320); + writew(0x0001, spec->mem_base + 0x320); + chipio_set_control_param(codec, 0x0D, 0x12); + break; + case QUIRK_R3DI: + chipio_set_control_param(codec, 0x0D, 0x21); + r3di_gpio_out_set(codec, R3DI_HEADPHONE_OUT); + break; + } + + snd_hda_codec_write(codec, spec->out_pins[0], 0, + AC_VERB_SET_EAPD_BTLENABLE, 0x00); + + /* disable speaker*/ + pin_ctl = snd_hda_codec_read(codec, spec->out_pins[0], 0, + AC_VERB_GET_PIN_WIDGET_CONTROL, 0); + snd_hda_set_pin_ctl(codec, spec->out_pins[0], + pin_ctl & ~PIN_HP); + + /* enable headphone, either front or rear */ + + if (snd_hda_jack_detect(codec, spec->unsol_tag_front_hp)) + headphone_nid = spec->out_pins[2]; + else if (snd_hda_jack_detect(codec, spec->unsol_tag_hp)) + headphone_nid = spec->out_pins[1]; + + pin_ctl = snd_hda_codec_read(codec, headphone_nid, 0, + AC_VERB_GET_PIN_WIDGET_CONTROL, 0); + snd_hda_set_pin_ctl(codec, headphone_nid, + pin_ctl | PIN_HP); + + if (spec->effects_switch[PLAY_ENHANCEMENT - EFFECT_START_NID]) + dspio_set_uint_param(codec, 0x80, 0x04, FLOAT_ONE); + else + dspio_set_uint_param(codec, 0x80, 0x04, FLOAT_ZERO); + break; + case SURROUND_OUT: + codec_dbg(codec, "%s surround\n", __func__); + /* Surround out config*/ + switch (spec->quirk) { + case QUIRK_SBZ: + writew(0x0007, spec->mem_base + 0x320); + writew(0x0104, spec->mem_base + 0x320); + writew(0x0101, spec->mem_base + 0x320); + chipio_set_control_param(codec, 0x0D, 0x18); + break; + case QUIRK_R3DI: + chipio_set_control_param(codec, 0x0D, 0x24); + r3di_gpio_out_set(codec, R3DI_LINE_OUT); + break; + } + /* enable line out node */ + pin_ctl = snd_hda_codec_read(codec, spec->out_pins[0], 0, + AC_VERB_GET_PIN_WIDGET_CONTROL, 0); + snd_hda_set_pin_ctl(codec, spec->out_pins[0], + pin_ctl | PIN_OUT); + /* Disable headphone out */ + pin_ctl = snd_hda_codec_read(codec, spec->out_pins[1], 0, + AC_VERB_GET_PIN_WIDGET_CONTROL, 0); + snd_hda_set_pin_ctl(codec, spec->out_pins[1], + pin_ctl & ~PIN_HP); + /* Enable EAPD on line out */ + snd_hda_codec_write(codec, spec->out_pins[0], 0, + AC_VERB_SET_EAPD_BTLENABLE, 0x01); + /* enable center/lfe out node */ + pin_ctl = snd_hda_codec_read(codec, spec->out_pins[2], 0, + AC_VERB_GET_PIN_WIDGET_CONTROL, 0); + snd_hda_set_pin_ctl(codec, spec->out_pins[2], + pin_ctl | PIN_OUT); + /* Now set rear surround node as out. */ + pin_ctl = snd_hda_codec_read(codec, spec->out_pins[3], 0, + AC_VERB_GET_PIN_WIDGET_CONTROL, 0); + snd_hda_set_pin_ctl(codec, spec->out_pins[3], + pin_ctl | PIN_OUT); + + if (spec->effects_switch[PLAY_ENHANCEMENT - EFFECT_START_NID]) + dspio_set_uint_param(codec, 0x80, 0x04, FLOAT_ONE); + else + dspio_set_uint_param(codec, 0x80, 0x04, FLOAT_EIGHT); + break; + } + + /* run through the output dsp commands for line-out */ + for (i = 0; i < alt_out_presets[spec->cur_out_type].commands; i++) { + err = dspio_set_uint_param(codec, + alt_out_presets[spec->cur_out_type].mids[i], + alt_out_presets[spec->cur_out_type].reqs[i], + alt_out_presets[spec->cur_out_type].vals[i]); + + if (err < 0) + goto exit; + } + +exit: + snd_hda_power_down_pm(codec); + + return err < 0 ? err : 0; +} + static void ca0132_unsol_hp_delayed(struct work_struct *work) { struct ca0132_spec *spec = container_of( to_delayed_work(work), struct ca0132_spec, unsol_hp_work); struct hda_jack_tbl *jack;
- ca0132_select_out(spec->codec); + if (spec->use_alt_functions) + ca0132_alt_select_out(spec->codec); + else + ca0132_select_out(spec->codec); + jack = snd_hda_jack_tbl_get(spec->codec, spec->unsol_tag_hp); if (jack) { jack->block_report = 0; @@ -3658,6 +3955,122 @@ static int ca0132_select_mic(struct hda_codec *codec) }
/* + * Select the active input. + * Mic detection isn't used, because it's kind of pointless on the SBZ. + * The front mic has no jack-detection, so the only way to switch to it + * is to do it manually in alsamixer. + */ +static int ca0132_alt_select_in(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + unsigned int tmp; + + codec_dbg(codec, "%s\n", __func__); + + snd_hda_power_up_pm(codec); + + chipio_set_stream_control(codec, 0x03, 0); + chipio_set_stream_control(codec, 0x04, 0); + + spec->cur_mic_type = spec->in_enum_val; + + switch (spec->cur_mic_type) { + case REAR_MIC: + switch (spec->quirk) { + case QUIRK_SBZ: + writew(0x0000, spec->mem_base + 0x320); + tmp = FLOAT_THREE; + break; + case QUIRK_R3DI: + r3di_gpio_mic_set(codec, R3DI_REAR_MIC); + tmp = FLOAT_ONE; + break; + default: + tmp = FLOAT_ONE; + break; + } + + chipio_set_conn_rate(codec, MEM_CONNID_MICIN1, SR_96_000); + chipio_set_conn_rate(codec, MEM_CONNID_MICOUT1, SR_96_000); + if (spec->quirk == QUIRK_R3DI) + chipio_set_conn_rate(codec, 0x0F, SR_96_000); + + dspio_set_uint_param(codec, 0x80, 0x00, tmp); + + chipio_set_stream_control(codec, 0x03, 1); + chipio_set_stream_control(codec, 0x04, 1); + + if (spec->quirk == QUIRK_SBZ) { + chipio_write(codec, 0x18B098, 0x0000000C); + chipio_write(codec, 0x18B09C, 0x0000000C); + } + break; + case REAR_LINE_IN: + ca0132_mic_boost_set(codec, 0); + switch (spec->quirk) { + case QUIRK_SBZ: + writew(0x0000, spec->mem_base + 0x320); + break; + case QUIRK_R3DI: + r3di_gpio_mic_set(codec, R3DI_REAR_MIC); + break; + } + + chipio_set_conn_rate(codec, MEM_CONNID_MICIN1, SR_96_000); + chipio_set_conn_rate(codec, MEM_CONNID_MICOUT1, SR_96_000); + if (spec->quirk == QUIRK_R3DI) + chipio_set_conn_rate(codec, 0x0F, SR_96_000); + + tmp = FLOAT_ZERO; + dspio_set_uint_param(codec, 0x80, 0x00, tmp); + + if (spec->quirk == QUIRK_SBZ) { + chipio_write(codec, 0x18B098, 0x00000000); + chipio_write(codec, 0x18B09C, 0x00000000); + } + + chipio_set_stream_control(codec, 0x03, 1); + chipio_set_stream_control(codec, 0x04, 1); + break; + case FRONT_MIC: + switch (spec->quirk) { + case QUIRK_SBZ: + writew(0x0100, spec->mem_base + 0x320); + writew(0x0005, spec->mem_base + 0x320); + tmp = FLOAT_THREE; + break; + case QUIRK_R3DI: + r3di_gpio_mic_set(codec, R3DI_FRONT_MIC); + tmp = FLOAT_ONE; + break; + default: + tmp = FLOAT_ONE; + break; + } + + chipio_set_conn_rate(codec, MEM_CONNID_MICIN1, SR_96_000); + chipio_set_conn_rate(codec, MEM_CONNID_MICOUT1, SR_96_000); + if (spec->quirk == QUIRK_R3DI) + chipio_set_conn_rate(codec, 0x0F, SR_96_000); + + dspio_set_uint_param(codec, 0x80, 0x00, tmp); + + chipio_set_stream_control(codec, 0x03, 1); + chipio_set_stream_control(codec, 0x04, 1); + + if (spec->quirk == QUIRK_SBZ) { + chipio_write(codec, 0x18B098, 0x0000000C); + chipio_write(codec, 0x18B09C, 0x000000CC); + } + break; + } + + snd_hda_power_down_pm(codec); + return 0; + +} + +/* * Check if VNODE settings take effect immediately. */ static bool ca0132_is_vnode_effective(struct hda_codec *codec, @@ -3739,7 +4152,8 @@ static int ca0132_effects_set(struct hda_codec *codec, hda_nid_t nid, long val) val = 0;
/* If Voice Focus on SBZ, set to two channel. */ - if ((nid == VOICE_FOCUS) && (spec->quirk == QUIRK_SBZ)) { + if ((nid == VOICE_FOCUS) && (spec->quirk == QUIRK_SBZ) + && (spec->cur_mic_type != REAR_LINE_IN)) { if (spec->effects_switch[CRYSTAL_VOICE - EFFECT_START_NID]) {
@@ -3757,7 +4171,8 @@ static int ca0132_effects_set(struct hda_codec *codec, hda_nid_t nid, long val) * For SBZ noise reduction, there's an extra command * to module ID 0x47. No clue why. */ - if ((nid == NOISE_REDUCTION) && (spec->quirk == QUIRK_SBZ)) { + if ((nid == NOISE_REDUCTION) && (spec->quirk == QUIRK_SBZ) + && (spec->cur_mic_type != REAR_LINE_IN)) { if (spec->effects_switch[CRYSTAL_VOICE - EFFECT_START_NID]) { if (spec->effects_switch[NOISE_REDUCTION - @@ -3770,6 +4185,11 @@ static int ca0132_effects_set(struct hda_codec *codec, hda_nid_t nid, long val)
dspio_set_uint_param(codec, 0x47, 0x00, tmp); } + + /* If rear line in disable effects. */ + if (spec->use_alt_functions && + spec->in_enum_val == REAR_LINE_IN) + val = 0; }
codec_dbg(codec, "ca0132_effect_set: nid=0x%x, val=%ld\n", @@ -3797,6 +4217,9 @@ static int ca0132_pe_switch_set(struct hda_codec *codec) codec_dbg(codec, "ca0132_pe_switch_set: val=%ld\n", spec->effects_switch[PLAY_ENHANCEMENT - EFFECT_START_NID]);
+ if (spec->use_alt_functions) + ca0132_alt_select_out(codec); + i = OUT_EFFECT_START_NID - EFFECT_START_NID; nid = OUT_EFFECT_START_NID; /* PE affects all out effects */ @@ -3888,8 +4311,12 @@ static int ca0132_vnode_switch_set(struct snd_kcontrol *kcontrol, if (nid == VNID_HP_SEL) { auto_jack = spec->vnode_lswitch[VNID_HP_ASEL - VNODE_START_NID]; - if (!auto_jack) - ca0132_select_out(codec); + if (!auto_jack) { + if (spec->use_alt_functions) + ca0132_alt_select_out(codec); + else + ca0132_select_out(codec); + } return 1; }
@@ -3902,7 +4329,10 @@ static int ca0132_vnode_switch_set(struct snd_kcontrol *kcontrol, }
if (nid == VNID_HP_ASEL) { - ca0132_select_out(codec); + if (spec->use_alt_functions) + ca0132_alt_select_out(codec); + else + ca0132_select_out(codec); return 1; }
@@ -3931,6 +4361,104 @@ static int ca0132_vnode_switch_set(struct snd_kcontrol *kcontrol, } /* End of control change helpers. */
+/* + * Input Select Control for alternative ca0132 codecs. This exists because + * front microphone has no auto-detect, and we need a way to set the rear + * as line-in + */ +static int ca0132_alt_input_source_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = IN_SRC_NUM_OF_INPUTS; + if (uinfo->value.enumerated.item >= IN_SRC_NUM_OF_INPUTS) + uinfo->value.enumerated.item = IN_SRC_NUM_OF_INPUTS - 1; + strcpy(uinfo->value.enumerated.name, + in_src_str[uinfo->value.enumerated.item]); + return 0; +} + +static int ca0132_alt_input_source_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + + ucontrol->value.enumerated.item[0] = spec->in_enum_val; + return 0; +} + +static int ca0132_alt_input_source_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + int sel = ucontrol->value.enumerated.item[0]; + unsigned int items = IN_SRC_NUM_OF_INPUTS; + + if (sel >= items) + return 0; + + codec_dbg(codec, "ca0132_alt_input_select: sel=%d, preset=%s\n", + sel, in_src_str[sel]); + + spec->in_enum_val = sel; + + ca0132_alt_select_in(codec); + + return 1; +} + +/* Sound Blaster Z Output Select Control */ +static int ca0132_alt_output_select_get_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = NUM_OF_OUTPUTS; + if (uinfo->value.enumerated.item >= NUM_OF_OUTPUTS) + uinfo->value.enumerated.item = NUM_OF_OUTPUTS - 1; + strcpy(uinfo->value.enumerated.name, + alt_out_presets[uinfo->value.enumerated.item].name); + return 0; +} + +static int ca0132_alt_output_select_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + + ucontrol->value.enumerated.item[0] = spec->out_enum_val; + return 0; +} + +static int ca0132_alt_output_select_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + int sel = ucontrol->value.enumerated.item[0]; + unsigned int items = NUM_OF_OUTPUTS; + unsigned int auto_jack; + + if (sel >= items) + return 0; + + codec_dbg(codec, "ca0132_alt_output_select: sel=%d, preset=%s\n", + sel, alt_out_presets[sel].name); + + spec->out_enum_val = sel; + + auto_jack = spec->vnode_lswitch[VNID_HP_ASEL - VNODE_START_NID]; + + if (!auto_jack) + ca0132_alt_select_out(codec); + + return 1; +} + static int ca0132_voicefx_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { @@ -4081,10 +4609,15 @@ static int ca0132_switch_put(struct snd_kcontrol *kcontrol, /* mic boost */ if (nid == spec->input_pins[0]) { spec->cur_mic_boost = *valp; + if (spec->use_alt_functions) { + if (spec->in_enum_val != REAR_LINE_IN) + changed = ca0132_mic_boost_set(codec, *valp); + } else { + /* Mic boost does not apply to Digital Mic */ + if (spec->cur_mic_type != DIGITAL_MIC) + changed = ca0132_mic_boost_set(codec, *valp); + }
- /* Mic boost does not apply to Digital Mic */ - if (spec->cur_mic_type != DIGITAL_MIC) - changed = ca0132_mic_boost_set(codec, *valp); goto exit; }
@@ -4258,6 +4791,39 @@ static int add_voicefx(struct hda_codec *codec) }
/* + * Create an Output Select enumerated control for codecs with surround + * out capabilities. + */ +static int ca0132_alt_add_output_enum(struct hda_codec *codec) +{ + struct snd_kcontrol_new knew = + HDA_CODEC_MUTE_MONO("Output Select", + OUTPUT_SOURCE_ENUM, 1, 0, HDA_OUTPUT); + knew.info = ca0132_alt_output_select_get_info; + knew.get = ca0132_alt_output_select_get; + knew.put = ca0132_alt_output_select_put; + return snd_hda_ctl_add(codec, OUTPUT_SOURCE_ENUM, + snd_ctl_new1(&knew, codec)); +} + +/* + * Create an Input Source enumerated control for the alternate ca0132 codecs + * because the front microphone has no auto-detect, and Line-in has to be set + * somehow. + */ +static int ca0132_alt_add_input_enum(struct hda_codec *codec) +{ + struct snd_kcontrol_new knew = + HDA_CODEC_MUTE_MONO("Input Source", + INPUT_SOURCE_ENUM, 1, 0, HDA_INPUT); + knew.info = ca0132_alt_input_source_info; + knew.get = ca0132_alt_input_source_get; + knew.put = ca0132_alt_input_source_put; + return snd_hda_ctl_add(codec, INPUT_SOURCE_ENUM, + snd_ctl_new1(&knew, codec)); +} + +/* * When changing Node IDs for Mixer Controls below, make sure to update * Node IDs in ca0132_config() as well. */ @@ -4318,6 +4884,15 @@ static int ca0132_build_controls(struct hda_codec *codec)
add_voicefx(codec);
+ /* + * If the codec uses alt_functions, you need the enumerated controls + * to select the new outputs and inputs, plus add the new mic boost + * setting control. + */ + if (spec->use_alt_functions) { + ca0132_alt_add_output_enum(codec); + ca0132_alt_add_input_enum(codec); + } #ifdef ENABLE_TUNING_CONTROLS add_tuning_ctls(codec); #endif @@ -5262,7 +5837,11 @@ static void ca0132_init_chip(struct hda_codec *codec) mutex_init(&spec->chipio_mutex);
spec->cur_out_type = SPEAKER_OUT; - spec->cur_mic_type = DIGITAL_MIC; + if (!spec->use_alt_functions) + spec->cur_mic_type = DIGITAL_MIC; + else + spec->cur_mic_type = REAR_MIC; + spec->cur_mic_boost = 0;
for (i = 0; i < VNODES_COUNT; i++) { @@ -5689,15 +6268,25 @@ static int ca0132_init(struct hda_codec *codec) VENDOR_CHIPIO_PARAM_EX_VALUE_SET, 0x20); }
- if (spec->quirk == QUIRK_SBZ) { + if (spec->quirk == QUIRK_SBZ) ca0132_gpio_setup(codec); - sbz_setup_defaults(codec); - }
snd_hda_sequence_write(codec, spec->spec_init_verbs); - - ca0132_select_out(codec); - ca0132_select_mic(codec); + switch (spec->quirk) { + case QUIRK_SBZ: + sbz_setup_defaults(codec); + ca0132_alt_select_out(codec); + ca0132_alt_select_in(codec); + break; + case QUIRK_R3DI: + ca0132_alt_select_out(codec); + ca0132_alt_select_in(codec); + break; + default: + ca0132_select_out(codec); + ca0132_select_mic(codec); + break; + }
snd_hda_jack_report_sync(codec);
Adds lookup table for floating point decibel volume, and new functions to allow for setting the decibel level on the DSP.
Signed-off-by: Connor McAdams conmanx360@gmail.com --- sound/pci/hda/patch_ca0132.c | 203 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 202 insertions(+), 1 deletion(-)
diff --git a/sound/pci/hda/patch_ca0132.c b/sound/pci/hda/patch_ca0132.c index 2a42faf..311df61 100644 --- a/sound/pci/hda/patch_ca0132.c +++ b/sound/pci/hda/patch_ca0132.c @@ -537,6 +537,31 @@ static const struct ca0132_alt_out_set alt_out_presets[] = { } };
+/* + * DSP volume setting structs. Req 1 is left volume, req 2 is right volume, + * and I don't know what the third req is, but it's always zero. I assume it's + * some sort of update or set command to tell the DSP there's new volume info. + */ +#define DSP_VOL_OUT 0 +#define DSP_VOL_IN 1 + +struct ct_dsp_volume_ctl { + hda_nid_t vnid; + int mid; /* module ID*/ + unsigned int reqs[3]; /* scp req ID */ +}; + +static struct ct_dsp_volume_ctl ca0132_alt_vol_ctls[] = { + { .vnid = VNID_SPK, + .mid = 0x32, + .reqs = {3, 4, 2} + }, + { .vnid = VNID_MIC, + .mid = 0x37, + .reqs = {2, 3, 1} + } +}; + enum hda_cmd_vendor_io { /* for DspIO node */ VENDOR_DSPIO_SCP_WRITE_DATA_LOW = 0x000, @@ -3248,6 +3273,24 @@ static unsigned int ca0132_capture_pcm_delay(struct hda_pcm_stream *info, .tlv = { .c = ca0132_volume_tlv }, \ .private_value = HDA_COMPOSE_AMP_VAL(nid, channel, 0, dir) }
+/* + * Creates a mixer control that uses defaults of HDA_CODEC_VOL except for the + * volume put, which is used for setting the DSP volume. This was done because + * the ca0132 functions were taking too much time and causing lag. + */ +#define CA0132_ALT_CODEC_VOL_MONO(xname, nid, channel, dir) \ + { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .subdevice = HDA_SUBDEV_AMP_FLAG, \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | \ + SNDRV_CTL_ELEM_ACCESS_TLV_READ | \ + SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK, \ + .info = snd_hda_mixer_amp_volume_info, \ + .get = snd_hda_mixer_amp_volume_get, \ + .put = ca0132_alt_volume_put, \ + .tlv = { .c = snd_hda_mixer_amp_tlv }, \ + .private_value = HDA_COMPOSE_AMP_VAL(nid, channel, 0, dir) } + #define CA0132_CODEC_MUTE_MONO(xname, nid, channel, dir) \ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ .name = xname, \ @@ -3260,9 +3303,40 @@ static unsigned int ca0132_capture_pcm_delay(struct hda_pcm_stream *info, /* stereo */ #define CA0132_CODEC_VOL(xname, nid, dir) \ CA0132_CODEC_VOL_MONO(xname, nid, 3, dir) +#define CA0132_ALT_CODEC_VOL(xname, nid, dir) \ + CA0132_ALT_CODEC_VOL_MONO(xname, nid, 3, dir) #define CA0132_CODEC_MUTE(xname, nid, dir) \ CA0132_CODEC_MUTE_MONO(xname, nid, 3, dir)
+/* lookup tables */ +/* + * Lookup table with decibel values for the DSP. When volume is changed in + * Windows, the DSP is also sent the dB value in floating point. In Windows, + * these values have decimal points, probably because the Windows driver + * actually uses floating point. We can't here, so I made a lookup table of + * values -90 to 9. -90 is the lowest decibel value for both the ADC's and the + * DAC's, and 9 is the maximum. + */ +static const unsigned int float_vol_db_lookup[] = { +0xC2B40000, 0xC2B20000, 0xC2B00000, 0xC2AE0000, 0xC2AC0000, 0xC2AA0000, +0xC2A80000, 0xC2A60000, 0xC2A40000, 0xC2A20000, 0xC2A00000, 0xC29E0000, +0xC29C0000, 0xC29A0000, 0xC2980000, 0xC2960000, 0xC2940000, 0xC2920000, +0xC2900000, 0xC28E0000, 0xC28C0000, 0xC28A0000, 0xC2880000, 0xC2860000, +0xC2840000, 0xC2820000, 0xC2800000, 0xC27C0000, 0xC2780000, 0xC2740000, +0xC2700000, 0xC26C0000, 0xC2680000, 0xC2640000, 0xC2600000, 0xC25C0000, +0xC2580000, 0xC2540000, 0xC2500000, 0xC24C0000, 0xC2480000, 0xC2440000, +0xC2400000, 0xC23C0000, 0xC2380000, 0xC2340000, 0xC2300000, 0xC22C0000, +0xC2280000, 0xC2240000, 0xC2200000, 0xC21C0000, 0xC2180000, 0xC2140000, +0xC2100000, 0xC20C0000, 0xC2080000, 0xC2040000, 0xC2000000, 0xC1F80000, +0xC1F00000, 0xC1E80000, 0xC1E00000, 0xC1D80000, 0xC1D00000, 0xC1C80000, +0xC1C00000, 0xC1B80000, 0xC1B00000, 0xC1A80000, 0xC1A00000, 0xC1980000, +0xC1900000, 0xC1880000, 0xC1800000, 0xC1700000, 0xC1600000, 0xC1500000, +0xC1400000, 0xC1300000, 0xC1200000, 0xC1100000, 0xC1000000, 0xC0E00000, +0xC0C00000, 0xC0A00000, 0xC0800000, 0xC0400000, 0xC0000000, 0xBF800000, +0x00000000, 0x3F800000, 0x40000000, 0x40400000, 0x40800000, 0x40A00000, +0x40C00000, 0x40E00000, 0x41000000, 0x41100000 +}; + /* The following are for tuning of products */ #ifdef ENABLE_TUNING_CONTROLS
@@ -4629,6 +4703,41 @@ static int ca0132_switch_put(struct snd_kcontrol *kcontrol, /* * Volume related */ +/* + * Sets the internal DSP decibel level to match the DAC for output, and the + * ADC for input. Currently only the SBZ sets dsp capture volume level, and + * all alternative codecs set DSP playback volume. + */ +static void ca0132_alt_dsp_volume_put(struct hda_codec *codec, hda_nid_t nid) +{ + struct ca0132_spec *spec = codec->spec; + unsigned int dsp_dir; + unsigned int lookup_val; + + if (nid == VNID_SPK) + dsp_dir = DSP_VOL_OUT; + else + dsp_dir = DSP_VOL_IN; + + lookup_val = spec->vnode_lvol[nid - VNODE_START_NID]; + + dspio_set_uint_param(codec, + ca0132_alt_vol_ctls[dsp_dir].mid, + ca0132_alt_vol_ctls[dsp_dir].reqs[0], + float_vol_db_lookup[lookup_val]); + + lookup_val = spec->vnode_rvol[nid - VNODE_START_NID]; + + dspio_set_uint_param(codec, + ca0132_alt_vol_ctls[dsp_dir].mid, + ca0132_alt_vol_ctls[dsp_dir].reqs[1], + float_vol_db_lookup[lookup_val]); + + dspio_set_uint_param(codec, + ca0132_alt_vol_ctls[dsp_dir].mid, + ca0132_alt_vol_ctls[dsp_dir].reqs[2], FLOAT_ZERO); +} + static int ca0132_volume_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { @@ -4730,6 +4839,51 @@ static int ca0132_volume_put(struct snd_kcontrol *kcontrol, return changed; }
+/* + * This function is the same as the one above, because using an if statement + * inside of the above volume control for the DSP volume would cause too much + * lag. This is a lot more smooth. + */ +static int ca0132_alt_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + hda_nid_t nid = get_amp_nid(kcontrol); + int ch = get_amp_channels(kcontrol); + long *valp = ucontrol->value.integer.value; + hda_nid_t vnid = 0; + int changed = 1; + + switch (nid) { + case 0x02: + vnid = VNID_SPK; + break; + case 0x07: + vnid = VNID_MIC; + break; + } + + /* store the left and right volume */ + if (ch & 1) { + spec->vnode_lvol[vnid - VNODE_START_NID] = *valp; + valp++; + } + if (ch & 2) { + spec->vnode_rvol[vnid - VNODE_START_NID] = *valp; + valp++; + } + + snd_hda_power_up(codec); + ca0132_alt_dsp_volume_put(codec, vnid); + mutex_lock(&codec->control_mutex); + changed = snd_hda_mixer_amp_volume_put(kcontrol, ucontrol); + mutex_unlock(&codec->control_mutex); + snd_hda_power_down(codec); + + return changed; +} + static int ca0132_volume_tlv(struct snd_kcontrol *kcontrol, int op_flag, unsigned int size, unsigned int __user *tlv) { @@ -4849,6 +5003,39 @@ static struct snd_kcontrol_new ca0132_mixer[] = { { } /* end */ };
+/* + * SBZ specific control mixer. Removes auto-detect for mic, and adds surround + * controls. Also sets both the Front Playback and Capture Volume controls to + * alt so they set the DSP's decibel level. + */ +static struct snd_kcontrol_new sbz_mixer[] = { + CA0132_ALT_CODEC_VOL("Front Playback Volume", 0x02, HDA_OUTPUT), + CA0132_CODEC_MUTE("Front Playback Switch", VNID_SPK, HDA_OUTPUT), + CA0132_ALT_CODEC_VOL("Capture Volume", 0x07, HDA_INPUT), + CA0132_CODEC_MUTE("Capture Switch", VNID_MIC, HDA_INPUT), + HDA_CODEC_VOLUME("What U Hear Capture Volume", 0x0a, 0, HDA_INPUT), + HDA_CODEC_MUTE("What U Hear Capture Switch", 0x0a, 0, HDA_INPUT), + CA0132_CODEC_MUTE_MONO("HP/Speaker Auto Detect Playback Switch", + VNID_HP_ASEL, 1, HDA_OUTPUT), + { } /* end */ +}; + +/* + * Same as the Sound Blaster Z, except doesn't use the alt volume for capture + * because it doesn't set decibel levels for the DSP for capture. + */ +static struct snd_kcontrol_new r3di_mixer[] = { + CA0132_ALT_CODEC_VOL("Front Playback Volume", 0x02, HDA_OUTPUT), + CA0132_CODEC_MUTE("Front Playback Switch", VNID_SPK, HDA_OUTPUT), + CA0132_CODEC_VOL("Capture Volume", VNID_MIC, HDA_INPUT), + CA0132_CODEC_MUTE("Capture Switch", VNID_MIC, HDA_INPUT), + HDA_CODEC_VOLUME("What U Hear Capture Volume", 0x0a, 0, HDA_INPUT), + HDA_CODEC_MUTE("What U Hear Capture Switch", 0x0a, 0, HDA_INPUT), + CA0132_CODEC_MUTE_MONO("HP/Speaker Auto Detect Playback Switch", + VNID_HP_ASEL, 1, HDA_OUTPUT), + { } /* end */ +}; + static int ca0132_build_controls(struct hda_codec *codec) { struct ca0132_spec *spec = codec->spec; @@ -6562,7 +6749,21 @@ static int patch_ca0132(struct hda_codec *codec)
spec->dsp_state = DSP_DOWNLOAD_INIT; spec->num_mixers = 1; - spec->mixers[0] = ca0132_mixer; + + /* Set which mixers each quirk uses. */ + switch (spec->quirk) { + case QUIRK_SBZ: + spec->mixers[0] = sbz_mixer; + snd_hda_codec_set_name(codec, "Sound Blaster Z"); + break; + case QUIRK_R3DI: + spec->mixers[0] = r3di_mixer; + snd_hda_codec_set_name(codec, "Recon3Di"); + break; + default: + spec->mixers[0] = ca0132_mixer; + break; + }
/* Setup whether or not to use alt functions */ switch (spec->quirk) {
Add function to set vipsource on cards that use_alt_controls. Different sequence. Also, add cvoice_switch_set at end of ca0132_select_in so that when switching between inputs cvoice state is maintained.
Signed-off-by: Connor McAdams conmanx360@gmail.com --- sound/pci/hda/patch_ca0132.c | 71 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-)
diff --git a/sound/pci/hda/patch_ca0132.c b/sound/pci/hda/patch_ca0132.c index 311df61..12c4548 100644 --- a/sound/pci/hda/patch_ca0132.c +++ b/sound/pci/hda/patch_ca0132.c @@ -3976,6 +3976,71 @@ static int ca0132_set_vipsource(struct hda_codec *codec, int val) return 1; }
+static int ca0132_alt_set_vipsource(struct hda_codec *codec, int val) +{ + struct ca0132_spec *spec = codec->spec; + unsigned int tmp; + + if (spec->dsp_state != DSP_DOWNLOADED) + return 0; + + codec_dbg(codec, "%s\n", __func__); + + chipio_set_stream_control(codec, 0x03, 0); + chipio_set_stream_control(codec, 0x04, 0); + + /* if CrystalVoice is off, vipsource should be 0 */ + if (!spec->effects_switch[CRYSTAL_VOICE - EFFECT_START_NID] || + (val == 0) || spec->in_enum_val == REAR_LINE_IN) { + codec_dbg(codec, "%s: off.", __func__); + chipio_set_control_param(codec, CONTROL_PARAM_VIP_SOURCE, 0); + + tmp = FLOAT_ZERO; + dspio_set_uint_param(codec, 0x80, 0x05, tmp); + + chipio_set_conn_rate(codec, MEM_CONNID_MICIN1, SR_96_000); + chipio_set_conn_rate(codec, MEM_CONNID_MICOUT1, SR_96_000); + if (spec->quirk == QUIRK_R3DI) + chipio_set_conn_rate(codec, 0x0F, SR_96_000); + + + if (spec->in_enum_val == REAR_LINE_IN) + tmp = FLOAT_ZERO; + else { + if (spec->quirk == QUIRK_SBZ) + tmp = FLOAT_THREE; + else + tmp = FLOAT_ONE; + } + + dspio_set_uint_param(codec, 0x80, 0x00, tmp); + + } else { + codec_dbg(codec, "%s: on.", __func__); + chipio_set_conn_rate(codec, MEM_CONNID_MICIN1, SR_16_000); + chipio_set_conn_rate(codec, MEM_CONNID_MICOUT1, SR_16_000); + if (spec->quirk == QUIRK_R3DI) + chipio_set_conn_rate(codec, 0x0F, SR_16_000); + + if (spec->effects_switch[VOICE_FOCUS - EFFECT_START_NID]) + tmp = FLOAT_TWO; + else + tmp = FLOAT_ONE; + dspio_set_uint_param(codec, 0x80, 0x00, tmp); + + tmp = FLOAT_ONE; + dspio_set_uint_param(codec, 0x80, 0x05, tmp); + + msleep(20); + chipio_set_control_param(codec, CONTROL_PARAM_VIP_SOURCE, val); + } + + chipio_set_stream_control(codec, 0x03, 1); + chipio_set_stream_control(codec, 0x04, 1); + + return 1; +} + /* * Select the active microphone. * If autodetect is enabled, mic will be selected based on jack detection. @@ -4138,6 +4203,7 @@ static int ca0132_alt_select_in(struct hda_codec *codec) } break; } + ca0132_cvoice_switch_set(codec);
snd_hda_power_down_pm(codec); return 0; @@ -4351,7 +4417,10 @@ static int ca0132_cvoice_switch_set(struct hda_codec *codec)
/* set correct vipsource */ oldval = stop_mic1(codec); - ret |= ca0132_set_vipsource(codec, 1); + if (spec->use_alt_functions) + ret |= ca0132_alt_set_vipsource(codec, 1); + else + ret |= ca0132_set_vipsource(codec, 1); resume_mic1(codec, oldval); return ret; }
Hi Connor,
Thank you for the patch! Yet something to improve:
[auto build test ERROR on sound/for-next] [also build test ERROR on v4.17-rc4 next-20180507] [if your patch is applied to the wrong git tree, please drop us a note to help improve the system]
url: https://github.com/0day-ci/linux/commits/Connor-McAdams/ALSA-hda-ca0132-Patc... base: https://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound.git for-next config: x86_64-randconfig-x012-201818 (attached as .config) compiler: gcc-7 (Debian 7.3.0-16) 7.3.0 reproduce: # save the attached .config to linux build tree make ARCH=x86_64
Note: the linux-review/Connor-McAdams/ALSA-hda-ca0132-Patch-Series-for-Recon3Di-and-Sound-Blaster-Z-Support/20180508-144100 HEAD 387bca1ff38f9c470b8965899c62b3bfc400ea88 builds fine. It only hurts bisectibility.
All errors (new ones prefixed by >>):
sound/pci/hda/patch_ca0132.c: In function 'ca0132_alt_select_in':
sound/pci/hda/patch_ca0132.c:4210:2: error: implicit declaration of function 'ca0132_cvoice_switch_set'; did you mean 'ca0132_mic_boost_set'? [-Werror=implicit-function-declaration]
ca0132_cvoice_switch_set(codec); ^~~~~~~~~~~~~~~~~~~~~~~~ ca0132_mic_boost_set sound/pci/hda/patch_ca0132.c: At top level:
sound/pci/hda/patch_ca0132.c:4403:12: error: static declaration of 'ca0132_cvoice_switch_set' follows non-static declaration
static int ca0132_cvoice_switch_set(struct hda_codec *codec) ^~~~~~~~~~~~~~~~~~~~~~~~ sound/pci/hda/patch_ca0132.c:4210:2: note: previous implicit declaration of 'ca0132_cvoice_switch_set' was here ca0132_cvoice_switch_set(codec); ^~~~~~~~~~~~~~~~~~~~~~~~ cc1: some warnings being treated as errors
vim +4210 sound/pci/hda/patch_ca0132.c
4099 4100 /* 4101 * Select the active input. 4102 * Mic detection isn't used, because it's kind of pointless on the SBZ. 4103 * The front mic has no jack-detection, so the only way to switch to it 4104 * is to do it manually in alsamixer. 4105 */ 4106 static int ca0132_alt_select_in(struct hda_codec *codec) 4107 { 4108 struct ca0132_spec *spec = codec->spec; 4109 unsigned int tmp; 4110 4111 codec_dbg(codec, "%s\n", __func__); 4112 4113 snd_hda_power_up_pm(codec); 4114 4115 chipio_set_stream_control(codec, 0x03, 0); 4116 chipio_set_stream_control(codec, 0x04, 0); 4117 4118 spec->cur_mic_type = spec->in_enum_val; 4119 4120 switch (spec->cur_mic_type) { 4121 case REAR_MIC: 4122 switch (spec->quirk) { 4123 case QUIRK_SBZ: 4124 writew(0x0000, spec->mem_base + 0x320); 4125 tmp = FLOAT_THREE; 4126 break; 4127 case QUIRK_R3DI: 4128 r3di_gpio_mic_set(codec, R3DI_REAR_MIC); 4129 tmp = FLOAT_ONE; 4130 break; 4131 default: 4132 tmp = FLOAT_ONE; 4133 break; 4134 } 4135 4136 chipio_set_conn_rate(codec, MEM_CONNID_MICIN1, SR_96_000); 4137 chipio_set_conn_rate(codec, MEM_CONNID_MICOUT1, SR_96_000); 4138 if (spec->quirk == QUIRK_R3DI) 4139 chipio_set_conn_rate(codec, 0x0F, SR_96_000); 4140 4141 dspio_set_uint_param(codec, 0x80, 0x00, tmp); 4142 4143 chipio_set_stream_control(codec, 0x03, 1); 4144 chipio_set_stream_control(codec, 0x04, 1); 4145 4146 if (spec->quirk == QUIRK_SBZ) { 4147 chipio_write(codec, 0x18B098, 0x0000000C); 4148 chipio_write(codec, 0x18B09C, 0x0000000C); 4149 } 4150 break; 4151 case REAR_LINE_IN: 4152 ca0132_mic_boost_set(codec, 0); 4153 switch (spec->quirk) { 4154 case QUIRK_SBZ: 4155 writew(0x0000, spec->mem_base + 0x320); 4156 break; 4157 case QUIRK_R3DI: 4158 r3di_gpio_mic_set(codec, R3DI_REAR_MIC); 4159 break; 4160 } 4161 4162 chipio_set_conn_rate(codec, MEM_CONNID_MICIN1, SR_96_000); 4163 chipio_set_conn_rate(codec, MEM_CONNID_MICOUT1, SR_96_000); 4164 if (spec->quirk == QUIRK_R3DI) 4165 chipio_set_conn_rate(codec, 0x0F, SR_96_000); 4166 4167 tmp = FLOAT_ZERO; 4168 dspio_set_uint_param(codec, 0x80, 0x00, tmp); 4169 4170 if (spec->quirk == QUIRK_SBZ) { 4171 chipio_write(codec, 0x18B098, 0x00000000); 4172 chipio_write(codec, 0x18B09C, 0x00000000); 4173 } 4174 4175 chipio_set_stream_control(codec, 0x03, 1); 4176 chipio_set_stream_control(codec, 0x04, 1); 4177 break; 4178 case FRONT_MIC: 4179 switch (spec->quirk) { 4180 case QUIRK_SBZ: 4181 writew(0x0100, spec->mem_base + 0x320); 4182 writew(0x0005, spec->mem_base + 0x320); 4183 tmp = FLOAT_THREE; 4184 break; 4185 case QUIRK_R3DI: 4186 r3di_gpio_mic_set(codec, R3DI_FRONT_MIC); 4187 tmp = FLOAT_ONE; 4188 break; 4189 default: 4190 tmp = FLOAT_ONE; 4191 break; 4192 } 4193 4194 chipio_set_conn_rate(codec, MEM_CONNID_MICIN1, SR_96_000); 4195 chipio_set_conn_rate(codec, MEM_CONNID_MICOUT1, SR_96_000); 4196 if (spec->quirk == QUIRK_R3DI) 4197 chipio_set_conn_rate(codec, 0x0F, SR_96_000); 4198 4199 dspio_set_uint_param(codec, 0x80, 0x00, tmp); 4200 4201 chipio_set_stream_control(codec, 0x03, 1); 4202 chipio_set_stream_control(codec, 0x04, 1); 4203 4204 if (spec->quirk == QUIRK_SBZ) { 4205 chipio_write(codec, 0x18B098, 0x0000000C); 4206 chipio_write(codec, 0x18B09C, 0x000000CC); 4207 } 4208 break; 4209 }
4210 ca0132_cvoice_switch_set(codec);
4211 4212 snd_hda_power_down_pm(codec); 4213 return 0; 4214 4215 } 4216 4217 /* 4218 * Check if VNODE settings take effect immediately. 4219 */ 4220 static bool ca0132_is_vnode_effective(struct hda_codec *codec, 4221 hda_nid_t vnid, 4222 hda_nid_t *shared_nid) 4223 { 4224 struct ca0132_spec *spec = codec->spec; 4225 hda_nid_t nid; 4226 4227 switch (vnid) { 4228 case VNID_SPK: 4229 nid = spec->shared_out_nid; 4230 break; 4231 case VNID_MIC: 4232 nid = spec->shared_mic_nid; 4233 break; 4234 default: 4235 return false; 4236 } 4237 4238 if (shared_nid) 4239 *shared_nid = nid; 4240 4241 return true; 4242 } 4243 4244 /* 4245 * The following functions are control change helpers. 4246 * They return 0 if no changed. Return 1 if changed. 4247 */ 4248 static int ca0132_voicefx_set(struct hda_codec *codec, int enable) 4249 { 4250 struct ca0132_spec *spec = codec->spec; 4251 unsigned int tmp; 4252 4253 /* based on CrystalVoice state to enable VoiceFX. */ 4254 if (enable) { 4255 tmp = spec->effects_switch[CRYSTAL_VOICE - EFFECT_START_NID] ? 4256 FLOAT_ONE : FLOAT_ZERO; 4257 } else { 4258 tmp = FLOAT_ZERO; 4259 } 4260 4261 dspio_set_uint_param(codec, ca0132_voicefx.mid, 4262 ca0132_voicefx.reqs[0], tmp); 4263 4264 return 1; 4265 } 4266 4267 /* 4268 * Set the effects parameters 4269 */ 4270 static int ca0132_effects_set(struct hda_codec *codec, hda_nid_t nid, long val) 4271 { 4272 struct ca0132_spec *spec = codec->spec; 4273 unsigned int on, tmp; 4274 int num_fx = OUT_EFFECTS_COUNT + IN_EFFECTS_COUNT; 4275 int err = 0; 4276 int idx = nid - EFFECT_START_NID; 4277 4278 if ((idx < 0) || (idx >= num_fx)) 4279 return 0; /* no changed */ 4280 4281 /* for out effect, qualify with PE */ 4282 if ((nid >= OUT_EFFECT_START_NID) && (nid < OUT_EFFECT_END_NID)) { 4283 /* if PE if off, turn off out effects. */ 4284 if (!spec->effects_switch[PLAY_ENHANCEMENT - EFFECT_START_NID]) 4285 val = 0; 4286 } 4287 4288 /* for in effect, qualify with CrystalVoice */ 4289 if ((nid >= IN_EFFECT_START_NID) && (nid < IN_EFFECT_END_NID)) { 4290 /* if CrystalVoice if off, turn off in effects. */ 4291 if (!spec->effects_switch[CRYSTAL_VOICE - EFFECT_START_NID]) 4292 val = 0; 4293 4294 /* Voice Focus applies to 2-ch Mic, Digital Mic */ 4295 if ((nid == VOICE_FOCUS) && (spec->cur_mic_type != DIGITAL_MIC)) 4296 val = 0; 4297 4298 /* If Voice Focus on SBZ, set to two channel. */ 4299 if ((nid == VOICE_FOCUS) && (spec->quirk == QUIRK_SBZ) 4300 && (spec->cur_mic_type != REAR_LINE_IN)) { 4301 if (spec->effects_switch[CRYSTAL_VOICE - 4302 EFFECT_START_NID]) { 4303 4304 if (spec->effects_switch[VOICE_FOCUS - 4305 EFFECT_START_NID]) { 4306 tmp = FLOAT_TWO; 4307 val = 1; 4308 } else 4309 tmp = FLOAT_ONE; 4310 4311 dspio_set_uint_param(codec, 0x80, 0x00, tmp); 4312 } 4313 } 4314 /* 4315 * For SBZ noise reduction, there's an extra command 4316 * to module ID 0x47. No clue why. 4317 */ 4318 if ((nid == NOISE_REDUCTION) && (spec->quirk == QUIRK_SBZ) 4319 && (spec->cur_mic_type != REAR_LINE_IN)) { 4320 if (spec->effects_switch[CRYSTAL_VOICE - 4321 EFFECT_START_NID]) { 4322 if (spec->effects_switch[NOISE_REDUCTION - 4323 EFFECT_START_NID]) 4324 tmp = FLOAT_ONE; 4325 else 4326 tmp = FLOAT_ZERO; 4327 } else 4328 tmp = FLOAT_ZERO; 4329 4330 dspio_set_uint_param(codec, 0x47, 0x00, tmp); 4331 } 4332 4333 /* If rear line in disable effects. */ 4334 if (spec->use_alt_functions && 4335 spec->in_enum_val == REAR_LINE_IN) 4336 val = 0; 4337 } 4338 4339 codec_dbg(codec, "ca0132_effect_set: nid=0x%x, val=%ld\n", 4340 nid, val); 4341 4342 on = (val == 0) ? FLOAT_ZERO : FLOAT_ONE; 4343 err = dspio_set_uint_param(codec, ca0132_effects[idx].mid, 4344 ca0132_effects[idx].reqs[0], on); 4345 4346 if (err < 0) 4347 return 0; /* no changed */ 4348 4349 return 1; 4350 } 4351 4352 /* 4353 * Turn on/off Playback Enhancements 4354 */ 4355 static int ca0132_pe_switch_set(struct hda_codec *codec) 4356 { 4357 struct ca0132_spec *spec = codec->spec; 4358 hda_nid_t nid; 4359 int i, ret = 0; 4360 4361 codec_dbg(codec, "ca0132_pe_switch_set: val=%ld\n", 4362 spec->effects_switch[PLAY_ENHANCEMENT - EFFECT_START_NID]); 4363 4364 if (spec->use_alt_functions) 4365 ca0132_alt_select_out(codec); 4366 4367 i = OUT_EFFECT_START_NID - EFFECT_START_NID; 4368 nid = OUT_EFFECT_START_NID; 4369 /* PE affects all out effects */ 4370 for (; nid < OUT_EFFECT_END_NID; nid++, i++) 4371 ret |= ca0132_effects_set(codec, nid, spec->effects_switch[i]); 4372 4373 return ret; 4374 } 4375 4376 /* Check if Mic1 is streaming, if so, stop streaming */ 4377 static int stop_mic1(struct hda_codec *codec) 4378 { 4379 struct ca0132_spec *spec = codec->spec; 4380 unsigned int oldval = snd_hda_codec_read(codec, spec->adcs[0], 0, 4381 AC_VERB_GET_CONV, 0); 4382 if (oldval != 0) 4383 snd_hda_codec_write(codec, spec->adcs[0], 0, 4384 AC_VERB_SET_CHANNEL_STREAMID, 4385 0); 4386 return oldval; 4387 } 4388 4389 /* Resume Mic1 streaming if it was stopped. */ 4390 static void resume_mic1(struct hda_codec *codec, unsigned int oldval) 4391 { 4392 struct ca0132_spec *spec = codec->spec; 4393 /* Restore the previous stream and channel */ 4394 if (oldval != 0) 4395 snd_hda_codec_write(codec, spec->adcs[0], 0, 4396 AC_VERB_SET_CHANNEL_STREAMID, 4397 oldval); 4398 } 4399 4400 /* 4401 * Turn on/off CrystalVoice 4402 */
4403 static int ca0132_cvoice_switch_set(struct hda_codec *codec)
4404 { 4405 struct ca0132_spec *spec = codec->spec; 4406 hda_nid_t nid; 4407 int i, ret = 0; 4408 unsigned int oldval; 4409 4410 codec_dbg(codec, "ca0132_cvoice_switch_set: val=%ld\n", 4411 spec->effects_switch[CRYSTAL_VOICE - EFFECT_START_NID]); 4412 4413 i = IN_EFFECT_START_NID - EFFECT_START_NID; 4414 nid = IN_EFFECT_START_NID; 4415 /* CrystalVoice affects all in effects */ 4416 for (; nid < IN_EFFECT_END_NID; nid++, i++) 4417 ret |= ca0132_effects_set(codec, nid, spec->effects_switch[i]); 4418 4419 /* including VoiceFX */ 4420 ret |= ca0132_voicefx_set(codec, (spec->voicefx_val ? 1 : 0)); 4421 4422 /* set correct vipsource */ 4423 oldval = stop_mic1(codec); 4424 if (spec->use_alt_functions) 4425 ret |= ca0132_alt_set_vipsource(codec, 1); 4426 else 4427 ret |= ca0132_set_vipsource(codec, 1); 4428 resume_mic1(codec, oldval); 4429 return ret; 4430 } 4431
--- 0-DAY kernel test infrastructure Open Source Technology Center https://lists.01.org/pipermail/kbuild-all Intel Corporation
This patch adds new controls to set the effect levels on the R3Di and SBZ. It also adds vmaster controls to control all surround sound channels. So that Surround effect switch doesn't conflict with Surround volume, FX: prefix added to all effect related switches.
Tested-by: Mariusz Ceier mceier+kernel@gmail.com Signed-off-by: Connor McAdams conmanx360@gmail.com --- sound/pci/hda/patch_ca0132.c | 761 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 751 insertions(+), 10 deletions(-)
diff --git a/sound/pci/hda/patch_ca0132.c b/sound/pci/hda/patch_ca0132.c index 12c4548..a9ca7ca 100644 --- a/sound/pci/hda/patch_ca0132.c +++ b/sound/pci/hda/patch_ca0132.c @@ -144,13 +144,26 @@ enum { CRYSTAL_VOICE, EFFECT_END_NID, OUTPUT_SOURCE_ENUM, - INPUT_SOURCE_ENUM + INPUT_SOURCE_ENUM, + XBASS_XOVER, + EQ_PRESET_ENUM, + SMART_VOLUME_ENUM, + MIC_BOOST_ENUM #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 @@ -494,6 +507,93 @@ static struct ct_voicefx_preset ca0132_voicefx_presets[] = { } };
+/* 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 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 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 command sequences for ca0132_alt_select_out */ #define ALT_OUT_SET_MAX_COMMANDS 9 /* Max number of commands in sequence */ struct ca0132_alt_out_set { @@ -844,6 +944,14 @@ struct ca0132_spec { /* ca0132_alt control related values */ unsigned char in_enum_val; unsigned char out_enum_val; + unsigned char mic_boost_enum_val; + unsigned char smart_volume_setting; + long fx_ctl_val[EFFECT_LEVEL_SLIDERS]; + long xbass_xover_freq; + long eq_preset_val; + unsigned int tlv[4]; + struct hda_vmaster_mute_hook vmaster_mute; +
struct hda_codec *codec; struct delayed_work unsol_hp_work; @@ -864,6 +972,13 @@ struct ca0132_spec { * surround sound support. */ bool use_alt_functions; + + /* + * Whether or not to use alt controls: volume effect sliders, EQ + * presets, smart volume presets, and new control names with FX prefix. + * Renames PlayEnhancement and CrystalVoice too. + */ + bool use_alt_controls; };
/* @@ -3337,6 +3452,54 @@ static const unsigned int float_vol_db_lookup[] = { 0x40C00000, 0x40E00000, 0x41000000, 0x41100000 };
+/* + * This table counts from float 0 to 1 in increments of .01, which is + * useful for a few different sliders. + */ +static const unsigned int float_zero_to_one_lookup[] = { +0x00000000, 0x3C23D70A, 0x3CA3D70A, 0x3CF5C28F, 0x3D23D70A, 0x3D4CCCCD, +0x3D75C28F, 0x3D8F5C29, 0x3DA3D70A, 0x3DB851EC, 0x3DCCCCCD, 0x3DE147AE, +0x3DF5C28F, 0x3E051EB8, 0x3E0F5C29, 0x3E19999A, 0x3E23D70A, 0x3E2E147B, +0x3E3851EC, 0x3E428F5C, 0x3E4CCCCD, 0x3E570A3D, 0x3E6147AE, 0x3E6B851F, +0x3E75C28F, 0x3E800000, 0x3E851EB8, 0x3E8A3D71, 0x3E8F5C29, 0x3E947AE1, +0x3E99999A, 0x3E9EB852, 0x3EA3D70A, 0x3EA8F5C3, 0x3EAE147B, 0x3EB33333, +0x3EB851EC, 0x3EBD70A4, 0x3EC28F5C, 0x3EC7AE14, 0x3ECCCCCD, 0x3ED1EB85, +0x3ED70A3D, 0x3EDC28F6, 0x3EE147AE, 0x3EE66666, 0x3EEB851F, 0x3EF0A3D7, +0x3EF5C28F, 0x3EFAE148, 0x3F000000, 0x3F028F5C, 0x3F051EB8, 0x3F07AE14, +0x3F0A3D71, 0x3F0CCCCD, 0x3F0F5C29, 0x3F11EB85, 0x3F147AE1, 0x3F170A3D, +0x3F19999A, 0x3F1C28F6, 0x3F1EB852, 0x3F2147AE, 0x3F23D70A, 0x3F266666, +0x3F28F5C3, 0x3F2B851F, 0x3F2E147B, 0x3F30A3D7, 0x3F333333, 0x3F35C28F, +0x3F3851EC, 0x3F3AE148, 0x3F3D70A4, 0x3F400000, 0x3F428F5C, 0x3F451EB8, +0x3F47AE14, 0x3F4A3D71, 0x3F4CCCCD, 0x3F4F5C29, 0x3F51EB85, 0x3F547AE1, +0x3F570A3D, 0x3F59999A, 0x3F5C28F6, 0x3F5EB852, 0x3F6147AE, 0x3F63D70A, +0x3F666666, 0x3F68F5C3, 0x3F6B851F, 0x3F6E147B, 0x3F70A3D7, 0x3F733333, +0x3F75C28F, 0x3F7851EC, 0x3F7AE148, 0x3F7D70A4, 0x3F800000 +}; + +/* + * This table counts from float 10 to 1000, which is the range of the x-bass + * crossover slider in Windows. + */ +static const unsigned int float_xbass_xover_lookup[] = { +0x41200000, 0x41A00000, 0x41F00000, 0x42200000, 0x42480000, 0x42700000, +0x428C0000, 0x42A00000, 0x42B40000, 0x42C80000, 0x42DC0000, 0x42F00000, +0x43020000, 0x430C0000, 0x43160000, 0x43200000, 0x432A0000, 0x43340000, +0x433E0000, 0x43480000, 0x43520000, 0x435C0000, 0x43660000, 0x43700000, +0x437A0000, 0x43820000, 0x43870000, 0x438C0000, 0x43910000, 0x43960000, +0x439B0000, 0x43A00000, 0x43A50000, 0x43AA0000, 0x43AF0000, 0x43B40000, +0x43B90000, 0x43BE0000, 0x43C30000, 0x43C80000, 0x43CD0000, 0x43D20000, +0x43D70000, 0x43DC0000, 0x43E10000, 0x43E60000, 0x43EB0000, 0x43F00000, +0x43F50000, 0x43FA0000, 0x43FF0000, 0x44020000, 0x44048000, 0x44070000, +0x44098000, 0x440C0000, 0x440E8000, 0x44110000, 0x44138000, 0x44160000, +0x44188000, 0x441B0000, 0x441D8000, 0x44200000, 0x44228000, 0x44250000, +0x44278000, 0x442A0000, 0x442C8000, 0x442F0000, 0x44318000, 0x44340000, +0x44368000, 0x44390000, 0x443B8000, 0x443E0000, 0x44408000, 0x44430000, +0x44458000, 0x44480000, 0x444A8000, 0x444D0000, 0x444F8000, 0x44520000, +0x44548000, 0x44570000, 0x44598000, 0x445C0000, 0x445E8000, 0x44610000, +0x44638000, 0x44660000, 0x44688000, 0x446B0000, 0x446D8000, 0x44700000, +0x44728000, 0x44750000, 0x44778000, 0x447A0000 +}; + /* The following are for tuning of products */ #ifdef ENABLE_TUNING_CONTROLS
@@ -3934,6 +4097,10 @@ static void ca0132_unsol_hp_delayed(struct work_struct *work) static void ca0132_set_dmic(struct hda_codec *codec, int enable); static int ca0132_mic_boost_set(struct hda_codec *codec, long val); static int ca0132_effects_set(struct hda_codec *codec, hda_nid_t nid, long val); +static void resume_mic1(struct hda_codec *codec, unsigned int oldval); +static int stop_mic1(struct hda_codec *codec); +static int ca0132_cvoice_switch_set(struct hda_codec *codec); +static int ca0132_alt_mic_boost_set(struct hda_codec *codec, long val);
/* * Select the active VIP source @@ -4143,6 +4310,7 @@ static int ca0132_alt_select_in(struct hda_codec *codec) chipio_write(codec, 0x18B098, 0x0000000C); chipio_write(codec, 0x18B09C, 0x0000000C); } + ca0132_alt_mic_boost_set(codec, spec->mic_boost_enum_val); break; case REAR_LINE_IN: ca0132_mic_boost_set(codec, 0); @@ -4201,6 +4369,7 @@ static int ca0132_alt_select_in(struct hda_codec *codec) chipio_write(codec, 0x18B098, 0x0000000C); chipio_write(codec, 0x18B09C, 0x000000CC); } + ca0132_alt_mic_boost_set(codec, spec->mic_boost_enum_val); break; } ca0132_cvoice_switch_set(codec); @@ -4440,6 +4609,16 @@ static int ca0132_mic_boost_set(struct hda_codec *codec, long val) return ret; }
+static int ca0132_alt_mic_boost_set(struct hda_codec *codec, long val) +{ + struct ca0132_spec *spec = codec->spec; + int ret = 0; + + ret = snd_hda_codec_amp_update(codec, spec->input_pins[0], 0, + HDA_INPUT, 0, HDA_AMP_VOLMASK, val); + return ret; +} + static int ca0132_vnode_switch_set(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { @@ -4503,6 +4682,207 @@ static int ca0132_vnode_switch_set(struct snd_kcontrol *kcontrol, return ret; } /* End of control change helpers. */ +/* + * Below I've added controls to mess with the effect levels, I've only enabled + * them on the Sound Blaster Z, but they would probably also work on the + * Chromebook. I figured they were probably tuned specifically for it, and left + * out for a reason. + */ + +/* Sets DSP effect level from the sliders above the controls */ +static int ca0132_alt_slider_ctl_set(struct hda_codec *codec, hda_nid_t nid, + const unsigned int *lookup, int idx) +{ + int i = 0; + unsigned int y; + /* + * For X_BASS, req 2 is actually crossover freq instead of + * effect level + */ + if (nid == X_BASS) + y = 2; + else + y = 1; + + snd_hda_power_up(codec); + if (nid == XBASS_XOVER) { + for (i = 0; i < OUT_EFFECTS_COUNT; i++) + if (ca0132_effects[i].nid == X_BASS) + break; + + dspio_set_param(codec, ca0132_effects[i].mid, 0x20, + ca0132_effects[i].reqs[1], + &(lookup[idx - 1]), sizeof(unsigned int)); + } else { + /* Find the actual effect structure */ + for (i = 0; i < OUT_EFFECTS_COUNT; i++) + if (nid == ca0132_effects[i].nid) + break; + + dspio_set_param(codec, ca0132_effects[i].mid, 0x20, + ca0132_effects[i].reqs[y], + &(lookup[idx]), sizeof(unsigned int)); + } + + snd_hda_power_down(codec); + + return 0; +} + +static int ca0132_alt_xbass_xover_slider_ctl_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + long *valp = ucontrol->value.integer.value; + + *valp = spec->xbass_xover_freq; + return 0; +} + +static int ca0132_alt_slider_ctl_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + hda_nid_t nid = get_amp_nid(kcontrol); + long *valp = ucontrol->value.integer.value; + int idx = nid - OUT_EFFECT_START_NID; + + *valp = spec->fx_ctl_val[idx]; + return 0; +} + +/* + * The X-bass crossover starts at 10hz, so the min is 1. The + * frequency is set in multiples of 10. + */ +static int ca0132_alt_xbass_xover_slider_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 1; + uinfo->value.integer.max = 100; + uinfo->value.integer.step = 1; + + return 0; +} + +static int ca0132_alt_effect_slider_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + int chs = get_amp_channels(kcontrol); + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = chs == 3 ? 2 : 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 100; + uinfo->value.integer.step = 1; + + return 0; +} + +static int ca0132_alt_xbass_xover_slider_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + hda_nid_t nid = get_amp_nid(kcontrol); + long *valp = ucontrol->value.integer.value; + int idx; + + /* any change? */ + if (spec->xbass_xover_freq == *valp) + return 0; + + spec->xbass_xover_freq = *valp; + + idx = *valp; + ca0132_alt_slider_ctl_set(codec, nid, float_xbass_xover_lookup, idx); + + return 0; +} + +static int ca0132_alt_effect_slider_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + hda_nid_t nid = get_amp_nid(kcontrol); + long *valp = ucontrol->value.integer.value; + int idx; + + idx = nid - EFFECT_START_NID; + /* any change? */ + if (spec->fx_ctl_val[idx] == *valp) + return 0; + + spec->fx_ctl_val[idx] = *valp; + + idx = *valp; + ca0132_alt_slider_ctl_set(codec, nid, float_zero_to_one_lookup, idx); + + return 0; +} + + +/* + * Mic Boost Enum for alternative ca0132 codecs. I didn't like that the original + * only has off or full 30 dB, and didn't like making a volume slider that has + * traditional 0-100 in alsamixer that goes in big steps. I like enum better. + */ +#define MIC_BOOST_NUM_OF_STEPS 4 +#define MIC_BOOST_ENUM_MAX_STRLEN 10 + +static int ca0132_alt_mic_boost_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + char *sfx = "dB"; + char namestr[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = MIC_BOOST_NUM_OF_STEPS; + if (uinfo->value.enumerated.item >= MIC_BOOST_NUM_OF_STEPS) + uinfo->value.enumerated.item = MIC_BOOST_NUM_OF_STEPS - 1; + sprintf(namestr, "%d %s", (uinfo->value.enumerated.item * 10), sfx); + strcpy(uinfo->value.enumerated.name, namestr); + return 0; +} + +static int ca0132_alt_mic_boost_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + + ucontrol->value.enumerated.item[0] = spec->mic_boost_enum_val; + return 0; +} + +static int ca0132_alt_mic_boost_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + int sel = ucontrol->value.enumerated.item[0]; + unsigned int items = MIC_BOOST_NUM_OF_STEPS; + + if (sel >= items) + return 0; + + codec_dbg(codec, "ca0132_alt_mic_boost: boost=%d\n", + sel); + + spec->mic_boost_enum_val = sel; + + if (spec->in_enum_val != REAR_LINE_IN) + ca0132_alt_mic_boost_set(codec, spec->mic_boost_enum_val); + + return 1; +} +
/* * Input Select Control for alternative ca0132 codecs. This exists because @@ -4602,6 +4982,135 @@ static int ca0132_alt_output_select_put(struct snd_kcontrol *kcontrol, return 1; }
+/* + * Smart Volume output setting control. Three different settings, Normal, + * which takes the value from the smart volume slider. The two others, loud + * and night, disregard the slider value and have uneditable values. + */ +#define NUM_OF_SVM_SETTINGS 3 +static const char *out_svm_set_enum_str[3] = {"Normal", "Loud", "Night" }; + +static int ca0132_alt_svm_setting_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = NUM_OF_SVM_SETTINGS; + if (uinfo->value.enumerated.item >= NUM_OF_SVM_SETTINGS) + uinfo->value.enumerated.item = NUM_OF_SVM_SETTINGS - 1; + strcpy(uinfo->value.enumerated.name, + out_svm_set_enum_str[uinfo->value.enumerated.item]); + return 0; +} + +static int ca0132_alt_svm_setting_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + + ucontrol->value.enumerated.item[0] = spec->smart_volume_setting; + return 0; +} + +static int ca0132_alt_svm_setting_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + int sel = ucontrol->value.enumerated.item[0]; + unsigned int items = NUM_OF_SVM_SETTINGS; + unsigned int idx = SMART_VOLUME - EFFECT_START_NID; + unsigned int tmp; + + if (sel >= items) + return 0; + + codec_dbg(codec, "ca0132_alt_svm_setting: sel=%d, preset=%s\n", + sel, out_svm_set_enum_str[sel]); + + spec->smart_volume_setting = sel; + + switch (sel) { + case 0: + tmp = FLOAT_ZERO; + break; + case 1: + tmp = FLOAT_ONE; + break; + case 2: + tmp = FLOAT_TWO; + break; + default: + tmp = FLOAT_ZERO; + break; + } + /* Req 2 is the Smart Volume Setting req. */ + dspio_set_uint_param(codec, ca0132_effects[idx].mid, + ca0132_effects[idx].reqs[2], tmp); + return 1; +} + +/* Sound Blaster Z EQ preset controls */ +static int ca0132_alt_eq_preset_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + unsigned int items = sizeof(ca0132_alt_eq_presets) + / sizeof(struct ct_eq_preset); + + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = items; + if (uinfo->value.enumerated.item >= items) + uinfo->value.enumerated.item = items - 1; + strcpy(uinfo->value.enumerated.name, + ca0132_alt_eq_presets[uinfo->value.enumerated.item].name); + return 0; +} + +static int ca0132_alt_eq_preset_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + + ucontrol->value.enumerated.item[0] = spec->eq_preset_val; + return 0; +} + +static int ca0132_alt_eq_preset_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ca0132_spec *spec = codec->spec; + int i, err = 0; + int sel = ucontrol->value.enumerated.item[0]; + unsigned int items = sizeof(ca0132_alt_eq_presets) + / sizeof(struct ct_eq_preset); + + if (sel >= items) + return 0; + + codec_dbg(codec, "%s: sel=%d, preset=%s\n", __func__, sel, + ca0132_alt_eq_presets[sel].name); + /* + * Idx 0 is default. + * Default needs to qualify with CrystalVoice state. + */ + for (i = 0; i < EQ_PRESET_MAX_PARAM_COUNT; i++) { + err = dspio_set_uint_param(codec, ca0132_alt_eq_enum.mid, + ca0132_alt_eq_enum.reqs[i], + ca0132_alt_eq_presets[sel].vals[i]); + if (err < 0) + break; + } + + if (err >= 0) + spec->eq_preset_val = sel; + + return 1; +} + static int ca0132_voicefx_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { @@ -4991,14 +5500,61 @@ static int ca0132_volume_tlv(struct snd_kcontrol *kcontrol, int op_flag, return err; }
+/* Add volume slider control for effect level */ +static int ca0132_alt_add_effect_slider(struct hda_codec *codec, hda_nid_t nid, + const char *pfx, int dir) +{ + char *fx = "FX:"; + char namestr[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; + int type = dir ? HDA_INPUT : HDA_OUTPUT; + struct snd_kcontrol_new knew = + HDA_CODEC_VOLUME_MONO(namestr, nid, 1, 0, type); + + sprintf(namestr, "%s %s %s Volume", fx, pfx, dirstr[dir]); + + knew.tlv.c = 0; + knew.tlv.p = 0; + + switch (nid) { + case XBASS_XOVER: + knew.info = ca0132_alt_xbass_xover_slider_info; + knew.get = ca0132_alt_xbass_xover_slider_ctl_get; + knew.put = ca0132_alt_xbass_xover_slider_put; + break; + default: + knew.info = ca0132_alt_effect_slider_info; + knew.get = ca0132_alt_slider_ctl_get; + knew.put = ca0132_alt_effect_slider_put; + knew.private_value = + HDA_COMPOSE_AMP_VAL(nid, 1, 0, type); + break; + } + + return snd_hda_ctl_add(codec, nid, snd_ctl_new1(&knew, codec)); +} + +/* + * Added FX: prefix for the alternative codecs, because otherwise the surround + * effect would conflict with the Surround sound volume control. Also seems more + * clear as to what the switches do. Left alone for others. + */ static int add_fx_switch(struct hda_codec *codec, hda_nid_t nid, const char *pfx, int dir) { + struct ca0132_spec *spec = codec->spec; + char *fx = "FX:"; char namestr[SNDRV_CTL_ELEM_ID_NAME_MAXLEN]; int type = dir ? HDA_INPUT : HDA_OUTPUT; struct snd_kcontrol_new knew = CA0132_CODEC_MUTE_MONO(namestr, nid, 1, type); - sprintf(namestr, "%s %s Switch", pfx, dirstr[dir]); + /* If using alt_controls, add FX: prefix. But, don't add FX: + * prefix to OutFX or InFX enable controls. + */ + if ((spec->use_alt_controls) && (nid <= IN_EFFECT_END_NID)) + sprintf(namestr, "%s %s %s Switch", fx, pfx, dirstr[dir]); + else + sprintf(namestr, "%s %s Switch", pfx, dirstr[dir]); + return snd_hda_ctl_add(codec, nid, snd_ctl_new1(&knew, codec)); }
@@ -5013,6 +5569,37 @@ static int add_voicefx(struct hda_codec *codec) return snd_hda_ctl_add(codec, VOICEFX, snd_ctl_new1(&knew, codec)); }
+/* Create the EQ Preset control */ +static int add_ca0132_alt_eq_presets(struct hda_codec *codec) +{ + struct snd_kcontrol_new knew = + HDA_CODEC_MUTE_MONO(ca0132_alt_eq_enum.name, + EQ_PRESET_ENUM, 1, 0, HDA_OUTPUT); + knew.info = ca0132_alt_eq_preset_info; + knew.get = ca0132_alt_eq_preset_get; + knew.put = ca0132_alt_eq_preset_put; + return snd_hda_ctl_add(codec, EQ_PRESET_ENUM, + snd_ctl_new1(&knew, codec)); +} + +/* + * Add enumerated control for the three different settings of the smart volume + * output effect. Normal just uses the slider value, and loud and night are + * their own things that ignore that value. + */ +static int ca0132_alt_add_svm_enum(struct hda_codec *codec) +{ + struct snd_kcontrol_new knew = + HDA_CODEC_MUTE_MONO("FX: Smart Volume Setting", + SMART_VOLUME_ENUM, 1, 0, HDA_OUTPUT); + knew.info = ca0132_alt_svm_setting_info; + knew.get = ca0132_alt_svm_setting_get; + knew.put = ca0132_alt_svm_setting_put; + return snd_hda_ctl_add(codec, SMART_VOLUME_ENUM, + snd_ctl_new1(&knew, codec)); + +} + /* * Create an Output Select enumerated control for codecs with surround * out capabilities. @@ -5047,6 +5634,72 @@ static int ca0132_alt_add_input_enum(struct hda_codec *codec) }
/* + * Add mic boost enumerated control. Switches through 0dB to 30dB. This adds + * more control than the original mic boost, which is either full 30dB or off. + */ +static int ca0132_alt_add_mic_boost_enum(struct hda_codec *codec) +{ + struct snd_kcontrol_new knew = + HDA_CODEC_MUTE_MONO("Mic Boost Capture Switch", + MIC_BOOST_ENUM, 1, 0, HDA_INPUT); + knew.info = ca0132_alt_mic_boost_info; + knew.get = ca0132_alt_mic_boost_get; + knew.put = ca0132_alt_mic_boost_put; + return snd_hda_ctl_add(codec, MIC_BOOST_ENUM, + snd_ctl_new1(&knew, codec)); + +} + +/* + * Need to create slave controls for the alternate codecs that have surround + * capabilities. + */ +static const char * const ca0132_alt_slave_pfxs[] = { + "Front", "Surround", "Center", "LFE", NULL, +}; + +/* + * Also need special channel map, because the default one is incorrect. + * I think this has to do with the pin for rear surround being 0x11, + * and the center/lfe being 0x10. Usually the pin order is the opposite. + */ +const struct snd_pcm_chmap_elem ca0132_alt_chmaps[] = { + { .channels = 2, + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR } }, + { .channels = 4, + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, + SNDRV_CHMAP_RL, SNDRV_CHMAP_RR } }, + { .channels = 6, + .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, + SNDRV_CHMAP_FC, SNDRV_CHMAP_LFE, + SNDRV_CHMAP_RL, SNDRV_CHMAP_RR } }, + { } +}; + +/* Add the correct chmap for streams with 6 channels. */ +static void ca0132_alt_add_chmap_ctls(struct hda_codec *codec) +{ + int err = 0; + struct hda_pcm *pcm; + + list_for_each_entry(pcm, &codec->pcm_list_head, list) { + struct hda_pcm_stream *hinfo = + &pcm->stream[SNDRV_PCM_STREAM_PLAYBACK]; + struct snd_pcm_chmap *chmap; + const struct snd_pcm_chmap_elem *elem; + + elem = ca0132_alt_chmaps; + if (hinfo->channels_max == 6) { + err = snd_pcm_add_chmap_ctls(pcm->pcm, + SNDRV_PCM_STREAM_PLAYBACK, + elem, hinfo->channels_max, 0, &chmap); + if (err < 0) + codec_dbg(codec, "snd_pcm_add_chmap_ctls failed!"); + } + } +} + +/* * When changing Node IDs for Mixer Controls below, make sure to update * Node IDs in ca0132_config() as well. */ @@ -5080,6 +5733,12 @@ static struct snd_kcontrol_new ca0132_mixer[] = { static struct snd_kcontrol_new sbz_mixer[] = { CA0132_ALT_CODEC_VOL("Front Playback Volume", 0x02, HDA_OUTPUT), CA0132_CODEC_MUTE("Front Playback Switch", VNID_SPK, HDA_OUTPUT), + HDA_CODEC_VOLUME("Surround Playback Volume", 0x04, 0, HDA_OUTPUT), + HDA_CODEC_MUTE("Surround Playback Switch", 0x04, 0, HDA_OUTPUT), + HDA_CODEC_VOLUME_MONO("Center Playback Volume", 0x03, 1, 0, HDA_OUTPUT), + HDA_CODEC_MUTE_MONO("Center Playback Switch", 0x03, 1, 0, HDA_OUTPUT), + HDA_CODEC_VOLUME_MONO("LFE Playback Volume", 0x03, 2, 0, HDA_OUTPUT), + HDA_CODEC_MUTE_MONO("LFE Playback Switch", 0x03, 2, 0, HDA_OUTPUT), CA0132_ALT_CODEC_VOL("Capture Volume", 0x07, HDA_INPUT), CA0132_CODEC_MUTE("Capture Switch", VNID_MIC, HDA_INPUT), HDA_CODEC_VOLUME("What U Hear Capture Volume", 0x0a, 0, HDA_INPUT), @@ -5096,6 +5755,12 @@ static struct snd_kcontrol_new sbz_mixer[] = { static struct snd_kcontrol_new r3di_mixer[] = { CA0132_ALT_CODEC_VOL("Front Playback Volume", 0x02, HDA_OUTPUT), CA0132_CODEC_MUTE("Front Playback Switch", VNID_SPK, HDA_OUTPUT), + HDA_CODEC_VOLUME("Surround Playback Volume", 0x04, 0, HDA_OUTPUT), + HDA_CODEC_MUTE("Surround Playback Switch", 0x04, 0, HDA_OUTPUT), + HDA_CODEC_VOLUME_MONO("Center Playback Volume", 0x03, 1, 0, HDA_OUTPUT), + HDA_CODEC_MUTE_MONO("Center Playback Switch", 0x03, 1, 0, HDA_OUTPUT), + HDA_CODEC_VOLUME_MONO("LFE Playback Volume", 0x03, 2, 0, HDA_OUTPUT), + HDA_CODEC_MUTE_MONO("LFE Playback Switch", 0x03, 2, 0, HDA_OUTPUT), CA0132_CODEC_VOL("Capture Volume", VNID_MIC, HDA_INPUT), CA0132_CODEC_MUTE("Capture Switch", VNID_MIC, HDA_INPUT), HDA_CODEC_VOLUME("What U Hear Capture Volume", 0x0a, 0, HDA_INPUT), @@ -5108,7 +5773,7 @@ static struct snd_kcontrol_new r3di_mixer[] = { static int ca0132_build_controls(struct hda_codec *codec) { struct ca0132_spec *spec = codec->spec; - int i, num_fx; + int i, num_fx, num_sliders; int err = 0;
/* Add Mixer controls */ @@ -5117,27 +5782,82 @@ static int ca0132_build_controls(struct hda_codec *codec) if (err < 0) return err; } + /* Setup vmaster with surround slaves for desktop ca0132 devices */ + if (spec->use_alt_functions) { + snd_hda_set_vmaster_tlv(codec, spec->dacs[0], HDA_OUTPUT, + spec->tlv); + snd_hda_add_vmaster(codec, "Master Playback Volume", + spec->tlv, ca0132_alt_slave_pfxs, + "Playback Volume"); + err = __snd_hda_add_vmaster(codec, "Master Playback Switch", + NULL, ca0132_alt_slave_pfxs, + "Playback Switch", + true, &spec->vmaster_mute.sw_kctl); + + }
/* Add in and out effects controls. * VoiceFX, PE and CrystalVoice are added separately. */ num_fx = OUT_EFFECTS_COUNT + IN_EFFECTS_COUNT; for (i = 0; i < num_fx; i++) { + /* SBZ breaks if Echo Cancellation is used */ + if (spec->quirk == QUIRK_SBZ) { + if (i == (ECHO_CANCELLATION - IN_EFFECT_START_NID + + OUT_EFFECTS_COUNT)) + continue; + } + err = add_fx_switch(codec, ca0132_effects[i].nid, ca0132_effects[i].name, ca0132_effects[i].direct); if (err < 0) return err; } + /* + * If codec has use_alt_controls set to true, add effect level sliders, + * EQ presets, and Smart Volume presets. Also, change names to add FX + * prefix, and change PlayEnhancement and CrystalVoice to match. + */ + if (spec->use_alt_controls) { + ca0132_alt_add_svm_enum(codec); + add_ca0132_alt_eq_presets(codec); + err = add_fx_switch(codec, PLAY_ENHANCEMENT, + "Enable OutFX", 0); + if (err < 0) + return err;
- err = add_fx_switch(codec, PLAY_ENHANCEMENT, "PlayEnhancement", 0); - if (err < 0) - return err; + err = add_fx_switch(codec, CRYSTAL_VOICE, + "Enable InFX", 1); + if (err < 0) + return err;
- err = add_fx_switch(codec, CRYSTAL_VOICE, "CrystalVoice", 1); - if (err < 0) - return err; + num_sliders = OUT_EFFECTS_COUNT - 1; + for (i = 0; i < num_sliders; i++) { + err = ca0132_alt_add_effect_slider(codec, + ca0132_effects[i].nid, + ca0132_effects[i].name, + ca0132_effects[i].direct); + if (err < 0) + return err; + } + + err = ca0132_alt_add_effect_slider(codec, XBASS_XOVER, + "X-Bass Crossover", EFX_DIR_OUT); + + if (err < 0) + return err; + } else { + err = add_fx_switch(codec, PLAY_ENHANCEMENT, + "PlayEnhancement", 0); + if (err < 0) + return err;
+ err = add_fx_switch(codec, CRYSTAL_VOICE, + "CrystalVoice", 1); + if (err < 0) + return err; + } add_voicefx(codec);
/* @@ -5148,6 +5868,7 @@ static int ca0132_build_controls(struct hda_codec *codec) if (spec->use_alt_functions) { ca0132_alt_add_output_enum(codec); ca0132_alt_add_input_enum(codec); + ca0132_alt_add_mic_boost_enum(codec); } #ifdef ENABLE_TUNING_CONTROLS add_tuning_ctls(codec); @@ -5173,6 +5894,10 @@ static int ca0132_build_controls(struct hda_codec *codec) if (err < 0) return err; } + + if (spec->use_alt_functions) + ca0132_alt_add_chmap_ctls(codec); + return 0; }
@@ -5227,6 +5952,11 @@ static int ca0132_build_pcms(struct hda_codec *codec) info = snd_hda_codec_pcm_new(codec, "CA0132 Analog"); if (!info) return -ENOMEM; + if (spec->use_alt_functions) { + info->own_chmap = true; + info->stream[SNDRV_PCM_STREAM_PLAYBACK].chmap + = ca0132_alt_chmaps; + } info->stream[SNDRV_PCM_STREAM_PLAYBACK] = ca0132_pcm_analog_playback; info->stream[SNDRV_PCM_STREAM_PLAYBACK].nid = spec->dacs[0]; info->stream[SNDRV_PCM_STREAM_PLAYBACK].channels_max = @@ -6115,6 +6845,15 @@ static void ca0132_init_chip(struct hda_codec *codec) on = (unsigned int)ca0132_effects[i].reqs[0]; spec->effects_switch[i] = on ? 1 : 0; } + /* + * Sets defaults for the effect slider controls, only for alternative + * ca0132 codecs. Also sets x-bass crossover frequency to 80hz. + */ + if (spec->use_alt_controls) { + spec->xbass_xover_freq = 8; + for (i = 0; i < EFFECT_LEVEL_SLIDERS; i++) + spec->fx_ctl_val[i] = effect_slider_defaults[i]; + }
spec->voicefx_val = 0; spec->effects_switch[PLAY_ENHANCEMENT - EFFECT_START_NID] = 1; @@ -6834,13 +7573,15 @@ static int patch_ca0132(struct hda_codec *codec) break; }
- /* Setup whether or not to use alt functions */ + /* Setup whether or not to use alt functions/controls */ switch (spec->quirk) { case QUIRK_SBZ: case QUIRK_R3DI: + spec->use_alt_controls = true; spec->use_alt_functions = true; break; default: + spec->use_alt_controls = false; spec->use_alt_functions = false; break; }
participants (2)
-
Connor McAdams
-
kbuild test robot