Re: [alsa-devel] [PATCH - CA0132 HDA Codec 1/2] ALSA: Update Creative CA0132 codec to add DSP features.
At Fri, 13 Jul 2012 16:44:28 -0700, ian_minett@creativelabs.com wrote:
From: Ian Minett ian_minett@creativelabs.com
Thanks very much for the feedback on the last submission, apologies for the delayed response. Please find attached the new set of patches for updating the CA0132 codec, which takes many of the suggestions into account.
- Ian
ALSA: Update Creative CA0132 HDA codec to introduce DSP features.
patch_ca0132.c:
- Add DSP support to codec.
- 3 capture subdevices supported: Analog Mic1/Digital Mic,
Analog Mic2 and What U Hear.
- Add jack detection kcontrol
- Use request_firmware() for loading DSP binaries.
Ideally, these should be split to each patch. Though I see it's not so trivial in this case...
But please provide more technical details. I can only guess what's VNID and what's effect module ID. Give more descriptions.
hda_intel.c:
- in azx_pcm_trigger(), change spin_lock()/spin_unlock() pair to
spin_lock_irqsave()/spin_unlock_irqrestore(). This still seems to be necessary - if an interrupt occurs after azx_stream_start() and before spin_unlock(), it will cause deadlock as azx_interrupt() also acquires chip->reg_lock.
Oh, this is basically because you are doing pretty bad things: calling PCM trigger callback from the codec driver. What's the reason behind it?
Since the whole patch lacks the big and small pictures, it's hard to judge...
ctefx.bin:
- DSP binary for firmware repository, for driver to load from
/lib/firmware directory.
Please submit the patch for adding this, too. Or, at least, give a kernel config to build without the firmware. Otherwise you'll break the driver.
Looking though the code, the biggest problem is that you are using float in the code. This is now allowed in the kernel at all. You have to rewrite without float type -- i.e. never use "float" in the code, for both fields/arguments and computations.
Also, some other review comments below:
diff --git a/sound/pci/hda/patch_ca0132.c b/sound/pci/hda/patch_ca0132.c index d0d3540..c7cdfe1 100644 --- a/sound/pci/hda/patch_ca0132.c +++ b/sound/pci/hda/patch_ca0132.c @@ -1,5 +1,5 @@ /*
- HD audio interface patch for Creative CA0132 chip
- HD audio interface patch for Creative Sound Core3D chip
- Copyright (c) 2011, Creative Technology Ltd.
@@ -25,18 +25,470 @@ #include <linux/delay.h> #include <linux/slab.h> #include <linux/pci.h> -#include <linux/mutex.h> #include <linux/module.h> +#include <linux/firmware.h> #include <sound/core.h> +#include <sound/tlv.h> #include "hda_codec.h" #include "hda_local.h" +#include "hda_jack.h" #include "hda_auto_parser.h"
+#include "ca0132_regs.h"
+/* Enable this to see more debug messages. */ +/*#define ENABLE_CA0132_DEBUG*/ +/* Enable this to see controls for tuning purpose. */ +/*#define ENABLE_TUNING_CONTROLS*/
+#define FLOAT_ZERO 0x00000000 +#define FLOAT_ONE 0x3f800000 +#define FLOAT_TWO 0x40000000 +#define FLOAT_MINUS_5 0xc0a00000
+#define UNSOL_TAG_HP 0x10 +#define UNSOL_TAG_AMIC1 0x12 +#define UNSOL_TAG_DSP 0x16
+#define SUCCEEDED(_x) (((int)(_x)) >= 0) +#define FAILED(_x) (((int)(_x)) < 0) +#define CT_OK (0) +#define CT_FAIL (-1)
Please avoid home-baked macros like this. Usually a function returns zero for succcess and a negative error code. No need to define special CT_XX macros.
+#define MIN(x, y) (((x) < (y)) ? (x) : (y)) +#define MAX(x, y) (((x) > (y)) ? (x) : (y))
Use the standard min() and max() macros.
+#define DSP_DMA_WRITE_BUFLEN_INIT (1UL<<18) +#define DSP_DMA_WRITE_BUFLEN_OVLY (1UL<<15)
+#define DMA_TRANSFER_FRAME_SIZE_NWORDS 8 +#define DMA_TRANSFER_MAX_FRAME_SIZE_NWORDS 32 +#define DMA_OVERLAY_FRAME_SIZE_NWORDS 2
+#define MASTERCONTROL 0x80 +#define MASTERCONTROL_ALLOC_DMA_CHAN 9 +#define MASTERCONTROL_QUERY_SPEAKER_EQ_ADDRESS 22
#define WIDGET_CHIP_CTRL 0x15 #define WIDGET_DSP_CTRL 0x16
-#define WUH_MEM_CONNID 10 -#define DSP_MEM_CONNID 16 +#define MEM_CONNID_MICIN1 3 +#define MEM_CONNID_MICIN2 5 +#define MEM_CONNID_MICOUT1 12 +#define MEM_CONNID_MICOUT2 14 +#define MEM_CONNID_WUH 10 +#define MEM_CONNID_DSP 16 +#define MEM_CONNID_DMIC 100
+#define SCP_SET 0 +#define SCP_GET 1
+#define SPEQ_FILE "ctspeq.bin" +#define SPEQ_SIZE 0x2040 +#define EFX_FILE "ctefx.bin" +#define EFX_SIZE 0xA003C
Don't forget to add MODULE_FIRMWARE() with these files, too.
I stop at this point... Too big to review...
thanks,
Takashi
From: Ian Minett ian_minett@creativelabs.com
Hi, Thanks again for the feedback - we've reworked the patch accordingly, to remove floats, tidy the use of macros and add the MODULE_FIRMWARE() lines.
You are right - since it is a pretty big change introducing all the DSP functionality it isn't trivial to break it into multiple small patches. I've added more info on what has been changed below in the git commit message.
The reason we need to modify the trigger callback in hda_intel.c is that the DSP code is downloaded from the host (driver) to the CA0132 chip via the HD-link. This means that the trigger callback is called from the codec driver.
I will submit the DSP firmware blob (ctefx.bin) as a separate patch against the alsa-firmware repository, for intended upstreaming to linux-firmware package. The CA0132 codec uses request_firmware() loader to find the DSP binary in the /lib/firmware directory (the firmware isn't required for building the CA0132 module). If this isn't the correct way to submit firmware binaries, we'd appreciate any info on what the correct method for this is.
Thank you very much, - Ian
---
ALSA: Update Creative CA0132 HDA codec to introduce DSP features.
patch_ca0132.c:
- Add DSP support to codec. Playback Effects and controls: - Play Enhancement On/Off This is the master control for Surround, Crystalizer, Dialog Plus, Smart Volume, Equalizer and X-Bass. - Surround On/Off - Crystalizer On/Off - Dialog Plus On/Off - Smart Volume On/Off - Equalizer On/Off - X-Bass On/Off
Capture Effects and controls: - Applied to Analog Mic-In1 / Stereo Digital-Mic. - CrystalVoice On/Off Master control for Acoustic Echo Cancellation, Voice Focus, Mic-Smart Volume, Voice FX. - Acoustic Echo Cancellation On/Off - Voice Focus (Beam Forming) On/Off Apply to Stereo Digital Mic, not Analog Mic-In1. - Smart Volume (Mic SVM) On/Off - Voice FX (Voice Morphing) Morph Effects preset selection include: - Neutral, - Male2Female, - Female2Male, - Scrappy Kid, - Elderly, - Orc, - Elf, - Dwarf, - Alien Brute, - Robot, - Marine, - Emo, - Deep Voice, - Munchkin - Add VNIDs (Virtual Node IDs) to denote controls that are not in the physical codec node ID. - Add effect module IDs to identify individual effect modules present in the DSP. - Playback Device: One 2-ch device for: * Built-in speaker/Headphone out - Mutually exclusive, and independent volume/mute control. - Only one will be active at any instance. - Headphone Out will take precedence over Built-in speaker when it's plugged in or detected. - Capture subdevices: 3 supported: * Analog Mic1/Digital Mic - Mutually exclusive, and independent volume/select control. - Only one will be active at any instance. - Analog Mic-In1 has +30dB Mic-Boost on/off control. - Analog-Mic1 In will take precedence over Built-in Stereo Digital-Mic when it's plugged in or detected. * Analog Mic2 * What U Hear. - Add jack detection kcontrol - Use request_firmware() for loading DSP binaries from /lib/firmware. - Mixer controls: - Volume level and Mute controls. - Capture select controls. - Mic-Boost control. - Effects controls (as above).
hda_intel.c: - in azx_pcm_trigger(), change spin_lock()/spin_unlock() pair to spin_lock_irqsave()/spin_unlock_irqrestore(). This is necessary for DSP code downloading via HD-link. If an interrupt occurs after azx_stream_start() and before spin_unlock(), it will cause deadlock as azx_interrupt() also acquires chip->reg_lock.
Developed and maintained by Creative Labs, Inc.
Signed-off-by: Ian Minett ian_minett@creativelabs.com
diff --git a/sound/pci/hda/ca0132_regs.h b/sound/pci/hda/ca0132_regs.h new file mode 100644 index 0000000..831ca9c --- /dev/null +++ b/sound/pci/hda/ca0132_regs.h @@ -0,0 +1,409 @@ +/* + * HD audio interface patch for Creative CA0132 chip. + * CA0132 registers defines. + * + * Copyright (c) 2011, Creative Technology Ltd. + * + * This driver is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This driver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __CA0132_REGS_H +#define __CA0312_REGS_H + +#define DSP_CHIP_OFFSET 0x100000 +#define DSP_DBGCNTL_MODULE_OFFSET 0xE30 +#define DSP_DBGCNTL_INST_OFFSET \ + (DSP_CHIP_OFFSET + DSP_DBGCNTL_MODULE_OFFSET) + +#define DSP_DBGCNTL_EXEC_LOBIT 0x0 +#define DSP_DBGCNTL_EXEC_HIBIT 0x3 +#define DSP_DBGCNTL_EXEC_MASK 0xF + +#define DSP_DBGCNTL_SS_LOBIT 0x4 +#define DSP_DBGCNTL_SS_HIBIT 0x7 +#define DSP_DBGCNTL_SS_MASK 0xF0 + +#define DSP_DBGCNTL_STATE_LOBIT 0xA +#define DSP_DBGCNTL_STATE_HIBIT 0xD +#define DSP_DBGCNTL_STATE_MASK 0x3C00 + +#define XRAM_CHIP_OFFSET 0x0 +#define XRAM_XRAM_CHANNEL_COUNT 0xE000 +#define XRAM_XRAM_MODULE_OFFSET 0x0 +#define XRAM_XRAM_CHAN_INCR 4 +#define XRAM_XRAM_INST_OFFSET(_chan) \ + (XRAM_CHIP_OFFSET + XRAM_XRAM_MODULE_OFFSET + \ + (_chan * XRAM_XRAM_CHAN_INCR)) + +#define YRAM_CHIP_OFFSET 0x40000 +#define YRAM_YRAM_CHANNEL_COUNT 0x8000 +#define YRAM_YRAM_MODULE_OFFSET 0x0 +#define YRAM_YRAM_CHAN_INCR 4 +#define YRAM_YRAM_INST_OFFSET(_chan) \ + (YRAM_CHIP_OFFSET + YRAM_YRAM_MODULE_OFFSET + \ + (_chan * YRAM_YRAM_CHAN_INCR)) + +#define UC_CHIP_OFFSET 0x80000 +#define UC_UC_CHANNEL_COUNT 0x10000 +#define UC_UC_MODULE_OFFSET 0x0 +#define UC_UC_CHAN_INCR 4 +#define UC_UC_INST_OFFSET(_chan) \ + (UC_CHIP_OFFSET + UC_UC_MODULE_OFFSET + \ + (_chan * UC_UC_CHAN_INCR)) + +#define AXRAM_CHIP_OFFSET 0x3C000 +#define AXRAM_AXRAM_CHANNEL_COUNT 0x1000 +#define AXRAM_AXRAM_MODULE_OFFSET 0x0 +#define AXRAM_AXRAM_CHAN_INCR 4 +#define AXRAM_AXRAM_INST_OFFSET(_chan) \ + (AXRAM_CHIP_OFFSET + AXRAM_AXRAM_MODULE_OFFSET + \ + (_chan * AXRAM_AXRAM_CHAN_INCR)) + +#define AYRAM_CHIP_OFFSET 0x78000 +#define AYRAM_AYRAM_CHANNEL_COUNT 0x1000 +#define AYRAM_AYRAM_MODULE_OFFSET 0x0 +#define AYRAM_AYRAM_CHAN_INCR 4 +#define AYRAM_AYRAM_INST_OFFSET(_chan) \ + (AYRAM_CHIP_OFFSET + AYRAM_AYRAM_MODULE_OFFSET + \ + (_chan * AYRAM_AYRAM_CHAN_INCR)) + +#define DSPDMAC_CHIP_OFFSET 0x110000 +#define DSPDMAC_DMA_CFG_CHANNEL_COUNT 12 +#define DSPDMAC_DMACFG_MODULE_OFFSET 0xF00 +#define DSPDMAC_DMACFG_CHAN_INCR 0x10 +#define DSPDMAC_DMACFG_INST_OFFSET(_chan) \ + (DSPDMAC_CHIP_OFFSET + DSPDMAC_DMACFG_MODULE_OFFSET + \ + (_chan * DSPDMAC_DMACFG_CHAN_INCR)) + +#define DSPDMAC_DMACFG_DBADR_LOBIT 0x0 +#define DSPDMAC_DMACFG_DBADR_HIBIT 0x10 +#define DSPDMAC_DMACFG_DBADR_MASK 0x1FFFF +#define DSPDMAC_DMACFG_LP_LOBIT 0x11 +#define DSPDMAC_DMACFG_LP_HIBIT 0x11 +#define DSPDMAC_DMACFG_LP_MASK 0x20000 + +#define DSPDMAC_DMACFG_AINCR_LOBIT 0x12 +#define DSPDMAC_DMACFG_AINCR_HIBIT 0x12 +#define DSPDMAC_DMACFG_AINCR_MASK 0x40000 + +#define DSPDMAC_DMACFG_DWR_LOBIT 0x13 +#define DSPDMAC_DMACFG_DWR_HIBIT 0x13 +#define DSPDMAC_DMACFG_DWR_MASK 0x80000 + +#define DSPDMAC_DMACFG_AJUMP_LOBIT 0x14 +#define DSPDMAC_DMACFG_AJUMP_HIBIT 0x17 +#define DSPDMAC_DMACFG_AJUMP_MASK 0xF00000 + +#define DSPDMAC_DMACFG_AMODE_LOBIT 0x18 +#define DSPDMAC_DMACFG_AMODE_HIBIT 0x19 +#define DSPDMAC_DMACFG_AMODE_MASK 0x3000000 + +#define DSPDMAC_DMACFG_LK_LOBIT 0x1A +#define DSPDMAC_DMACFG_LK_HIBIT 0x1A +#define DSPDMAC_DMACFG_LK_MASK 0x4000000 + +#define DSPDMAC_DMACFG_AICS_LOBIT 0x1B +#define DSPDMAC_DMACFG_AICS_HIBIT 0x1F +#define DSPDMAC_DMACFG_AICS_MASK 0xF8000000 + +#define DSPDMAC_DMACFG_LP_SINGLE 0 +#define DSPDMAC_DMACFG_LP_LOOPING 1 + +#define DSPDMAC_DMACFG_AINCR_XANDY 0 +#define DSPDMAC_DMACFG_AINCR_XORY 1 + +#define DSPDMAC_DMACFG_DWR_DMA_RD 0 +#define DSPDMAC_DMACFG_DWR_DMA_WR 1 + +#define DSPDMAC_DMACFG_AMODE_LINEAR 0 +#define DSPDMAC_DMACFG_AMODE_RSV1 1 +#define DSPDMAC_DMACFG_AMODE_WINTLV 2 +#define DSPDMAC_DMACFG_AMODE_GINTLV 3 + +#define DSPDMAC_DSP_ADR_OFS_CHANNEL_COUNT 12 +#define DSPDMAC_DSPADROFS_MODULE_OFFSET 0xF04 +#define DSPDMAC_DSPADROFS_CHAN_INCR 0x10 +#define DSPDMAC_DSPADROFS_INST_OFFSET(_chan) \ + (DSPDMAC_CHIP_OFFSET + DSPDMAC_DSPADROFS_MODULE_OFFSET + \ + (_chan * DSPDMAC_DSPADROFS_CHAN_INCR)) + +#define DSPDMAC_DSPADROFS_COFS_LOBIT 0x0 +#define DSPDMAC_DSPADROFS_COFS_HIBIT 0xF +#define DSPDMAC_DSPADROFS_COFS_MASK 0xFFFF + +#define DSPDMAC_DSPADROFS_BOFS_LOBIT 0x10 +#define DSPDMAC_DSPADROFS_BOFS_HIBIT 0x1F +#define DSPDMAC_DSPADROFS_BOFS_MASK 0xFFFF0000 + +#define DSPDMAC_DSP_ADR_WOFS_CHANNEL_COUNT 12 +#define DSPDMAC_DSPADRWOFS_MODULE_OFFSET 0xF04 +#define DSPDMAC_DSPADRWOFS_CHAN_INCR 0x10 + +#define DSPDMAC_DSPADRWOFS_INST_OFFSET(_chan) \ + (DSPDMAC_CHIP_OFFSET + DSPDMAC_DSPADRWOFS_MODULE_OFFSET + \ + (_chan * DSPDMAC_DSPADRWOFS_CHAN_INCR)) + +#define DSPDMAC_DSPADRWOFS_WCOFS_LOBIT 0x0 +#define DSPDMAC_DSPADRWOFS_WCOFS_HIBIT 0xA +#define DSPDMAC_DSPADRWOFS_WCOFS_MASK 0x7FF + +#define DSPDMAC_DSPADRWOFS_WCBFR_LOBIT 0xB +#define DSPDMAC_DSPADRWOFS_WCBFR_HIBIT 0xF +#define DSPDMAC_DSPADRWOFS_WCBFR_MASK 0xF800 + +#define DSPDMAC_DSPADRWOFS_WBOFS_LOBIT 0x10 +#define DSPDMAC_DSPADRWOFS_WBOFS_HIBIT 0x1A +#define DSPDMAC_DSPADRWOFS_WBOFS_MASK 0x7FF0000 + +#define DSPDMAC_DSPADRWOFS_WBBFR_LOBIT 0x1B +#define DSPDMAC_DSPADRWOFS_WBBFR_HIBIT 0x1F +#define DSPDMAC_DSPADRWOFS_WBBFR_MASK 0xF8000000 + +#define DSPDMAC_DSP_ADR_GOFS_CHANNEL_COUNT 12 +#define DSPDMAC_DSPADRGOFS_MODULE_OFFSET 0xF04 +#define DSPDMAC_DSPADRGOFS_CHAN_INCR 0x10 +#define DSPDMAC_DSPADRGOFS_INST_OFFSET(_chan) \ + (DSPDMAC_CHIP_OFFSET + DSPDMAC_DSPADRGOFS_MODULE_OFFSET + \ + (_chan * DSPDMAC_DSPADRGOFS_CHAN_INCR)) + +#define DSPDMAC_DSPADRGOFS_GCOFS_LOBIT 0x0 +#define DSPDMAC_DSPADRGOFS_GCOFS_HIBIT 0x9 +#define DSPDMAC_DSPADRGOFS_GCOFS_MASK 0x3FF + +#define DSPDMAC_DSPADRGOFS_GCS_LOBIT 0xA +#define DSPDMAC_DSPADRGOFS_GCS_HIBIT 0xC +#define DSPDMAC_DSPADRGOFS_GCS_MASK 0x1C00 + +#define DSPDMAC_DSPADRGOFS_GCBFR_LOBIT 0xD +#define DSPDMAC_DSPADRGOFS_GCBFR_HIBIT 0xF +#define DSPDMAC_DSPADRGOFS_GCBFR_MASK 0xE000 + +#define DSPDMAC_DSPADRGOFS_GBOFS_LOBIT 0x10 +#define DSPDMAC_DSPADRGOFS_GBOFS_HIBIT 0x19 +#define DSPDMAC_DSPADRGOFS_GBOFS_MASK 0x3FF0000 + +#define DSPDMAC_DSPADRGOFS_GBS_LOBIT 0x1A +#define DSPDMAC_DSPADRGOFS_GBS_HIBIT 0x1C +#define DSPDMAC_DSPADRGOFS_GBS_MASK 0x1C000000 + +#define DSPDMAC_DSPADRGOFS_GBBFR_LOBIT 0x1D +#define DSPDMAC_DSPADRGOFS_GBBFR_HIBIT 0x1F +#define DSPDMAC_DSPADRGOFS_GBBFR_MASK 0xE0000000 + +#define DSPDMAC_XFR_CNT_CHANNEL_COUNT 12 +#define DSPDMAC_XFRCNT_MODULE_OFFSET 0xF08 +#define DSPDMAC_XFRCNT_CHAN_INCR 0x10 + +#define DSPDMAC_XFRCNT_INST_OFFSET(_chan) \ + (DSPDMAC_CHIP_OFFSET + DSPDMAC_XFRCNT_MODULE_OFFSET + \ + (_chan * DSPDMAC_XFRCNT_CHAN_INCR)) + +#define DSPDMAC_XFRCNT_CCNT_LOBIT 0x0 +#define DSPDMAC_XFRCNT_CCNT_HIBIT 0xF +#define DSPDMAC_XFRCNT_CCNT_MASK 0xFFFF + +#define DSPDMAC_XFRCNT_BCNT_LOBIT 0x10 +#define DSPDMAC_XFRCNT_BCNT_HIBIT 0x1F +#define DSPDMAC_XFRCNT_BCNT_MASK 0xFFFF0000 + +#define DSPDMAC_IRQ_CNT_CHANNEL_COUNT 12 +#define DSPDMAC_IRQCNT_MODULE_OFFSET 0xF0C +#define DSPDMAC_IRQCNT_CHAN_INCR 0x10 +#define DSPDMAC_IRQCNT_INST_OFFSET(_chan) \ + (DSPDMAC_CHIP_OFFSET + DSPDMAC_IRQCNT_MODULE_OFFSET + \ + (_chan * DSPDMAC_IRQCNT_CHAN_INCR)) + +#define DSPDMAC_IRQCNT_CICNT_LOBIT 0x0 +#define DSPDMAC_IRQCNT_CICNT_HIBIT 0xF +#define DSPDMAC_IRQCNT_CICNT_MASK 0xFFFF + +#define DSPDMAC_IRQCNT_BICNT_LOBIT 0x10 +#define DSPDMAC_IRQCNT_BICNT_HIBIT 0x1F +#define DSPDMAC_IRQCNT_BICNT_MASK 0xFFFF0000 + +#define DSPDMAC_AUD_CHSEL_CHANNEL_COUNT 12 +#define DSPDMAC_AUDCHSEL_MODULE_OFFSET 0xFC0 +#define DSPDMAC_AUDCHSEL_CHAN_INCR 0x4 +#define DSPDMAC_AUDCHSEL_INST_OFFSET(_chan) \ + (DSPDMAC_CHIP_OFFSET + DSPDMAC_AUDCHSEL_MODULE_OFFSET + \ + (_chan * DSPDMAC_AUDCHSEL_CHAN_INCR)) + +#define DSPDMAC_AUDCHSEL_ACS_LOBIT 0x0 +#define DSPDMAC_AUDCHSEL_ACS_HIBIT 0x1F +#define DSPDMAC_AUDCHSEL_ACS_MASK 0xFFFFFFFF + +#define DSPDMAC_CHNLSTART_MODULE_OFFSET 0xFF0 +#define DSPDMAC_CHNLSTART_INST_OFFSET \ + (DSPDMAC_CHIP_OFFSET + DSPDMAC_CHNLSTART_MODULE_OFFSET) + +#define DSPDMAC_CHNLSTART_EN_LOBIT 0x0 +#define DSPDMAC_CHNLSTART_EN_HIBIT 0xB +#define DSPDMAC_CHNLSTART_EN_MASK 0xFFF + +#define DSPDMAC_CHNLSTART_VAI1_LOBIT 0xC +#define DSPDMAC_CHNLSTART_VAI1_HIBIT 0xF +#define DSPDMAC_CHNLSTART_VAI1_MASK 0xF000 + +#define DSPDMAC_CHNLSTART_DIS_LOBIT 0x10 +#define DSPDMAC_CHNLSTART_DIS_HIBIT 0x1B +#define DSPDMAC_CHNLSTART_DIS_MASK 0xFFF0000 + +#define DSPDMAC_CHNLSTART_VAI2_LOBIT 0x1C +#define DSPDMAC_CHNLSTART_VAI2_HIBIT 0x1F +#define DSPDMAC_CHNLSTART_VAI2_MASK 0xF0000000 + +#define DSPDMAC_CHNLSTATUS_MODULE_OFFSET 0xFF4 +#define DSPDMAC_CHNLSTATUS_INST_OFFSET \ + (DSPDMAC_CHIP_OFFSET + DSPDMAC_CHNLSTATUS_MODULE_OFFSET) + +#define DSPDMAC_CHNLSTATUS_ISC_LOBIT 0x0 +#define DSPDMAC_CHNLSTATUS_ISC_HIBIT 0xB +#define DSPDMAC_CHNLSTATUS_ISC_MASK 0xFFF + +#define DSPDMAC_CHNLSTATUS_AOO_LOBIT 0xC +#define DSPDMAC_CHNLSTATUS_AOO_HIBIT 0xC +#define DSPDMAC_CHNLSTATUS_AOO_MASK 0x1000 + +#define DSPDMAC_CHNLSTATUS_AOU_LOBIT 0xD +#define DSPDMAC_CHNLSTATUS_AOU_HIBIT 0xD +#define DSPDMAC_CHNLSTATUS_AOU_MASK 0x2000 + +#define DSPDMAC_CHNLSTATUS_AIO_LOBIT 0xE +#define DSPDMAC_CHNLSTATUS_AIO_HIBIT 0xE +#define DSPDMAC_CHNLSTATUS_AIO_MASK 0x4000 + +#define DSPDMAC_CHNLSTATUS_AIU_LOBIT 0xF +#define DSPDMAC_CHNLSTATUS_AIU_HIBIT 0xF +#define DSPDMAC_CHNLSTATUS_AIU_MASK 0x8000 + +#define DSPDMAC_CHNLSTATUS_IEN_LOBIT 0x10 +#define DSPDMAC_CHNLSTATUS_IEN_HIBIT 0x1B +#define DSPDMAC_CHNLSTATUS_IEN_MASK 0xFFF0000 + +#define DSPDMAC_CHNLSTATUS_VAI0_LOBIT 0x1C +#define DSPDMAC_CHNLSTATUS_VAI0_HIBIT 0x1F +#define DSPDMAC_CHNLSTATUS_VAI0_MASK 0xF0000000 + +#define DSPDMAC_CHNLPROP_MODULE_OFFSET 0xFF8 +#define DSPDMAC_CHNLPROP_INST_OFFSET \ + (DSPDMAC_CHIP_OFFSET + DSPDMAC_CHNLPROP_MODULE_OFFSET) + +#define DSPDMAC_CHNLPROP_DCON_LOBIT 0x0 +#define DSPDMAC_CHNLPROP_DCON_HIBIT 0xB +#define DSPDMAC_CHNLPROP_DCON_MASK 0xFFF + +#define DSPDMAC_CHNLPROP_FFS_LOBIT 0xC +#define DSPDMAC_CHNLPROP_FFS_HIBIT 0xC +#define DSPDMAC_CHNLPROP_FFS_MASK 0x1000 + +#define DSPDMAC_CHNLPROP_NAJ_LOBIT 0xD +#define DSPDMAC_CHNLPROP_NAJ_HIBIT 0xD +#define DSPDMAC_CHNLPROP_NAJ_MASK 0x2000 + +#define DSPDMAC_CHNLPROP_ENH_LOBIT 0xE +#define DSPDMAC_CHNLPROP_ENH_HIBIT 0xE +#define DSPDMAC_CHNLPROP_ENH_MASK 0x4000 + +#define DSPDMAC_CHNLPROP_MSPCE_LOBIT 0x10 +#define DSPDMAC_CHNLPROP_MSPCE_HIBIT 0x1B +#define DSPDMAC_CHNLPROP_MSPCE_MASK 0xFFF0000 + +#define DSPDMAC_CHNLPROP_AC_LOBIT 0x1C +#define DSPDMAC_CHNLPROP_AC_HIBIT 0x1F +#define DSPDMAC_CHNLPROP_AC_MASK 0xF0000000 + +#define DSPDMAC_ACTIVE_MODULE_OFFSET 0xFFC +#define DSPDMAC_ACTIVE_INST_OFFSET \ + (DSPDMAC_CHIP_OFFSET + DSPDMAC_ACTIVE_MODULE_OFFSET) + +#define DSPDMAC_ACTIVE_AAR_LOBIT 0x0 +#define DSPDMAC_ACTIVE_AAR_HIBIT 0xB +#define DSPDMAC_ACTIVE_AAR_MASK 0xFFF + +#define DSPDMAC_ACTIVE_WFR_LOBIT 0xC +#define DSPDMAC_ACTIVE_WFR_HIBIT 0x17 +#define DSPDMAC_ACTIVE_WFR_MASK 0xFFF000 + +#define DSP_AUX_MEM_BASE 0xE000 +#define INVALID_CHIP_ADDRESS (~0UL) + +#define X_SIZE (XRAM_XRAM_CHANNEL_COUNT * XRAM_XRAM_CHAN_INCR) +#define Y_SIZE (YRAM_YRAM_CHANNEL_COUNT * YRAM_YRAM_CHAN_INCR) +#define AX_SIZE (AXRAM_AXRAM_CHANNEL_COUNT * AXRAM_AXRAM_CHAN_INCR) +#define AY_SIZE (AYRAM_AYRAM_CHANNEL_COUNT * AYRAM_AYRAM_CHAN_INCR) +#define UC_SIZE (UC_UC_CHANNEL_COUNT * UC_UC_CHAN_INCR) + +#define XEXT_SIZE (X_SIZE + AX_SIZE) +#define YEXT_SIZE (Y_SIZE + AY_SIZE) + +#define U64K 0x10000UL + +#define X_END (XRAM_CHIP_OFFSET + X_SIZE) +#define X_EXT (XRAM_CHIP_OFFSET + XEXT_SIZE) +#define AX_END (XRAM_CHIP_OFFSET + U64K*4) + +#define Y_END (YRAM_CHIP_OFFSET + Y_SIZE) +#define Y_EXT (YRAM_CHIP_OFFSET + YEXT_SIZE) +#define AY_END (YRAM_CHIP_OFFSET + U64K*4) + +#define UC_END (UC_CHIP_OFFSET + UC_SIZE) + +#define X_RANGE_MAIN(a, s) \ + (((a)+((s)-1)*XRAM_XRAM_CHAN_INCR < X_END)) +#define X_RANGE_AUX(a, s) \ + (((a) >= X_END) && ((a)+((s)-1)*XRAM_XRAM_CHAN_INCR < AX_END)) +#define X_RANGE_EXT(a, s) \ + (((a)+((s)-1)*XRAM_XRAM_CHAN_INCR < X_EXT)) +#define X_RANGE_ALL(a, s) \ + (((a)+((s)-1)*XRAM_XRAM_CHAN_INCR < AX_END)) + +#define Y_RANGE_MAIN(a, s) \ + (((a) >= YRAM_CHIP_OFFSET) && \ + ((a)+((s)-1)*YRAM_YRAM_CHAN_INCR < Y_END)) +#define Y_RANGE_AUX(a, s) \ + (((a) >= Y_END) && \ + ((a)+((s)-1)*YRAM_YRAM_CHAN_INCR < AY_END)) +#define Y_RANGE_EXT(a, s) \ + (((a) >= YRAM_CHIP_OFFSET) && \ + ((a)+((s)-1)*YRAM_YRAM_CHAN_INCR < Y_EXT)) +#define Y_RANGE_ALL(a, s) \ + (((a) >= YRAM_CHIP_OFFSET) && \ + ((a)+((s)-1)*YRAM_YRAM_CHAN_INCR < AY_END)) + +#define UC_RANGE(a, s) \ + (((a) >= UC_CHIP_OFFSET) && \ + ((a)+((s)-1)*UC_UC_CHAN_INCR < UC_END)) + +#define X_OFF(a) \ + (((a) - XRAM_CHIP_OFFSET) / XRAM_XRAM_CHAN_INCR) +#define AX_OFF(a) \ + (((a) % (AXRAM_AXRAM_CHANNEL_COUNT * \ + AXRAM_AXRAM_CHAN_INCR)) / AXRAM_AXRAM_CHAN_INCR) + +#define Y_OFF(a) \ + (((a) - YRAM_CHIP_OFFSET) / YRAM_YRAM_CHAN_INCR) +#define AY_OFF(a) \ + (((a) % (AYRAM_AYRAM_CHANNEL_COUNT * \ + AYRAM_AYRAM_CHAN_INCR)) / AYRAM_AYRAM_CHAN_INCR) + +#define UC_OFF(a) (((a) - UC_CHIP_OFFSET) / UC_UC_CHAN_INCR) + +#define X_EXT_MAIN_SIZE(a) (XRAM_XRAM_CHANNEL_COUNT - X_OFF(a)) +#define X_EXT_AUX_SIZE(a, s) ((s) - X_EXT_MAIN_SIZE(a)) + +#define Y_EXT_MAIN_SIZE(a) (YRAM_YRAM_CHANNEL_COUNT - Y_OFF(a)) +#define Y_EXT_AUX_SIZE(a, s) ((s) - Y_EXT_MAIN_SIZE(a)) + +#endif diff --git a/sound/pci/hda/patch_ca0132.c b/sound/pci/hda/patch_ca0132.c index d0d3540..825f5b4 100644 --- a/sound/pci/hda/patch_ca0132.c +++ b/sound/pci/hda/patch_ca0132.c @@ -1,5 +1,5 @@ /* - * HD audio interface patch for Creative CA0132 chip + * HD audio interface patch for Creative Sound Core3D chip * * Copyright (c) 2011, Creative Technology Ltd. * @@ -25,18 +25,469 @@ #include <linux/delay.h> #include <linux/slab.h> #include <linux/pci.h> -#include <linux/mutex.h> #include <linux/module.h> +#include <linux/firmware.h> #include <sound/core.h> +#include <sound/tlv.h> #include "hda_codec.h" #include "hda_local.h" +#include "hda_jack.h" #include "hda_auto_parser.h"
+#include "ca0132_regs.h" + +/* Enable this to see more debug messages. */ +/*#define ENABLE_CA0132_DEBUG*/ +/* Enable this to see controls for tuning purpose. */ +/*#define ENABLE_TUNING_CONTROLS*/ + +#define FLOAT_ZERO 0x00000000 +#define FLOAT_ONE 0x3f800000 +#define FLOAT_TWO 0x40000000 +#define FLOAT_MINUS_5 0xc0a00000 + +#define UNSOL_TAG_HP 0x10 +#define UNSOL_TAG_AMIC1 0x12 +#define UNSOL_TAG_DSP 0x16 + +#define SUCCEEDED(_x) (((int)(_x)) >= 0) +#define FAILED(_x) (((int)(_x)) < 0) + +#define DSP_DMA_WRITE_BUFLEN_INIT (1UL<<18) +#define DSP_DMA_WRITE_BUFLEN_OVLY (1UL<<15) + +#define DMA_TRANSFER_FRAME_SIZE_NWORDS 8 +#define DMA_TRANSFER_MAX_FRAME_SIZE_NWORDS 32 +#define DMA_OVERLAY_FRAME_SIZE_NWORDS 2 + +#define MASTERCONTROL 0x80 +#define MASTERCONTROL_ALLOC_DMA_CHAN 9 +#define MASTERCONTROL_QUERY_SPEAKER_EQ_ADDRESS 22 + #define WIDGET_CHIP_CTRL 0x15 #define WIDGET_DSP_CTRL 0x16
-#define WUH_MEM_CONNID 10 -#define DSP_MEM_CONNID 16 +#define MEM_CONNID_MICIN1 3 +#define MEM_CONNID_MICIN2 5 +#define MEM_CONNID_MICOUT1 12 +#define MEM_CONNID_MICOUT2 14 +#define MEM_CONNID_WUH 10 +#define MEM_CONNID_DSP 16 +#define MEM_CONNID_DMIC 100 + +#define SCP_SET 0 +#define SCP_GET 1 + +#define SPEQ_FILE "ctspeq.bin" +#define SPEQ_SIZE 0x2040 +#define EFX_FILE "ctefx.bin" + +MODULE_FIRMWARE(SPEQ_FILE); +MODULE_FIRMWARE(EFX_FILE); + +/* Debug message controls */ +#ifdef ENABLE_CA0132_DEBUG + +#define CTASSERT(x) \ + { if (!(x)) snd_printdd(KERN_ERR "CTASSERT failed.\n"); } + +#define CA0132_LOG(fmt, args...) snd_printdd(fmt, ##args) +#define CA0132_DSP_LOG(msg) snd_printdd(KERN_INFO "[%s]\n", msg) +#define FAIL_MSG(n, s) fail_debug_out(n, s) + +static int fail_debug_out(int status, const char *s) +{ + snd_printdd(KERN_ERR "[%s]\n", s); + return status; +} +#else + +#define CTASSERT(x) do { } while (0) +#define CA0132_LOG(fmt, args...) do { } while (0) +#define CA0132_DSP_LOG(msg) do { } while (0) +#define FAIL_MSG(n, s) fail_debug_out(n, s) + +static int fail_debug_out(int status, const char *s) +{ + return status; +} +#endif + +static char *dirstr[2] = { "Playback", "Capture" }; + +enum { + SPEAKER_OUT, + HEADPHONE_OUT +}; + +enum { + DIGITAL_MIC, + LINE_MIC_IN +}; + +enum { +#define VNODE_START_NID 0x80 + VNID_SPK = VNODE_START_NID, /* Speaker vnid */ + VNID_MIC, + VNID_HP_SEL, + VNID_AMIC1_SEL, + VNID_HP_ASEL, + VNID_AMIC1_ASEL, + VNODE_END_NID, +#define VNODES_COUNT (VNODE_END_NID - VNODE_START_NID) + +#define EFFECT_START_NID 0x90 +#define OUT_EFFECT_START_NID EFFECT_START_NID + SURROUND = OUT_EFFECT_START_NID, + CRYSTALIZER, + DIALOG_PLUS, + SMART_VOLUME, + X_BASS, + EQUALIZER, + OUT_EFFECT_END_NID, +#define OUT_EFFECTS_COUNT (OUT_EFFECT_END_NID - OUT_EFFECT_START_NID) + +#define IN_EFFECT_START_NID OUT_EFFECT_END_NID + ECHO_CANCELLATION = IN_EFFECT_START_NID, + VOICE_FOCUS, + MIC_SVM, + IN_EFFECT_END_NID, +#define IN_EFFECTS_COUNT (IN_EFFECT_END_NID - IN_EFFECT_START_NID) + + VOICEFX = IN_EFFECT_END_NID, + PLAY_ENHANCEMENT, + CRYSTAL_VOICE, + EFFECT_END_NID +#define EFFECTS_COUNT (EFFECT_END_NID - EFFECT_START_NID) +}; + +/* Effects values size*/ +#define EFFECT_VALS_MAX_COUNT 12 + +struct ct_effect { + char name[44]; + hda_nid_t nid; + int mid; /*effect module ID*/ + int reqs[EFFECT_VALS_MAX_COUNT]; /*effect module request*/ + int direct; /* 0:output; 1:input*/ + int params; /* number of default non-on/off params */ + /*effect default values, 1st is on/off. */ + unsigned int def_vals[EFFECT_VALS_MAX_COUNT]; +}; + +static struct ct_effect ca0132_effects[EFFECTS_COUNT] = { + { "Surround", + SURROUND, + 0x96, + {0, 1}, + 0, + 1, + {0x3F800000, 0x3F2B851F} + }, + { "Crystalizer", + CRYSTALIZER, + 0x96, + {7, 8}, + 0, + 1, + {0x3F800000, 0x3F266666} + }, + { "Dialog Plus", + DIALOG_PLUS, + 0x96, + {2, 3}, + 0, + 1, + {0x00000000, 0x3F000000} + }, + { "Smart Volume", + SMART_VOLUME, + 0x96, + {4, 5, 6}, + 0, + 2, + {0x3F800000, 0x3F3D70A4, 0x00000000} + }, + { "X-Bass", + X_BASS, + 0x96, + {24, 23, 25}, + 0, + 2, + {0x3F800000, 0x42A00000, 0x3F000000} + }, + { "Equalizer", + EQUALIZER, + 0x96, + {9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20}, + 0, + 11, + {0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000} + }, + { "Echo Cancellation", + ECHO_CANCELLATION, + 0x95, + {0, 1, 2, 3}, + 1, + 3, + {0x00000000, 0x3F3A9692, 0x00000000, 0x00000000} + }, + { "Voice Focus", + VOICE_FOCUS, + 0x95, + {6, 7, 8, 9}, + 1, + 3, + {0x3F800000, 0x3D7DF3B6, 0x41F00000, 0x41F00000} + }, + { "Mic SVM", + MIC_SVM, + 0x95, + {44, 45}, + 1, + 1, + {0x00000000, 0x3F3D70A4} + }, + { "VoiceFX", + VOICEFX, + 0x95, + {10, 11, 12, 13, 14, 15, 16, 17, 18}, + 1, + 8, + {0x00000000, 0x43C80000, 0x44AF0000, 0x44FA0000, 0x3F800000, + 0x3F800000, 0x3F800000, 0x00000000, 0x00000000} + } +}; + + +/* Tuning controls */ +#ifdef ENABLE_TUNING_CONTROLS + +enum { +#define TUNING_CTL_START_NID 0xC0 + WEDGE_ANGLE = TUNING_CTL_START_NID, + SVM_LEVEL, + EQUALIZER_BAND_0, + EQUALIZER_BAND_1, + EQUALIZER_BAND_2, + EQUALIZER_BAND_3, + EQUALIZER_BAND_4, + EQUALIZER_BAND_5, + EQUALIZER_BAND_6, + EQUALIZER_BAND_7, + EQUALIZER_BAND_8, + EQUALIZER_BAND_9, + TUNING_CTL_END_NID +#define TUNING_CTLS_COUNT (TUNING_CTL_END_NID - TUNING_CTL_START_NID) +}; + +struct ct_tuning_ctl { + char name[44]; + hda_nid_t parent_nid; + hda_nid_t nid; + int mid; /*effect module ID*/ + int req; /*effect module request*/ + int direct; /* 0:output; 1:input*/ + unsigned int def_val;/*effect default values*/ +}; + +static struct ct_tuning_ctl ca0132_tuning_ctls[] = { + { "Wedge Angle", + VOICE_FOCUS, + WEDGE_ANGLE, + 0x95, + 8, + 1, + 0x41F00000 + }, + { "SVM Level", + MIC_SVM, + SVM_LEVEL, + 0x95, + 45, + 1, + 0x3F3D70A4 + }, + { "EQ Band0", + EQUALIZER, + EQUALIZER_BAND_0, + 0x96, + 11, + 0, + 0x00000000 + }, + { "EQ Band1", + EQUALIZER, + EQUALIZER_BAND_1, + 0x96, + 12, + 0, + 0x00000000 + }, + { "EQ Band2", + EQUALIZER, + EQUALIZER_BAND_2, + 0x96, + 13, + 0, + 0x00000000 + }, + { "EQ Band3", + EQUALIZER, + EQUALIZER_BAND_3, + 0x96, + 14, + 0, + 0x00000000 + }, + { "EQ Band4", + EQUALIZER, + EQUALIZER_BAND_4, + 0x96, + 15, + 0, + 0x00000000 + }, + { "EQ Band5", + EQUALIZER, + EQUALIZER_BAND_5, + 0x96, + 16, + 0, + 0x00000000 + }, + { "EQ Band6", + EQUALIZER, + EQUALIZER_BAND_6, + 0x96, + 17, + 0, + 0x00000000 + }, + { "EQ Band7", + EQUALIZER, + EQUALIZER_BAND_7, + 0x96, + 18, + 0, + 0x00000000 + }, + { "EQ Band8", + EQUALIZER, + EQUALIZER_BAND_8, + 0x96, + 19, + 0, + 0x00000000 + }, + { "EQ Band9", + EQUALIZER, + EQUALIZER_BAND_9, + 0x96, + 20, + 0, + 0x00000000 + } +}; +#endif + +/* Voice FX Presets */ +#define VOICEFX_MAX_PARAM_COUNT 9 + +struct ct_voicefx { + char *name; + hda_nid_t nid; + int mid; + int reqs[VOICEFX_MAX_PARAM_COUNT]; /*effect module request*/ +}; + +struct ct_voicefx_preset { + char *name; /*preset name*/ + unsigned int vals[VOICEFX_MAX_PARAM_COUNT]; +}; + +struct ct_voicefx ca0132_voicefx = { + "VoiceFX Capture Switch", + VOICEFX, + 0x95, + {10, 11, 12, 13, 14, 15, 16, 17, 18} +}; + +struct ct_voicefx_preset ca0132_voicefx_presets[] = { + { "Neutral", + { 0x00000000, 0x43C80000, 0x44AF0000, + 0x44FA0000, 0x3F800000, 0x3F800000, + 0x3F800000, 0x00000000, 0x00000000 } + }, + { "Female2Male", + { 0x3F800000, 0x43C80000, 0x44AF0000, + 0x44FA0000, 0x3F19999A, 0x3F866666, + 0x3F800000, 0x00000000, 0x00000000 } + }, + { "Male2Female", + { 0x3F800000, 0x43C80000, 0x44AF0000, + 0x450AC000, 0x4017AE14, 0x3F6B851F, + 0x3F800000, 0x00000000, 0x00000000 } + }, + { "ScrappyKid", + { 0x3F800000, 0x43C80000, 0x44AF0000, + 0x44FA0000, 0x40400000, 0x3F28F5C3, + 0x3F800000, 0x00000000, 0x00000000 } + }, + { "Elderly", + { 0x3F800000, 0x44324000, 0x44BB8000, + 0x44E10000, 0x3FB33333, 0x3FB9999A, + 0x3F800000, 0x3E3A2E43, 0x00000000 } + }, + { "Org", + { 0x3F800000, 0x43EA0000, 0x44A52000, + 0x45098000, 0x3F266666, 0x3FC00000, + 0x3F800000, 0x00000000, 0x00000000 } + }, + { "Elf", + { 0x3F800000, 0x43C70000, 0x44AE6000, + 0x45193000, 0x3F8E147B, 0x3F75C28F, + 0x3F800000, 0x00000000, 0x00000000 } + }, + { "Dwarf", + { 0x3F800000, 0x43930000, 0x44BEE000, + 0x45007000, 0x3F451EB8, 0x3F7851EC, + 0x3F800000, 0x00000000, 0x00000000 } + }, + { "AlienBrute", + { 0x3F800000, 0x43BFC5AC, 0x44B28FDF, + 0x451F6000, 0x3F266666, 0x3FA7D945, + 0x3F800000, 0x3CF5C28F, 0x00000000 } + }, + { "Robot", + { 0x3F800000, 0x43C80000, 0x44AF0000, + 0x44FA0000, 0x3FB2718B, 0x3F800000, + 0xBC07010E, 0x00000000, 0x00000000 } + }, + { "Marine", + { 0x3F800000, 0x43C20000, 0x44906000, + 0x44E70000, 0x3F4CCCCD, 0x3F8A3D71, + 0x3F0A3D71, 0x00000000, 0x00000000 } + }, + { "Emo", + { 0x3F800000, 0x43C80000, 0x44AF0000, + 0x44FA0000, 0x3F800000, 0x3F800000, + 0x3E4CCCCD, 0x00000000, 0x00000000 } + }, + { "DeepVoice", + { 0x3F800000, 0x43A9C5AC, 0x44AA4FDF, + 0x44FFC000, 0x3EDBB56F, 0x3F99C4CA, + 0x3F800000, 0x00000000, 0x00000000 } + }, + { "Munchkin", + { 0x3F800000, 0x43C80000, 0x44AF0000, + 0x44FA0000, 0x3F1A043C, 0x3F800000, + 0x3F800000, 0x00000000, 0x00000000 } + } +};
enum hda_cmd_vendor_io { /* for DspIO node */ @@ -62,7 +513,11 @@ enum hda_cmd_vendor_io { VENDOR_CHIPIO_HIC_POST_READ = 0x702, VENDOR_CHIPIO_HIC_READ_DATA = 0xF03,
+ VENDOR_CHIPIO_8051_DATA_WRITE = 0x707, + VENDOR_CHIPIO_8051_DATA_READ = 0xF07, + VENDOR_CHIPIO_CT_EXTENSIONS_ENABLE = 0x70A, + VENDOR_CHIPIO_CT_EXTENSIONS_GET = 0xF0A,
VENDOR_CHIPIO_PLL_PMU_WRITE = 0x70C, VENDOR_CHIPIO_PLL_PMU_READ = 0xF0C, @@ -70,23 +525,29 @@ enum hda_cmd_vendor_io { VENDOR_CHIPIO_8051_ADDRESS_HIGH = 0x70E, VENDOR_CHIPIO_FLAG_SET = 0x70F, VENDOR_CHIPIO_FLAGS_GET = 0xF0F, - VENDOR_CHIPIO_PARAMETER_SET = 0x710, - VENDOR_CHIPIO_PARAMETER_GET = 0xF10, + VENDOR_CHIPIO_PARAM_SET = 0x710, + VENDOR_CHIPIO_PARAM_GET = 0xF10,
VENDOR_CHIPIO_PORT_ALLOC_CONFIG_SET = 0x711, VENDOR_CHIPIO_PORT_ALLOC_SET = 0x712, VENDOR_CHIPIO_PORT_ALLOC_GET = 0xF12, VENDOR_CHIPIO_PORT_FREE_SET = 0x713,
- VENDOR_CHIPIO_PARAMETER_EX_ID_GET = 0xF17, - VENDOR_CHIPIO_PARAMETER_EX_ID_SET = 0x717, - VENDOR_CHIPIO_PARAMETER_EX_VALUE_GET = 0xF18, - VENDOR_CHIPIO_PARAMETER_EX_VALUE_SET = 0x718 + VENDOR_CHIPIO_PARAM_EX_ID_GET = 0xF17, + VENDOR_CHIPIO_PARAM_EX_ID_SET = 0x717, + VENDOR_CHIPIO_PARAM_EX_VALUE_GET = 0xF18, + VENDOR_CHIPIO_PARAM_EX_VALUE_SET = 0x718, + + VENDOR_CHIPIO_DMIC_CTL_SET = 0x788, + VENDOR_CHIPIO_DMIC_CTL_GET = 0xF88, + VENDOR_CHIPIO_DMIC_PIN_SET = 0x789, + VENDOR_CHIPIO_DMIC_PIN_GET = 0xF89, + VENDOR_CHIPIO_DMIC_MCLK_SET = 0x78A, + VENDOR_CHIPIO_DMIC_MCLK_GET = 0xF8A, + + VENDOR_CHIPIO_EAPD_SEL_SET = 0x78D };
-/* - * Control flag IDs - */ enum control_flag_id { /* Connection manager stream setup is bypassed/enabled */ CONTROL_FLAG_C_MGR = 0, @@ -131,7 +592,7 @@ enum control_flag_id { /* Impedance for ramp generator on Port_A 16 Ohm/10K Ohm */ CONTROL_FLAG_PORT_A_10KOHM_LOAD = 20, /* Impedance for ramp generator on Port_D, 16 Ohm/10K Ohm */ - CONTROL_FLAG_PORT_D_10K0HM_LOAD = 21, + CONTROL_FLAG_PORT_D_10KOHM_LOAD = 21, /* ASI rate is 48kHz/96kHz */ CONTROL_FLAG_ASI_96KHZ = 22, /* DAC power settings able to control attached ports no/yes */ @@ -142,10 +603,7 @@ enum control_flag_id { CONTROL_FLAGS_MAX = (CONTROL_FLAG_CONTROL_STOP_OK_ENABLE+1) };
-/* - * Control parameter IDs - */ -enum control_parameter_id { +enum control_param_id { /* 0: force HDA, 1: allow DSP if HDA Spdif1Out stream is idle */ CONTROL_PARAM_SPDIF1_SOURCE = 2,
@@ -175,9 +633,6 @@ enum control_parameter_id { CONTROL_PARAM_NODE_ID = 31 };
-/* - * Dsp Io Status codes - */ enum hda_vendor_status_dspio { /* Success */ VENDOR_STATUS_DSPIO_OK = 0x00, @@ -189,9 +644,6 @@ enum hda_vendor_status_dspio { VENDOR_STATUS_DSPIO_SCP_RESPONSE_QUEUE_EMPTY = 0x03 };
-/* - * Chip Io Status codes - */ enum hda_vendor_status_chipio { /* Success */ VENDOR_STATUS_CHIPIO_OK = 0x00, @@ -199,9 +651,6 @@ enum hda_vendor_status_chipio { VENDOR_STATUS_CHIPIO_BUSY = 0x01 };
-/* - * CA0132 sample rate - */ enum ca0132_sample_rate { SR_6_000 = 0x00, SR_8_000 = 0x01, @@ -225,112 +674,115 @@ enum ca0132_sample_rate { SR_RATE_UNKNOWN = 0x1F };
-/* - * Scp Helper function - */ -enum get_set { - IS_SET = 0, - IS_GET = 1, +enum dsp_download_state { + DSP_DOWNLOAD_FAILED = -1, + DSP_DOWNLOAD_INIT = 0, + DSP_DOWNLOADING = 1, + DSP_DOWNLOADED = 2 };
-/* - * Duplicated from ca0110 codec - */ +struct hda_stream_format { + unsigned int sample_rate; + unsigned short valid_bits_per_sample; + unsigned short container_size; + unsigned short number_channels; +};
-static void init_output(struct hda_codec *codec, hda_nid_t pin, hda_nid_t dac) -{ - if (pin) { - snd_hda_set_pin_ctl(codec, pin, PIN_HP); - if (get_wcaps(codec, pin) & AC_WCAP_OUT_AMP) - snd_hda_codec_write(codec, pin, 0, - AC_VERB_SET_AMP_GAIN_MUTE, - AMP_OUT_UNMUTE); - } - if (dac) - snd_hda_codec_write(codec, dac, 0, - AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO); -} +/* retrieve parameters from hda format */ +#define get_hdafmt_chs(fmt) (fmt & 0xf) +#define get_hdafmt_bits(fmt) ((fmt >> 4) & 0x7) +#define get_hdafmt_rate(fmt) ((fmt >> 8) & 0x7f) +#define get_hdafmt_type(fmt) ((fmt >> 15) & 0x1)
-static void init_input(struct hda_codec *codec, hda_nid_t pin, hda_nid_t adc) -{ - if (pin) { - snd_hda_set_pin_ctl(codec, pin, PIN_IN | - snd_hda_get_default_vref(codec, pin)); - if (get_wcaps(codec, pin) & AC_WCAP_IN_AMP) - snd_hda_codec_write(codec, pin, 0, - AC_VERB_SET_AMP_GAIN_MUTE, - AMP_IN_UNMUTE(0)); - } - if (adc) - snd_hda_codec_write(codec, adc, 0, AC_VERB_SET_AMP_GAIN_MUTE, - AMP_IN_UNMUTE(0)); -} +struct ca0132_spec { + struct snd_kcontrol_new *mixers[5]; + unsigned int num_mixers; + const struct hda_verb *base_init_verbs; + const struct hda_verb *base_exit_verbs; + const struct hda_verb *init_verbs[5]; + unsigned int num_init_verbs; /* exclude base init verbs */ + struct auto_pin_cfg autocfg;
-static char *dirstr[2] = { "Playback", "Capture" }; + /* Nodes configurations */ + struct hda_multi_out multiout; + hda_nid_t out_pins[5]; + hda_nid_t dacs[5]; + unsigned int num_outputs; + hda_nid_t input_pins[5]; + hda_nid_t adcs[5]; + unsigned int num_inputs; + hda_nid_t shared_mic_nid; + hda_nid_t shared_out_nid; + struct hda_pcm pcm_rec[5]; /* PCM information */ + + /* chip access */ + struct mutex chipio_mutex; /* chip access mutex */ + u32 curr_chip_addx; + + /* DSP download related */ + enum dsp_download_state dsp_state; + unsigned int dsp_stream_id; + unsigned int wait_scp; + unsigned int wait_scp_header; + unsigned int wait_num_data; + unsigned int scp_resp_header; + unsigned int scp_resp_data[4]; + unsigned int scp_resp_count; + + /* mixer and effects related */ + unsigned char dmic_ctl; + int cur_out_type; + int cur_mic_type; + long vnode_lvol[VNODES_COUNT]; + long vnode_rvol[VNODES_COUNT]; + long vnode_lswitch[VNODES_COUNT]; + long vnode_rswitch[VNODES_COUNT]; + long effects_switch[EFFECTS_COUNT]; + long voicefx_val; + long cur_mic_boost; + + #ifdef ENABLE_TUNING_CONTROLS + long cur_ctl_vals[TUNING_CTLS_COUNT]; + #endif +};
-static int _add_switch(struct hda_codec *codec, hda_nid_t nid, const char *pfx, - int chan, int dir) +/* + * CA0132 codec access + */ +unsigned int codec_send_command(struct hda_codec *codec, hda_nid_t nid, + unsigned int verb, unsigned int parm, unsigned int *res) { - char namestr[44]; - int type = dir ? HDA_INPUT : HDA_OUTPUT; - struct snd_kcontrol_new knew = - HDA_CODEC_MUTE_MONO(namestr, nid, chan, 0, type); - sprintf(namestr, "%s %s Switch", pfx, dirstr[dir]); - return snd_hda_ctl_add(codec, nid, snd_ctl_new1(&knew, codec)); + unsigned int response; + response = snd_hda_codec_read(codec, nid, 0, verb, parm); + *res = response; + + return ((response == -1) ? -1 : 0); }
-static int _add_volume(struct hda_codec *codec, hda_nid_t nid, const char *pfx, - int chan, int dir) +static int codec_set_converter_format(struct hda_codec *codec, hda_nid_t nid, + unsigned short converter_format, unsigned int *res) { - char namestr[44]; - int type = dir ? HDA_INPUT : HDA_OUTPUT; - struct snd_kcontrol_new knew = - HDA_CODEC_VOLUME_MONO(namestr, nid, chan, 0, type); - sprintf(namestr, "%s %s Volume", pfx, dirstr[dir]); - return snd_hda_ctl_add(codec, nid, snd_ctl_new1(&knew, codec)); + return codec_send_command(codec, nid, VENDOR_CHIPIO_STREAM_FORMAT, + converter_format & 0xffff, res); }
-#define add_out_switch(codec, nid, pfx) _add_switch(codec, nid, pfx, 3, 0) -#define add_out_volume(codec, nid, pfx) _add_volume(codec, nid, pfx, 3, 0) -#define add_in_switch(codec, nid, pfx) _add_switch(codec, nid, pfx, 3, 1) -#define add_in_volume(codec, nid, pfx) _add_volume(codec, nid, pfx, 3, 1) -#define add_mono_switch(codec, nid, pfx, chan) \ - _add_switch(codec, nid, pfx, chan, 0) -#define add_mono_volume(codec, nid, pfx, chan) \ - _add_volume(codec, nid, pfx, chan, 0) -#define add_in_mono_switch(codec, nid, pfx, chan) \ - _add_switch(codec, nid, pfx, chan, 1) -#define add_in_mono_volume(codec, nid, pfx, chan) \ - _add_volume(codec, nid, pfx, chan, 1) +static int codec_set_converter_stream_channel(struct hda_codec *codec, + hda_nid_t nid, unsigned char stream, + unsigned char channel, unsigned int *res) +{ + unsigned char converter_stream_channel = 0;
+ converter_stream_channel = (stream << 4) | (channel & 0x0f); + return codec_send_command(codec, nid, AC_VERB_SET_CHANNEL_STREAMID, + converter_stream_channel, res); +}
/* - * CA0132 specific + * CA0132 chip access stuffs */ - -struct ca0132_spec { - struct auto_pin_cfg autocfg; - struct hda_multi_out multiout; - hda_nid_t out_pins[AUTO_CFG_MAX_OUTS]; - hda_nid_t dacs[AUTO_CFG_MAX_OUTS]; - hda_nid_t hp_dac; - hda_nid_t input_pins[AUTO_PIN_LAST]; - hda_nid_t adcs[AUTO_PIN_LAST]; - hda_nid_t dig_out; - hda_nid_t dig_in; - unsigned int num_inputs; - long curr_hp_switch; - long curr_hp_volume[2]; - long curr_speaker_switch; - struct mutex chipio_mutex; - const char *input_labels[AUTO_PIN_LAST]; - struct hda_pcm pcm_rec[2]; /* PCM information */ -}; - -/* Chip access helper function */ static int chipio_send(struct hda_codec *codec, unsigned int reg, - unsigned int data) + u32 data) { unsigned int res; int retry = 50; @@ -342,284 +794,3098 @@ static int chipio_send(struct hda_codec *codec, if (res == VENDOR_STATUS_CHIPIO_OK) return 0; } while (--retry); - return -EIO; + + CTASSERT(0); + return -1; }
-/* - * Write chip address through the vendor widget -- NOT protected by the Mutex! - */ -static int chipio_write_address(struct hda_codec *codec, - unsigned int chip_addx) +static int chipio_write_addx(struct hda_codec *codec, u32 chip_addx) { - int res; + struct ca0132_spec *spec = codec->spec; + int status = 0; + + if (spec->curr_chip_addx == chip_addx) + return 0;
/* send low 16 bits of the address */ - res = chipio_send(codec, VENDOR_CHIPIO_ADDRESS_LOW, - chip_addx & 0xffff); + status = chipio_send(codec, VENDOR_CHIPIO_ADDRESS_LOW, + chip_addx & 0xffff);
- if (res != -EIO) { + if (SUCCEEDED(status)) { /* send high 16 bits of the address */ - res = chipio_send(codec, VENDOR_CHIPIO_ADDRESS_HIGH, - chip_addx >> 16); + status = chipio_send(codec, VENDOR_CHIPIO_ADDRESS_HIGH, + chip_addx >> 16); }
- return res; -} + CTASSERT(SUCCEEDED(status)); + spec->curr_chip_addx = SUCCEEDED(status) ? chip_addx : ~0UL;
-/* - * Write data through the vendor widget -- NOT protected by the Mutex! - */ + return status; +}
-static int chipio_write_data(struct hda_codec *codec, unsigned int data) +static int chipio_write_data(struct hda_codec *codec, u32 data) { - int res; + struct ca0132_spec *spec = codec->spec; + int status = 0;
/* send low 16 bits of the data */ - res = chipio_send(codec, VENDOR_CHIPIO_DATA_LOW, data & 0xffff); + status = chipio_send(codec, VENDOR_CHIPIO_DATA_LOW, data & 0xffff);
- if (res != -EIO) { + if (SUCCEEDED(status)) { /* send high 16 bits of the data */ - res = chipio_send(codec, VENDOR_CHIPIO_DATA_HIGH, - data >> 16); + status = chipio_send(codec, VENDOR_CHIPIO_DATA_HIGH, + data >> 16); }
- return res; + CTASSERT(SUCCEEDED(status)); + spec->curr_chip_addx = SUCCEEDED(status) ? + (spec->curr_chip_addx + 4) : ~0UL; + + return status; +} + +static int chipio_write_data_multiple(struct hda_codec *codec, + const u32 *data, + unsigned int count) +{ + int status = 0; + + if (data == NULL) + return FAIL_MSG(-1, "chipio_write_data null ptr"); + + while ((count-- != 0) && (status == 0)) + status = chipio_write_data(codec, *data++); + + return status; }
-/* - * Read data through the vendor widget -- NOT protected by the Mutex! - */ static int chipio_read_data(struct hda_codec *codec, unsigned int *data) { - int res; + struct ca0132_spec *spec = codec->spec; + int status = 0;
/* post read */ - res = chipio_send(codec, VENDOR_CHIPIO_HIC_POST_READ, 0); + status = chipio_send(codec, VENDOR_CHIPIO_HIC_POST_READ, 0);
- if (res != -EIO) { + if (SUCCEEDED(status)) { /* read status */ - res = chipio_send(codec, VENDOR_CHIPIO_STATUS, 0); + status = chipio_send(codec, VENDOR_CHIPIO_STATUS, 0); }
- if (res != -EIO) { + if (SUCCEEDED(status)) { /* read data */ *data = snd_hda_codec_read(codec, WIDGET_CHIP_CTRL, 0, - VENDOR_CHIPIO_HIC_READ_DATA, - 0); + VENDOR_CHIPIO_HIC_READ_DATA, 0); }
- return res; + CTASSERT(SUCCEEDED(status)); + spec->curr_chip_addx = SUCCEEDED(status) ? + (spec->curr_chip_addx + 4) : ~0UL; + + return status; }
-/* - * Write given value to the given address through the chip I/O widget. - * protected by the Mutex - */ -static int chipio_write(struct hda_codec *codec, - unsigned int chip_addx, const unsigned int data) +static int chipio_write(struct hda_codec *codec, u32 chip_addx, + const u32 data) { struct ca0132_spec *spec = codec->spec; - int err; + int status;
mutex_lock(&spec->chipio_mutex); - - /* 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: + status = chipio_write_addx(codec, chip_addx); + if (SUCCEEDED(status)) + status = chipio_write_data(codec, data); mutex_unlock(&spec->chipio_mutex); - return err; + + return status; }
-/* - * Read the given address through the chip I/O widget - * protected by the Mutex - */ -static int chipio_read(struct hda_codec *codec, - unsigned int chip_addx, unsigned int *data) +static int chipio_write_multiple(struct hda_codec *codec, + u32 chip_addx, + const u32 *data, + unsigned int count) { struct ca0132_spec *spec = codec->spec; - int err; + int status;
mutex_lock(&spec->chipio_mutex); + status = chipio_write_addx(codec, chip_addx); + if (SUCCEEDED(status)) + status = chipio_write_data_multiple(codec, data, count); + mutex_unlock(&spec->chipio_mutex);
- /* write the address, and if successful proceed to write data */ - err = chipio_write_address(codec, chip_addx); - if (err < 0) - goto exit; + return status; +}
- err = chipio_read_data(codec, data); - if (err < 0) - goto exit; +static int chipio_read(struct hda_codec *codec, u32 chip_addx, + unsigned int *data) +{ + struct ca0132_spec *spec = codec->spec; + int status;
-exit: + mutex_lock(&spec->chipio_mutex); + status = chipio_write_addx(codec, chip_addx); + if (SUCCEEDED(status)) + status = chipio_read_data(codec, data); mutex_unlock(&spec->chipio_mutex); - return err; + + return status; }
-/* - * PCM stuffs - */ -static void ca0132_setup_stream(struct hda_codec *codec, hda_nid_t nid, - u32 stream_tag, - int channel_id, int format) +static void chipio_set_control_flag(struct hda_codec *codec, + enum control_flag_id flag_id, + bool flag_state) { - unsigned int oldval, newval; + unsigned int val; + unsigned int flag_bit;
- if (!nid) - return; + flag_bit = (flag_state ? 1 : 0); + val = (flag_bit << 7) | (flag_id); + snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0, + VENDOR_CHIPIO_FLAG_SET, val); +}
- snd_printdd("ca0132_setup_stream: " - "NID=0x%x, stream=0x%x, channel=%d, format=0x%x\n", - nid, stream_tag, channel_id, format); +static void chipio_set_control_param(struct hda_codec *codec, + enum control_param_id param_id, int param_val) +{ + struct ca0132_spec *spec = codec->spec; + int val;
- /* update the format-id if changed */ - oldval = snd_hda_codec_read(codec, nid, 0, - AC_VERB_GET_STREAM_FORMAT, - 0); - if (oldval != format) { - msleep(20); - snd_hda_codec_write(codec, nid, 0, - AC_VERB_SET_STREAM_FORMAT, - format); + 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 { + mutex_lock(&spec->chipio_mutex); + 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); + } else + CA0132_LOG("set_control_param:FAIL! id=0x%x, val=%d\n", + param_id, param_val); + mutex_unlock(&spec->chipio_mutex); } +}
- oldval = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_CONV, 0); - newval = (stream_tag << 4) | channel_id; - if (oldval != newval) { - snd_hda_codec_write(codec, nid, 0, - AC_VERB_SET_CHANNEL_STREAMID, - newval); - } +static void chipio_set_conn_rate(struct hda_codec *codec, + int connid, enum ca0132_sample_rate rate) +{ + chipio_set_control_param(codec, CONTROL_PARAM_CONN_POINT_ID, connid); + chipio_set_control_param(codec, CONTROL_PARAM_CONN_POINT_SAMPLE_RATE, + rate); }
-static void ca0132_cleanup_stream(struct hda_codec *codec, hda_nid_t nid) +static void chipio_enable_clocks(struct hda_codec *codec) { - snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_STREAM_FORMAT, 0); - snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_CHANNEL_STREAMID, 0); + struct ca0132_spec *spec = codec->spec; + + mutex_lock(&spec->chipio_mutex); + snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0, + VENDOR_CHIPIO_8051_ADDRESS_LOW, 0); + snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0, + VENDOR_CHIPIO_PLL_PMU_WRITE, 0xff); + snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0, + VENDOR_CHIPIO_8051_ADDRESS_LOW, 5); + snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0, + VENDOR_CHIPIO_PLL_PMU_WRITE, 0x0b); + snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0, + VENDOR_CHIPIO_8051_ADDRESS_LOW, 6); + snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0, + VENDOR_CHIPIO_PLL_PMU_WRITE, 0xff); + mutex_unlock(&spec->chipio_mutex); }
+ /* - * PCM callbacks + * CA0132 DSP IO stuffs */ -static int ca0132_playback_pcm_prepare(struct hda_pcm_stream *hinfo, - struct hda_codec *codec, - unsigned int stream_tag, - unsigned int format, - struct snd_pcm_substream *substream) +static int dspio_send(struct hda_codec *codec, unsigned int reg, + unsigned int data) { - struct ca0132_spec *spec = codec->spec; + unsigned int res; + int retry = 50;
- ca0132_setup_stream(codec, spec->dacs[0], stream_tag, 0, format); + /* send bits of data specified by reg to dsp */ + do { + res = snd_hda_codec_read(codec, WIDGET_DSP_CTRL, 0, reg, data); + if ((res >= 0) && (res != VENDOR_STATUS_DSPIO_BUSY)) + return res; + } while (--retry);
- return 0; + CTASSERT(retry); + return -1; }
-static int ca0132_playback_pcm_cleanup(struct hda_pcm_stream *hinfo, - struct hda_codec *codec, - struct snd_pcm_substream *substream) +static void dspio_write_wait(struct hda_codec *codec) { - struct ca0132_spec *spec = codec->spec; + int cur_val, prv_val; + int retry = 50; + + cur_val = 0; + do { + prv_val = cur_val; + msleep(20); + dspio_send(codec, VENDOR_DSPIO_SCP_POST_COUNT_QUERY, 1); + dspio_send(codec, VENDOR_DSPIO_STATUS, 0); + cur_val = snd_hda_codec_read(codec, WIDGET_DSP_CTRL, 0, + VENDOR_DSPIO_SCP_READ_COUNT, 0); + } while (cur_val && (cur_val == prv_val) && --retry); +} + +static int dspio_write(struct hda_codec *codec, unsigned int scp_data) +{ + struct ca0132_spec *spec = codec->spec; + int status; + + dspio_write_wait(codec); + + mutex_lock(&spec->chipio_mutex); + status = dspio_send(codec, VENDOR_DSPIO_SCP_WRITE_DATA_LOW, + scp_data & 0xffff); + if (SUCCEEDED(status)) + status = dspio_send(codec, VENDOR_DSPIO_SCP_WRITE_DATA_HIGH, + scp_data >> 16); + mutex_unlock(&spec->chipio_mutex); + + /* OK, now check if the write itself has executed*/ + if (SUCCEEDED(status)) + status = snd_hda_codec_read(codec, WIDGET_DSP_CTRL, 0, + VENDOR_DSPIO_STATUS, 0); + + if (status == VENDOR_STATUS_DSPIO_SCP_COMMAND_QUEUE_FULL) + CA0132_LOG("dspio_write: SCP_COMMAND_QUEUE_FULL!!!\n\n"); + + return (status == VENDOR_STATUS_DSPIO_SCP_COMMAND_QUEUE_FULL) ? + -1 : 0; +} + +static int dspio_write_multiple(struct hda_codec *codec, + unsigned int *buffer, unsigned int size) +{ + int status = 0; + unsigned int count; + + if ((buffer == NULL)) + return -1; + + count = 0; + while (count < size) { + status = dspio_write(codec, *buffer++); + if (status != 0) + break; + count++; + } + + return status; +} + +static int dspio_read(struct hda_codec *codec, unsigned int *data) +{ + int status; + + status = dspio_send(codec, VENDOR_DSPIO_SCP_POST_READ_DATA, 0); + if (SUCCEEDED(status)) + status = dspio_send(codec, VENDOR_DSPIO_STATUS, 0); + + if (FAILED(status) || + (status == VENDOR_STATUS_DSPIO_SCP_RESPONSE_QUEUE_EMPTY)) + return -1; + + *data = snd_hda_codec_read(codec, WIDGET_DSP_CTRL, 0, + VENDOR_DSPIO_SCP_READ_DATA, 0); + + return 0; +} + +static int dspio_read_multiple(struct hda_codec *codec, unsigned int *buffer, + unsigned int *buf_size, unsigned int size_count) +{ + int status = 0; + unsigned int size = *buf_size; + unsigned int count; + unsigned int skip_count; + unsigned int dummy; + + if ((buffer == NULL)) + return -1; + + count = 0; + while (count < size && count < size_count) { + status = dspio_read(codec, buffer++); + if (status != 0) + break; + count++; + } + + skip_count = count; + if (SUCCEEDED(status)) { + while (skip_count < size) { + status = dspio_read(codec, &dummy); + if (status != 0) + break; + skip_count++; + } + } + *buf_size = count; + + return status; +} + +static inline unsigned int +make_scp_header(unsigned int target_id, unsigned int source_id, + unsigned int get_flag, unsigned int req, + unsigned int device_flag, unsigned int resp_flag, + unsigned int error_flag, unsigned int data_size) +{ + unsigned int header = 0; + + header = (data_size & 0x1f) << 27; + header |= (error_flag & 0x01) << 26; + header |= (resp_flag & 0x01) << 25; + header |= (device_flag & 0x01) << 24; + header |= (req & 0x7f) << 17; + header |= (get_flag & 0x01) << 16; + header |= (source_id & 0xff) << 8; + header |= target_id & 0xff; + + return header; +} + +static inline void +extract_scp_header(unsigned int header, + unsigned int *target_id, unsigned int *source_id, + unsigned int *get_flag, unsigned int *req, + unsigned int *device_flag, unsigned int *resp_flag, + unsigned int *error_flag, unsigned int *data_size) +{ + if (data_size) + *data_size = (header >> 27) & 0x1f; + if (error_flag) + *error_flag = (header >> 26) & 0x01; + if (resp_flag) + *resp_flag = (header >> 25) & 0x01; + if (device_flag) + *device_flag = (header >> 24) & 0x01; + if (req) + *req = (header >> 17) & 0x7f; + if (get_flag) + *get_flag = (header >> 16) & 0x01; + if (source_id) + *source_id = (header >> 8) & 0xff; + if (target_id) + *target_id = header & 0xff; +} + +#define SCP_MAX_DATA_WORDS (16) + +/* Structure to contain any SCP message */ +struct scp_msg { + unsigned int hdr; + unsigned int data[SCP_MAX_DATA_WORDS]; +}; + +static void dspio_clear_response_queue(struct hda_codec *codec) +{ + unsigned int dummy = 0; + int status = -1; + + /* clear all from the response queue */ + do { + status = dspio_read(codec, &dummy); + } while (status == 0); +} + +static int dspio_get_response_data(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + unsigned int data = 0; + unsigned int count; + + if (FAILED(dspio_read(codec, &data))) + return -1; + + CA0132_LOG("dspio_get_response_data: 0x%08x\n", data); + if ((data & 0x00ffffff) == spec->wait_scp_header) { + spec->scp_resp_header = data; + spec->scp_resp_count = data >> 27; + count = spec->wait_num_data; + dspio_read_multiple(codec, spec->scp_resp_data, + &spec->scp_resp_count, count); + return 0; + } + + return -1; +} + +static int dspio_send_scp_message(struct hda_codec *codec, + unsigned char *send_buf, + unsigned int send_buf_size, + unsigned char *return_buf, + unsigned int return_buf_size, + unsigned int *bytes_returned) +{ + struct ca0132_spec *spec = codec->spec; + int retry; + int status = -1; + unsigned int scp_send_size = 0; + unsigned int total_size; + bool waiting_for_resp = false; + unsigned int header; + struct scp_msg *ret_msg; + unsigned int resp_src_id, resp_target_id; + unsigned int data_size, src_id, target_id, get_flag, device_flag; + + if (bytes_returned) + *bytes_returned = 0; + + /* get scp header from buffer */ + header = *((unsigned int *)send_buf); + extract_scp_header(header, &target_id, &src_id, &get_flag, NULL, + &device_flag, NULL, NULL, &data_size); + scp_send_size = data_size + 1; + total_size = (scp_send_size * 4); + + CTASSERT(send_buf_size >= total_size); + if (send_buf_size < total_size) + return -1; + + if (get_flag || device_flag) { + if (!return_buf || return_buf_size < 4 || !bytes_returned) { + CTASSERT(0); + return -1; + } + spec->wait_scp_header = *((unsigned int *)send_buf); + + /* swap source id with target id */ + resp_target_id = src_id; + resp_src_id = target_id; + spec->wait_scp_header &= 0xffff0000; + spec->wait_scp_header |= (resp_src_id << 8) | (resp_target_id); + spec->wait_num_data = return_buf_size/sizeof(unsigned int) - 1; + spec->wait_scp = 1; + waiting_for_resp = true; + } + + status = dspio_write_multiple(codec, (unsigned int *)send_buf, + scp_send_size); + if (FAILED(status)) { + CTASSERT(0); + spec->wait_scp = 0; + return status; + } + + if (waiting_for_resp) { + memset(return_buf, 0, return_buf_size); + retry = 50; + do { + msleep(20); + } while (spec->wait_scp && (--retry != 0)); + waiting_for_resp = false; + if (retry != 0) { + ret_msg = (struct scp_msg *)return_buf; + memcpy(&ret_msg->hdr, &spec->scp_resp_header, 4); + memcpy(&ret_msg->data, spec->scp_resp_data, + spec->wait_num_data); + *bytes_returned = (spec->scp_resp_count + 1) * 4; + status = 0; + } else { + CTASSERT(*bytes_returned == 0); + status = -1; + } + spec->wait_scp = 0; + } + + return status; +} + +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 status = 0; + struct scp_msg scp_send, scp_reply; + unsigned int ret_bytes, send_size, ret_size; + unsigned int send_get_flag, reply_resp_flag, reply_error_flag; + unsigned int reply_data_size; + + memset(&scp_send, 0, sizeof(scp_send)); + memset(&scp_reply, 0, sizeof(scp_reply)); + + CTASSERT((len == 0 || data != NULL) && len <= SCP_MAX_DATA_WORDS); + if ((len != 0 && data == NULL) || (len > SCP_MAX_DATA_WORDS)) + return -1; + + CTASSERT(dir != SCP_GET || reply != NULL); + if (dir == SCP_GET && reply == NULL) + return FAIL_MSG(-1, "dspio_scp get but has no buffer"); + + CTASSERT((reply == NULL) || (reply_len != NULL && (*reply_len > 0))); + if (reply != NULL && (reply_len == NULL || (*reply_len == 0))) + return FAIL_MSG(-1, "dspio_scp bad resp buf len parms"); + + scp_send.hdr = make_scp_header(mod_id, 0x20, (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); + memcpy(scp_send.data, data, len); + } + + ret_bytes = 0; + send_size = sizeof(unsigned int) + len; + status = dspio_send_scp_message(codec, (unsigned char *)&scp_send, + send_size, (unsigned char *)&scp_reply, + sizeof(scp_reply), &ret_bytes); + +/* for debugging */ +#ifdef ENABLE_CA0132_DEBUG +{ + unsigned int x, *p, i; + unsigned int source_id; + unsigned int target_id; + unsigned int req; + unsigned int get_flag; + unsigned int resp_flag; + unsigned int device_flag; + unsigned int error_flag; + unsigned int data_size; + + memcpy(&x, &scp_send.hdr, sizeof(unsigned int)); + extract_scp_header(x, &target_id, &source_id, + &get_flag, &req, &device_flag, &resp_flag, + &error_flag, &data_size); + + snd_printdd( + "ScpDispatch ----- REQ: " + "HDR=0x%08x " + "SID=%04x " + "TID=%04x " + "Req=%04x " + "GRDE=%d %d %d %d " + "Siz=%d [ ", + x, + source_id, + target_id, + req, + get_flag, + resp_flag, + device_flag, + error_flag, + data_size); + + for (x = 0; x < data_size; x++) + snd_printdd("0x%08x ", scp_send.data[x].ui); + + snd_printdd("]\n"); + + if (get_flag) { + if (ret_bytes >= sizeof(scp_reply.hdr)) { + memcpy(&x, &scp_reply.hdr, sizeof(unsigned int)); + extract_scp_header(x, &target_id, &source_id, + &get_flag, &req, &device_flag, + &resp_flag, &error_flag, &data_size); + + snd_printdd( + " ----- REP: " + "HDR=0x%08x " + "SID=%04x " + "TID=%04x " + "Req=%04x " + "GRDE=%d %d %d %d " + "Siz=%d [ ", + x, + source_id, + target_id, + req, + get_flag, + resp_flag, + device_flag, + error_flag, + data_size); + + for (x = 0; x < data_size; x++) + snd_printdd("0x%08x ", scp_reply.data[x].ui); + } else { + snd_printdd("REP: (too short,nbytes=%d) [", ret_bytes); + + for (p = (unsigned int *)&scp_reply, i = 0; + i < ret_bytes; p++, i++) { + snd_printdd("0x%04x ", *p); + } + } + snd_printdd("]\n"); + } +} +#endif + + if (FAILED(status)) + return FAIL_MSG(status, "dspio_scp: send scp msg failed"); + + /* extract send and reply headers members */ + extract_scp_header(scp_send.hdr, NULL, NULL, &send_get_flag, + NULL, NULL, NULL, NULL, NULL); + extract_scp_header(scp_reply.hdr, NULL, NULL, NULL, NULL, NULL, + &reply_resp_flag, &reply_error_flag, + &reply_data_size); + + if (!send_get_flag) + return 0; + + if (reply_resp_flag && !reply_error_flag) { + ret_size = (ret_bytes - sizeof(scp_reply.hdr)) + / sizeof(unsigned int); + + CTASSERT(dir == SCP_GET && reply != NULL && reply_len != NULL); + + if (*reply_len < ret_size*sizeof(unsigned int)) { + status = FAIL_MSG(-1, "reply too long for buf"); + } else if (ret_size != reply_data_size) { + status = FAIL_MSG(-1, "RetLen and HdrLen .NE."); + } else { + *reply_len = ret_size*sizeof(unsigned int); + memcpy(reply, scp_reply.data, *reply_len); + } + } else { + status = FAIL_MSG(-1, "reply ill-formed or errflag set"); + } + + return status; +} + +static int dspio_set_param(struct hda_codec *codec, int mod_id, + int req, void *data, unsigned int len) +{ + return dspio_scp(codec, mod_id, req, SCP_SET, data, len, NULL, NULL); +} + +static int dspio_alloc_dma_chan(struct hda_codec *codec, unsigned int *dma_chan) +{ + int status = 0; + unsigned int size = sizeof(dma_chan); + + CA0132_DSP_LOG(" dspio_alloc_dma_chan() -- begin"); + status = dspio_scp(codec, MASTERCONTROL, MASTERCONTROL_ALLOC_DMA_CHAN, + SCP_GET, NULL, 0, dma_chan, &size); + + if (FAILED(status)) { + CA0132_DSP_LOG("dspio_alloc_dma_chan: SCP Failed"); + return -1; + } + + if ((*dma_chan + 1) == 0) { + CA0132_DSP_LOG("no free dma channels to allocate"); + return -1; + } + + CA0132_LOG("dspio_alloc_dma_chan: chan=%d\n", *dma_chan); + CA0132_DSP_LOG(" dspio_alloc_dma_chan() -- complete"); + + return status; +} + +static int dspio_free_dma_chan(struct hda_codec *codec, unsigned int dma_chan) +{ + int status = 0; + unsigned int dummy = 0; + + CA0132_DSP_LOG(" dspio_free_dma_chan() -- begin"); + CA0132_LOG("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); + + if (FAILED(status)) { + CA0132_DSP_LOG("dspio_free_dma_chan: SCP Failed"); + return -1; + } + + CA0132_DSP_LOG(" dspio_free_dma_chan() -- complete"); + + return status; +} + +/* + * CA0132 DSP access stuffs + */ +static int dsp_set_run_state(struct hda_codec *codec) +{ + unsigned int dbg_ctrl_reg; + unsigned int halt_state; + int err; + + err = chipio_read(codec, DSP_DBGCNTL_INST_OFFSET, &dbg_ctrl_reg); + if (err < 0) + return err; + + halt_state = (dbg_ctrl_reg & DSP_DBGCNTL_STATE_MASK) >> + DSP_DBGCNTL_STATE_LOBIT; + + if (halt_state != 0) { + dbg_ctrl_reg &= ~((halt_state << DSP_DBGCNTL_SS_LOBIT) & + DSP_DBGCNTL_SS_MASK); + err = chipio_write(codec, DSP_DBGCNTL_INST_OFFSET, + dbg_ctrl_reg); + if (err < 0) + return err; + + dbg_ctrl_reg |= (halt_state << DSP_DBGCNTL_EXEC_LOBIT) & + DSP_DBGCNTL_EXEC_MASK; + err = chipio_write(codec, DSP_DBGCNTL_INST_OFFSET, + dbg_ctrl_reg); + if (err < 0) + return err; + } + + return 0; +} + +static int dsp_reset(struct hda_codec *codec) +{ + unsigned int res; + int retry = 20; + + CA0132_LOG("dsp_reset\n"); + do { + res = dspio_send(codec, VENDOR_DSPIO_DSP_INIT, 0); + retry--; + } while (res == -1 && retry); + + if (!retry) { + CA0132_LOG("dsp_reset timeout\n"); + return -1; + } + + return 0; +} + +static unsigned int dsp_chip_to_dsp_addx(unsigned int chip_addx, + bool *code, bool *yram) +{ + *code = *yram = false; + + if (UC_RANGE(chip_addx, 1)) { + *code = true; + return UC_OFF(chip_addx); + } else if (X_RANGE_ALL(chip_addx, 1)) { + return X_OFF(chip_addx); + } else if (Y_RANGE_ALL(chip_addx, 1)) { + *yram = true; + return Y_OFF(chip_addx); + } + + return (unsigned int)INVALID_CHIP_ADDRESS; +} + +static bool dsp_is_dma_active(struct hda_codec *codec, unsigned int dma_chan) +{ + unsigned int dma_chnlstart_reg; + + chipio_read(codec, DSPDMAC_CHNLSTART_INST_OFFSET, &dma_chnlstart_reg); + + return ((dma_chnlstart_reg & (1 << + (DSPDMAC_CHNLSTART_EN_LOBIT + dma_chan))) != 0); +} + +static int dsp_dma_setup_common(struct hda_codec *codec, + unsigned int chip_addx, + unsigned int dma_chan, + unsigned int port_map_mask, + bool ovly) +{ + int status = 0; + unsigned int chnl_prop; + unsigned int dsp_addx; + unsigned int active; + bool code, yram; + + CA0132_DSP_LOG("-- dsp_dma_setup_common() -- Begin ---------"); + + CTASSERT(dma_chan < DSPDMAC_DMA_CFG_CHANNEL_COUNT); + if (dma_chan >= DSPDMAC_DMA_CFG_CHANNEL_COUNT) + return FAIL_MSG(-1, "dma chan num invalid"); + + if (dsp_is_dma_active(codec, dma_chan)) + return FAIL_MSG(-1, "dma already active"); + + dsp_addx = dsp_chip_to_dsp_addx(chip_addx, &code, &yram); + + if (dsp_addx == INVALID_CHIP_ADDRESS) + return FAIL_MSG(-1, "invalid chip addr"); + + chnl_prop = DSPDMAC_CHNLPROP_AC_MASK; + active = 0; + + CA0132_DSP_LOG(" dsp_dma_setup_common() start reg pgm"); + + if (ovly) { + status = chipio_read(codec, DSPDMAC_CHNLPROP_INST_OFFSET, + &chnl_prop); + + if (FAILED(status)) + return FAIL_MSG(-1, "read CHNLPROP Reg fail"); + + CA0132_DSP_LOG(" dsp_dma_setup_common() Read CHNLPROP"); + } + + if (!code) + chnl_prop &= ~(1 << (DSPDMAC_CHNLPROP_MSPCE_LOBIT + dma_chan)); + else + chnl_prop |= (1 << (DSPDMAC_CHNLPROP_MSPCE_LOBIT + dma_chan)); + + chnl_prop &= ~(1 << (DSPDMAC_CHNLPROP_DCON_LOBIT + dma_chan)); + + status = chipio_write(codec, DSPDMAC_CHNLPROP_INST_OFFSET, chnl_prop); + if (FAILED(status)) + return FAIL_MSG(-1, "write CHNLPROP Reg fail"); + + CA0132_DSP_LOG(" dsp_dma_setup_common() Write CHNLPROP"); + + if (ovly) { + status = chipio_read(codec, DSPDMAC_ACTIVE_INST_OFFSET, + &active); + + if (FAILED(status)) + return FAIL_MSG(-1, "read ACTIVE Reg fail"); + + CA0132_DSP_LOG(" dsp_dma_setup_common() Read ACTIVE"); + } + + active &= (~(1 << (DSPDMAC_ACTIVE_AAR_LOBIT + dma_chan))) & + DSPDMAC_ACTIVE_AAR_MASK; + + status = chipio_write(codec, DSPDMAC_ACTIVE_INST_OFFSET, active); + if (FAILED(status)) + return FAIL_MSG(-1, "write ACTIVE Reg fail"); + + CA0132_DSP_LOG(" dsp_dma_setup_common() Write ACTIVE"); + + status = chipio_write(codec, DSPDMAC_AUDCHSEL_INST_OFFSET(dma_chan), + port_map_mask); + if (FAILED(status)) + return FAIL_MSG(-1, "write AUDCHSEL Reg fail"); + + CA0132_DSP_LOG(" dsp_dma_setup_common() Write AUDCHSEL"); + + status = chipio_write(codec, DSPDMAC_IRQCNT_INST_OFFSET(dma_chan), + DSPDMAC_IRQCNT_BICNT_MASK | DSPDMAC_IRQCNT_CICNT_MASK); + if (FAILED(status)) + return FAIL_MSG(-1, "write IRQCNT Reg fail"); + + CA0132_DSP_LOG(" dsp_dma_setup_common() Write IRQCNT"); + + CA0132_LOG( + "ChipA=0x%x,DspA=0x%x,dmaCh=%u, " + "CHSEL=0x%x,CHPROP=0x%x,Active=0x%x\n", + chip_addx, dsp_addx, dma_chan, + port_map_mask, chnl_prop, active); + + CA0132_DSP_LOG("-- dsp_dma_setup_common() -- Complete ---------"); + + return 0; +} + +static int dsp_dma_setup(struct hda_codec *codec, + unsigned int chip_addx, + unsigned int count, + unsigned int dma_chan) +{ + int status = 0; + bool code, yram; + unsigned int dsp_addx; + unsigned int addr_field; + unsigned int incr_field; + unsigned int base_cnt; + unsigned int cur_cnt; + unsigned int dma_cfg = 0; + unsigned int adr_ofs = 0; + unsigned int xfr_cnt = 0; + const unsigned int max_dma_count = 1 << (DSPDMAC_XFRCNT_BCNT_HIBIT - + DSPDMAC_XFRCNT_BCNT_LOBIT + 1); + + CA0132_DSP_LOG("-- dsp_dma_setup() -- Begin ---------"); + + CTASSERT(count <= max_dma_count); + if (count > max_dma_count) + return FAIL_MSG(-1, "count too big"); + + dsp_addx = dsp_chip_to_dsp_addx(chip_addx, &code, &yram); + if (dsp_addx == INVALID_CHIP_ADDRESS) + return FAIL_MSG(-1, "invalid chip addr"); + + CA0132_DSP_LOG(" dsp_dma_setup() start reg pgm"); + + addr_field = dsp_addx << DSPDMAC_DMACFG_DBADR_LOBIT; + incr_field = 0; + + if (!code) { + addr_field <<= 1; + if (yram) + addr_field |= (1 << DSPDMAC_DMACFG_DBADR_LOBIT); + + incr_field = (1 << DSPDMAC_DMACFG_AINCR_LOBIT); + } + + CTASSERT((addr_field & DSPDMAC_DMACFG_DBADR_MASK) == addr_field); + dma_cfg = addr_field + incr_field; + status = chipio_write(codec, DSPDMAC_DMACFG_INST_OFFSET(dma_chan), + dma_cfg); + if (FAILED(status)) + return FAIL_MSG(-1, "write DMACFG Reg fail"); + + CA0132_DSP_LOG(" dsp_dma_setup() Write DMACFG"); + + adr_ofs = (count - 1) << (DSPDMAC_DSPADROFS_BOFS_LOBIT + + (code ? 0 : 1)); + CTASSERT((adr_ofs & DSPDMAC_DSPADROFS_BOFS_MASK) == adr_ofs); + + status = chipio_write(codec, DSPDMAC_DSPADROFS_INST_OFFSET(dma_chan), + adr_ofs); + if (FAILED(status)) + return FAIL_MSG(-1, "write DSPADROFS Reg fail"); + + CA0132_DSP_LOG(" dsp_dma_setup() Write DSPADROFS"); + + base_cnt = (count - 1) << DSPDMAC_XFRCNT_BCNT_LOBIT; + CTASSERT((base_cnt & DSPDMAC_XFRCNT_BCNT_MASK) == base_cnt); + + cur_cnt = (count - 1) << DSPDMAC_XFRCNT_CCNT_LOBIT; + CTASSERT((cur_cnt & DSPDMAC_XFRCNT_CCNT_MASK) == cur_cnt); + + xfr_cnt = base_cnt | cur_cnt; + + status = chipio_write(codec, + DSPDMAC_XFRCNT_INST_OFFSET(dma_chan), xfr_cnt); + if (FAILED(status)) + return FAIL_MSG(-1, "write XFRCNT Reg fail"); + + CA0132_DSP_LOG(" dsp_dma_setup() Write XFRCNT"); + + CA0132_LOG( + "ChipA=0x%x, cnt=0x%x, DMACFG=0x%x, " + "ADROFS=0x%x, XFRCNT=0x%x\n", + chip_addx, count, dma_cfg, adr_ofs, xfr_cnt); + + CA0132_DSP_LOG("-- dsp_dma_setup() -- Complete ---------"); + + return 0; +} + +static int dsp_dma_start(struct hda_codec *codec, + unsigned int dma_chan, bool ovly) +{ + unsigned int reg = 0; + int status = 0; + + CA0132_DSP_LOG("-- dsp_dma_start() -- Begin ---------"); + + if (ovly) { + status = chipio_read(codec, + DSPDMAC_CHNLSTART_INST_OFFSET, ®); + + if (FAILED(status)) + return FAIL_MSG(-1, "read CHNLSTART reg fail"); + + CA0132_DSP_LOG("-- dsp_dma_start() Read CHNLSTART"); + + reg &= ~(DSPDMAC_CHNLSTART_EN_MASK | + DSPDMAC_CHNLSTART_DIS_MASK); + } + + status = chipio_write(codec, DSPDMAC_CHNLSTART_INST_OFFSET, + reg | (1 << (dma_chan + DSPDMAC_CHNLSTART_EN_LOBIT))); + if (FAILED(status)) + return FAIL_MSG(-1, "write CHNLSTART reg fail"); + + CA0132_DSP_LOG("-- dsp_dma_start() -- Complete ---------"); + + return status; +} + +static int dsp_dma_stop(struct hda_codec *codec, + unsigned int dma_chan, bool ovly) +{ + unsigned int reg = 0; + int status = 0; + + CA0132_DSP_LOG("-- dsp_dma_stop() -- Begin ---------"); + + if (ovly) { + status = chipio_read(codec, + DSPDMAC_CHNLSTART_INST_OFFSET, ®); + + if (FAILED(status)) + return FAIL_MSG(-1, "read CHNLSTART reg fail"); + + CA0132_DSP_LOG("-- dsp_dma_stop() Read CHNLSTART"); + reg &= ~(DSPDMAC_CHNLSTART_EN_MASK | + DSPDMAC_CHNLSTART_DIS_MASK); + } + + status = chipio_write(codec, DSPDMAC_CHNLSTART_INST_OFFSET, + reg | (1 << (dma_chan + DSPDMAC_CHNLSTART_DIS_LOBIT))); + if (FAILED(status)) + return FAIL_MSG(-1, "write CHNLSTART reg fail"); + + CA0132_DSP_LOG("-- dsp_dma_stop() -- Complete ---------"); + + return status; +} + +static int dsp_allocate_router_ports(struct hda_codec *codec, + unsigned int num_chans, + unsigned int ports_per_channel, + unsigned int start_device, + unsigned int *port_map) +{ + int status = 0; + int res; + u8 val; + + status = chipio_send(codec, VENDOR_CHIPIO_STATUS, 0); + if (FAILED(status)) + return status; + + val = start_device << 6; + val |= (ports_per_channel - 1) << 4; + val |= num_chans - 1; + + snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0, + VENDOR_CHIPIO_PORT_ALLOC_CONFIG_SET, + val); + + snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0, + VENDOR_CHIPIO_PORT_ALLOC_SET, + MEM_CONNID_DSP); + + status = chipio_send(codec, VENDOR_CHIPIO_STATUS, 0); + if (FAILED(status)) + return status; + + res = snd_hda_codec_read(codec, WIDGET_CHIP_CTRL, 0, + VENDOR_CHIPIO_PORT_ALLOC_GET, 0); + + *port_map = res; + + return (res < 0) ? -1 : 0; +} + +static int dsp_free_router_ports(struct hda_codec *codec) +{ + int status = 0; + + status = chipio_send(codec, VENDOR_CHIPIO_STATUS, 0); + if (FAILED(status)) + return status; + + snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0, + VENDOR_CHIPIO_PORT_FREE_SET, + MEM_CONNID_DSP); + + status = chipio_send(codec, VENDOR_CHIPIO_STATUS, 0); + + return status; +} + +static int dsp_allocate_ports(struct hda_codec *codec, + unsigned int num_chans, + unsigned int rate_multi, unsigned int *port_map) +{ + int status = -1; + + CA0132_DSP_LOG(" dsp_allocate_ports() -- begin"); + + if ((rate_multi != 1) && (rate_multi != 2) && (rate_multi != 4)) + return FAIL_MSG(status, "bad rate multiple"); + + status = dsp_allocate_router_ports(codec, num_chans, + rate_multi, 0, port_map); + + CA0132_DSP_LOG(" dsp_allocate_ports() -- complete"); + + return status; +} + +static int dsp_free_ports(struct hda_codec *codec) +{ + int status; + + CA0132_DSP_LOG(" dsp_free_ports() -- begin"); + + status = dsp_free_router_ports(codec); + if (FAILED(status)) + return FAIL_MSG(-1, "free router ports fail"); + + CA0132_DSP_LOG(" dsp_free_ports() -- complete"); + + return status; +} + +static int dsp_allocate_ports_format(struct hda_codec *codec, + const unsigned short fmt, + unsigned int *port_map) +{ + int status = -1; + unsigned int num_chans; + + unsigned int sample_rate_div = ((get_hdafmt_rate(fmt) >> 0) & 3) + 1; + unsigned int sample_rate_mul = ((get_hdafmt_rate(fmt) >> 3) & 3) + 1; + unsigned int rate_multi = sample_rate_mul / sample_rate_div; + + if ((rate_multi != 1) && (rate_multi != 2) && (rate_multi != 4)) + return FAIL_MSG(-1, "bad rate multiple"); + + num_chans = get_hdafmt_chs(fmt) + 1; + + status = dsp_allocate_ports(codec, num_chans, rate_multi, port_map); + + return status; +} + +/* + * HDA DMA engine stuffs for DSP code download + */ +struct dma_engine { + struct hda_codec *codec; + unsigned short m_converter_format; + void *m_buffer_addr; + unsigned int m_buffer_size; + unsigned int m_req_size; + struct snd_pcm_substream *substream; +}; + +enum dma_state { + DMA_STATE_RESET = 0, + DMA_STATE_STOP = 1, + DMA_STATE_RUN = 2 +}; + +#define azx_pcm_open(a) (a->ops->open(a)) +#define azx_pcm_close(a) (a->ops->close(a)) +#define azx_pcm_prepare(a) (a->ops->prepare(a)) +#define azx_pcm_trigger(a, b) (a->ops->trigger(a, b)) +#define azx_pcm_hw_free(a) (a->ops->hw_free(a)) + +static int dma_convert_to_hda_format( + struct hda_stream_format *stream_format, + unsigned short *hda_format) +{ + unsigned int format_val; + + format_val = snd_hda_calc_stream_format( + stream_format->sample_rate, + stream_format->number_channels, + SNDRV_PCM_FORMAT_S32_LE, + stream_format->container_size, 0); + + if (hda_format) + *hda_format = (unsigned short)format_val; + + return 0; +} + +static int dma_init( + struct hda_codec *codec, + struct dma_engine **pp_dma, + struct hda_stream_format *stream_format, + unsigned short *format, + unsigned int req_size) +{ + struct dma_engine *dma; + struct snd_pcm_substream *substream; + struct snd_pcm *pcm; + struct snd_pcm_runtime *runtime; + unsigned int bits; + snd_pcm_uframes_t frames; + + *pp_dma = NULL; + dma = kzalloc(sizeof(*dma), GFP_KERNEL); + memset((void *)dma, 0, sizeof(*dma)); + + dma_convert_to_hda_format(stream_format, format); + dma->m_converter_format = *format; + + dma->substream = NULL; + pcm = codec->pcm_info->pcm; + for (substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; + substream; substream = substream->next) { + if (codec->pcm_info->pcm_type == HDA_PCM_TYPE_SPDIF) + continue; + + if (!SUBSTREAM_BUSY(substream)) { + dma->substream = substream; + break; + } + } + + if (NULL == dma->substream) { + kfree(dma); + return -1; + } + + runtime = kzalloc(sizeof(*runtime), GFP_KERNEL); + memset((void *)runtime, 0, sizeof(*runtime)); + dma->substream->runtime = runtime; + dma->substream->private_data = pcm->private_data; + + azx_pcm_open(dma->substream); + req_size = req_size * 2; + snd_pcm_lib_malloc_pages(dma->substream, req_size); + + runtime->rate = stream_format->sample_rate; + runtime->channels = stream_format->number_channels; + runtime->format = SNDRV_PCM_FORMAT_S32_LE; + runtime->no_period_wakeup = 1; + + bits = snd_pcm_format_physical_width(runtime->format); + runtime->sample_bits = bits; + bits *= runtime->channels; + runtime->frame_bits = bits; + frames = 1; + while (bits % 8 != 0) { + bits *= 2; + frames *= 2; + } + runtime->byte_align = bits / 8; + runtime->min_align = frames; + runtime->buffer_size = bytes_to_frames(runtime, req_size); + runtime->period_size = runtime->buffer_size; + dma->m_req_size = req_size; + dma->codec = codec; + + *pp_dma = dma; + CA0132_LOG("dma_init: succeeded.\n"); + return 0; +} + +static int dma_prepare(struct dma_engine *dma) +{ + struct snd_pcm_runtime *runtime; + int err; + + CA0132_LOG("dma_prepare: begin\n"); + runtime = dma->substream->runtime; + + err = azx_pcm_prepare(dma->substream); + if (err < 0) + return -1; + + dma->m_buffer_size = snd_pcm_lib_buffer_bytes(dma->substream); + dma->m_buffer_addr = runtime->dma_area; + + return 0; +} + +static int dma_reset(struct dma_engine *dma) +{ + struct snd_pcm_runtime *runtime = dma->substream->runtime; + + CA0132_LOG("dma_reset: begin\n"); + azx_pcm_hw_free(dma->substream); + snd_pcm_lib_malloc_pages(dma->substream, dma->m_req_size); + + azx_pcm_prepare(dma->substream); + dma->m_buffer_size = snd_pcm_lib_buffer_bytes(dma->substream); + dma->m_buffer_addr = runtime->dma_area; + + return 0; +} + +static int dma_set_state(struct dma_engine *dma, enum dma_state state) +{ + int cmd; + + CA0132_LOG("dma_set_state state=%d\n", state); + + switch (state) { + case DMA_STATE_RESET: + dma_reset(dma); + return 0; + case DMA_STATE_STOP: + cmd = SNDRV_PCM_TRIGGER_STOP; + break; + case DMA_STATE_RUN: + cmd = SNDRV_PCM_TRIGGER_START; + break; + default: + return 0; + } + + azx_pcm_trigger(dma->substream, cmd); + + return 0; +} + +static unsigned int dma_get_buffer_size(struct dma_engine *dma) +{ + return dma->m_buffer_size; +} + +static unsigned int *dma_get_buffer_addr(struct dma_engine *dma) +{ + return dma->m_buffer_addr; +} + +static int dma_free_buffer(struct dma_engine *dma) +{ + azx_pcm_hw_free(dma->substream); + azx_pcm_close(dma->substream); + kfree(dma->substream->runtime); + dma->substream->runtime = NULL; + return 0; +} + +static int dma_xfer(struct dma_engine *dma, + const unsigned int *data, + unsigned int count) +{ + memcpy(dma->m_buffer_addr, data, count); + return 0; +} + +static void dma_get_converter_format( + struct dma_engine *dma, + unsigned short *format) +{ + if (format) + *format = dma->m_converter_format; +} + +static unsigned int dma_get_stream_id(struct dma_engine *dma) +{ + struct ca0132_spec *spec = dma->codec->spec; + + return spec->dsp_stream_id; +} + +static int dma_exit(struct dma_engine *dma) +{ + CA0132_LOG("dma_exit\n"); + kfree(dma); + return 0; +} + +/* + * CA0132 chip DSP image segment stuffs + */ +struct dsp_image_seg { + u32 magic; + u32 chip_addr; + u32 count; + u32 data[0]; +}; + +static const u32 g_magic_value = 0x4c46584d; +static const u32 g_chip_addr_magic_value = 0xFFFFFF01; + +static bool is_valid(const struct dsp_image_seg *p) +{ + return p->magic == g_magic_value; +} + +static bool is_hci_prog_list_seg(const struct dsp_image_seg *p) +{ + return g_chip_addr_magic_value == p->chip_addr; +} + +static bool is_last(const struct dsp_image_seg *p) +{ + return p->count == 0; +} + +static size_t dsp_sizeof(const struct dsp_image_seg *p) +{ + return sizeof(*p) + p->count*sizeof(u32); +} + +static const struct dsp_image_seg *get_next_seg_ptr( + const struct dsp_image_seg *p) +{ + return (struct dsp_image_seg *)((unsigned char *)(p) + dsp_sizeof(p)); +} + +/* + * CA0132 chip DSP trannsfer stuffs. For DSP download. + */ +#define INVALID_DMA_CHANNEL (~0UL) + +static int dspxfr_hci_write(struct hda_codec *codec, + const struct dsp_image_seg *fls) +{ + int status = 0; + const u32 *data; + unsigned int count; + + CTASSERT(fls != NULL && fls->chip_addr == g_chip_addr_magic_value); + if (fls == NULL || fls->chip_addr != g_chip_addr_magic_value) + return FAIL_MSG(-1, "hci_write invalid params"); + + count = fls->count; + data = (u32 *)(fls->data); + while (SUCCEEDED(status) && count >= 2) { + status = chipio_write(codec, data[0], data[1]); + if (FAILED(status)) + status = FAIL_MSG(status, "hci_write chipio failed"); + count -= 2; + data += 2; + } + return status; +} + +static int dspxfr_one_seg(struct hda_codec *codec, + const struct dsp_image_seg *fls, + unsigned int reloc, + struct dma_engine *dma_engine, + unsigned int dma_chan, + unsigned int port_map_mask, + bool ovly) +{ + int status = 0; + bool comm_dma_setup_done = false; + const unsigned int *data; + unsigned int chip_addx; + unsigned int words_to_write; + unsigned int buffer_size_words; + unsigned int *buffer_addx; + unsigned short hda_format; + unsigned int sample_rate_div; + unsigned int sample_rate_mul; + unsigned int num_chans; + unsigned int hda_frame_size_words; + unsigned int remainder_words; + const u32 *data_remainder; + u32 chip_addx_remainder; + unsigned int run_size_words; + const struct dsp_image_seg *hci_write = NULL; + int retry; + + CTASSERT(fls != NULL); + if (fls == NULL) + return -1; + if (is_hci_prog_list_seg(fls)) { + hci_write = fls; + fls = get_next_seg_ptr(fls); + } + + if (hci_write && (!fls || is_last(fls))) { + CA0132_LOG("hci_write\n"); + return dspxfr_hci_write(codec, hci_write); + } + + if (fls == NULL || dma_engine == NULL || port_map_mask == 0) { + CA0132_LOG("Invalid Params\n"); + return -1; + } + + data = fls->data; + chip_addx = fls->chip_addr, + words_to_write = fls->count; + + if (!words_to_write) + return hci_write ? dspxfr_hci_write(codec, hci_write) : 0; + if (reloc) + chip_addx = (chip_addx & (0xFFFF0000 << 2)) + (reloc << 2); + + if (!UC_RANGE(chip_addx, words_to_write) && + !X_RANGE_ALL(chip_addx, words_to_write) && + !Y_RANGE_ALL(chip_addx, words_to_write)) { + CA0132_LOG("Invalid chip_addx Params\n"); + return -1; + } + + buffer_size_words = (unsigned int)dma_get_buffer_size(dma_engine) / + sizeof(u32); + + buffer_addx = dma_get_buffer_addr(dma_engine); + + if (buffer_addx == NULL) + status = FAIL_MSG(-1, "dma_engine buffer NULL"); + + dma_get_converter_format(dma_engine, &hda_format); + sample_rate_div = ((get_hdafmt_rate(hda_format) >> 0) & 3) + 1; + sample_rate_mul = ((get_hdafmt_rate(hda_format) >> 3) & 3) + 1; + num_chans = get_hdafmt_chs(hda_format) + 1; + + hda_frame_size_words = ((sample_rate_div == 0) ? 0 : + (num_chans * sample_rate_mul / sample_rate_div)); + + buffer_size_words = min(buffer_size_words, + (unsigned int)(UC_RANGE(chip_addx, 1) ? + 65536 : 32768)); + buffer_size_words -= buffer_size_words % hda_frame_size_words; + CA0132_LOG( + "chpadr=0x%08x frmsz=%u nchan=%u " + "rate_mul=%u div=%u bufsz=%u\n", + chip_addx, hda_frame_size_words, num_chans, + sample_rate_mul, sample_rate_div, buffer_size_words); + + CTASSERT(SUCCEEDED(status)); + CTASSERT(buffer_addx != NULL); + CTASSERT(buffer_size_words >= hda_frame_size_words); + CTASSERT(hda_frame_size_words > 0); + + if ((buffer_addx == NULL) || (hda_frame_size_words == 0) || + (buffer_size_words < hda_frame_size_words)) { + status = FAIL_MSG(-1, "dspxfr_one_seg:failed"); + } + + if (FAILED(status)) + return status; + + remainder_words = words_to_write % hda_frame_size_words; + data_remainder = data; + chip_addx_remainder = chip_addx; + + data += remainder_words; + chip_addx += remainder_words*sizeof(u32); + words_to_write -= remainder_words; + + while ((words_to_write != 0) && SUCCEEDED(status)) { + run_size_words = min(buffer_size_words, words_to_write); + CA0132_LOG("dspxfr (seg loop)cnt=%u rs=%u remainder=%u\n", + words_to_write, run_size_words, remainder_words); + dma_set_state(dma_engine, DMA_STATE_STOP); + dma_xfer(dma_engine, data, run_size_words*sizeof(u32)); + if (!comm_dma_setup_done && SUCCEEDED(status)) { + status = dsp_dma_stop(codec, dma_chan, ovly); + if (SUCCEEDED(status)) + status = dsp_dma_setup_common(codec, chip_addx, + dma_chan, port_map_mask, ovly); + comm_dma_setup_done = true; + } + + if (SUCCEEDED(status)) + status = dsp_dma_setup(codec, chip_addx, + run_size_words, dma_chan); + if (SUCCEEDED(status)) + status = dsp_dma_start(codec, dma_chan, ovly); + if (SUCCEEDED(status) && !dsp_is_dma_active(codec, dma_chan)) + status = FAIL_MSG(-1, "dspxfr:DMA did not start"); + if (SUCCEEDED(status)) + status = dma_set_state(dma_engine, DMA_STATE_RUN); + if (SUCCEEDED(status)) { + if (remainder_words != 0) { + status = chipio_write_multiple(codec, + chip_addx_remainder, + data_remainder, + remainder_words); + remainder_words = 0; + } + if (hci_write) { + status = dspxfr_hci_write(codec, hci_write); + hci_write = NULL; + } + retry = 5000; + while (dsp_is_dma_active(codec, dma_chan)) { + if (--retry <= 0) + break; + } + CA0132_DSP_LOG("+++++ DMA complete"); + dma_set_state(dma_engine, DMA_STATE_STOP); + dma_set_state(dma_engine, DMA_STATE_RESET); + } + + CTASSERT(run_size_words <= words_to_write); + data += run_size_words; + chip_addx += run_size_words*sizeof(u32); + words_to_write -= run_size_words; + } + + if (SUCCEEDED(status) && (remainder_words != 0)) { + status = chipio_write_multiple(codec, chip_addx_remainder, + data_remainder, remainder_words); + } + + return status; +} + +static int dspxfr_image(struct hda_codec *codec, + const struct dsp_image_seg *fls_data, + unsigned int reloc, struct hda_stream_format *format, + bool ovly) +{ + int status = 0; + unsigned short hda_format = 0; + unsigned int response; + unsigned char stream_id = 0; + struct dma_engine *dma_engine; + unsigned int dma_chan; + unsigned int port_map_mask; + unsigned int buf_size; + + CTASSERT(fls_data != NULL); + if (fls_data == NULL) + return -1; + + buf_size = ovly ? DSP_DMA_WRITE_BUFLEN_OVLY : DSP_DMA_WRITE_BUFLEN_INIT; + status = dma_init(codec, &dma_engine, format, &hda_format, buf_size); + + if (FAILED(status)) + return -1; + + dma_chan = 0; + do { + status = codec_set_converter_format(codec, WIDGET_CHIP_CTRL, + hda_format, &response); + if (FAILED(status)) { + status = FAIL_MSG(status, "set converter format fail"); + break; + } + + status = dma_prepare(dma_engine); + if (FAILED(status)) + break; + + if (ovly) { + status = dspio_alloc_dma_chan(codec, &dma_chan); + if (FAILED(status)) { + status = FAIL_MSG(status, "alloc dmachan fail"); + dma_chan = (unsigned int)INVALID_DMA_CHANNEL; + break; + } + } + + port_map_mask = 0; + status = dsp_allocate_ports_format(codec, hda_format, + &port_map_mask); + if (FAILED(status)) { + status = FAIL_MSG(status, "alloc parts fail"); + break; + } + + stream_id = dma_get_stream_id(dma_engine); + status = codec_set_converter_stream_channel(codec, + WIDGET_CHIP_CTRL, stream_id, 0, &response); + if (FAILED(status)) { + status = FAIL_MSG(status, "set stream chan fail"); + break; + } + + while (SUCCEEDED(status) && (fls_data != NULL) && + !is_last(fls_data)) { + if (!is_valid(fls_data)) { + status = FAIL_MSG(-1, "FLS check fail"); + break; + } + status = dspxfr_one_seg(codec, fls_data, reloc, + dma_engine, dma_chan, + port_map_mask, ovly); + if (is_hci_prog_list_seg(fls_data)) + fls_data = get_next_seg_ptr(fls_data); + + CTASSERT(fls_data != NULL); + if ((fls_data != NULL) && !is_last(fls_data)) + fls_data = get_next_seg_ptr(fls_data); + + CTASSERT(fls_data != NULL); + } + + if (port_map_mask != 0) + status = dsp_free_ports(codec); + + if (FAILED(status)) + break; + + status = codec_set_converter_stream_channel(codec, + WIDGET_CHIP_CTRL, 0, 0, &response); + } while (0); + + if (ovly && (dma_chan != INVALID_DMA_CHANNEL)) + status = dspio_free_dma_chan(codec, dma_chan); + + status = dma_set_state(dma_engine, DMA_STATE_RESET); + if (FAILED(status)) + status = FAIL_MSG(status, "dma set state Reset fail"); + + status = dma_free_buffer(dma_engine); + if (FAILED(status)) + status = FAIL_MSG(status, "dma free buffer fail"); + + dma_exit(dma_engine); + + return status; +} + +/* + * CA0132 DSP download stuffs. + */ +static void dspload_post_setup(struct hda_codec *codec) +{ + CA0132_DSP_LOG("---- dspload_post_setup ------"); + + /*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); +} + +static int dspload_image(struct hda_codec *codec, + const struct dsp_image_seg *fls, + bool ovly, + unsigned int reloc, + bool autostart, + int router_chans) +{ + int status = 0; + struct hda_stream_format stream_format; + + CA0132_DSP_LOG("---- dspload_image begin ------"); + if (router_chans == 0) { + if (!ovly) + router_chans = DMA_TRANSFER_FRAME_SIZE_NWORDS; + else + router_chans = DMA_OVERLAY_FRAME_SIZE_NWORDS; + } + + stream_format.sample_rate = 48000; + stream_format.number_channels = (unsigned short)router_chans; + + while (stream_format.number_channels > 16) { + stream_format.sample_rate *= 2; + stream_format.number_channels /= 2; + } + + stream_format.container_size = 32; + stream_format.valid_bits_per_sample = 32; + + do { + CA0132_DSP_LOG("Ready to program DMA"); + if (!ovly) + status = dsp_reset(codec); + + if (FAILED(status)) + break; + + CA0132_DSP_LOG("dsp_reset() complete"); + status = dspxfr_image(codec, fls, reloc, &stream_format, ovly); + + if (FAILED(status)) + break; + + CA0132_DSP_LOG("dspxfr_image() complete"); + if (autostart && !ovly) { + dspload_post_setup(codec); + status = dsp_set_run_state(codec); + } + + CA0132_DSP_LOG("LOAD FINISHED"); + } while (0); + + return status; +} + +static int dspload_get_speakereq_addx(struct hda_codec *codec, + unsigned int *x, + unsigned int *y) +{ + int status = 0; + struct { unsigned short x, y; } speakereq_info; + unsigned int size = sizeof(speakereq_info); + + CA0132_DSP_LOG("dspload_get_speakereq_addx() -- begin"); + status = dspio_scp(codec, MASTERCONTROL, + MASTERCONTROL_QUERY_SPEAKER_EQ_ADDRESS, + SCP_GET, NULL, 0, &speakereq_info, &size); + + if (FAILED(status)) { + CA0132_DSP_LOG("dspload_get_speakereq_addx: SCP Failed"); + return -1; + } + + *x = speakereq_info.x; + *y = speakereq_info.y; + CA0132_LOG("dspload_get_speakereq_addx: X=0x%x Y=0x%x\n", *x, *y); + + CA0132_DSP_LOG("dspload_get_speakereq_addx() -- complete"); + + return status; +} + +static int dspload_speakereq(struct hda_codec *codec) +{ + int status = 0; + const struct dsp_image_seg *image_x, *image_y; + const struct firmware *fw_entry; + unsigned int x, y; + + CA0132_DSP_LOG("dspload_speakereq() -- begin"); + + if (request_firmware(&fw_entry, SPEQ_FILE, codec->bus->card->dev) != 0) + return -1; + + if (fw_entry->size != SPEQ_SIZE) { + status = -1; + goto done_release_firmware; + } + + image_x = (struct dsp_image_seg *)(fw_entry->data + 0x10); + image_y = (struct dsp_image_seg *)(fw_entry->data + 0x1028); + + status = dspload_get_speakereq_addx(codec, &x, &y); + if (FAILED(status)) + goto done_release_firmware; + + status = dspload_image(codec, image_x, 1, x, 0, 8); + if (FAILED(status)) + goto done_release_firmware; + + status = dspload_image(codec, image_y, 1, y, 0, 8); + +done_release_firmware: + release_firmware(fw_entry); + + CA0132_DSP_LOG("dspload_speakereq() -- complete"); + + return status; +} + +static bool dspload_is_loaded(struct hda_codec *codec) +{ + unsigned int data = 0; + int status = 0; + + status = chipio_read(codec, 0x40004, &data); + if (FAILED(status) || (data != 1)) + return false; + + return true; +} + +static bool dspload_wait_loaded(struct hda_codec *codec) +{ + int retry = 100; + + do { + msleep(20); + if (dspload_is_loaded(codec)) { + pr_info("DOWNLOAD OK :-) DSP IS RUNNING.\n"); + return true; + } + } while (--retry); + + pr_err("DOWNLOAD FAILED!!! DSP IS NOT RUNNING.\n"); + return false; +} + +/* + * PCM stuffs. + */ +static void ca0132_setup_stream(struct hda_codec *codec, hda_nid_t nid, + u32 stream_tag, + int channel_id, int format) +{ + unsigned int oldval, newval; + + if (!nid) + return; + + CA0132_LOG( + "ca0132_setup_stream: NID=0x%x, stream=0x%x, " + "channel=%d, format=0x%x\n", + nid, stream_tag, channel_id, format); + + /* update the format-id if changed */ + oldval = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_STREAM_FORMAT, 0); + if (oldval != format) { + msleep(20); + snd_hda_codec_write(codec, nid, 0, + AC_VERB_SET_STREAM_FORMAT, + format); + } + + oldval = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_CONV, 0); + newval = (stream_tag << 4) | channel_id; + if (oldval != newval) { + snd_hda_codec_write(codec, nid, 0, + AC_VERB_SET_CHANNEL_STREAMID, + newval); + } +} + +static void ca0132_cleanup_stream(struct hda_codec *codec, hda_nid_t nid) +{ + unsigned int val; + + if (!nid) + return; + + CA0132_LOG("ca0132_cleanup_stream: NID=0x%x\n", nid); + + val = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_CONV, 0); + if (!val) + return; + + snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_STREAM_FORMAT, 0); + snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_CHANNEL_STREAMID, 0); +} + +/* + * PCM playbacks + */ + +static int ca0132_playback_pcm_prepare(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + unsigned int stream_tag, + unsigned int format, + struct snd_pcm_substream *substream) +{ + struct ca0132_spec *spec = codec->spec; + + if (spec->dsp_state == DSP_DOWNLOADING) { + spec->dsp_stream_id = stream_tag; + return 0; + } + + ca0132_setup_stream(codec, spec->dacs[0], stream_tag, 0, format); + return 0; +} + +static int ca0132_playback_pcm_cleanup(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + struct ca0132_spec *spec = codec->spec; + + if (spec->dsp_state == DSP_DOWNLOADING) + return 0; + + /*Allow stream some time to flush effects tail*/ + msleep(50);
ca0132_cleanup_stream(codec, spec->dacs[0]); + return 0; +} + +/* + * PCM capture + */ + +static int ca0132_capture_pcm_prepare(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + unsigned int stream_tag, + unsigned int format, + struct snd_pcm_substream *substream) +{ + struct ca0132_spec *spec = codec->spec; + + if (spec->dsp_state == DSP_DOWNLOADING) + return 0; + + ca0132_setup_stream(codec, hinfo->nid, stream_tag, 0, format); + return 0; +} + +static int ca0132_capture_pcm_cleanup(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + struct ca0132_spec *spec = codec->spec; + + if (spec->dsp_state == DSP_DOWNLOADING) + return 0; + + ca0132_cleanup_stream(codec, hinfo->nid); + return 0; +} + + +/* + * Controls stuffs. + */ + +/* + * Mixer controls helpers. + */ +#define CA0132_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 = ca0132_volume_info, \ + .get = ca0132_volume_get, \ + .put = ca0132_volume_put, \ + .tlv = { .c = ca0132_volume_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, \ + .subdevice = HDA_SUBDEV_AMP_FLAG, \ + .info = snd_hda_mixer_amp_switch_info, \ + .get = ca0132_switch_get, \ + .put = ca0132_switch_put, \ + .private_value = HDA_COMPOSE_AMP_VAL(nid, channel, 0, dir) } + +/* stereo */ +#define CA0132_CODEC_VOL(xname, nid, dir) \ + CA0132_CODEC_VOL_MONO(xname, nid, 3, dir) +#define CA0132_CODEC_MUTE(xname, nid, dir) \ + CA0132_CODEC_MUTE_MONO(xname, nid, 3, dir) + + +/* The followings are for tuning of products */ +#ifdef ENABLE_TUNING_CONTROLS + +static unsigned int voice_focus_vals_lookup[] = { +0x41A00000, 0x41A80000, 0x41B00000, 0x41B80000, 0x41C00000, 0x41C80000, +0x41D00000, 0x41D80000, 0x41E00000, 0x41E80000, 0x41F00000, 0x41F80000, +0x42000000, 0x42040000, 0x42080000, 0x420C0000, 0x42100000, 0x42140000, +0x42180000, 0x421C0000, 0x42200000, 0x42240000, 0x42280000, 0x422C0000, +0x42300000, 0x42340000, 0x42380000, 0x423C0000, 0x42400000, 0x42440000, +0x42480000, 0x424C0000, 0x42500000, 0x42540000, 0x42580000, 0x425C0000, +0x42600000, 0x42640000, 0x42680000, 0x426C0000, 0x42700000, 0x42740000, +0x42780000, 0x427C0000, 0x42800000, 0x42820000, 0x42840000, 0x42860000, +0x42880000, 0x428A0000, 0x428C0000, 0x428E0000, 0x42900000, 0x42920000, +0x42940000, 0x42960000, 0x42980000, 0x429A0000, 0x429C0000, 0x429E0000, +0x42A00000, 0x42A20000, 0x42A40000, 0x42A60000, 0x42A80000, 0x42AA0000, +0x42AC0000, 0x42AE0000, 0x42B00000, 0x42B20000, 0x42B40000, 0x42B60000, +0x42B80000, 0x42BA0000, 0x42BC0000, 0x42BE0000, 0x42C00000, 0x42C20000, +0x42C40000, 0x42C60000, 0x42C80000, 0x42CA0000, 0x42CC0000, 0x42CE0000, +0x42D00000, 0x42D20000, 0x42D40000, 0x42D60000, 0x42D80000, 0x42DA0000, +0x42DC0000, 0x42DE0000, 0x42E00000, 0x42E20000, 0x42E40000, 0x42E60000, +0x42E80000, 0x42EA0000, 0x42EC0000, 0x42EE0000, 0x42F00000, 0x42F20000, +0x42F40000, 0x42F60000, 0x42F80000, 0x42FA0000, 0x42FC0000, 0x42FE0000, +0x43000000, 0x43010000, 0x43020000, 0x43030000, 0x43040000, 0x43050000, +0x43060000, 0x43070000, 0x43080000, 0x43090000, 0x430A0000, 0x430B0000, +0x430C0000, 0x430D0000, 0x430E0000, 0x430F0000, 0x43100000, 0x43110000, +0x43120000, 0x43130000, 0x43140000, 0x43150000, 0x43160000, 0x43170000, +0x43180000, 0x43190000, 0x431A0000, 0x431B0000, 0x431C0000, 0x431D0000, +0x431E0000, 0x431F0000, 0x43200000, 0x43210000, 0x43220000, 0x43230000, +0x43240000, 0x43250000, 0x43260000, 0x43270000, 0x43280000, 0x43290000, +0x432A0000, 0x432B0000, 0x432C0000, 0x432D0000, 0x432E0000, 0x432F0000, +0x43300000, 0x43310000, 0x43320000, 0x43330000, 0x43340000 +}; + +static unsigned int mic_svm_vals_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 +}; + +static unsigned int equalizer_vals_lookup[] = { +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, 0x41200000, 0x41300000, +0x41400000, 0x41500000, 0x41600000, 0x41700000, 0x41800000, 0x41880000, +0x41900000, 0x41980000, 0x41A00000, 0x41A80000, 0x41B00000, 0x41B80000, +0x41C00000 +}; + +static int tuning_ctl_set(struct hda_codec *codec, hda_nid_t nid, + unsigned int *lookup, int idx) +{ + int i = 0; + + for (i = 0; i < TUNING_CTLS_COUNT; i++) + if (nid == ca0132_tuning_ctls[i].nid) + break; + + snd_hda_power_up(codec); + dspio_set_param(codec, ca0132_tuning_ctls[i].mid, + ca0132_tuning_ctls[i].req, + &(lookup[idx]), sizeof(unsigned int)); + snd_hda_power_down(codec); + + return 1; +} + +static int tuning_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 - TUNING_CTL_START_NID; + + *valp = spec->cur_ctl_vals[idx]; + return 0; +} + +static int voice_focus_ctl_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 = 20; + uinfo->value.integer.max = 180; + uinfo->value.integer.step = 1; + + return 0; +} + +static int voice_focus_ctl_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 - TUNING_CTL_START_NID; + /* any change? */ + if (spec->cur_ctl_vals[idx] == *valp) + return 0; + + spec->cur_ctl_vals[idx] = *valp; + + idx = *valp - 20; + tuning_ctl_set(codec, nid, voice_focus_vals_lookup, idx); + + return 1; +} + +static int mic_svm_ctl_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 mic_svm_ctl_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 - TUNING_CTL_START_NID; + /* any change? */ + if (spec->cur_ctl_vals[idx] == *valp) + return 0; + + spec->cur_ctl_vals[idx] = *valp; + + idx = *valp; + tuning_ctl_set(codec, nid, mic_svm_vals_lookup, idx); + + return 0; +} + +static int equalizer_ctl_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 = 48; + uinfo->value.integer.step = 1; + + return 0; +} + +static int equalizer_ctl_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 - TUNING_CTL_START_NID; + /* any change? */ + if (spec->cur_ctl_vals[idx] == *valp) + return 0; + + spec->cur_ctl_vals[idx] = *valp; + + idx = *valp; + tuning_ctl_set(codec, nid, equalizer_vals_lookup, idx); + + return 1; +} + +DECLARE_TLV_DB_SCALE(voice_focus_db_scale, 2000, 100, 0); +DECLARE_TLV_DB_SCALE(eq_db_scale, -2400, 100, 0); + +static int add_tuning_control(struct hda_codec *codec, + hda_nid_t pnid, hda_nid_t nid, + const char *name, int dir) +{ + char namestr[44]; + int type = dir ? HDA_INPUT : HDA_OUTPUT; + struct snd_kcontrol_new knew = + HDA_CODEC_VOLUME_MONO(namestr, nid, 1, 0, type); + + knew.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ; + knew.tlv.c = 0; + knew.tlv.p = 0; + switch (pnid) { + case VOICE_FOCUS: + knew.info = voice_focus_ctl_info; + knew.get = tuning_ctl_get; + knew.put = voice_focus_ctl_put; + knew.tlv.p = voice_focus_db_scale; + break; + case MIC_SVM: + knew.info = mic_svm_ctl_info; + knew.get = tuning_ctl_get; + knew.put = mic_svm_ctl_put; + break; + case EQUALIZER: + knew.info = equalizer_ctl_info; + knew.get = tuning_ctl_get; + knew.put = equalizer_ctl_put; + knew.tlv.p = eq_db_scale; + break; + default: + return 0; + } + knew.private_value = + HDA_COMPOSE_AMP_VAL(nid, 1, 0, type); + sprintf(namestr, "%s %s Volume", name, dirstr[dir]); + return snd_hda_ctl_add(codec, nid, snd_ctl_new1(&knew, codec)); +} + +static int add_tuning_ctls(struct hda_codec *codec) +{ + int i; + int err; + + for (i = 0; i < TUNING_CTLS_COUNT; i++) { + err = add_tuning_control(codec, + ca0132_tuning_ctls[i].parent_nid, + ca0132_tuning_ctls[i].nid, + ca0132_tuning_ctls[i].name, + ca0132_tuning_ctls[i].direct); + if (err < 0) + return err; + } + + return 0; +} + +static void ca0132_init_tuning_defaults(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + int i; + + /* Wedge Angle defaults to 30. 10 below is 30 - 20. 20 is min. */ + spec->cur_ctl_vals[WEDGE_ANGLE - TUNING_CTL_START_NID] = 10; + /* SVM level defaults to 0.74. */ + spec->cur_ctl_vals[SVM_LEVEL - TUNING_CTL_START_NID] = 74; + + /* EQ defaults to 0dB. */ + for (i = 2; i < TUNING_CTLS_COUNT; i++) + spec->cur_ctl_vals[i] = 24; +} +#endif + +static int ca0132_select_out(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + unsigned int pin_ctl; + int jack_present; + int auto_jack; + unsigned int tmp; + int err; + + CA0132_LOG("ca0132_select_out\n"); + + snd_hda_power_up(codec); + + auto_jack = spec->vnode_lswitch[VNID_HP_ASEL - VNODE_START_NID]; + + if (auto_jack) + jack_present = snd_hda_jack_detect(codec, spec->out_pins[1]); + else + jack_present = + spec->vnode_lswitch[VNID_HP_SEL - VNODE_START_NID]; + + if (jack_present) + spec->cur_out_type = HEADPHONE_OUT; + else + spec->cur_out_type = SPEAKER_OUT; + + if (spec->cur_out_type == SPEAKER_OUT) { + CA0132_LOG("ca0132_select_out speaker\n"); + /*speaker out config*/ + tmp = FLOAT_ONE; + err = dspio_set_param(codec, 0x80, 0x04, + &tmp, sizeof(unsigned int)); + if (err < 0) + goto exit; + /*enable speaker EQ*/ + tmp = FLOAT_ONE; + err = dspio_set_param(codec, 0x8f, 0x00, + &tmp, sizeof(unsigned int)); + if (err < 0) + goto exit; + /*speaker EQ bypass attenuation is 0*/ + tmp = FLOAT_ZERO; + err = dspio_set_param(codec, 0x8f, 0x01, + &tmp, sizeof(unsigned int)); + if (err < 0) + goto exit; + + /* disable headphone node */ + pin_ctl = snd_hda_codec_read(codec, spec->out_pins[1], 0, + AC_VERB_GET_PIN_WIDGET_CONTROL, 0); + snd_hda_codec_write(codec, spec->out_pins[1], 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, + pin_ctl & 0xBF); + /* disable headphone EAPD */ + snd_hda_codec_write(codec, spec->out_pins[0], 0, + AC_VERB_SET_EAPD_BTLENABLE, 0x00); + /* enable speaker node */ + pin_ctl = snd_hda_codec_read(codec, spec->out_pins[0], 0, + AC_VERB_GET_PIN_WIDGET_CONTROL, 0); + snd_hda_codec_write(codec, spec->out_pins[0], 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, + pin_ctl | 0x40); + /* enable speaker EAPD */ + snd_hda_codec_write(codec, spec->out_pins[0], 0, + VENDOR_CHIPIO_EAPD_SEL_SET, 0x00); + snd_hda_codec_write(codec, spec->out_pins[0], 0, + AC_VERB_SET_EAPD_BTLENABLE, 0x02); + } else { + CA0132_LOG("ca0132_select_out hp\n"); + /*headphone out config*/ + tmp = FLOAT_ZERO; + err = dspio_set_param(codec, 0x80, 0x04, + &tmp, sizeof(unsigned int)); + if (err < 0) + goto exit; + /*disable speaker EQ*/ + tmp = FLOAT_ZERO; + err = dspio_set_param(codec, 0x8f, 0x00, + &tmp, sizeof(unsigned int)); + if (err < 0) + goto exit; + /*speaker EQ bypass attenuation is -5.0*/ + tmp = FLOAT_MINUS_5; + err = dspio_set_param(codec, 0x8f, 0x01, + &tmp, sizeof(unsigned int)); + if (err < 0) + goto exit; + + /* disable speaker*/ + pin_ctl = snd_hda_codec_read(codec, spec->out_pins[0], 0, + AC_VERB_GET_PIN_WIDGET_CONTROL, 0); + snd_hda_codec_write(codec, spec->out_pins[0], 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, + pin_ctl & 0xBF); + /* disable speaker EAPD */ + snd_hda_codec_write(codec, spec->out_pins[0], 0, + AC_VERB_SET_EAPD_BTLENABLE, 0x00); + /* enable headphone*/ + pin_ctl = snd_hda_codec_read(codec, spec->out_pins[1], 0, + AC_VERB_GET_PIN_WIDGET_CONTROL, 0); + snd_hda_codec_write(codec, spec->out_pins[1], 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, + pin_ctl | 0x40); + /* enable headphone EAPD */ + snd_hda_codec_write(codec, spec->out_pins[1], 0, + VENDOR_CHIPIO_EAPD_SEL_SET, 0x02); + snd_hda_codec_write(codec, spec->out_pins[0], 0, + AC_VERB_SET_EAPD_BTLENABLE, 0x02); + } + +exit: + snd_hda_power_down(codec); + + return err < 0 ? err : 0; +} + +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 int ca0132_select_mic(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + int jack_present; + int auto_jack; + + CA0132_LOG("ca0132_select_mic\n"); + + snd_hda_power_up(codec); + + auto_jack = spec->vnode_lswitch[VNID_AMIC1_ASEL - VNODE_START_NID]; + + if (auto_jack) + jack_present = snd_hda_jack_detect(codec, spec->input_pins[0]); + else + jack_present = + spec->vnode_lswitch[VNID_AMIC1_SEL - VNODE_START_NID]; + + if (jack_present) + spec->cur_mic_type = LINE_MIC_IN; + else + spec->cur_mic_type = DIGITAL_MIC; + + if (spec->cur_mic_type == DIGITAL_MIC) { + /* enable digital Mic */ + chipio_set_conn_rate(codec, MEM_CONNID_DMIC, SR_32_000); + ca0132_set_dmic(codec, 1); + ca0132_mic_boost_set(codec, 0); + /* set voice focus */ + ca0132_effects_set(codec, VOICE_FOCUS, + spec->effects_switch + [VOICE_FOCUS - EFFECT_START_NID]); + } else { + /* disable digital Mic */ + chipio_set_conn_rate(codec, MEM_CONNID_DMIC, SR_96_000); + ca0132_set_dmic(codec, 0); + ca0132_mic_boost_set(codec, spec->cur_mic_boost); + /* disable voice focus */ + ca0132_effects_set(codec, VOICE_FOCUS, 0); + } + + snd_hda_power_down(codec);
return 0; }
/* - * Digital out + * Check if VNODE settings take effect immediately. + */ +static bool ca0132_is_vnode_effective(struct hda_codec *codec, + hda_nid_t vnid, + hda_nid_t *shared_nid) +{ + struct ca0132_spec *spec = codec->spec; + hda_nid_t nid; + bool effective = false; + + switch (vnid) { + case VNID_SPK: + nid = spec->shared_out_nid; + effective = true; + break; + case VNID_MIC: + nid = spec->shared_mic_nid; + effective = true; + break; + default: + break; + } + + if (effective && shared_nid) + *shared_nid = nid; + + return effective; +} + +/* + * The following functions are control change helpers. + * They return 0 if no changed. Return 1 if changed. */ -static int ca0132_dig_playback_pcm_prepare(struct hda_pcm_stream *hinfo, - struct hda_codec *codec, - unsigned int stream_tag, - unsigned int format, - struct snd_pcm_substream *substream) +static int ca0132_voicefx_set(struct hda_codec *codec, int enable) +{ + struct ca0132_spec *spec = codec->spec; + unsigned int tmp; + + /* based on CrystalVoice state to enable VoiceFX. */ + if (enable) { + tmp = spec->effects_switch[CRYSTAL_VOICE - EFFECT_START_NID] ? + FLOAT_ONE : FLOAT_ZERO; + } else { + tmp = FLOAT_ZERO; + } + + dspio_set_param(codec, ca0132_voicefx.mid, + ca0132_voicefx.reqs[0], &tmp, sizeof(unsigned int)); + + return 1; +} + +static int ca0132_effects_set(struct hda_codec *codec, hda_nid_t nid, long val) +{ + struct ca0132_spec *spec = codec->spec; + unsigned int on; + int num_fx = OUT_EFFECTS_COUNT + IN_EFFECTS_COUNT; + int err = 0; + int idx = nid - EFFECT_START_NID; + + if ((idx < 0) || (idx >= num_fx)) + return 0; /* no changed */ + + /* for out effect, qualify with PE */ + if ((nid >= OUT_EFFECT_START_NID) && (nid < OUT_EFFECT_END_NID)) { + /* if PE if off, turn off out effects. */ + if (!spec->effects_switch[PLAY_ENHANCEMENT - EFFECT_START_NID]) + val = 0; + } + + /* for in effect, qualify with CrystalVoice */ + if ((nid >= IN_EFFECT_START_NID) && (nid < IN_EFFECT_END_NID)) { + /* if CrystalVoice if off, turn off in effects. */ + if (!spec->effects_switch[CRYSTAL_VOICE - EFFECT_START_NID]) + val = 0; + + /* Voice Focus applies to 2-ch Mic, Digital Mic */ + if ((nid == VOICE_FOCUS) && (spec->cur_mic_type != DIGITAL_MIC)) + val = 0; + } + + CA0132_LOG("ca0132_effect_set: nid=0x%x, val=%ld\n", nid, val); + + on = (val == 0) ? FLOAT_ZERO : FLOAT_ONE; + err = dspio_set_param(codec, ca0132_effects[idx].mid, + ca0132_effects[idx].reqs[0], + &on, sizeof(unsigned int)); + if (err < 0) + return 0; /* no changed */ + + return 1; +} + +static int ca0132_pe_switch_set(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + hda_nid_t nid; + int i, ret = 0; + + CA0132_LOG("ca0132_pe_switch_set: val=%ld\n", + spec->effects_switch[PLAY_ENHANCEMENT - EFFECT_START_NID]); + + i = OUT_EFFECT_START_NID - EFFECT_START_NID; + nid = OUT_EFFECT_START_NID; + /* PE affects all out effects */ + for (; nid < OUT_EFFECT_END_NID; nid++, i++) + ret |= ca0132_effects_set(codec, nid, spec->effects_switch[i]); + + return ret; +} + +static int ca0132_cvoice_switch_set(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + hda_nid_t nid; + int i, ret = 0; + + CA0132_LOG("ca0132_cvoice_switch_set: val=%ld\n", + spec->effects_switch[CRYSTAL_VOICE - EFFECT_START_NID]); + + i = IN_EFFECT_START_NID - EFFECT_START_NID; + nid = IN_EFFECT_START_NID; + /* CrystalVoice affects all in effects */ + for (; nid < IN_EFFECT_END_NID; nid++, i++) + ret |= ca0132_effects_set(codec, nid, spec->effects_switch[i]); + + /* including VoiceFX */ + ret |= ca0132_voicefx_set(codec, (spec->voicefx_val ? 1 : 0)); + return ret; +} + +static int ca0132_mic_boost_set(struct hda_codec *codec, long val) +{ + struct ca0132_spec *spec = codec->spec; + int ret = 0; + + if (val) /* on */ + ret = snd_hda_codec_amp_update(codec, spec->input_pins[0], 0, + HDA_INPUT, 0, HDA_AMP_VOLMASK, 3); + else /* off */ + ret = snd_hda_codec_amp_update(codec, spec->input_pins[0], 0, + HDA_INPUT, 0, HDA_AMP_VOLMASK, 0); + + return ret; +} + +static int ca0132_vnode_switch_set(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) { + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + hda_nid_t nid = get_amp_nid(kcontrol); + hda_nid_t shared_nid = 0; + bool effective; + int ret = 0; struct ca0132_spec *spec = codec->spec; + int auto_jack; + + if (nid == VNID_HP_SEL) { + auto_jack = + spec->vnode_lswitch[VNID_HP_ASEL - VNODE_START_NID]; + if (!auto_jack) + ca0132_select_out(codec); + return 1; + } + + if (nid == VNID_AMIC1_SEL) { + auto_jack = + spec->vnode_lswitch[VNID_AMIC1_ASEL - VNODE_START_NID]; + if (!auto_jack) + ca0132_select_mic(codec); + return 1; + }
- ca0132_setup_stream(codec, spec->dig_out, stream_tag, 0, format); + if (nid == VNID_HP_ASEL) { + ca0132_select_out(codec); + return 1; + } + + if (nid == VNID_AMIC1_ASEL) { + ca0132_select_mic(codec); + return 1; + } + + /* if effective conditions, then update hw immediately. */ + effective = ca0132_is_vnode_effective(codec, nid, &shared_nid); + if (effective) { + int dir = get_amp_direction(kcontrol); + int ch = get_amp_channels(kcontrol); + unsigned long pval; + + mutex_lock(&codec->control_mutex); + pval = kcontrol->private_value; + kcontrol->private_value = HDA_COMPOSE_AMP_VAL(shared_nid, ch, + 0, dir); + ret = snd_hda_mixer_amp_switch_put(kcontrol, ucontrol); + kcontrol->private_value = pval; + mutex_unlock(&codec->control_mutex); + } + + return ret; +} +/* End of control change helpers. */ + +static int ca0132_voicefx_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + unsigned int items = sizeof(ca0132_voicefx_presets) + / sizeof(struct ct_voicefx_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_voicefx_presets[uinfo->value.enumerated.item].name); + return 0; +} + +static int ca0132_voicefx_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->voicefx_val; return 0; }
-static int ca0132_dig_playback_pcm_cleanup(struct hda_pcm_stream *hinfo, - struct hda_codec *codec, - struct snd_pcm_substream *substream) +static int ca0132_voicefx_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_voicefx_presets) + / sizeof(struct ct_voicefx_preset); + + if (sel >= items) + return 0; + + CA0132_LOG("ca0132_voicefx_put: sel=%d, preset=%s\n", + sel, ca0132_voicefx_presets[sel].name); + + /* + * Idx 0 is default. + * Default needs to qualify with CrystalVoice state. + */ + for (i = 0; i < VOICEFX_MAX_PARAM_COUNT; i++) { + err = dspio_set_param(codec, ca0132_voicefx.mid, + ca0132_voicefx.reqs[i], + &(ca0132_voicefx_presets[sel].vals[i]), + sizeof(unsigned int)); + if (err < 0) + break; + } + + if (err >= 0) { + spec->voicefx_val = sel; + /* enable voice fx */ + ca0132_voicefx_set(codec, (sel ? 1 : 0)); + } + + return 1; +} + +static int ca0132_switch_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); + int ch = get_amp_channels(kcontrol); + long *valp = ucontrol->value.integer.value; + + /* vnode */ + if ((nid >= VNODE_START_NID) && (nid < VNODE_END_NID)) { + if (ch & 1) { + *valp = spec->vnode_lswitch[nid - VNODE_START_NID]; + valp++; + } + if (ch & 2) { + *valp = spec->vnode_rswitch[nid - VNODE_START_NID]; + valp++; + } + return 0; + } + + /* effects, include PE and CrystalVoice */ + if ((nid >= EFFECT_START_NID) && (nid < EFFECT_END_NID)) { + *valp = spec->effects_switch[nid - EFFECT_START_NID]; + return 0; + }
- ca0132_cleanup_stream(codec, spec->dig_out); + /* mic boost */ + if (nid == spec->input_pins[0]) { + *valp = spec->cur_mic_boost; + return 0; + }
return 0; }
+static int ca0132_switch_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; + int changed = 1; + + CA0132_LOG("ca0132_switch_put: nid=0x%x, val=%ld\n", nid, *valp); + + snd_hda_power_up(codec); + /* vnode */ + if ((nid >= VNODE_START_NID) && (nid < VNODE_END_NID)) { + if (ch & 1) { + spec->vnode_lswitch[nid - VNODE_START_NID] = *valp; + valp++; + } + if (ch & 2) { + spec->vnode_rswitch[nid - VNODE_START_NID] = *valp; + valp++; + } + changed = ca0132_vnode_switch_set(kcontrol, ucontrol); + goto exit; + } + + /* PE */ + if (nid == PLAY_ENHANCEMENT) { + spec->effects_switch[nid - EFFECT_START_NID] = *valp; + changed = ca0132_pe_switch_set(codec); + goto exit; + } + + /* CrystalVoice */ + if (nid == CRYSTAL_VOICE) { + spec->effects_switch[nid - EFFECT_START_NID] = *valp; + changed = ca0132_cvoice_switch_set(codec); + goto exit; + } + + /* out and in effects */ + if (((nid >= OUT_EFFECT_START_NID) && (nid < OUT_EFFECT_END_NID)) || + ((nid >= IN_EFFECT_START_NID) && (nid < IN_EFFECT_END_NID))) { + spec->effects_switch[nid - EFFECT_START_NID] = *valp; + changed = ca0132_effects_set(codec, nid, *valp); + goto exit; + } + + /* mic boost */ + if (nid == spec->input_pins[0]) { + spec->cur_mic_boost = *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; + } + +exit: + snd_hda_power_down(codec); + return changed; +} + /* - * Analog capture + * Volume related */ -static int ca0132_capture_pcm_prepare(struct hda_pcm_stream *hinfo, - struct hda_codec *codec, - unsigned int stream_tag, - unsigned int format, - struct snd_pcm_substream *substream) +static int ca0132_volume_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) { + 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); + int dir = get_amp_direction(kcontrol); + unsigned long pval; + int err; + + switch (nid) { + case VNID_SPK: + /* follow shared_out info */ + nid = spec->shared_out_nid; + mutex_lock(&codec->control_mutex); + pval = kcontrol->private_value; + kcontrol->private_value = HDA_COMPOSE_AMP_VAL(nid, ch, 0, dir); + err = snd_hda_mixer_amp_volume_info(kcontrol, uinfo); + kcontrol->private_value = pval; + mutex_unlock(&codec->control_mutex); + break; + case VNID_MIC: + /* follow shared_mic info */ + nid = spec->shared_mic_nid; + mutex_lock(&codec->control_mutex); + pval = kcontrol->private_value; + kcontrol->private_value = HDA_COMPOSE_AMP_VAL(nid, ch, 0, dir); + err = snd_hda_mixer_amp_volume_info(kcontrol, uinfo); + kcontrol->private_value = pval; + mutex_unlock(&codec->control_mutex); + break; + default: + err = snd_hda_mixer_amp_volume_info(kcontrol, uinfo); + } + return err; +}
- ca0132_setup_stream(codec, spec->adcs[substream->number], - stream_tag, 0, format); +static int ca0132_volume_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); + int ch = get_amp_channels(kcontrol); + long *valp = ucontrol->value.integer.value;
+ /* store the left and right volume */ + if (ch & 1) { + *valp = spec->vnode_lvol[nid - VNODE_START_NID]; + valp++; + } + if (ch & 2) { + *valp = spec->vnode_rvol[nid - VNODE_START_NID]; + valp++; + } return 0; }
-static int ca0132_capture_pcm_cleanup(struct hda_pcm_stream *hinfo, - struct hda_codec *codec, - struct snd_pcm_substream *substream) +static int ca0132_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 shared_nid = 0; + bool effective; + int changed = 1; + + /* store the left and right volume */ + if (ch & 1) { + spec->vnode_lvol[nid - VNODE_START_NID] = *valp; + valp++; + } + if (ch & 2) { + spec->vnode_rvol[nid - VNODE_START_NID] = *valp; + valp++; + } + + /* if effective conditions, then update hw immediately. */ + effective = ca0132_is_vnode_effective(codec, nid, &shared_nid); + if (effective) { + int dir = get_amp_direction(kcontrol); + unsigned long pval; + + snd_hda_power_up(codec); + mutex_lock(&codec->control_mutex); + pval = kcontrol->private_value; + kcontrol->private_value = HDA_COMPOSE_AMP_VAL(shared_nid, ch, + 0, dir); + changed = snd_hda_mixer_amp_volume_put(kcontrol, ucontrol); + kcontrol->private_value = pval; + 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) +{ + 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); + int dir = get_amp_direction(kcontrol); + unsigned long pval; + int err; + + switch (nid) { + case VNID_SPK: + /* follow shared_out tlv */ + nid = spec->shared_out_nid; + mutex_lock(&codec->control_mutex); + pval = kcontrol->private_value; + kcontrol->private_value = HDA_COMPOSE_AMP_VAL(nid, ch, 0, dir); + err = snd_hda_mixer_amp_tlv(kcontrol, op_flag, size, tlv); + kcontrol->private_value = pval; + mutex_unlock(&codec->control_mutex); + break; + case VNID_MIC: + /* follow shared_mic tlv */ + nid = spec->shared_mic_nid; + mutex_lock(&codec->control_mutex); + pval = kcontrol->private_value; + kcontrol->private_value = HDA_COMPOSE_AMP_VAL(nid, ch, 0, dir); + err = snd_hda_mixer_amp_tlv(kcontrol, op_flag, size, tlv); + kcontrol->private_value = pval; + mutex_unlock(&codec->control_mutex); + break; + default: + err = snd_hda_mixer_amp_tlv(kcontrol, op_flag, size, tlv); + } + return err; +} + +static int add_fx_switch(struct hda_codec *codec, hda_nid_t nid, + const char *pfx, int dir) +{ + char namestr[44]; + 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]); + return snd_hda_ctl_add(codec, nid, snd_ctl_new1(&knew, codec)); +} + +static int add_voicefx(struct hda_codec *codec) { - struct ca0132_spec *spec = codec->spec; - - ca0132_cleanup_stream(codec, spec->adcs[substream->number]); - - return 0; + struct snd_kcontrol_new knew = + HDA_CODEC_MUTE_MONO(ca0132_voicefx.name, + VOICEFX, 1, 0, HDA_INPUT); + knew.info = ca0132_voicefx_info; + knew.get = ca0132_voicefx_get; + knew.put = ca0132_voicefx_put; + return snd_hda_ctl_add(codec, VOICEFX, snd_ctl_new1(&knew, codec)); }
/* - * Digital capture + * When changing Node IDs for Mixer Controls below, make sure to update + * Node IDs in ca0132_config() as well. */ -static int ca0132_dig_capture_pcm_prepare(struct hda_pcm_stream *hinfo, - struct hda_codec *codec, - unsigned int stream_tag, - unsigned int format, - struct snd_pcm_substream *substream) +static struct snd_kcontrol_new ca0132_mixer[] = { + CA0132_CODEC_VOL("Master Playback Volume", VNID_SPK, HDA_OUTPUT), + CA0132_CODEC_MUTE("Master 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("Analog-Mic2 Capture Volume", 0x08, 0, HDA_INPUT), + HDA_CODEC_MUTE("Analog-Mic2 Capture Switch", 0x08, 0, 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("Mic1-Boost (30dB) Capture Switch", + 0x12, 1, HDA_INPUT), + CA0132_CODEC_MUTE_MONO("HP/Speaker Playback Switch", + VNID_HP_SEL, 1, HDA_OUTPUT), + CA0132_CODEC_MUTE_MONO("AMic1/DMic Capture Switch", + VNID_AMIC1_SEL, 1, HDA_INPUT), + CA0132_CODEC_MUTE_MONO("HP/Speaker Auto Detect Playback Switch", + VNID_HP_ASEL, 1, HDA_OUTPUT), + CA0132_CODEC_MUTE_MONO("AMic1/DMic Auto Detect Capture Switch", + VNID_AMIC1_ASEL, 1, HDA_INPUT), + { } /* end */ +}; + +static int ca0132_build_controls(struct hda_codec *codec) { struct ca0132_spec *spec = codec->spec; + int i, num_fx; + int err = 0;
- ca0132_setup_stream(codec, spec->dig_in, stream_tag, 0, format); + /* Add Mixer controls */ + for (i = 0; i < spec->num_mixers; i++) { + err = snd_hda_add_new_ctls(codec, spec->mixers[i]); + if (err < 0) + return err; + }
- return 0; -} + /* 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++) { + err = add_fx_switch(codec, ca0132_effects[i].nid, + ca0132_effects[i].name, + ca0132_effects[i].direct); + if (err < 0) + return err; + }
-static int ca0132_dig_capture_pcm_cleanup(struct hda_pcm_stream *hinfo, - struct hda_codec *codec, - struct snd_pcm_substream *substream) -{ - struct ca0132_spec *spec = codec->spec; + 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);
- ca0132_cleanup_stream(codec, spec->dig_in); + #ifdef ENABLE_TUNING_CONTROLS + add_tuning_ctls(codec); + #endif + + err = snd_hda_jack_add_kctls(codec, &spec->autocfg); + if (err < 0) + return err;
return 0; }
/* + * PCM */ static struct hda_pcm_stream ca0132_pcm_analog_playback = { .substreams = 1, .channels_min = 2, - .channels_max = 2, + .channels_max = 6, .ops = { .prepare = ca0132_playback_pcm_prepare, .cleanup = ca0132_playback_pcm_cleanup @@ -636,26 +3902,6 @@ static struct hda_pcm_stream ca0132_pcm_analog_capture = { }, };
-static struct hda_pcm_stream ca0132_pcm_digital_playback = { - .substreams = 1, - .channels_min = 2, - .channels_max = 2, - .ops = { - .prepare = ca0132_dig_playback_pcm_prepare, - .cleanup = ca0132_dig_playback_pcm_cleanup - }, -}; - -static struct hda_pcm_stream ca0132_pcm_digital_capture = { - .substreams = 1, - .channels_min = 2, - .channels_max = 2, - .ops = { - .prepare = ca0132_dig_capture_pcm_prepare, - .cleanup = ca0132_dig_capture_pcm_cleanup - }, -}; - static int ca0132_build_pcms(struct hda_codec *codec) { struct ca0132_spec *spec = codec->spec; @@ -668,391 +3914,548 @@ static int ca0132_build_pcms(struct hda_codec *codec) 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 = - spec->multiout.max_channels; + spec->multiout.max_channels; info->stream[SNDRV_PCM_STREAM_CAPTURE] = ca0132_pcm_analog_capture; - info->stream[SNDRV_PCM_STREAM_CAPTURE].substreams = spec->num_inputs; + info->stream[SNDRV_PCM_STREAM_CAPTURE].substreams = 1; info->stream[SNDRV_PCM_STREAM_CAPTURE].nid = spec->adcs[0]; codec->num_pcms++;
- if (!spec->dig_out && !spec->dig_in) - return 0; + info++; + info->name = "CA0132 Analog Mic-In2"; + 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]; + codec->num_pcms++;
info++; - info->name = "CA0132 Digital"; - info->pcm_type = HDA_PCM_TYPE_SPDIF; - if (spec->dig_out) { - info->stream[SNDRV_PCM_STREAM_PLAYBACK] = - ca0132_pcm_digital_playback; - info->stream[SNDRV_PCM_STREAM_PLAYBACK].nid = spec->dig_out; - } - if (spec->dig_in) { - info->stream[SNDRV_PCM_STREAM_CAPTURE] = - ca0132_pcm_digital_capture; - info->stream[SNDRV_PCM_STREAM_CAPTURE].nid = spec->dig_in; - } + info->name = "CA0132 What U Hear"; + 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[2]; codec->num_pcms++;
return 0; }
-#define REG_CODEC_MUTE 0x18b014 -#define REG_CODEC_HP_VOL_L 0x18b070 -#define REG_CODEC_HP_VOL_R 0x18b074 - -static int ca0132_hp_switch_get(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) +static void init_output(struct hda_codec *codec, hda_nid_t pin, hda_nid_t dac) { - struct hda_codec *codec = snd_kcontrol_chip(kcontrol); - struct ca0132_spec *spec = codec->spec; - long *valp = ucontrol->value.integer.value; - - *valp = spec->curr_hp_switch; - return 0; + if (pin) { + snd_hda_codec_write(codec, pin, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP); + if (get_wcaps(codec, pin) & AC_WCAP_OUT_AMP) + snd_hda_codec_write(codec, pin, 0, + AC_VERB_SET_AMP_GAIN_MUTE, + AMP_OUT_UNMUTE); + } + if (dac) + snd_hda_codec_write(codec, dac, 0, AC_VERB_SET_AMP_GAIN_MUTE, + AMP_OUT_ZERO); }
-static int ca0132_hp_switch_put(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) +static void init_input(struct hda_codec *codec, hda_nid_t pin, hda_nid_t adc) { - struct hda_codec *codec = snd_kcontrol_chip(kcontrol); - struct ca0132_spec *spec = codec->spec; - long *valp = ucontrol->value.integer.value; - unsigned int data; - int err; - - /* any change? */ - if (spec->curr_hp_switch == *valp) - return 0; - - snd_hda_power_up(codec); + if (pin) { + snd_hda_codec_write(codec, pin, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, + PIN_VREF80); + if (get_wcaps(codec, pin) & AC_WCAP_IN_AMP) + snd_hda_codec_write(codec, pin, 0, + AC_VERB_SET_AMP_GAIN_MUTE, + AMP_IN_UNMUTE(0)); + } + if (adc) { + snd_hda_codec_write(codec, adc, 0, AC_VERB_SET_AMP_GAIN_MUTE, + AMP_IN_UNMUTE(0));
- err = chipio_read(codec, REG_CODEC_MUTE, &data); - if (err < 0) - goto exit; + /* init to 0 dB and unmute. */ + snd_hda_codec_amp_stereo(codec, adc, HDA_INPUT, 0, + HDA_AMP_VOLMASK, 0x5a); + snd_hda_codec_amp_stereo(codec, adc, HDA_INPUT, 0, + HDA_AMP_MUTE, 0); + } +}
- /* *valp 0 is mute, 1 is unmute */ - data = (data & 0x7f) | (*valp ? 0 : 0x80); - err = chipio_write(codec, REG_CODEC_MUTE, data); - if (err < 0) - goto exit; +static void ca0132_init_unsol(struct hda_codec *codec) +{ + snd_hda_jack_detect_enable(codec, UNSOL_TAG_HP, UNSOL_TAG_HP); + snd_hda_jack_detect_enable(codec, UNSOL_TAG_AMIC1, UNSOL_TAG_AMIC1); +}
- spec->curr_hp_switch = *valp; +static void refresh_amp_caps(struct hda_codec *codec, hda_nid_t nid, int dir) +{ + unsigned int caps;
- exit: - snd_hda_power_down(codec); - return err < 0 ? err : 1; + caps = snd_hda_param_read(codec, nid, dir == HDA_OUTPUT ? + AC_PAR_AMP_OUT_CAP : AC_PAR_AMP_IN_CAP); + snd_hda_override_amp_caps(codec, nid, dir, caps); }
-static int ca0132_speaker_switch_get(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) +static void ca0132_set_dmic(struct hda_codec *codec, int enable) { - struct hda_codec *codec = snd_kcontrol_chip(kcontrol); struct ca0132_spec *spec = codec->spec; - long *valp = ucontrol->value.integer.value; - - *valp = spec->curr_speaker_switch; - return 0; + unsigned int tmp; + u8 val; + + CA0132_LOG("ca0132_set_dmic: enable=%d\n", enable); + + if (enable) { + /* set DMic input as 2-ch */ + tmp = FLOAT_TWO; + dspio_set_param(codec, 0x80, 0x00, &tmp, sizeof(unsigned int)); + + val = spec->dmic_ctl; + val |= 0x80; + snd_hda_codec_write(codec, spec->input_pins[0], 0, + VENDOR_CHIPIO_DMIC_CTL_SET, val); + + if (!(spec->dmic_ctl & 0x20)) + chipio_set_control_flag(codec, CONTROL_FLAG_DMIC, 1); + } else { + /* set AMic input as mono */ + tmp = FLOAT_ONE; + dspio_set_param(codec, 0x80, 0x00, &tmp, sizeof(unsigned int)); + + val = spec->dmic_ctl; + /* clear bit7 and bit5 to disable dmic */ + val &= 0x5f; + snd_hda_codec_write(codec, spec->input_pins[0], 0, + VENDOR_CHIPIO_DMIC_CTL_SET, val); + + if (!(spec->dmic_ctl & 0x20)) + chipio_set_control_flag(codec, CONTROL_FLAG_DMIC, 0); + } }
-static int ca0132_speaker_switch_put(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) +static void ca0132_init_dmic(struct hda_codec *codec) { - struct hda_codec *codec = snd_kcontrol_chip(kcontrol); struct ca0132_spec *spec = codec->spec; - long *valp = ucontrol->value.integer.value; - unsigned int data; - int err; - - /* any change? */ - if (spec->curr_speaker_switch == *valp) - return 0; - - snd_hda_power_up(codec); - - err = chipio_read(codec, REG_CODEC_MUTE, &data); - if (err < 0) - goto exit; - - /* *valp 0 is mute, 1 is unmute */ - data = (data & 0xef) | (*valp ? 0 : 0x10); - err = chipio_write(codec, REG_CODEC_MUTE, data); - if (err < 0) - goto exit; - - spec->curr_speaker_switch = *valp; - - exit: - snd_hda_power_down(codec); - return err < 0 ? err : 1; + u8 val; + + /* Setup Digital Mic here, but don't enable. + * Enable based on jack detect. + */ + + /* MCLK uses MPIO1, set to enable. + * Bit 2-0: MPIO select + * Bit 3: set to disable + * Bit 7-4: reserved + */ + val = 0x01; + snd_hda_codec_write(codec, spec->input_pins[0], 0, + VENDOR_CHIPIO_DMIC_MCLK_SET, val); + + /* Data1 uses MPIO3. Data2 not use + * Bit 2-0: Data1 MPIO select + * Bit 3: set disable Data1 + * Bit 6-4: Data2 MPIO select + * Bit 7: set disable Data2 + */ + val = 0x83; + snd_hda_codec_write(codec, spec->input_pins[0], 0, + VENDOR_CHIPIO_DMIC_PIN_SET, val); + + /* Use Ch-0 and Ch-1. Rate is 48K, mode 1. Disable DMic first. + * Bit 3-0: Channel mask + * Bit 4: set for 48KHz, clear for 32KHz + * Bit 5: mode + * Bit 6: set to select Data2, clear for Data1 + * Bit 7: set to enable DMic, clear for AMic + */ + val = 0x23; + /* keep a copy of dmic ctl val for enable/disable dmic purpuse */ + spec->dmic_ctl = val; + snd_hda_codec_write(codec, spec->input_pins[0], 0, + VENDOR_CHIPIO_DMIC_CTL_SET, val); }
-static int ca0132_hp_volume_get(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) +static void ca0132_init_analog_mic2(struct hda_codec *codec) { - struct hda_codec *codec = snd_kcontrol_chip(kcontrol); struct ca0132_spec *spec = codec->spec; - long *valp = ucontrol->value.integer.value;
- *valp++ = spec->curr_hp_volume[0]; - *valp = spec->curr_hp_volume[1]; - return 0; + mutex_lock(&spec->chipio_mutex); + 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_ADDRESS_LOW, 0x2D); + 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); + mutex_unlock(&spec->chipio_mutex); }
-static int ca0132_hp_volume_put(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) +static void ca0132_refresh_widget_caps(struct hda_codec *codec) { - struct hda_codec *codec = snd_kcontrol_chip(kcontrol); struct ca0132_spec *spec = codec->spec; - long *valp = ucontrol->value.integer.value; - long left_vol, right_vol; - unsigned int data; - int val; - int err; + int i; + hda_nid_t nid;
- left_vol = *valp++; - right_vol = *valp; + CA0132_LOG("ca0132_refresh_widget_caps.\n"); + nid = codec->start_nid; + for (i = 0; i < codec->num_nodes; i++, nid++) + codec->wcaps[i] = snd_hda_param_read(codec, nid, + AC_PAR_AUDIO_WIDGET_CAP);
- /* any change? */ - if ((spec->curr_hp_volume[0] == left_vol) && - (spec->curr_hp_volume[1] == right_vol)) - return 0; + for (i = 0; i < spec->multiout.num_dacs; i++) + refresh_amp_caps(codec, spec->dacs[i], HDA_OUTPUT);
- snd_hda_power_up(codec); + for (i = 0; i < spec->num_outputs; i++) + refresh_amp_caps(codec, spec->out_pins[i], HDA_OUTPUT);
- err = chipio_read(codec, REG_CODEC_HP_VOL_L, &data); - if (err < 0) - goto exit; + for (i = 0; i < spec->num_inputs; i++) { + refresh_amp_caps(codec, spec->adcs[i], HDA_INPUT); + refresh_amp_caps(codec, spec->input_pins[i], HDA_INPUT); + } +}
- val = 31 - left_vol; - data = (data & 0xe0) | val; - err = chipio_write(codec, REG_CODEC_HP_VOL_L, data); - if (err < 0) - goto exit; +static void ca0132_setup_defaults(struct hda_codec *codec) +{ + unsigned int tmp; + int num_fx; + int idx, i;
- val = 31 - right_vol; - data = (data & 0xe0) | val; - err = chipio_write(codec, REG_CODEC_HP_VOL_R, data); - if (err < 0) - goto exit; + if (!dspload_is_loaded(codec)) + return;
- spec->curr_hp_volume[0] = left_vol; - spec->curr_hp_volume[1] = right_vol; + /* 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_param(codec, ca0132_effects[idx].mid, + ca0132_effects[idx].reqs[i], + &(ca0132_effects[idx].def_vals[i]), + sizeof(unsigned int)); + } + }
- exit: - snd_hda_power_down(codec); - return err < 0 ? err : 1; -} + /* set AMic1 and AMic2 as mono mic */ + tmp = FLOAT_ONE; + dspio_set_param(codec, 0x80, 0x00, &tmp, sizeof(unsigned int)); + dspio_set_param(codec, 0x80, 0x01, &tmp, sizeof(unsigned int));
-static int add_hp_switch(struct hda_codec *codec, hda_nid_t nid) -{ - struct snd_kcontrol_new knew = - HDA_CODEC_MUTE_MONO("Headphone Playback Switch", - nid, 1, 0, HDA_OUTPUT); - knew.get = ca0132_hp_switch_get; - knew.put = ca0132_hp_switch_put; - return snd_hda_ctl_add(codec, nid, snd_ctl_new1(&knew, codec)); + /* set AMic1 as CrystalVoice input */ + tmp = FLOAT_ONE; + dspio_set_param(codec, 0x80, 0x05, &tmp, sizeof(unsigned int)); }
-static int add_hp_volume(struct hda_codec *codec, hda_nid_t nid) +static void ca0132_init_flags(struct hda_codec *codec) { - struct snd_kcontrol_new knew = - HDA_CODEC_VOLUME_MONO("Headphone Playback Volume", - nid, 3, 0, HDA_OUTPUT); - knew.get = ca0132_hp_volume_get; - knew.put = ca0132_hp_volume_put; - return snd_hda_ctl_add(codec, nid, snd_ctl_new1(&knew, 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); }
-static int add_speaker_switch(struct hda_codec *codec, hda_nid_t nid) +static void ca0132_set_dsp_msr(struct hda_codec *codec, bool is96k) { - struct snd_kcontrol_new knew = - HDA_CODEC_MUTE_MONO("Speaker Playback Switch", - nid, 1, 0, HDA_OUTPUT); - knew.get = ca0132_speaker_switch_get; - knew.put = ca0132_speaker_switch_put; - return snd_hda_ctl_add(codec, nid, snd_ctl_new1(&knew, codec)); + chipio_set_control_flag(codec, CONTROL_FLAG_DSP_96KHZ, is96k); + chipio_set_control_flag(codec, CONTROL_FLAG_DAC_96KHZ, is96k); + chipio_set_control_flag(codec, CONTROL_FLAG_SRC_RATE_96KHZ, is96k); + chipio_set_control_flag(codec, CONTROL_FLAG_SRC_CLOCK_196MHZ, is96k); + chipio_set_control_flag(codec, CONTROL_FLAG_ADC_B_96KHZ, is96k); + chipio_set_control_flag(codec, CONTROL_FLAG_ADC_C_96KHZ, is96k); + + chipio_set_conn_rate(codec, MEM_CONNID_MICIN1, SR_16_000); + chipio_set_conn_rate(codec, MEM_CONNID_MICOUT1, SR_16_000); + chipio_set_conn_rate(codec, MEM_CONNID_WUH, SR_48_000); }
-static void ca0132_fix_hp_caps(struct hda_codec *codec) +static bool ca0132_download_dsp_images(struct hda_codec *codec) { - struct ca0132_spec *spec = codec->spec; - struct auto_pin_cfg *cfg = &spec->autocfg; - unsigned int caps; + bool dsp_loaded = false; + const struct dsp_image_seg *dsp_os_image; + const struct firmware *fw_entry; + + if (request_firmware(&fw_entry, EFX_FILE, codec->bus->card->dev) != 0) + return false; + + dsp_os_image = (struct dsp_image_seg *)(fw_entry->data); + dspload_image(codec, dsp_os_image, 0, 0, true, 0); + dsp_loaded = dspload_wait_loaded(codec); + + release_firmware(fw_entry);
- /* set mute-capable, 1db step, 32 steps, ofs 6 */ - caps = 0x80031f06; - snd_hda_override_amp_caps(codec, cfg->hp_pins[0], HDA_OUTPUT, caps); + if (dsp_loaded) + dspload_speakereq(codec); + + return dsp_loaded; }
-static int ca0132_build_controls(struct hda_codec *codec) +static void ca0132_download_dsp(struct hda_codec *codec) { struct ca0132_spec *spec = codec->spec; - struct auto_pin_cfg *cfg = &spec->autocfg; - int i, err;
- if (spec->multiout.num_dacs) { - err = add_speaker_switch(codec, spec->out_pins[0]); - if (err < 0) - return err; + spec->dsp_state = DSP_DOWNLOAD_INIT; + /* check if there is power cut-off to DSP */ + if (dspload_is_loaded(codec)) { + /* dsp is already loaded. */ + /*spec->dsp_state = DSP_DOWNLOADED;*/ }
- if (cfg->hp_outs) { - ca0132_fix_hp_caps(codec); - err = add_hp_switch(codec, cfg->hp_pins[0]); - if (err < 0) - return err; - err = add_hp_volume(codec, cfg->hp_pins[0]); - if (err < 0) - return err; + if (spec->dsp_state == DSP_DOWNLOAD_INIT) { + 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; }
- for (i = 0; i < spec->num_inputs; i++) { - const char *label = spec->input_labels[i]; + if (spec->dsp_state == DSP_DOWNLOADED) + ca0132_set_dsp_msr(codec, true); +}
- err = add_in_switch(codec, spec->adcs[i], label); - if (err < 0) - return err; - err = add_in_volume(codec, spec->adcs[i], label); - if (err < 0) - return err; - if (cfg->inputs[i].type == AUTO_PIN_MIC) { - /* add Mic-Boost */ - err = add_in_mono_volume(codec, spec->input_pins[i], - "Mic Boost", 1); - if (err < 0) - return err; - } - } +static void ca0132_process_dsp_response(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec;
- if (spec->dig_out) { - err = snd_hda_create_spdif_out_ctls(codec, spec->dig_out, - spec->dig_out); - if (err < 0) - return err; - err = add_out_volume(codec, spec->dig_out, "IEC958"); - if (err < 0) - return err; + CA0132_LOG("ca0132_process_dsp_response\n"); + if (spec->wait_scp) { + if (dspio_get_response_data(codec) >= 0) + spec->wait_scp = 0; }
- if (spec->dig_in) { - err = snd_hda_create_spdif_in_ctls(codec, spec->dig_in); - if (err < 0) - return err; - err = add_in_volume(codec, spec->dig_in, "IEC958"); - if (err < 0) - return err; - } - return 0; + dspio_clear_response_queue(codec); }
- -static void ca0132_set_ct_ext(struct hda_codec *codec, int enable) +static void ca0132_unsol_event(struct hda_codec *codec, unsigned int res) { - /* Set Creative extension */ - snd_printdd("SET CREATIVE EXTENSION\n"); - snd_hda_codec_write(codec, WIDGET_CHIP_CTRL, 0, - VENDOR_CHIPIO_CT_EXTENSIONS_ENABLE, - enable); - msleep(20); + CA0132_LOG("ca0132_unsol_event: 0x%x\n", res); + + + if (((res >> AC_UNSOL_RES_TAG_SHIFT) & 0x3f) == UNSOL_TAG_DSP) { + ca0132_process_dsp_response(codec); + } else { + res = snd_hda_jack_get_action(codec, + (res >> AC_UNSOL_RES_TAG_SHIFT) & 0x3f); + + CA0132_LOG("snd_hda_jack_get_action: 0x%x\n", res); + + switch (res) { + case UNSOL_TAG_HP: + ca0132_select_out(codec); + break; + case UNSOL_TAG_AMIC1: + ca0132_select_mic(codec); + break; + default: + break; + } + snd_hda_jack_report_sync(codec); + } }
+/* + * Verbs tables. + */
-static void ca0132_config(struct hda_codec *codec) -{ - struct ca0132_spec *spec = codec->spec; - struct auto_pin_cfg *cfg = &spec->autocfg; - - /* line-outs */ - cfg->line_outs = 1; - cfg->line_out_pins[0] = 0x0b; /* front */ - cfg->line_out_type = AUTO_PIN_LINE_OUT; - - spec->dacs[0] = 0x02; - spec->out_pins[0] = 0x0b; - spec->multiout.dac_nids = spec->dacs; - spec->multiout.num_dacs = 1; - spec->multiout.max_channels = 2; - - /* headphone */ - cfg->hp_outs = 1; - cfg->hp_pins[0] = 0x0f; - - spec->hp_dac = 0; - spec->multiout.hp_nid = 0; +/* Sends before DSP download. */ +static struct hda_verb ca0132_base_init_verbs[] = { + /*enable ct extension*/ + {0x15, VENDOR_CHIPIO_CT_EXTENSIONS_ENABLE, 0x1}, + /*enable DSP node unsol, needed for DSP download*/ + {0x16, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | UNSOL_TAG_DSP}, + {} +};
- /* inputs */ - cfg->num_inputs = 2; /* Mic-in and line-in */ - cfg->inputs[0].pin = 0x12; - cfg->inputs[0].type = AUTO_PIN_MIC; - cfg->inputs[1].pin = 0x11; - cfg->inputs[1].type = AUTO_PIN_LINE_IN; +/* Send at exit. */ +static struct hda_verb ca0132_base_exit_verbs[] = { + /*set afg to D3*/ + {0x01, AC_VERB_SET_POWER_STATE, 0x03}, + /*disable ct extension*/ + {0x15, VENDOR_CHIPIO_CT_EXTENSIONS_ENABLE, 0}, + {} +};
- /* Mic-in */ - spec->input_pins[0] = 0x12; - spec->input_labels[0] = "Mic-In"; - spec->adcs[0] = 0x07; +/* Other verbs tables. Sends after DSP download. */ +static struct hda_verb ca0132_init_verbs0[] = { + /* chip init verbs */ + {0x15, 0x70D, 0xF0}, + {0x15, 0x70E, 0xFE}, + {0x15, 0x707, 0x75}, + {0x15, 0x707, 0xD3}, + {0x15, 0x707, 0x09}, + {0x15, 0x707, 0x53}, + {0x15, 0x707, 0xD4}, + {0x15, 0x707, 0xEF}, + {0x15, 0x707, 0x75}, + {0x15, 0x707, 0xD3}, + {0x15, 0x707, 0x09}, + {0x15, 0x707, 0x02}, + {0x15, 0x707, 0x37}, + {0x15, 0x707, 0x78}, + {0x15, 0x53C, 0xCE}, + {0x15, 0x575, 0xC9}, + {0x15, 0x53D, 0xCE}, + {0x15, 0x5B7, 0xC9}, + {0x15, 0x70D, 0xE8}, + {0x15, 0x70E, 0xFE}, + {0x15, 0x707, 0x02}, + {0x15, 0x707, 0x68}, + {0x15, 0x707, 0x62}, + {0x15, 0x53A, 0xCE}, + {0x15, 0x546, 0xC9}, + {0x15, 0x53B, 0xCE}, + {0x15, 0x5E8, 0xC9}, + {0x15, 0x717, 0x0D}, + {0x15, 0x718, 0x20}, + {} +};
- /* Line-In */ - spec->input_pins[1] = 0x11; - spec->input_labels[1] = "Line-In"; - spec->adcs[1] = 0x08; - spec->num_inputs = 2; -} +static struct hda_verb ca0132_init_verbs1[] = { + {0x10, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | UNSOL_TAG_HP}, + {0x12, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | UNSOL_TAG_AMIC1}, + /* config EAPD */ + {0x0b, 0x78D, 0x00}, + /*{0x0b, AC_VERB_SET_EAPD_BTLENABLE, 0x02},*/ + /*{0x10, 0x78D, 0x02},*/ + /*{0x10, AC_VERB_SET_EAPD_BTLENABLE, 0x02},*/ + {} +};
static void ca0132_init_chip(struct hda_codec *codec) { struct ca0132_spec *spec = codec->spec; + int num_fx; + int i; + unsigned int on;
mutex_init(&spec->chipio_mutex); + + spec->cur_out_type = SPEAKER_OUT; + spec->cur_mic_type = DIGITAL_MIC; + spec->cur_mic_boost = 0; + + for (i = 0; i < VNODES_COUNT; i++) { + spec->vnode_lvol[i] = 0x5a; + spec->vnode_rvol[i] = 0x5a; + spec->vnode_lswitch[i] = 0; + spec->vnode_rswitch[i] = 0; + } + + /* + * Default states for effects are in ca0132_effects[]. + */ + num_fx = OUT_EFFECTS_COUNT + IN_EFFECTS_COUNT; + for (i = 0; i < num_fx; i++) { + on = (unsigned int)ca0132_effects[i].reqs[0]; + spec->effects_switch[i] = on ? 1 : 0; + } + + spec->voicefx_val = 0; + spec->effects_switch[PLAY_ENHANCEMENT - EFFECT_START_NID] = 1; + spec->effects_switch[CRYSTAL_VOICE - EFFECT_START_NID] = 0; + + #ifdef ENABLE_TUNING_CONTROLS + ca0132_init_tuning_defaults(codec); + #endif }
static void ca0132_exit_chip(struct hda_codec *codec) { /* put any chip cleanup stuffs here. */ + + if (dspload_is_loaded(codec)) + dsp_reset(codec); }
static int ca0132_init(struct hda_codec *codec) { struct ca0132_spec *spec = codec->spec; - struct auto_pin_cfg *cfg = &spec->autocfg; int i;
- for (i = 0; i < spec->multiout.num_dacs; i++) { - init_output(codec, spec->out_pins[i], - spec->multiout.dac_nids[i]); - } - init_output(codec, cfg->hp_pins[0], spec->hp_dac); - init_output(codec, cfg->dig_out_pins[0], spec->dig_out); + spec->dsp_state = DSP_DOWNLOAD_INIT; + spec->curr_chip_addx = (unsigned int)INVALID_CHIP_ADDRESS; + + snd_hda_power_up(codec); + + ca0132_init_flags(codec); + snd_hda_sequence_write(codec, spec->base_init_verbs); + ca0132_download_dsp(codec); + ca0132_refresh_widget_caps(codec); + 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]);
for (i = 0; i < spec->num_inputs; i++) init_input(codec, spec->input_pins[i], spec->adcs[i]);
- init_input(codec, cfg->dig_in_pin, spec->dig_in); + for (i = 0; i < spec->num_init_verbs; i++) + snd_hda_sequence_write(codec, spec->init_verbs[i]);
- ca0132_set_ct_ext(codec, 1); + ca0132_init_unsol(codec); + + ca0132_select_out(codec); + ca0132_select_mic(codec); + + snd_hda_jack_report_sync(codec); + + snd_hda_power_down(codec);
return 0; }
- static void ca0132_free(struct hda_codec *codec) { - ca0132_set_ct_ext(codec, 0); + struct ca0132_spec *spec = codec->spec; + + snd_hda_power_up(codec); + snd_hda_sequence_write(codec, spec->base_exit_verbs); ca0132_exit_chip(codec); + snd_hda_power_down(codec); kfree(codec->spec); }
+ static 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 = ca0132_unsol_event, };
+static void ca0132_config(struct hda_codec *codec) +{ + struct ca0132_spec *spec = codec->spec; + + spec->dacs[0] = 0x2; + spec->dacs[1] = 0x3; + spec->dacs[2] = 0x4; + + spec->multiout.dac_nids = spec->dacs; + spec->multiout.num_dacs = 3; + spec->multiout.max_channels = 2; + + spec->num_outputs = 2; + spec->out_pins[0] = 0x0b; /* speaker out */ + spec->out_pins[1] = 0x10; /* headphone out */ + spec->shared_out_nid = 0x2;
+ spec->num_inputs = 3; + spec->adcs[0] = 0x7; /* digital mic / analog mic1 */ + spec->adcs[1] = 0x8; /* analog mic2 */ + spec->adcs[2] = 0xa; /* what u hear */ + spec->shared_mic_nid = 0x7; + + spec->input_pins[0] = 0x12; + spec->input_pins[1] = 0x11; + spec->input_pins[2] = 0x13; +}
static int patch_ca0132(struct hda_codec *codec) { struct ca0132_spec *spec; + int err;
snd_printdd("patch_ca0132\n");
@@ -1061,27 +4464,41 @@ static int patch_ca0132(struct hda_codec *codec) return -ENOMEM; codec->spec = spec;
+ spec->num_mixers = 1; + spec->mixers[0] = ca0132_mixer; + + spec->base_init_verbs = ca0132_base_init_verbs; + spec->base_exit_verbs = ca0132_base_exit_verbs; + spec->init_verbs[0] = ca0132_init_verbs0; + spec->init_verbs[1] = ca0132_init_verbs1; + spec->num_init_verbs = 2; + ca0132_init_chip(codec);
ca0132_config(codec);
+ err = snd_hda_parse_pin_defcfg(codec, &spec->autocfg, NULL, 0); + if (err < 0) + return err; + codec->patch_ops = ca0132_patch_ops;
return 0; }
+ /* * patch entries */ static struct hda_codec_preset snd_hda_preset_ca0132[] = { - { .id = 0x11020011, .name = "CA0132", .patch = patch_ca0132 }, + { .id = 0x11020011, .name = "CA0132", .patch = patch_ca0132 }, {} /* terminator */ };
MODULE_ALIAS("snd-hda-codec-id:11020011");
MODULE_LICENSE("GPL"); -MODULE_DESCRIPTION("Creative CA0132, CA0132 HD-audio codec"); +MODULE_DESCRIPTION("Creative Sound Core3D codec");
static struct hda_codec_preset_list ca0132_list = { .preset = snd_hda_preset_ca0132,
From: Ian Minett ian_minett@creativelabs.com
in azx_pcm_trigger(). This change is to prevent deadlock when an interrupt occurs, caused by chip->reg_lock contention.
Signed-off-by: Ian Minett ian_minett@creativelabs.com
diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c index 796472d..0ccffde 100644 --- a/sound/pci/hda/hda_intel.c +++ b/sound/pci/hda/hda_intel.c @@ -551,6 +551,7 @@ enum { (AZX_DCAPS_NVIDIA_SNOOP | AZX_DCAPS_RIRB_DELAY | AZX_DCAPS_NO_MSI |\ AZX_DCAPS_ALIGN_BUFSIZE)
+/* quirks for Creative CTHDA */ #define AZX_DCAPS_PRESET_CTHDA \ (AZX_DCAPS_NO_MSI | AZX_DCAPS_POSFIX_LPIB | AZX_DCAPS_4K_BDLE_BOUNDARY)
@@ -1930,6 +1931,7 @@ static int azx_pcm_trigger(struct snd_pcm_substream *substream, int cmd) struct snd_pcm_substream *s; int rstart = 0, start, nsync = 0, sbits = 0; int nwait, timeout; + unsigned long flags;
switch (cmd) { case SNDRV_PCM_TRIGGER_START: @@ -1956,7 +1958,7 @@ static int azx_pcm_trigger(struct snd_pcm_substream *substream, int cmd) snd_pcm_trigger_done(s, substream); }
- spin_lock(&chip->reg_lock); + spin_lock_irqsave(&chip->reg_lock, flags); if (nsync > 1) { /* first, set SYNC bits of corresponding streams */ if (chip->driver_caps & AZX_DCAPS_OLD_SSYNC) @@ -1980,7 +1982,7 @@ static int azx_pcm_trigger(struct snd_pcm_substream *substream, int cmd) } azx_dev->running = start; } - spin_unlock(&chip->reg_lock); + spin_unlock_irqrestore(&chip->reg_lock, flags); if (start) { if (nsync == 1) return 0;
At Wed, 25 Jul 2012 11:01:19 -0700, Ian Minett wrote:
From: Ian Minett ian_minett@creativelabs.com
Hi, Thanks again for the feedback - we've reworked the patch accordingly, to remove floats, tidy the use of macros and add the MODULE_FIRMWARE() lines.
You are right - since it is a pretty big change introducing all the DSP functionality it isn't trivial to break it into multiple small patches. I've added more info on what has been changed below in the git commit message.
The reason we need to modify the trigger callback in hda_intel.c is that the DSP code is downloaded from the host (driver) to the CA0132 chip via the HD-link. This means that the trigger callback is called from the codec driver.
Well, this is the biggest problem. What the patch does is a layer violation. Such an operation must not be performed in the codec driver. I guess you know it very well when you look again how ugly hacks you need for setting up a fake PCM instance and so on.
One possible way would be to move these DSP loading code into hda_intel.c. But then it bloats up the generic controller driver.
Another possibility is to fork hda_intel.c and move the Creative's controller chip driver there (not meant the code in patch_ca0132.c). You can strip down many workarounds found in hda_intel.c (e.g. bdl_pos_adj, position_fix, probing errors, vga_switcheroo, etc) as a bonus. And the new controller driver can still use the same hda_codec.c and the rest as long as it uses the same structure.
I will submit the DSP firmware blob (ctefx.bin) as a separate patch against the alsa-firmware repository, for intended upstreaming to linux-firmware package. The CA0132 codec uses request_firmware() loader to find the DSP binary in the /lib/firmware directory (the firmware isn't required for building the CA0132 module). If this isn't the correct way to submit firmware binaries, we'd appreciate any info on what the correct method for this is.
Yes, this is the correct way.
But, still one concern is that people may very likely build the kernel without taking the firmware. Then suddenly it breaks just by the kernel update -- this shouldn't happen.
So, we'd need a kernel config, or at least a module parameter, to enable/disable the new DSP stuff. When it's off, the whole DSP isn't requested and it behaves just likes a standard HD-audio.
thanks,
Takashi
participants (2)
-
Ian Minett
-
Takashi Iwai