[alsa-devel] [PATCHv5 0/5] Motorola Droid 4 Audio Support
Hi,
This adds audio support to Motorola Droid 4. I dropped the regulator from the DT binding as requested by Mark.
Tested: - Playing via both DACs using Speaker, Earpiece, Headphone - Recording using internal Mic - Volume Controls
Known not to work: - 3.5mm detection support (requires closed source firmware, needs further investigation) - Modem / Bluetooth Audio (this needs something more sophisticated than the audio-graph-card driver. I will have a look once the basic support has been merged)
Changes since PATCHv4: * Replace verbose GPL text with SPDX header * Use "GPL v2" as MODULE_LICENSE * Drop "default MFD_CPCAP" for the codec driver * Use SOC_SINGLE instead of SOC_ENUM for Phase invert switches * Find DT node by node name instead of by compatible property - Drop Acked-by from Rob Herring * Add new patch for MFD driver to implement the mentioned DT change
Changes since PATCHv3: * Drop regulator from DT binding, request VAUDIO from global regulator namespace instead
Changes since PATCHv2: * Fix a whitespace issue * Fix const notes Takashi provided * Fix a DAPM route issue I accidently introduced in v2
Changes since PATCHv1: * Add patch from Tony exporting soc_dpcm_runtime_update * Integrate DT patch for vaudio initial mode * Split dt-binding from codec patch and add Ack from Rob * Fix CPCAP position in Kconfig/Makefile * Avoid "err +=" constructs * Simplify reset function * Drop cpcap_audio_read/write helpers * Do not use tertiary operator for mute register value * Update Input Mux logic * Switch from simple-audio-card to audio-graph-card
-- Sebastian
Sebastian Reichel (5): dt-bindings: mfd: motorola-cpcap: document audio-codec ASoC: codec: cpcap: new codec mfd: motorola-cpcap: Add audio-codec support ARM: dts: motorola-cpcap-mapphone: add audio-codec ARM: dts: omap4-droid4: add soundcard
.../devicetree/bindings/mfd/motorola-cpcap.txt | 42 + arch/arm/boot/dts/motorola-cpcap-mapphone.dtsi | 13 + arch/arm/boot/dts/omap4-droid4-xt894.dts | 78 + drivers/mfd/motorola-cpcap.c | 51 +- sound/soc/codecs/Kconfig | 4 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/cpcap.c | 1568 ++++++++++++++++++++ 7 files changed, 1757 insertions(+), 1 deletion(-) create mode 100644 sound/soc/codecs/cpcap.c
This adds the DT binding for the audio-codec sub-module found inside the Motorola CPCAP PMIC.
Signed-off-by: Sebastian Reichel sebastian.reichel@collabora.co.uk --- .../devicetree/bindings/mfd/motorola-cpcap.txt | 42 ++++++++++++++++++++++ 1 file changed, 42 insertions(+)
diff --git a/Documentation/devicetree/bindings/mfd/motorola-cpcap.txt b/Documentation/devicetree/bindings/mfd/motorola-cpcap.txt index 15bc885f9df4..82c3a7140660 100644 --- a/Documentation/devicetree/bindings/mfd/motorola-cpcap.txt +++ b/Documentation/devicetree/bindings/mfd/motorola-cpcap.txt @@ -12,6 +12,30 @@ Required properties: - spi-max-frequency : Typically set to 3000000 - spi-cs-high : SPI chip select direction
+Optional subnodes: + +The sub-functions of CPCAP get their own node with their own compatible values, +which are described in the following files: + +- Documentation/devicetree/bindings/power/supply/cpcap-battery.txt +- Documentation/devicetree/bindings/power/supply/cpcap-charger.txt +- Documentation/devicetree/bindings/regulator/cpcap-regulator.txt +- Documentation/devicetree/bindings/phy/phy-cpcap-usb.txt +- Documentation/devicetree/bindings/input/cpcap-pwrbutton.txt +- Documentation/devicetree/bindings/rtc/cpcap-rtc.txt +- Documentation/devicetree/bindings/leds/leds-cpcap.txt +- Documentation/devicetree/bindings/iio/adc/cpcap-adc.txt + +The only exception is the audio codec. Instead of a compatible value its +node must be named "audio-codec". + +Required properties for the audio-codec subnode: + +- #sound-dai-cells = <1>; + +The audio-codec provides two DAIs. The first one is connected to the +Stereo HiFi DAC and the second one is connected to the Voice DAC. + Example:
&mcspi1 { @@ -26,6 +50,24 @@ Example: #size-cells = <0>; spi-max-frequency = <3000000>; spi-cs-high; + + audio-codec { + #sound-dai-cells = <1>; + + /* HiFi */ + port@0 { + endpoint { + remote-endpoint = <&cpu_dai1>; + }; + }; + + /* Voice */ + port@1 { + endpoint { + remote-endpoint = <&cpu_dai2>; + }; + }; + }; }; };
On Fri, Feb 23, 2018 at 09:02:50PM +0100, Sebastian Reichel wrote:
This adds the DT binding for the audio-codec sub-module found inside the Motorola CPCAP PMIC.
Acked-by: Mark Brown broonie@kernel.org
On Fri, Feb 23, 2018 at 09:02:50PM +0100, Sebastian Reichel wrote:
This adds the DT binding for the audio-codec sub-module found inside the Motorola CPCAP PMIC.
Signed-off-by: Sebastian Reichel sebastian.reichel@collabora.co.uk
.../devicetree/bindings/mfd/motorola-cpcap.txt | 42 ++++++++++++++++++++++ 1 file changed, 42 insertions(+)
Reviewed-by: Rob Herring robh@kernel.org
On Fri, 23 Feb 2018, Sebastian Reichel wrote:
This adds the DT binding for the audio-codec sub-module found inside the Motorola CPCAP PMIC.
Signed-off-by: Sebastian Reichel sebastian.reichel@collabora.co.uk
.../devicetree/bindings/mfd/motorola-cpcap.txt | 42 ++++++++++++++++++++++ 1 file changed, 42 insertions(+)
diff --git a/Documentation/devicetree/bindings/mfd/motorola-cpcap.txt b/Documentation/devicetree/bindings/mfd/motorola-cpcap.txt index 15bc885f9df4..82c3a7140660 100644 --- a/Documentation/devicetree/bindings/mfd/motorola-cpcap.txt +++ b/Documentation/devicetree/bindings/mfd/motorola-cpcap.txt @@ -12,6 +12,30 @@ Required properties:
- spi-max-frequency : Typically set to 3000000
- spi-cs-high : SPI chip select direction
+Optional subnodes:
+The sub-functions of CPCAP get their own node with their own compatible values, +which are described in the following files:
+- Documentation/devicetree/bindings/power/supply/cpcap-battery.txt +- Documentation/devicetree/bindings/power/supply/cpcap-charger.txt +- Documentation/devicetree/bindings/regulator/cpcap-regulator.txt +- Documentation/devicetree/bindings/phy/phy-cpcap-usb.txt +- Documentation/devicetree/bindings/input/cpcap-pwrbutton.txt +- Documentation/devicetree/bindings/rtc/cpcap-rtc.txt +- Documentation/devicetree/bindings/leds/leds-cpcap.txt +- Documentation/devicetree/bindings/iio/adc/cpcap-adc.txt
Nit: Best to use relative path names here (hint: DT docs might not live in there kernel forever).
Other than that:
For my own reference: Acked-for-MFD-by: Lee Jones lee.jones@linaro.org
Motorola CPCAP is a PMIC with audio functionality, that can be found on Motorola Droid 4 and probably a few other phones from Motorola's Droid series.
The driver has been written from scratch using Motorola's Android driver, register dumps from running Android and datasheet for NXP MC13783UG (which is similar to Motorola CPCAP, but not the same).
The chip provides two audio interfaces, that can be muxed to two different audio codecs. One provides support for stereo output (named StDAC or HiFi), while the other only provides mono output (named Voice). Only the Voice codec provides a Capture interface.
Signed-off-by: Sebastian Reichel sebastian.reichel@collabora.co.uk --- sound/soc/codecs/Kconfig | 4 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/cpcap.c | 1568 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1574 insertions(+) create mode 100644 sound/soc/codecs/cpcap.c
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 2b331f7266ab..5bd94841feb6 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -408,6 +408,10 @@ config SND_SOC_ALC5632 config SND_SOC_BT_SCO tristate "Dummy BT SCO codec driver"
+config SND_SOC_CPCAP + tristate "Motorola CPCAP codec" + depends on MFD_CPCAP + config SND_SOC_CQ0093VC tristate
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index da1571336f1e..2aeee1ba034e 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -36,6 +36,7 @@ snd-soc-ak4671-objs := ak4671.o snd-soc-ak5386-objs := ak5386.o snd-soc-arizona-objs := arizona.o snd-soc-bt-sco-objs := bt-sco.o +snd-soc-cpcap-objs := cpcap.o snd-soc-cq93vc-objs := cq93vc.o snd-soc-cs35l32-objs := cs35l32.o snd-soc-cs35l33-objs := cs35l33.o @@ -282,6 +283,7 @@ obj-$(CONFIG_SND_SOC_ALC5632) += snd-soc-alc5632.o obj-$(CONFIG_SND_SOC_ARIZONA) += snd-soc-arizona.o obj-$(CONFIG_SND_SOC_BT_SCO) += snd-soc-bt-sco.o obj-$(CONFIG_SND_SOC_CQ0093VC) += snd-soc-cq93vc.o +obj-$(CONFIG_SND_SOC_CPCAP) += snd-soc-cpcap.o obj-$(CONFIG_SND_SOC_CS35L32) += snd-soc-cs35l32.o obj-$(CONFIG_SND_SOC_CS35L33) += snd-soc-cs35l33.o obj-$(CONFIG_SND_SOC_CS35L34) += snd-soc-cs35l34.o diff --git a/sound/soc/codecs/cpcap.c b/sound/soc/codecs/cpcap.c new file mode 100644 index 000000000000..aedb267d4581 --- /dev/null +++ b/sound/soc/codecs/cpcap.c @@ -0,0 +1,1568 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ALSA SoC CPCAP codec driver + * + * Copyright (C) 2017 - 2018 Sebastian Reichel sre@kernel.org + * + * Very loosely based on original driver from Motorola: + * Copyright (C) 2007 - 2009 Motorola, Inc. + */ + +#include <linux/module.h> +#include <linux/regmap.h> +#include <linux/platform_device.h> +#include <linux/mfd/motorola-cpcap.h> +#include <sound/core.h> +#include <sound/soc.h> +#include <sound/tlv.h> + +/* Register 513 CPCAP_REG_CC --- CODEC */ +#define CPCAP_BIT_CDC_CLK2 15 +#define CPCAP_BIT_CDC_CLK1 14 +#define CPCAP_BIT_CDC_CLK0 13 +#define CPCAP_BIT_CDC_SR3 12 +#define CPCAP_BIT_CDC_SR2 11 +#define CPCAP_BIT_CDC_SR1 10 +#define CPCAP_BIT_CDC_SR0 9 +#define CPCAP_BIT_CDC_CLOCK_TREE_RESET 8 +#define CPCAP_BIT_MIC2_CDC_EN 7 +#define CPCAP_BIT_CDC_EN_RX 6 +#define CPCAP_BIT_DF_RESET 5 +#define CPCAP_BIT_MIC1_CDC_EN 4 +#define CPCAP_BIT_AUDOHPF_1 3 +#define CPCAP_BIT_AUDOHPF_0 2 +#define CPCAP_BIT_AUDIHPF_1 1 +#define CPCAP_BIT_AUDIHPF_0 0 + +/* Register 514 CPCAP_REG_CDI --- CODEC Digital Audio Interface */ +#define CPCAP_BIT_CDC_PLL_SEL 15 +#define CPCAP_BIT_CLK_IN_SEL 13 +#define CPCAP_BIT_DIG_AUD_IN 12 +#define CPCAP_BIT_CDC_CLK_EN 11 +#define CPCAP_BIT_CDC_DIG_AUD_FS1 10 +#define CPCAP_BIT_CDC_DIG_AUD_FS0 9 +#define CPCAP_BIT_MIC2_TIMESLOT2 8 +#define CPCAP_BIT_MIC2_TIMESLOT1 7 +#define CPCAP_BIT_MIC2_TIMESLOT0 6 +#define CPCAP_BIT_MIC1_RX_TIMESLOT2 5 +#define CPCAP_BIT_MIC1_RX_TIMESLOT1 4 +#define CPCAP_BIT_MIC1_RX_TIMESLOT0 3 +#define CPCAP_BIT_FS_INV 2 +#define CPCAP_BIT_CLK_INV 1 +#define CPCAP_BIT_SMB_CDC 0 + +/* Register 515 CPCAP_REG_SDAC --- Stereo DAC */ +#define CPCAP_BIT_FSYNC_CLK_IN_COMMON 11 +#define CPCAP_BIT_SLAVE_PLL_CLK_INPUT 10 +#define CPCAP_BIT_ST_CLOCK_TREE_RESET 9 +#define CPCAP_BIT_DF_RESET_ST_DAC 8 +#define CPCAP_BIT_ST_SR3 7 +#define CPCAP_BIT_ST_SR2 6 +#define CPCAP_BIT_ST_SR1 5 +#define CPCAP_BIT_ST_SR0 4 +#define CPCAP_BIT_ST_DAC_CLK2 3 +#define CPCAP_BIT_ST_DAC_CLK1 2 +#define CPCAP_BIT_ST_DAC_CLK0 1 +#define CPCAP_BIT_ST_DAC_EN 0 + +/* Register 516 CPCAP_REG_SDACDI --- Stereo DAC Digital Audio Interface */ +#define CPCAP_BIT_ST_L_TIMESLOT2 13 +#define CPCAP_BIT_ST_L_TIMESLOT1 12 +#define CPCAP_BIT_ST_L_TIMESLOT0 11 +#define CPCAP_BIT_ST_R_TIMESLOT2 10 +#define CPCAP_BIT_ST_R_TIMESLOT1 9 +#define CPCAP_BIT_ST_R_TIMESLOT0 8 +#define CPCAP_BIT_ST_DAC_CLK_IN_SEL 7 +#define CPCAP_BIT_ST_FS_INV 6 +#define CPCAP_BIT_ST_CLK_INV 5 +#define CPCAP_BIT_ST_DIG_AUD_FS1 4 +#define CPCAP_BIT_ST_DIG_AUD_FS0 3 +#define CPCAP_BIT_DIG_AUD_IN_ST_DAC 2 +#define CPCAP_BIT_ST_CLK_EN 1 +#define CPCAP_BIT_SMB_ST_DAC 0 + +/* Register 517 CPCAP_REG_TXI --- TX Interface */ +#define CPCAP_BIT_PTT_TH 15 +#define CPCAP_BIT_PTT_CMP_EN 14 +#define CPCAP_BIT_HS_ID_TX 13 +#define CPCAP_BIT_MB_ON2 12 +#define CPCAP_BIT_MB_ON1L 11 +#define CPCAP_BIT_MB_ON1R 10 +#define CPCAP_BIT_RX_L_ENCODE 9 +#define CPCAP_BIT_RX_R_ENCODE 8 +#define CPCAP_BIT_MIC2_MUX 7 +#define CPCAP_BIT_MIC2_PGA_EN 6 +#define CPCAP_BIT_CDET_DIS 5 +#define CPCAP_BIT_EMU_MIC_MUX 4 +#define CPCAP_BIT_HS_MIC_MUX 3 +#define CPCAP_BIT_MIC1_MUX 2 +#define CPCAP_BIT_MIC1_PGA_EN 1 +#define CPCAP_BIT_DLM 0 + +/* Register 518 CPCAP_REG_TXMP --- Mic Gain */ +#define CPCAP_BIT_MB_BIAS_R1 11 +#define CPCAP_BIT_MB_BIAS_R0 10 +#define CPCAP_BIT_MIC2_GAIN_4 9 +#define CPCAP_BIT_MIC2_GAIN_3 8 +#define CPCAP_BIT_MIC2_GAIN_2 7 +#define CPCAP_BIT_MIC2_GAIN_1 6 +#define CPCAP_BIT_MIC2_GAIN_0 5 +#define CPCAP_BIT_MIC1_GAIN_4 4 +#define CPCAP_BIT_MIC1_GAIN_3 3 +#define CPCAP_BIT_MIC1_GAIN_2 2 +#define CPCAP_BIT_MIC1_GAIN_1 1 +#define CPCAP_BIT_MIC1_GAIN_0 0 + +/* Register 519 CPCAP_REG_RXOA --- RX Output Amplifier */ +#define CPCAP_BIT_UNUSED_519_15 15 +#define CPCAP_BIT_UNUSED_519_14 14 +#define CPCAP_BIT_UNUSED_519_13 13 +#define CPCAP_BIT_STDAC_LOW_PWR_DISABLE 12 +#define CPCAP_BIT_HS_LOW_PWR 11 +#define CPCAP_BIT_HS_ID_RX 10 +#define CPCAP_BIT_ST_HS_CP_EN 9 +#define CPCAP_BIT_EMU_SPKR_R_EN 8 +#define CPCAP_BIT_EMU_SPKR_L_EN 7 +#define CPCAP_BIT_HS_L_EN 6 +#define CPCAP_BIT_HS_R_EN 5 +#define CPCAP_BIT_A4_LINEOUT_L_EN 4 +#define CPCAP_BIT_A4_LINEOUT_R_EN 3 +#define CPCAP_BIT_A2_LDSP_L_EN 2 +#define CPCAP_BIT_A2_LDSP_R_EN 1 +#define CPCAP_BIT_A1_EAR_EN 0 + +/* Register 520 CPCAP_REG_RXVC --- RX Volume Control */ +#define CPCAP_BIT_VOL_EXT3 15 +#define CPCAP_BIT_VOL_EXT2 14 +#define CPCAP_BIT_VOL_EXT1 13 +#define CPCAP_BIT_VOL_EXT0 12 +#define CPCAP_BIT_VOL_DAC3 11 +#define CPCAP_BIT_VOL_DAC2 10 +#define CPCAP_BIT_VOL_DAC1 9 +#define CPCAP_BIT_VOL_DAC0 8 +#define CPCAP_BIT_VOL_DAC_LSB_1dB1 7 +#define CPCAP_BIT_VOL_DAC_LSB_1dB0 6 +#define CPCAP_BIT_VOL_CDC3 5 +#define CPCAP_BIT_VOL_CDC2 4 +#define CPCAP_BIT_VOL_CDC1 3 +#define CPCAP_BIT_VOL_CDC0 2 +#define CPCAP_BIT_VOL_CDC_LSB_1dB1 1 +#define CPCAP_BIT_VOL_CDC_LSB_1dB0 0 + +/* Register 521 CPCAP_REG_RXCOA --- Codec to Output Amp Switches */ +#define CPCAP_BIT_PGA_CDC_EN 10 +#define CPCAP_BIT_CDC_SW 9 +#define CPCAP_BIT_PGA_OUTR_USBDP_CDC_SW 8 +#define CPCAP_BIT_PGA_OUTL_USBDN_CDC_SW 7 +#define CPCAP_BIT_ALEFT_HS_CDC_SW 6 +#define CPCAP_BIT_ARIGHT_HS_CDC_SW 5 +#define CPCAP_BIT_A4_LINEOUT_L_CDC_SW 4 +#define CPCAP_BIT_A4_LINEOUT_R_CDC_SW 3 +#define CPCAP_BIT_A2_LDSP_L_CDC_SW 2 +#define CPCAP_BIT_A2_LDSP_R_CDC_SW 1 +#define CPCAP_BIT_A1_EAR_CDC_SW 0 + +/* Register 522 CPCAP_REG_RXSDOA --- RX Stereo DAC to Output Amp Switches */ +#define CPCAP_BIT_PGA_DAC_EN 12 +#define CPCAP_BIT_ST_DAC_SW 11 +#define CPCAP_BIT_MONO_DAC1 10 +#define CPCAP_BIT_MONO_DAC0 9 +#define CPCAP_BIT_PGA_OUTR_USBDP_DAC_SW 8 +#define CPCAP_BIT_PGA_OUTL_USBDN_DAC_SW 7 +#define CPCAP_BIT_ALEFT_HS_DAC_SW 6 +#define CPCAP_BIT_ARIGHT_HS_DAC_SW 5 +#define CPCAP_BIT_A4_LINEOUT_L_DAC_SW 4 +#define CPCAP_BIT_A4_LINEOUT_R_DAC_SW 3 +#define CPCAP_BIT_A2_LDSP_L_DAC_SW 2 +#define CPCAP_BIT_A2_LDSP_R_DAC_SW 1 +#define CPCAP_BIT_A1_EAR_DAC_SW 0 + +/* Register 523 CPCAP_REG_RXEPOA --- RX External PGA to Output Amp Switches */ +#define CPCAP_BIT_PGA_EXT_L_EN 14 +#define CPCAP_BIT_PGA_EXT_R_EN 13 +#define CPCAP_BIT_PGA_IN_L_SW 12 +#define CPCAP_BIT_PGA_IN_R_SW 11 +#define CPCAP_BIT_MONO_EXT1 10 +#define CPCAP_BIT_MONO_EXT0 9 +#define CPCAP_BIT_PGA_OUTR_USBDP_EXT_SW 8 +#define CPCAP_BIT_PGA_OUTL_USBDN_EXT_SW 7 +#define CPCAP_BIT_ALEFT_HS_EXT_SW 6 +#define CPCAP_BIT_ARIGHT_HS_EXT_SW 5 +#define CPCAP_BIT_A4_LINEOUT_L_EXT_SW 4 +#define CPCAP_BIT_A4_LINEOUT_R_EXT_SW 3 +#define CPCAP_BIT_A2_LDSP_L_EXT_SW 2 +#define CPCAP_BIT_A2_LDSP_R_EXT_SW 1 +#define CPCAP_BIT_A1_EAR_EXT_SW 0 + +/* Register 525 CPCAP_REG_A2LA --- SPK Amplifier and Clock Config for Headset */ +#define CPCAP_BIT_NCP_CLK_SYNC 7 +#define CPCAP_BIT_A2_CLK_SYNC 6 +#define CPCAP_BIT_A2_FREE_RUN 5 +#define CPCAP_BIT_A2_CLK2 4 +#define CPCAP_BIT_A2_CLK1 3 +#define CPCAP_BIT_A2_CLK0 2 +#define CPCAP_BIT_A2_CLK_IN 1 +#define CPCAP_BIT_A2_CONFIG 0 + +#define SLEEP_ACTIVATE_POWER 2 +#define CLOCK_TREE_RESET_TIME 1 + +/* constants for ST delay workaround */ +#define STM_STDAC_ACTIVATE_RAMP_TIME 1 +#define STM_STDAC_EN_TEST_PRE 0x090C +#define STM_STDAC_EN_TEST_POST 0x0000 +#define STM_STDAC_EN_ST_TEST1_PRE 0x2400 +#define STM_STDAC_EN_ST_TEST1_POST 0x0400 + +struct cpcap_reg_info { + u16 reg; + u16 mask; + u16 val; +}; + +static const struct cpcap_reg_info cpcap_default_regs[] = { + { CPCAP_REG_CC, 0xFFFF, 0x0000 }, + { CPCAP_REG_CC, 0xFFFF, 0x0000 }, + { CPCAP_REG_CDI, 0xBFFF, 0x0000 }, + { CPCAP_REG_SDAC, 0x0FFF, 0x0000 }, + { CPCAP_REG_SDACDI, 0x3FFF, 0x0000 }, + { CPCAP_REG_TXI, 0x0FDF, 0x0000 }, + { CPCAP_REG_TXMP, 0x0FFF, 0x0400 }, + { CPCAP_REG_RXOA, 0x01FF, 0x0000 }, + { CPCAP_REG_RXVC, 0xFF3C, 0x0000 }, + { CPCAP_REG_RXCOA, 0x07FF, 0x0000 }, + { CPCAP_REG_RXSDOA, 0x1FFF, 0x0000 }, + { CPCAP_REG_RXEPOA, 0x7FFF, 0x0000 }, + { CPCAP_REG_A2LA, BIT(CPCAP_BIT_A2_FREE_RUN), + BIT(CPCAP_BIT_A2_FREE_RUN) }, +}; + +enum cpcap_dai { + CPCAP_DAI_HIFI, + CPCAP_DAI_VOICE, +}; + +struct cpcap_audio { + struct snd_soc_codec *codec; + struct regmap *regmap; + + u16 vendor; + + int codec_clk_id; + int codec_freq; + int codec_format; +}; + +static int cpcap_st_workaround(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); + struct cpcap_audio *cpcap = snd_soc_codec_get_drvdata(codec); + int err = 0; + + /* Only CPCAP from ST requires workaround */ + if (cpcap->vendor != CPCAP_VENDOR_ST) + return 0; + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + err = regmap_write(cpcap->regmap, CPCAP_REG_TEST, + STM_STDAC_EN_TEST_PRE); + if (err) + return err; + err = regmap_write(cpcap->regmap, CPCAP_REG_ST_TEST1, + STM_STDAC_EN_ST_TEST1_PRE); + break; + case SND_SOC_DAPM_POST_PMU: + msleep(STM_STDAC_ACTIVATE_RAMP_TIME); + + err = regmap_write(cpcap->regmap, CPCAP_REG_ST_TEST1, + STM_STDAC_EN_ST_TEST1_POST); + if (err) + return err; + err = regmap_write(cpcap->regmap, CPCAP_REG_TEST, + STM_STDAC_EN_TEST_POST); + break; + default: + break; + } + + return err; +} + +/* Capture Gain Control: 0dB to 31dB in 1dB steps */ +static const DECLARE_TLV_DB_SCALE(mic_gain_tlv, 0, 100, 0); + +/* Playback Gain Control: -33dB to 12dB in 3dB steps */ +static const DECLARE_TLV_DB_SCALE(vol_tlv, -3300, 300, 0); + +static const struct snd_kcontrol_new cpcap_snd_controls[] = { + /* Playback Gain */ + SOC_SINGLE_TLV("HiFi Playback Volume", + CPCAP_REG_RXVC, CPCAP_BIT_VOL_DAC0, 0xF, 0, vol_tlv), + SOC_SINGLE_TLV("Voice Playback Volume", + CPCAP_REG_RXVC, CPCAP_BIT_VOL_CDC0, 0xF, 0, vol_tlv), + SOC_SINGLE_TLV("Ext Playback Volume", + CPCAP_REG_RXVC, CPCAP_BIT_VOL_EXT0, 0xF, 0, vol_tlv), + + /* Capture Gain */ + SOC_SINGLE_TLV("Mic1 Capture Volume", + CPCAP_REG_TXMP, CPCAP_BIT_MIC1_GAIN_0, 0x1F, 0, mic_gain_tlv), + SOC_SINGLE_TLV("Mic2 Capture Volume", + CPCAP_REG_TXMP, CPCAP_BIT_MIC2_GAIN_0, 0x1F, 0, mic_gain_tlv), + + /* Phase Invert */ + SOC_SINGLE("Hifi Left Phase Invert Switch", + CPCAP_REG_RXSDOA, CPCAP_BIT_MONO_DAC0, 1, 0), + SOC_SINGLE("Ext Left Phase Invert Switch", + CPCAP_REG_RXEPOA, CPCAP_BIT_MONO_EXT0, 1, 0), +}; + +static const char * const cpcap_out_mux_texts[] = { + "Off", "Voice", "HiFi", "Ext" +}; + +static const char * const cpcap_in_right_mux_texts[] = { + "Off", "Mic 1", "Headset Mic", "EMU Mic", "Ext Right" +}; + +static const char * const cpcap_in_left_mux_texts[] = { + "Off", "Mic 2", "Ext Left" +}; + +/* + * input muxes use unusual register layout, so that we need to use custom + * getter/setter methods + */ +static SOC_ENUM_SINGLE_EXT_DECL(cpcap_input_left_mux_enum, + cpcap_in_left_mux_texts); +static SOC_ENUM_SINGLE_EXT_DECL(cpcap_input_right_mux_enum, + cpcap_in_right_mux_texts); + +/* + * mux uses same bit in CPCAP_REG_RXCOA, CPCAP_REG_RXSDOA & CPCAP_REG_RXEPOA; + * even though the register layout makes it look like a mixer, this is a mux. + * Enabling multiple inputs will result in no audio being forwarded. + */ +static SOC_ENUM_SINGLE_DECL(cpcap_earpiece_mux_enum, 0, 0, cpcap_out_mux_texts); +static SOC_ENUM_SINGLE_DECL(cpcap_spkr_r_mux_enum, 0, 1, cpcap_out_mux_texts); +static SOC_ENUM_SINGLE_DECL(cpcap_spkr_l_mux_enum, 0, 2, cpcap_out_mux_texts); +static SOC_ENUM_SINGLE_DECL(cpcap_line_r_mux_enum, 0, 3, cpcap_out_mux_texts); +static SOC_ENUM_SINGLE_DECL(cpcap_line_l_mux_enum, 0, 4, cpcap_out_mux_texts); +static SOC_ENUM_SINGLE_DECL(cpcap_hs_r_mux_enum, 0, 5, cpcap_out_mux_texts); +static SOC_ENUM_SINGLE_DECL(cpcap_hs_l_mux_enum, 0, 6, cpcap_out_mux_texts); +static SOC_ENUM_SINGLE_DECL(cpcap_emu_l_mux_enum, 0, 7, cpcap_out_mux_texts); +static SOC_ENUM_SINGLE_DECL(cpcap_emu_r_mux_enum, 0, 8, cpcap_out_mux_texts); + +static int cpcap_output_mux_get_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_soc_dapm_kcontrol_codec(kcontrol); + struct cpcap_audio *cpcap = snd_soc_codec_get_drvdata(codec); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int shift = e->shift_l; + int reg_voice, reg_hifi, reg_ext, status; + int err; + + err = regmap_read(cpcap->regmap, CPCAP_REG_RXCOA, ®_voice); + if (err) + return err; + err = regmap_read(cpcap->regmap, CPCAP_REG_RXSDOA, ®_hifi); + if (err) + return err; + err = regmap_read(cpcap->regmap, CPCAP_REG_RXEPOA, ®_ext); + if (err) + return err; + + reg_voice = (reg_voice >> shift) & 1; + reg_hifi = (reg_hifi >> shift) & 1; + reg_ext = (reg_ext >> shift) & 1; + status = reg_ext << 2 | reg_hifi << 1 | reg_voice; + + switch (status) { + case 0x04: + ucontrol->value.enumerated.item[0] = 3; + break; + case 0x02: + ucontrol->value.enumerated.item[0] = 2; + break; + case 0x01: + ucontrol->value.enumerated.item[0] = 1; + break; + default: + ucontrol->value.enumerated.item[0] = 0; + break; + } + + return 0; +} + +static int cpcap_output_mux_put_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_soc_dapm_kcontrol_codec(kcontrol); + struct cpcap_audio *cpcap = snd_soc_codec_get_drvdata(codec); + struct snd_soc_dapm_context *dapm = + snd_soc_dapm_kcontrol_dapm(kcontrol); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int muxval = ucontrol->value.enumerated.item[0]; + unsigned int mask = BIT(e->shift_l); + u16 reg_voice = 0x00, reg_hifi = 0x00, reg_ext = 0x00; + int err; + + switch (muxval) { + case 1: + reg_voice = mask; + break; + case 2: + reg_hifi = mask; + break; + case 3: + reg_ext = mask; + break; + default: + break; + } + + err = regmap_update_bits(cpcap->regmap, CPCAP_REG_RXCOA, + mask, reg_voice); + if (err) + return err; + err = regmap_update_bits(cpcap->regmap, CPCAP_REG_RXSDOA, + mask, reg_hifi); + if (err) + return err; + err = regmap_update_bits(cpcap->regmap, CPCAP_REG_RXEPOA, + mask, reg_ext); + if (err) + return err; + + snd_soc_dapm_mux_update_power(dapm, kcontrol, muxval, e, NULL); + + return 0; +} + +static int cpcap_input_right_mux_get_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_soc_dapm_kcontrol_codec(kcontrol); + struct cpcap_audio *cpcap = snd_soc_codec_get_drvdata(codec); + int regval, mask; + int err; + + err = regmap_read(cpcap->regmap, CPCAP_REG_TXI, ®val); + if (err) + return err; + + mask = 0; + mask |= BIT(CPCAP_BIT_MIC1_MUX); + mask |= BIT(CPCAP_BIT_HS_MIC_MUX); + mask |= BIT(CPCAP_BIT_EMU_MIC_MUX); + mask |= BIT(CPCAP_BIT_RX_R_ENCODE); + + switch (regval & mask) { + case BIT(CPCAP_BIT_RX_R_ENCODE): + ucontrol->value.enumerated.item[0] = 4; + break; + case BIT(CPCAP_BIT_EMU_MIC_MUX): + ucontrol->value.enumerated.item[0] = 3; + break; + case BIT(CPCAP_BIT_HS_MIC_MUX): + ucontrol->value.enumerated.item[0] = 2; + break; + case BIT(CPCAP_BIT_MIC1_MUX): + ucontrol->value.enumerated.item[0] = 1; + break; + default: + ucontrol->value.enumerated.item[0] = 0; + break; + } + + return 0; +} + +static int cpcap_input_right_mux_put_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_soc_dapm_kcontrol_codec(kcontrol); + struct cpcap_audio *cpcap = snd_soc_codec_get_drvdata(codec); + struct snd_soc_dapm_context *dapm = + snd_soc_dapm_kcontrol_dapm(kcontrol); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int muxval = ucontrol->value.enumerated.item[0]; + int regval = 0, mask; + int err; + + mask = 0; + mask |= BIT(CPCAP_BIT_MIC1_MUX); + mask |= BIT(CPCAP_BIT_HS_MIC_MUX); + mask |= BIT(CPCAP_BIT_EMU_MIC_MUX); + mask |= BIT(CPCAP_BIT_RX_R_ENCODE); + + switch (muxval) { + case 1: + regval = BIT(CPCAP_BIT_MIC1_MUX); + break; + case 2: + regval = BIT(CPCAP_BIT_HS_MIC_MUX); + break; + case 3: + regval = BIT(CPCAP_BIT_EMU_MIC_MUX); + break; + case 4: + regval = BIT(CPCAP_BIT_RX_R_ENCODE); + break; + default: + break; + } + + err = regmap_update_bits(cpcap->regmap, CPCAP_REG_TXI, + mask, regval); + if (err) + return err; + + snd_soc_dapm_mux_update_power(dapm, kcontrol, muxval, e, NULL); + + return 0; +} + +static int cpcap_input_left_mux_get_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_soc_dapm_kcontrol_codec(kcontrol); + struct cpcap_audio *cpcap = snd_soc_codec_get_drvdata(codec); + int regval, mask; + int err; + + err = regmap_read(cpcap->regmap, CPCAP_REG_TXI, ®val); + if (err) + return err; + + mask = 0; + mask |= BIT(CPCAP_BIT_MIC2_MUX); + mask |= BIT(CPCAP_BIT_RX_L_ENCODE); + + switch (regval & mask) { + case BIT(CPCAP_BIT_RX_L_ENCODE): + ucontrol->value.enumerated.item[0] = 2; + break; + case BIT(CPCAP_BIT_MIC2_MUX): + ucontrol->value.enumerated.item[0] = 1; + break; + default: + ucontrol->value.enumerated.item[0] = 0; + break; + } + + return 0; +} + +static int cpcap_input_left_mux_put_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_soc_dapm_kcontrol_codec(kcontrol); + struct cpcap_audio *cpcap = snd_soc_codec_get_drvdata(codec); + struct snd_soc_dapm_context *dapm = + snd_soc_dapm_kcontrol_dapm(kcontrol); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int muxval = ucontrol->value.enumerated.item[0]; + int regval = 0, mask; + int err; + + mask = 0; + mask |= BIT(CPCAP_BIT_MIC2_MUX); + mask |= BIT(CPCAP_BIT_RX_L_ENCODE); + + switch (muxval) { + case 1: + regval = BIT(CPCAP_BIT_MIC2_MUX); + break; + case 2: + regval = BIT(CPCAP_BIT_RX_L_ENCODE); + break; + default: + break; + } + + err = regmap_update_bits(cpcap->regmap, CPCAP_REG_TXI, + mask, regval); + if (err) + return err; + + snd_soc_dapm_mux_update_power(dapm, kcontrol, muxval, e, NULL); + + return 0; +} + +static const struct snd_kcontrol_new cpcap_input_left_mux = + SOC_DAPM_ENUM_EXT("Input Left", cpcap_input_left_mux_enum, + cpcap_input_left_mux_get_enum, + cpcap_input_left_mux_put_enum); +static const struct snd_kcontrol_new cpcap_input_right_mux = + SOC_DAPM_ENUM_EXT("Input Right", cpcap_input_right_mux_enum, + cpcap_input_right_mux_get_enum, + cpcap_input_right_mux_put_enum); +static const struct snd_kcontrol_new cpcap_emu_left_mux = + SOC_DAPM_ENUM_EXT("EMU Left", cpcap_emu_l_mux_enum, + cpcap_output_mux_get_enum, cpcap_output_mux_put_enum); +static const struct snd_kcontrol_new cpcap_emu_right_mux = + SOC_DAPM_ENUM_EXT("EMU Right", cpcap_emu_r_mux_enum, + cpcap_output_mux_get_enum, cpcap_output_mux_put_enum); +static const struct snd_kcontrol_new cpcap_hs_left_mux = + SOC_DAPM_ENUM_EXT("Headset Left", cpcap_hs_l_mux_enum, + cpcap_output_mux_get_enum, cpcap_output_mux_put_enum); +static const struct snd_kcontrol_new cpcap_hs_right_mux = + SOC_DAPM_ENUM_EXT("Headset Right", cpcap_hs_r_mux_enum, + cpcap_output_mux_get_enum, cpcap_output_mux_put_enum); +static const struct snd_kcontrol_new cpcap_line_left_mux = + SOC_DAPM_ENUM_EXT("Line Left", cpcap_line_l_mux_enum, + cpcap_output_mux_get_enum, cpcap_output_mux_put_enum); +static const struct snd_kcontrol_new cpcap_line_right_mux = + SOC_DAPM_ENUM_EXT("Line Right", cpcap_line_r_mux_enum, + cpcap_output_mux_get_enum, cpcap_output_mux_put_enum); +static const struct snd_kcontrol_new cpcap_speaker_left_mux = + SOC_DAPM_ENUM_EXT("Speaker Left", cpcap_spkr_l_mux_enum, + cpcap_output_mux_get_enum, cpcap_output_mux_put_enum); +static const struct snd_kcontrol_new cpcap_speaker_right_mux = + SOC_DAPM_ENUM_EXT("Speaker Right", cpcap_spkr_r_mux_enum, + cpcap_output_mux_get_enum, cpcap_output_mux_put_enum); +static const struct snd_kcontrol_new cpcap_earpiece_mux = + SOC_DAPM_ENUM_EXT("Earpiece", cpcap_earpiece_mux_enum, + cpcap_output_mux_get_enum, cpcap_output_mux_put_enum); + +static const struct snd_kcontrol_new cpcap_hifi_mono_mixer_controls[] = { + SOC_DAPM_SINGLE("HiFi Mono Playback Switch", + CPCAP_REG_RXSDOA, CPCAP_BIT_MONO_DAC1, 1, 0), +}; +static const struct snd_kcontrol_new cpcap_ext_mono_mixer_controls[] = { + SOC_DAPM_SINGLE("Ext Mono Playback Switch", + CPCAP_REG_RXEPOA, CPCAP_BIT_MONO_EXT0, 1, 0), +}; + +static const struct snd_kcontrol_new cpcap_extr_mute_control = + SOC_DAPM_SINGLE("Switch", + CPCAP_REG_RXEPOA, CPCAP_BIT_PGA_IN_R_SW, 1, 0); +static const struct snd_kcontrol_new cpcap_extl_mute_control = + SOC_DAPM_SINGLE("Switch", + CPCAP_REG_RXEPOA, CPCAP_BIT_PGA_IN_L_SW, 1, 0); + +static const struct snd_kcontrol_new cpcap_voice_loopback = + SOC_DAPM_SINGLE("Switch", + CPCAP_REG_TXI, CPCAP_BIT_DLM, 1, 0); + +static const struct snd_soc_dapm_widget cpcap_dapm_widgets[] = { + /* DAIs */ + SND_SOC_DAPM_AIF_IN("HiFi RX", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("Voice RX", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("Voice TX", NULL, 0, SND_SOC_NOPM, 0, 0), + + /* Power Supply */ + SND_SOC_DAPM_REGULATOR_SUPPLY("VAUDIO", SLEEP_ACTIVATE_POWER, 0), + + /* Highpass Filters */ + SND_SOC_DAPM_REG(snd_soc_dapm_pga, "Highpass Filter RX", + CPCAP_REG_CC, CPCAP_BIT_AUDIHPF_0, 0x3, 0x3, 0x0), + SND_SOC_DAPM_REG(snd_soc_dapm_pga, "Highpass Filter TX", + CPCAP_REG_CC, CPCAP_BIT_AUDOHPF_0, 0x3, 0x3, 0x0), + + /* Clocks */ + SND_SOC_DAPM_SUPPLY("HiFi DAI Clock", + CPCAP_REG_SDACDI, CPCAP_BIT_ST_CLK_EN, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("Voice DAI Clock", + CPCAP_REG_CDI, CPCAP_BIT_CDC_CLK_EN, 0, NULL, 0), + + /* Microphone Bias */ + SND_SOC_DAPM_SUPPLY("MIC1R Bias", + CPCAP_REG_TXI, CPCAP_BIT_MB_ON1R, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("MIC1L Bias", + CPCAP_REG_TXI, CPCAP_BIT_MB_ON1L, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("MIC2 Bias", + CPCAP_REG_TXI, CPCAP_BIT_MB_ON2, 0, NULL, 0), + + /* Inputs */ + SND_SOC_DAPM_INPUT("MICR"), + SND_SOC_DAPM_INPUT("HSMIC"), + SND_SOC_DAPM_INPUT("EMUMIC"), + SND_SOC_DAPM_INPUT("MICL"), + SND_SOC_DAPM_INPUT("EXTR"), + SND_SOC_DAPM_INPUT("EXTL"), + + /* Capture Route */ + SND_SOC_DAPM_MUX("Right Capture Route", + SND_SOC_NOPM, 0, 0, &cpcap_input_right_mux), + SND_SOC_DAPM_MUX("Left Capture Route", + SND_SOC_NOPM, 0, 0, &cpcap_input_left_mux), + + /* Capture PGAs */ + SND_SOC_DAPM_PGA("Microphone 1 PGA", + CPCAP_REG_TXI, CPCAP_BIT_MIC1_PGA_EN, 0, NULL, 0), + SND_SOC_DAPM_PGA("Microphone 2 PGA", + CPCAP_REG_TXI, CPCAP_BIT_MIC2_PGA_EN, 0, NULL, 0), + + /* ADC */ + SND_SOC_DAPM_ADC("ADC Right", NULL, + CPCAP_REG_CC, CPCAP_BIT_MIC1_CDC_EN, 0), + SND_SOC_DAPM_ADC("ADC Left", NULL, + CPCAP_REG_CC, CPCAP_BIT_MIC2_CDC_EN, 0), + + /* DAC */ + SND_SOC_DAPM_DAC_E("DAC HiFi", NULL, + CPCAP_REG_SDAC, CPCAP_BIT_ST_DAC_EN, 0, + cpcap_st_workaround, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_DAC_E("DAC Voice", NULL, + CPCAP_REG_CC, CPCAP_BIT_CDC_EN_RX, 0, + cpcap_st_workaround, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU), + + /* Playback PGA */ + SND_SOC_DAPM_PGA("HiFi PGA", + CPCAP_REG_RXSDOA, CPCAP_BIT_PGA_DAC_EN, 0, NULL, 0), + SND_SOC_DAPM_PGA("Voice PGA", + CPCAP_REG_RXCOA, CPCAP_BIT_PGA_CDC_EN, 0, NULL, 0), + SND_SOC_DAPM_PGA_E("Ext Right PGA", + CPCAP_REG_RXEPOA, CPCAP_BIT_PGA_EXT_R_EN, 0, + NULL, 0, + cpcap_st_workaround, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_PGA_E("Ext Left PGA", + CPCAP_REG_RXEPOA, CPCAP_BIT_PGA_EXT_L_EN, 0, + NULL, 0, + cpcap_st_workaround, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU), + + /* Playback Switch */ + SND_SOC_DAPM_SWITCH("Ext Right Enable", SND_SOC_NOPM, 0, 0, + &cpcap_extr_mute_control), + SND_SOC_DAPM_SWITCH("Ext Left Enable", SND_SOC_NOPM, 0, 0, + &cpcap_extl_mute_control), + + /* Loopback Switch */ + SND_SOC_DAPM_SWITCH("Voice Loopback", SND_SOC_NOPM, 0, 0, + &cpcap_voice_loopback), + + /* Mono Mixer */ + SOC_MIXER_ARRAY("HiFi Mono Left Mixer", SND_SOC_NOPM, 0, 0, + cpcap_hifi_mono_mixer_controls), + SOC_MIXER_ARRAY("HiFi Mono Right Mixer", SND_SOC_NOPM, 0, 0, + cpcap_hifi_mono_mixer_controls), + SOC_MIXER_ARRAY("Ext Mono Left Mixer", SND_SOC_NOPM, 0, 0, + cpcap_ext_mono_mixer_controls), + SOC_MIXER_ARRAY("Ext Mono Right Mixer", SND_SOC_NOPM, 0, 0, + cpcap_ext_mono_mixer_controls), + + /* Output Routes */ + SND_SOC_DAPM_MUX("Earpiece Playback Route", SND_SOC_NOPM, 0, 0, + &cpcap_earpiece_mux), + SND_SOC_DAPM_MUX("Speaker Right Playback Route", SND_SOC_NOPM, 0, 0, + &cpcap_speaker_right_mux), + SND_SOC_DAPM_MUX("Speaker Left Playback Route", SND_SOC_NOPM, 0, 0, + &cpcap_speaker_left_mux), + SND_SOC_DAPM_MUX("Lineout Right Playback Route", SND_SOC_NOPM, 0, 0, + &cpcap_line_right_mux), + SND_SOC_DAPM_MUX("Lineout Left Playback Route", SND_SOC_NOPM, 0, 0, + &cpcap_line_left_mux), + SND_SOC_DAPM_MUX("Headset Right Playback Route", SND_SOC_NOPM, 0, 0, + &cpcap_hs_right_mux), + SND_SOC_DAPM_MUX("Headset Left Playback Route", SND_SOC_NOPM, 0, 0, + &cpcap_hs_left_mux), + SND_SOC_DAPM_MUX("EMU Right Playback Route", SND_SOC_NOPM, 0, 0, + &cpcap_emu_right_mux), + SND_SOC_DAPM_MUX("EMU Left Playback Route", SND_SOC_NOPM, 0, 0, + &cpcap_emu_left_mux), + + /* Output Amplifier */ + SND_SOC_DAPM_PGA("Earpiece PGA", + CPCAP_REG_RXOA, CPCAP_BIT_A1_EAR_EN, 0, NULL, 0), + SND_SOC_DAPM_PGA("Speaker Right PGA", + CPCAP_REG_RXOA, CPCAP_BIT_A2_LDSP_R_EN, 0, NULL, 0), + SND_SOC_DAPM_PGA("Speaker Left PGA", + CPCAP_REG_RXOA, CPCAP_BIT_A2_LDSP_L_EN, 0, NULL, 0), + SND_SOC_DAPM_PGA("Lineout Right PGA", + CPCAP_REG_RXOA, CPCAP_BIT_A4_LINEOUT_R_EN, 0, NULL, 0), + SND_SOC_DAPM_PGA("Lineout Left PGA", + CPCAP_REG_RXOA, CPCAP_BIT_A4_LINEOUT_L_EN, 0, NULL, 0), + SND_SOC_DAPM_PGA("Headset Right PGA", + CPCAP_REG_RXOA, CPCAP_BIT_HS_R_EN, 0, NULL, 0), + SND_SOC_DAPM_PGA("Headset Left PGA", + CPCAP_REG_RXOA, CPCAP_BIT_HS_L_EN, 0, NULL, 0), + SND_SOC_DAPM_PGA("EMU Right PGA", + CPCAP_REG_RXOA, CPCAP_BIT_EMU_SPKR_R_EN, 0, NULL, 0), + SND_SOC_DAPM_PGA("EMU Left PGA", + CPCAP_REG_RXOA, CPCAP_BIT_EMU_SPKR_L_EN, 0, NULL, 0), + + /* Headet Charge Pump */ + SND_SOC_DAPM_SUPPLY("Headset Charge Pump", + CPCAP_REG_RXOA, CPCAP_BIT_ST_HS_CP_EN, 0, NULL, 0), + + /* Outputs */ + SND_SOC_DAPM_OUTPUT("EP"), + SND_SOC_DAPM_OUTPUT("SPKR"), + SND_SOC_DAPM_OUTPUT("SPKL"), + SND_SOC_DAPM_OUTPUT("LINER"), + SND_SOC_DAPM_OUTPUT("LINEL"), + SND_SOC_DAPM_OUTPUT("HSR"), + SND_SOC_DAPM_OUTPUT("HSL"), + SND_SOC_DAPM_OUTPUT("EMUR"), + SND_SOC_DAPM_OUTPUT("EMUL"), +}; + +static const struct snd_soc_dapm_route intercon[] = { + /* Power Supply */ + {"HiFi PGA", NULL, "VAUDIO"}, + {"Voice PGA", NULL, "VAUDIO"}, + {"Ext Right PGA", NULL, "VAUDIO"}, + {"Ext Left PGA", NULL, "VAUDIO"}, + {"Microphone 1 PGA", NULL, "VAUDIO"}, + {"Microphone 2 PGA", NULL, "VAUDIO"}, + + /* Stream -> AIF */ + {"HiFi RX", NULL, "HiFi Playback"}, + {"Voice RX", NULL, "Voice Playback"}, + {"Voice Capture", NULL, "Voice TX"}, + + /* AIF clocks */ + {"HiFi RX", NULL, "HiFi DAI Clock"}, + {"Voice RX", NULL, "Voice DAI Clock"}, + {"Voice TX", NULL, "Voice DAI Clock"}, + + /* Digital Loopback */ + {"Voice Loopback", "Switch", "Voice TX"}, + {"Voice RX", NULL, "Voice Loopback"}, + + /* Highpass Filters */ + {"Highpass Filter RX", NULL, "Voice RX"}, + {"Voice TX", NULL, "Highpass Filter TX"}, + + /* AIF -> DAC mapping */ + {"DAC HiFi", NULL, "HiFi RX"}, + {"DAC Voice", NULL, "Highpass Filter RX"}, + + /* DAC -> PGA */ + {"HiFi PGA", NULL, "DAC HiFi"}, + {"Voice PGA", NULL, "DAC Voice"}, + + /* Ext Input -> PGA */ + {"Ext Right PGA", NULL, "EXTR"}, + {"Ext Left PGA", NULL, "EXTL"}, + + /* Ext PGA -> Ext Playback Switch */ + {"Ext Right Enable", "Switch", "Ext Right PGA"}, + {"Ext Left Enable", "Switch", "Ext Left PGA"}, + + /* HiFi PGA -> Mono Mixer */ + {"HiFi Mono Left Mixer", NULL, "HiFi PGA"}, + {"HiFi Mono Left Mixer", "HiFi Mono Playback Switch", "HiFi PGA"}, + {"HiFi Mono Right Mixer", NULL, "HiFi PGA"}, + {"HiFi Mono Right Mixer", "HiFi Mono Playback Switch", "HiFi PGA"}, + + /* Ext Playback Switch -> Ext Mono Mixer */ + {"Ext Mono Right Mixer", NULL, "Ext Right Enable"}, + {"Ext Mono Right Mixer", "Ext Mono Playback Switch", "Ext Left Enable"}, + {"Ext Mono Left Mixer", NULL, "Ext Left Enable"}, + {"Ext Mono Left Mixer", "Ext Mono Playback Switch", "Ext Right Enable"}, + + /* HiFi Mono Mixer -> Output Route */ + {"Earpiece Playback Route", "HiFi", "HiFi Mono Right Mixer"}, + {"Speaker Right Playback Route", "HiFi", "HiFi Mono Right Mixer"}, + {"Speaker Left Playback Route", "HiFi", "HiFi Mono Left Mixer"}, + {"Lineout Right Playback Route", "HiFi", "HiFi Mono Right Mixer"}, + {"Lineout Left Playback Route", "HiFi", "HiFi Mono Left Mixer"}, + {"Headset Right Playback Route", "HiFi", "HiFi Mono Right Mixer"}, + {"Headset Left Playback Route", "HiFi", "HiFi Mono Left Mixer"}, + {"EMU Right Playback Route", "HiFi", "HiFi Mono Right Mixer"}, + {"EMU Left Playback Route", "HiFi", "HiFi Mono Left Mixer"}, + + /* Voice PGA -> Output Route */ + {"Earpiece Playback Route", "Voice", "Voice PGA"}, + {"Speaker Right Playback Route", "Voice", "Voice PGA"}, + {"Speaker Left Playback Route", "Voice", "Voice PGA"}, + {"Lineout Right Playback Route", "Voice", "Voice PGA"}, + {"Lineout Left Playback Route", "Voice", "Voice PGA"}, + {"Headset Right Playback Route", "Voice", "Voice PGA"}, + {"Headset Left Playback Route", "Voice", "Voice PGA"}, + {"EMU Right Playback Route", "Voice", "Voice PGA"}, + {"EMU Left Playback Route", "Voice", "Voice PGA"}, + + /* Ext Mono Mixer -> Output Route */ + {"Earpiece Playback Route", "Ext", "Ext Mono Right Mixer"}, + {"Speaker Right Playback Route", "Ext", "Ext Mono Right Mixer"}, + {"Speaker Left Playback Route", "Ext", "Ext Mono Left Mixer"}, + {"Lineout Right Playback Route", "Ext", "Ext Mono Right Mixer"}, + {"Lineout Left Playback Route", "Ext", "Ext Mono Left Mixer"}, + {"Headset Right Playback Route", "Ext", "Ext Mono Right Mixer"}, + {"Headset Left Playback Route", "Ext", "Ext Mono Left Mixer"}, + {"EMU Right Playback Route", "Ext", "Ext Mono Right Mixer"}, + {"EMU Left Playback Route", "Ext", "Ext Mono Left Mixer"}, + + /* Output Route -> Output Amplifier */ + {"Earpiece PGA", NULL, "Earpiece Playback Route"}, + {"Speaker Right PGA", NULL, "Speaker Right Playback Route"}, + {"Speaker Left PGA", NULL, "Speaker Left Playback Route"}, + {"Lineout Right PGA", NULL, "Lineout Right Playback Route"}, + {"Lineout Left PGA", NULL, "Lineout Left Playback Route"}, + {"Headset Right PGA", NULL, "Headset Right Playback Route"}, + {"Headset Left PGA", NULL, "Headset Left Playback Route"}, + {"EMU Right PGA", NULL, "EMU Right Playback Route"}, + {"EMU Left PGA", NULL, "EMU Left Playback Route"}, + + /* Output Amplifier -> Output */ + {"EP", NULL, "Earpiece PGA"}, + {"SPKR", NULL, "Speaker Right PGA"}, + {"SPKL", NULL, "Speaker Left PGA"}, + {"LINER", NULL, "Lineout Right PGA"}, + {"LINEL", NULL, "Lineout Left PGA"}, + {"HSR", NULL, "Headset Right PGA"}, + {"HSL", NULL, "Headset Left PGA"}, + {"EMUR", NULL, "EMU Right PGA"}, + {"EMUL", NULL, "EMU Left PGA"}, + + /* Headset Charge Pump -> Headset */ + {"HSR", NULL, "Headset Charge Pump"}, + {"HSL", NULL, "Headset Charge Pump"}, + + /* Mic -> Mic Route */ + {"Right Capture Route", "Mic 1", "MICR"}, + {"Right Capture Route", "Headset Mic", "HSMIC"}, + {"Right Capture Route", "EMU Mic", "EMUMIC"}, + {"Right Capture Route", "Ext Right", "EXTR"}, + {"Left Capture Route", "Mic 2", "MICL"}, + {"Left Capture Route", "Ext Left", "EXTL"}, + + /* Input Route -> Microphone PGA */ + {"Microphone 1 PGA", NULL, "Right Capture Route"}, + {"Microphone 2 PGA", NULL, "Left Capture Route"}, + + /* Microphone PGA -> ADC */ + {"ADC Right", NULL, "Microphone 1 PGA"}, + {"ADC Left", NULL, "Microphone 2 PGA"}, + + /* ADC -> Stream */ + {"Highpass Filter TX", NULL, "ADC Right"}, + {"Highpass Filter TX", NULL, "ADC Left"}, + + /* Mic Bias */ + {"MICL", NULL, "MIC1L Bias"}, + {"MICR", NULL, "MIC1R Bias"}, +}; + +static int cpcap_set_sysclk(struct cpcap_audio *cpcap, enum cpcap_dai dai, + int clk_id, int freq) +{ + u16 clkfreqreg, clkfreqshift; + u16 clkfreqmask, clkfreqval; + u16 clkidreg, clkidshift; + u16 mask, val; + int err; + + switch (dai) { + case CPCAP_DAI_HIFI: + clkfreqreg = CPCAP_REG_SDAC; + clkfreqshift = CPCAP_BIT_ST_DAC_CLK0; + clkidreg = CPCAP_REG_SDACDI; + clkidshift = CPCAP_BIT_ST_DAC_CLK_IN_SEL; + break; + case CPCAP_DAI_VOICE: + clkfreqreg = CPCAP_REG_CC; + clkfreqshift = CPCAP_BIT_CDC_CLK0; + clkidreg = CPCAP_REG_CDI; + clkidshift = CPCAP_BIT_CLK_IN_SEL; + break; + default: + dev_err(cpcap->codec->dev, "invalid DAI: %d", dai); + return -EINVAL; + } + + /* setup clk id */ + if (clk_id < 0 || clk_id > 1) { + dev_err(cpcap->codec->dev, "invalid clk id %d", clk_id); + return -EINVAL; + } + err = regmap_update_bits(cpcap->regmap, clkidreg, BIT(clkidshift), + clk_id ? BIT(clkidshift) : 0); + if (err) + return err; + + /* enable PLL for Voice DAI */ + if (dai == CPCAP_DAI_VOICE) { + mask = BIT(CPCAP_BIT_CDC_PLL_SEL); + val = BIT(CPCAP_BIT_CDC_PLL_SEL); + err = regmap_update_bits(cpcap->regmap, CPCAP_REG_CDI, + mask, val); + if (err) + return err; + } + + /* setup frequency */ + clkfreqmask = 0x7 << clkfreqshift; + switch (freq) { + case 15360000: + clkfreqval = 0x01 << clkfreqshift; + break; + case 16800000: + clkfreqval = 0x02 << clkfreqshift; + break; + case 19200000: + clkfreqval = 0x03 << clkfreqshift; + break; + case 26000000: + clkfreqval = 0x04 << clkfreqshift; + break; + case 33600000: + clkfreqval = 0x05 << clkfreqshift; + break; + case 38400000: + clkfreqval = 0x06 << clkfreqshift; + break; + default: + dev_err(cpcap->codec->dev, "unsupported freq %u", freq); + return -EINVAL; + } + + err = regmap_update_bits(cpcap->regmap, clkfreqreg, + clkfreqmask, clkfreqval); + if (err) + return err; + + if (dai == CPCAP_DAI_VOICE) { + cpcap->codec_clk_id = clk_id; + cpcap->codec_freq = freq; + } + + return 0; +} + +static int cpcap_set_samprate(struct cpcap_audio *cpcap, enum cpcap_dai dai, + int samplerate) +{ + struct snd_soc_codec *codec = cpcap->codec; + u16 sampreg, sampmask, sampshift, sampval, sampreset; + int err, sampreadval; + + switch (dai) { + case CPCAP_DAI_HIFI: + sampreg = CPCAP_REG_SDAC; + sampshift = CPCAP_BIT_ST_SR0; + sampreset = BIT(CPCAP_BIT_DF_RESET_ST_DAC) | + BIT(CPCAP_BIT_ST_CLOCK_TREE_RESET); + break; + case CPCAP_DAI_VOICE: + sampreg = CPCAP_REG_CC; + sampshift = CPCAP_BIT_CDC_SR0; + sampreset = BIT(CPCAP_BIT_DF_RESET) | + BIT(CPCAP_BIT_CDC_CLOCK_TREE_RESET); + break; + default: + dev_err(codec->dev, "invalid DAI: %d", dai); + return -EINVAL; + } + + sampmask = 0xF << sampshift | sampreset; + switch (samplerate) { + case 48000: + sampval = 0x8 << sampshift; + break; + case 44100: + sampval = 0x7 << sampshift; + break; + case 32000: + sampval = 0x6 << sampshift; + break; + case 24000: + sampval = 0x5 << sampshift; + break; + case 22050: + sampval = 0x4 << sampshift; + break; + case 16000: + sampval = 0x3 << sampshift; + break; + case 12000: + sampval = 0x2 << sampshift; + break; + case 11025: + sampval = 0x1 << sampshift; + break; + case 8000: + sampval = 0x0 << sampshift; + break; + default: + dev_err(codec->dev, "unsupported samplerate %d", samplerate); + return -EINVAL; + } + err = regmap_update_bits(cpcap->regmap, sampreg, + sampmask, sampval | sampreset); + if (err) + return err; + + /* Wait for clock tree reset to complete */ + mdelay(CLOCK_TREE_RESET_TIME); + + err = regmap_read(cpcap->regmap, sampreg, &sampreadval); + if (err) + return err; + + if (sampreadval & sampreset) { + dev_err(codec->dev, "reset self-clear failed: %04x", + sampreadval); + return -EIO; + } + + return 0; +} + +static int cpcap_hifi_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct cpcap_audio *cpcap = snd_soc_codec_get_drvdata(codec); + int rate = params_rate(params); + + dev_dbg(codec->dev, "HiFi setup HW params: rate=%d", rate); + return cpcap_set_samprate(cpcap, CPCAP_DAI_HIFI, rate); +} + +static int cpcap_hifi_set_dai_sysclk(struct snd_soc_dai *codec_dai, int clk_id, + unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct cpcap_audio *cpcap = snd_soc_codec_get_drvdata(codec); + struct device *dev = codec->dev; + + dev_dbg(dev, "HiFi setup sysclk: clk_id=%u, freq=%u", clk_id, freq); + return cpcap_set_sysclk(cpcap, CPCAP_DAI_HIFI, clk_id, freq); +} + +static int cpcap_hifi_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct cpcap_audio *cpcap = snd_soc_codec_get_drvdata(codec); + struct device *dev = codec->dev; + static const u16 reg = CPCAP_REG_SDACDI; + static const u16 mask = + BIT(CPCAP_BIT_SMB_ST_DAC) | + BIT(CPCAP_BIT_ST_CLK_INV) | + BIT(CPCAP_BIT_ST_FS_INV) | + BIT(CPCAP_BIT_ST_DIG_AUD_FS0) | + BIT(CPCAP_BIT_ST_DIG_AUD_FS1) | + BIT(CPCAP_BIT_ST_L_TIMESLOT0) | + BIT(CPCAP_BIT_ST_L_TIMESLOT1) | + BIT(CPCAP_BIT_ST_L_TIMESLOT2) | + BIT(CPCAP_BIT_ST_R_TIMESLOT0) | + BIT(CPCAP_BIT_ST_R_TIMESLOT1) | + BIT(CPCAP_BIT_ST_R_TIMESLOT2); + u16 val = 0x0000; + + dev_dbg(dev, "HiFi setup dai format (%08x)", fmt); + + /* + * "HiFi Playback" should always be configured as + * SND_SOC_DAIFMT_CBM_CFM - codec clk & frm master + * SND_SOC_DAIFMT_I2S - I2S mode + */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + val &= ~BIT(CPCAP_BIT_SMB_ST_DAC); + break; + default: + dev_err(dev, "HiFi dai fmt failed: CPCAP should be master"); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_IB_IF: + val |= BIT(CPCAP_BIT_ST_FS_INV); + val |= BIT(CPCAP_BIT_ST_CLK_INV); + break; + case SND_SOC_DAIFMT_IB_NF: + val &= ~BIT(CPCAP_BIT_ST_FS_INV); + val |= BIT(CPCAP_BIT_ST_CLK_INV); + break; + case SND_SOC_DAIFMT_NB_IF: + val |= BIT(CPCAP_BIT_ST_FS_INV); + val &= ~BIT(CPCAP_BIT_ST_CLK_INV); + break; + case SND_SOC_DAIFMT_NB_NF: + val &= ~BIT(CPCAP_BIT_ST_FS_INV); + val &= ~BIT(CPCAP_BIT_ST_CLK_INV); + break; + default: + dev_err(dev, "HiFi dai fmt failed: unsupported clock invert mode"); + return -EINVAL; + } + + if (val & BIT(CPCAP_BIT_ST_CLK_INV)) + val &= ~BIT(CPCAP_BIT_ST_CLK_INV); + else + val |= BIT(CPCAP_BIT_ST_CLK_INV); + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + val |= BIT(CPCAP_BIT_ST_DIG_AUD_FS0); + val |= BIT(CPCAP_BIT_ST_DIG_AUD_FS1); + break; + default: + /* 01 - 4 slots network mode */ + val |= BIT(CPCAP_BIT_ST_DIG_AUD_FS0); + val &= ~BIT(CPCAP_BIT_ST_DIG_AUD_FS1); + /* L on slot 1 */ + val |= BIT(CPCAP_BIT_ST_L_TIMESLOT0); + break; + } + + dev_dbg(dev, "HiFi dai format: val=%04x", val); + return regmap_update_bits(cpcap->regmap, reg, mask, val); +} + +static int cpcap_hifi_set_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + struct cpcap_audio *cpcap = snd_soc_codec_get_drvdata(codec); + static const u16 reg = CPCAP_REG_RXSDOA; + static const u16 mask = BIT(CPCAP_BIT_ST_DAC_SW); + u16 val; + + if (mute) + val = 0; + else + val = BIT(CPCAP_BIT_ST_DAC_SW); + + dev_dbg(codec->dev, "HiFi mute: %d", mute); + return regmap_update_bits(cpcap->regmap, reg, mask, val); +} + +static const struct snd_soc_dai_ops cpcap_dai_hifi_ops = { + .hw_params = cpcap_hifi_hw_params, + .set_sysclk = cpcap_hifi_set_dai_sysclk, + .set_fmt = cpcap_hifi_set_dai_fmt, + .digital_mute = cpcap_hifi_set_mute, +}; + +static int cpcap_voice_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct device *dev = codec->dev; + struct cpcap_audio *cpcap = snd_soc_codec_get_drvdata(codec); + static const u16 reg_cdi = CPCAP_REG_CDI; + int rate = params_rate(params); + int channels = params_channels(params); + int direction = substream->stream; + u16 val, mask; + int err; + + dev_dbg(dev, "Voice setup HW params: rate=%d, direction=%d, chan=%d", + rate, direction, channels); + + err = cpcap_set_samprate(cpcap, CPCAP_DAI_VOICE, rate); + if (err) + return err; + + if (direction == SNDRV_PCM_STREAM_CAPTURE) { + mask = 0x0000; + mask |= CPCAP_BIT_MIC1_RX_TIMESLOT0; + mask |= CPCAP_BIT_MIC1_RX_TIMESLOT1; + mask |= CPCAP_BIT_MIC1_RX_TIMESLOT2; + mask |= CPCAP_BIT_MIC2_TIMESLOT0; + mask |= CPCAP_BIT_MIC2_TIMESLOT1; + mask |= CPCAP_BIT_MIC2_TIMESLOT2; + val = 0x0000; + if (channels >= 2) + val = BIT(CPCAP_BIT_MIC1_RX_TIMESLOT0); + err = regmap_update_bits(cpcap->regmap, reg_cdi, mask, val); + if (err) + return err; + } + + return 0; +} + +static int cpcap_voice_set_dai_sysclk(struct snd_soc_dai *codec_dai, int clk_id, + unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct cpcap_audio *cpcap = snd_soc_codec_get_drvdata(codec); + + dev_dbg(codec->dev, "Voice setup sysclk: clk_id=%u, freq=%u", + clk_id, freq); + return cpcap_set_sysclk(cpcap, CPCAP_DAI_VOICE, clk_id, freq); +} + +static int cpcap_voice_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct cpcap_audio *cpcap = snd_soc_codec_get_drvdata(codec); + static const u16 mask = BIT(CPCAP_BIT_SMB_CDC) | + BIT(CPCAP_BIT_CLK_INV) | + BIT(CPCAP_BIT_FS_INV) | + BIT(CPCAP_BIT_CDC_DIG_AUD_FS0) | + BIT(CPCAP_BIT_CDC_DIG_AUD_FS1); + u16 val = 0x0000; + int err; + + dev_dbg(codec->dev, "Voice setup dai format (%08x)", fmt); + + /* + * "Voice Playback" and "Voice Capture" should always be + * configured as SND_SOC_DAIFMT_CBM_CFM - codec clk & frm + * master + */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + val &= ~BIT(CPCAP_BIT_SMB_CDC); + break; + default: + dev_err(codec->dev, "Voice dai fmt failed: CPCAP should be the master"); + val &= ~BIT(CPCAP_BIT_SMB_CDC); + break; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_IB_IF: + val |= BIT(CPCAP_BIT_CLK_INV); + val |= BIT(CPCAP_BIT_FS_INV); + break; + case SND_SOC_DAIFMT_IB_NF: + val |= BIT(CPCAP_BIT_CLK_INV); + val &= ~BIT(CPCAP_BIT_FS_INV); + break; + case SND_SOC_DAIFMT_NB_IF: + val &= ~BIT(CPCAP_BIT_CLK_INV); + val |= BIT(CPCAP_BIT_FS_INV); + break; + case SND_SOC_DAIFMT_NB_NF: + val &= ~BIT(CPCAP_BIT_CLK_INV); + val &= ~BIT(CPCAP_BIT_FS_INV); + break; + default: + dev_err(codec->dev, "Voice dai fmt failed: unsupported clock invert mode"); + break; + } + + if (val & BIT(CPCAP_BIT_CLK_INV)) + val &= ~BIT(CPCAP_BIT_CLK_INV); + else + val |= BIT(CPCAP_BIT_CLK_INV); + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + /* 11 - true I2S mode */ + val |= BIT(CPCAP_BIT_CDC_DIG_AUD_FS0); + val |= BIT(CPCAP_BIT_CDC_DIG_AUD_FS1); + break; + default: + /* 4 timeslots network mode */ + val |= BIT(CPCAP_BIT_CDC_DIG_AUD_FS0); + val &= ~BIT(CPCAP_BIT_CDC_DIG_AUD_FS1); + break; + } + + dev_dbg(codec->dev, "Voice dai format: val=%04x", val); + err = regmap_update_bits(cpcap->regmap, CPCAP_REG_CDI, mask, val); + if (err) + return err; + + cpcap->codec_format = val; + return 0; +} + +static int cpcap_voice_set_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + struct cpcap_audio *cpcap = snd_soc_codec_get_drvdata(codec); + static const u16 reg = CPCAP_REG_RXCOA; + static const u16 mask = BIT(CPCAP_BIT_CDC_SW); + u16 val; + + if (mute) + val = 0; + else + val = BIT(CPCAP_BIT_CDC_SW); + + dev_dbg(codec->dev, "Voice mute: %d", mute); + return regmap_update_bits(cpcap->regmap, reg, mask, val); +}; + +static const struct snd_soc_dai_ops cpcap_dai_voice_ops = { + .hw_params = cpcap_voice_hw_params, + .set_sysclk = cpcap_voice_set_dai_sysclk, + .set_fmt = cpcap_voice_set_dai_fmt, + .digital_mute = cpcap_voice_set_mute, +}; + +static struct snd_soc_dai_driver cpcap_dai[] = { +{ + .id = 0, + .name = "cpcap-hifi", + .playback = { + .stream_name = "HiFi Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FORMAT_S24_LE, + }, + .ops = &cpcap_dai_hifi_ops, +}, +{ + .id = 1, + .name = "cpcap-voice", + .playback = { + .stream_name = "Voice Playback", + .channels_min = 1, + .channels_max = 1, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "Voice Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &cpcap_dai_voice_ops, +}, +}; + +static int cpcap_dai_mux(struct cpcap_audio *cpcap, bool swap_dai_configuration) +{ + u16 hifi_val, voice_val; + u16 hifi_mask = BIT(CPCAP_BIT_DIG_AUD_IN_ST_DAC); + u16 voice_mask = BIT(CPCAP_BIT_DIG_AUD_IN); + int err; + + + + if (!swap_dai_configuration) { + /* Codec on DAI0, HiFi on DAI1 */ + voice_val = 0; + hifi_val = hifi_mask; + } else { + /* Codec on DAI1, HiFi on DAI0 */ + voice_val = voice_mask; + hifi_val = 0; + } + + err = regmap_update_bits(cpcap->regmap, CPCAP_REG_CDI, + voice_mask, voice_val); + if (err) + return err; + + err = regmap_update_bits(cpcap->regmap, CPCAP_REG_SDACDI, + hifi_mask, hifi_val); + if (err) + return err; + + return 0; +} + +static int cpcap_audio_reset(struct snd_soc_codec *codec, + bool swap_dai_configuration) +{ + struct cpcap_audio *cpcap = snd_soc_codec_get_drvdata(codec); + int i, err = 0; + + dev_dbg(codec->dev, "init audio codec"); + + for (i = 0; i < ARRAY_SIZE(cpcap_default_regs); i++) { + err = regmap_update_bits(cpcap->regmap, + cpcap_default_regs[i].reg, + cpcap_default_regs[i].mask, + cpcap_default_regs[i].val); + if (err) + return err; + } + + /* setup default settings */ + err = cpcap_dai_mux(cpcap, swap_dai_configuration); + if (err) + return err; + + err = cpcap_set_sysclk(cpcap, CPCAP_DAI_HIFI, 0, 26000000); + if (err) + return err; + err = cpcap_set_sysclk(cpcap, CPCAP_DAI_VOICE, 0, 26000000); + if (err) + return err; + + err = cpcap_set_samprate(cpcap, CPCAP_DAI_HIFI, 48000); + if (err) + return err; + + err = cpcap_set_samprate(cpcap, CPCAP_DAI_VOICE, 48000); + if (err) + return err; + + return 0; +} + +static int cpcap_soc_probe(struct snd_soc_codec *codec) +{ + struct cpcap_audio *cpcap; + int err; + + cpcap = devm_kzalloc(codec->dev, sizeof(*cpcap), GFP_KERNEL); + if (!cpcap) + return -ENOMEM; + snd_soc_codec_set_drvdata(codec, cpcap); + cpcap->codec = codec; + + cpcap->regmap = dev_get_regmap(codec->dev->parent, NULL); + if (!cpcap->regmap) + return -ENODEV; + snd_soc_codec_init_regmap(codec, cpcap->regmap); + + err = cpcap_get_vendor(codec->dev, cpcap->regmap, &cpcap->vendor); + if (err) + return err; + + return cpcap_audio_reset(codec, false); +} + +static struct snd_soc_codec_driver soc_codec_dev_cpcap = { + .probe = cpcap_soc_probe, + + .component_driver = { + .controls = cpcap_snd_controls, + .num_controls = ARRAY_SIZE(cpcap_snd_controls), + .dapm_widgets = cpcap_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(cpcap_dapm_widgets), + .dapm_routes = intercon, + .num_dapm_routes = ARRAY_SIZE(intercon), + }, +}; + +static int cpcap_codec_probe(struct platform_device *pdev) +{ + struct device_node *codec_node = + of_get_child_by_name(pdev->dev.parent->of_node, "audio-codec"); + + pdev->dev.of_node = codec_node; + + return snd_soc_register_codec(&pdev->dev, &soc_codec_dev_cpcap, + cpcap_dai, ARRAY_SIZE(cpcap_dai)); +} + +static int cpcap_codec_remove(struct platform_device *pdev) +{ + snd_soc_unregister_codec(&pdev->dev); + return 0; +} + +static struct platform_driver cpcap_codec_driver = { + .probe = cpcap_codec_probe, + .remove = cpcap_codec_remove, + .driver = { + .name = "cpcap-codec", + }, +}; +module_platform_driver(cpcap_codec_driver); + +MODULE_ALIAS("platform:cpcap-codec"); +MODULE_DESCRIPTION("ASoC CPCAP codec driver"); +MODULE_AUTHOR("Sebastian Reichel"); +MODULE_LICENSE("GPL v2");
Hi Sebastian,
I love your patch! Perhaps something to improve:
[auto build test WARNING on robh/for-next] [also build test WARNING on v4.16-rc2 next-20180223] [if your patch is applied to the wrong git tree, please drop us a note to help improve the system]
url: https://github.com/0day-ci/linux/commits/Sebastian-Reichel/Motorola-Droid-4-... base: https://git.kernel.org/pub/scm/linux/kernel/git/robh/linux.git for-next reproduce: # apt-get install sparse make ARCH=x86_64 allmodconfig make C=1 CF=-D__CHECK_ENDIAN__
sparse warnings: (new ones prefixed by >>)
sound/soc/codecs/cpcap.c:1406:54: sparse: restricted snd_pcm_format_t degrades to integer
vim +1406 sound/soc/codecs/cpcap.c
1396 1397 static struct snd_soc_dai_driver cpcap_dai[] = { 1398 { 1399 .id = 0, 1400 .name = "cpcap-hifi", 1401 .playback = { 1402 .stream_name = "HiFi Playback", 1403 .channels_min = 2, 1404 .channels_max = 2, 1405 .rates = SNDRV_PCM_RATE_8000_48000,
1406 .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FORMAT_S24_LE,
1407 }, 1408 .ops = &cpcap_dai_hifi_ops, 1409 }, 1410 { 1411 .id = 1, 1412 .name = "cpcap-voice", 1413 .playback = { 1414 .stream_name = "Voice Playback", 1415 .channels_min = 1, 1416 .channels_max = 1, 1417 .rates = SNDRV_PCM_RATE_8000_48000, 1418 .formats = SNDRV_PCM_FMTBIT_S16_LE, 1419 }, 1420 .capture = { 1421 .stream_name = "Voice Capture", 1422 .channels_min = 1, 1423 .channels_max = 2, 1424 .rates = SNDRV_PCM_RATE_8000_48000, 1425 .formats = SNDRV_PCM_FMTBIT_S16_LE, 1426 }, 1427 .ops = &cpcap_dai_voice_ops, 1428 }, 1429 }; 1430
--- 0-DAY kernel test infrastructure Open Source Technology Center https://lists.01.org/pipermail/kbuild-all Intel Corporation
On Fri, Feb 23, 2018 at 09:02:51PM +0100, Sebastian Reichel wrote:
- switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
- case SND_SOC_DAIFMT_I2S:
val |= BIT(CPCAP_BIT_ST_DIG_AUD_FS0);
val |= BIT(CPCAP_BIT_ST_DIG_AUD_FS1);
break;
- default:
/* 01 - 4 slots network mode */
val |= BIT(CPCAP_BIT_ST_DIG_AUD_FS0);
val &= ~BIT(CPCAP_BIT_ST_DIG_AUD_FS1);
/* L on slot 1 */
val |= BIT(CPCAP_BIT_ST_L_TIMESLOT0);
break;
- }
The default case here is really one specific mode, it looks like it's either DSP_A or DSP_B. Most likely DSP_A but you should check. Otherwise this looks good so I'll apply it, please send a followup patch fixing this to match only the specific mode that's actually supported.
The patch
ASoC: cpcap: new codec
has been applied to the asoc tree at
https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git
All being well this means that it will be integrated into the linux-next tree (usually sometime in the next 24 hours) and sent to Linus during the next merge window (or sooner if it is a bug fix), however if problems are discovered then the patch may be dropped or reverted.
You may get further e-mails resulting from automated or manual testing and review of the tree, please engage with people reporting problems and send followup patches addressing any issues that are reported if needed.
If any updates are required or you are submitting further changes they should be sent as incremental updates against current git, existing patches will not be replaced.
Please add any relevant lists and maintainers to the CCs when replying to this mail.
Thanks, Mark
From f6cdf2d3445d73f5b27ec8e620b8fe7a1f5c0ea1 Mon Sep 17 00:00:00 2001
From: Sebastian Reichel sebastian.reichel@collabora.co.uk Date: Fri, 23 Feb 2018 21:02:51 +0100 Subject: [PATCH] ASoC: cpcap: new codec
Motorola CPCAP is a PMIC with audio functionality, that can be found on Motorola Droid 4 and probably a few other phones from Motorola's Droid series.
The driver has been written from scratch using Motorola's Android driver, register dumps from running Android and datasheet for NXP MC13783UG (which is similar to Motorola CPCAP, but not the same).
The chip provides two audio interfaces, that can be muxed to two different audio codecs. One provides support for stereo output (named StDAC or HiFi), while the other only provides mono output (named Voice). Only the Voice codec provides a Capture interface.
Signed-off-by: Sebastian Reichel sebastian.reichel@collabora.co.uk Acked-by: Tony Lindgren tony@atomide.com Signed-off-by: Mark Brown broonie@kernel.org --- sound/soc/codecs/Kconfig | 4 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/cpcap.c | 1568 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1574 insertions(+) create mode 100644 sound/soc/codecs/cpcap.c
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 2b331f7266ab..5bd94841feb6 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -408,6 +408,10 @@ config SND_SOC_ALC5632 config SND_SOC_BT_SCO tristate "Dummy BT SCO codec driver"
+config SND_SOC_CPCAP + tristate "Motorola CPCAP codec" + depends on MFD_CPCAP + config SND_SOC_CQ0093VC tristate
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index da1571336f1e..2aeee1ba034e 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -36,6 +36,7 @@ snd-soc-ak4671-objs := ak4671.o snd-soc-ak5386-objs := ak5386.o snd-soc-arizona-objs := arizona.o snd-soc-bt-sco-objs := bt-sco.o +snd-soc-cpcap-objs := cpcap.o snd-soc-cq93vc-objs := cq93vc.o snd-soc-cs35l32-objs := cs35l32.o snd-soc-cs35l33-objs := cs35l33.o @@ -282,6 +283,7 @@ obj-$(CONFIG_SND_SOC_ALC5632) += snd-soc-alc5632.o obj-$(CONFIG_SND_SOC_ARIZONA) += snd-soc-arizona.o obj-$(CONFIG_SND_SOC_BT_SCO) += snd-soc-bt-sco.o obj-$(CONFIG_SND_SOC_CQ0093VC) += snd-soc-cq93vc.o +obj-$(CONFIG_SND_SOC_CPCAP) += snd-soc-cpcap.o obj-$(CONFIG_SND_SOC_CS35L32) += snd-soc-cs35l32.o obj-$(CONFIG_SND_SOC_CS35L33) += snd-soc-cs35l33.o obj-$(CONFIG_SND_SOC_CS35L34) += snd-soc-cs35l34.o diff --git a/sound/soc/codecs/cpcap.c b/sound/soc/codecs/cpcap.c new file mode 100644 index 000000000000..aedb267d4581 --- /dev/null +++ b/sound/soc/codecs/cpcap.c @@ -0,0 +1,1568 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ALSA SoC CPCAP codec driver + * + * Copyright (C) 2017 - 2018 Sebastian Reichel sre@kernel.org + * + * Very loosely based on original driver from Motorola: + * Copyright (C) 2007 - 2009 Motorola, Inc. + */ + +#include <linux/module.h> +#include <linux/regmap.h> +#include <linux/platform_device.h> +#include <linux/mfd/motorola-cpcap.h> +#include <sound/core.h> +#include <sound/soc.h> +#include <sound/tlv.h> + +/* Register 513 CPCAP_REG_CC --- CODEC */ +#define CPCAP_BIT_CDC_CLK2 15 +#define CPCAP_BIT_CDC_CLK1 14 +#define CPCAP_BIT_CDC_CLK0 13 +#define CPCAP_BIT_CDC_SR3 12 +#define CPCAP_BIT_CDC_SR2 11 +#define CPCAP_BIT_CDC_SR1 10 +#define CPCAP_BIT_CDC_SR0 9 +#define CPCAP_BIT_CDC_CLOCK_TREE_RESET 8 +#define CPCAP_BIT_MIC2_CDC_EN 7 +#define CPCAP_BIT_CDC_EN_RX 6 +#define CPCAP_BIT_DF_RESET 5 +#define CPCAP_BIT_MIC1_CDC_EN 4 +#define CPCAP_BIT_AUDOHPF_1 3 +#define CPCAP_BIT_AUDOHPF_0 2 +#define CPCAP_BIT_AUDIHPF_1 1 +#define CPCAP_BIT_AUDIHPF_0 0 + +/* Register 514 CPCAP_REG_CDI --- CODEC Digital Audio Interface */ +#define CPCAP_BIT_CDC_PLL_SEL 15 +#define CPCAP_BIT_CLK_IN_SEL 13 +#define CPCAP_BIT_DIG_AUD_IN 12 +#define CPCAP_BIT_CDC_CLK_EN 11 +#define CPCAP_BIT_CDC_DIG_AUD_FS1 10 +#define CPCAP_BIT_CDC_DIG_AUD_FS0 9 +#define CPCAP_BIT_MIC2_TIMESLOT2 8 +#define CPCAP_BIT_MIC2_TIMESLOT1 7 +#define CPCAP_BIT_MIC2_TIMESLOT0 6 +#define CPCAP_BIT_MIC1_RX_TIMESLOT2 5 +#define CPCAP_BIT_MIC1_RX_TIMESLOT1 4 +#define CPCAP_BIT_MIC1_RX_TIMESLOT0 3 +#define CPCAP_BIT_FS_INV 2 +#define CPCAP_BIT_CLK_INV 1 +#define CPCAP_BIT_SMB_CDC 0 + +/* Register 515 CPCAP_REG_SDAC --- Stereo DAC */ +#define CPCAP_BIT_FSYNC_CLK_IN_COMMON 11 +#define CPCAP_BIT_SLAVE_PLL_CLK_INPUT 10 +#define CPCAP_BIT_ST_CLOCK_TREE_RESET 9 +#define CPCAP_BIT_DF_RESET_ST_DAC 8 +#define CPCAP_BIT_ST_SR3 7 +#define CPCAP_BIT_ST_SR2 6 +#define CPCAP_BIT_ST_SR1 5 +#define CPCAP_BIT_ST_SR0 4 +#define CPCAP_BIT_ST_DAC_CLK2 3 +#define CPCAP_BIT_ST_DAC_CLK1 2 +#define CPCAP_BIT_ST_DAC_CLK0 1 +#define CPCAP_BIT_ST_DAC_EN 0 + +/* Register 516 CPCAP_REG_SDACDI --- Stereo DAC Digital Audio Interface */ +#define CPCAP_BIT_ST_L_TIMESLOT2 13 +#define CPCAP_BIT_ST_L_TIMESLOT1 12 +#define CPCAP_BIT_ST_L_TIMESLOT0 11 +#define CPCAP_BIT_ST_R_TIMESLOT2 10 +#define CPCAP_BIT_ST_R_TIMESLOT1 9 +#define CPCAP_BIT_ST_R_TIMESLOT0 8 +#define CPCAP_BIT_ST_DAC_CLK_IN_SEL 7 +#define CPCAP_BIT_ST_FS_INV 6 +#define CPCAP_BIT_ST_CLK_INV 5 +#define CPCAP_BIT_ST_DIG_AUD_FS1 4 +#define CPCAP_BIT_ST_DIG_AUD_FS0 3 +#define CPCAP_BIT_DIG_AUD_IN_ST_DAC 2 +#define CPCAP_BIT_ST_CLK_EN 1 +#define CPCAP_BIT_SMB_ST_DAC 0 + +/* Register 517 CPCAP_REG_TXI --- TX Interface */ +#define CPCAP_BIT_PTT_TH 15 +#define CPCAP_BIT_PTT_CMP_EN 14 +#define CPCAP_BIT_HS_ID_TX 13 +#define CPCAP_BIT_MB_ON2 12 +#define CPCAP_BIT_MB_ON1L 11 +#define CPCAP_BIT_MB_ON1R 10 +#define CPCAP_BIT_RX_L_ENCODE 9 +#define CPCAP_BIT_RX_R_ENCODE 8 +#define CPCAP_BIT_MIC2_MUX 7 +#define CPCAP_BIT_MIC2_PGA_EN 6 +#define CPCAP_BIT_CDET_DIS 5 +#define CPCAP_BIT_EMU_MIC_MUX 4 +#define CPCAP_BIT_HS_MIC_MUX 3 +#define CPCAP_BIT_MIC1_MUX 2 +#define CPCAP_BIT_MIC1_PGA_EN 1 +#define CPCAP_BIT_DLM 0 + +/* Register 518 CPCAP_REG_TXMP --- Mic Gain */ +#define CPCAP_BIT_MB_BIAS_R1 11 +#define CPCAP_BIT_MB_BIAS_R0 10 +#define CPCAP_BIT_MIC2_GAIN_4 9 +#define CPCAP_BIT_MIC2_GAIN_3 8 +#define CPCAP_BIT_MIC2_GAIN_2 7 +#define CPCAP_BIT_MIC2_GAIN_1 6 +#define CPCAP_BIT_MIC2_GAIN_0 5 +#define CPCAP_BIT_MIC1_GAIN_4 4 +#define CPCAP_BIT_MIC1_GAIN_3 3 +#define CPCAP_BIT_MIC1_GAIN_2 2 +#define CPCAP_BIT_MIC1_GAIN_1 1 +#define CPCAP_BIT_MIC1_GAIN_0 0 + +/* Register 519 CPCAP_REG_RXOA --- RX Output Amplifier */ +#define CPCAP_BIT_UNUSED_519_15 15 +#define CPCAP_BIT_UNUSED_519_14 14 +#define CPCAP_BIT_UNUSED_519_13 13 +#define CPCAP_BIT_STDAC_LOW_PWR_DISABLE 12 +#define CPCAP_BIT_HS_LOW_PWR 11 +#define CPCAP_BIT_HS_ID_RX 10 +#define CPCAP_BIT_ST_HS_CP_EN 9 +#define CPCAP_BIT_EMU_SPKR_R_EN 8 +#define CPCAP_BIT_EMU_SPKR_L_EN 7 +#define CPCAP_BIT_HS_L_EN 6 +#define CPCAP_BIT_HS_R_EN 5 +#define CPCAP_BIT_A4_LINEOUT_L_EN 4 +#define CPCAP_BIT_A4_LINEOUT_R_EN 3 +#define CPCAP_BIT_A2_LDSP_L_EN 2 +#define CPCAP_BIT_A2_LDSP_R_EN 1 +#define CPCAP_BIT_A1_EAR_EN 0 + +/* Register 520 CPCAP_REG_RXVC --- RX Volume Control */ +#define CPCAP_BIT_VOL_EXT3 15 +#define CPCAP_BIT_VOL_EXT2 14 +#define CPCAP_BIT_VOL_EXT1 13 +#define CPCAP_BIT_VOL_EXT0 12 +#define CPCAP_BIT_VOL_DAC3 11 +#define CPCAP_BIT_VOL_DAC2 10 +#define CPCAP_BIT_VOL_DAC1 9 +#define CPCAP_BIT_VOL_DAC0 8 +#define CPCAP_BIT_VOL_DAC_LSB_1dB1 7 +#define CPCAP_BIT_VOL_DAC_LSB_1dB0 6 +#define CPCAP_BIT_VOL_CDC3 5 +#define CPCAP_BIT_VOL_CDC2 4 +#define CPCAP_BIT_VOL_CDC1 3 +#define CPCAP_BIT_VOL_CDC0 2 +#define CPCAP_BIT_VOL_CDC_LSB_1dB1 1 +#define CPCAP_BIT_VOL_CDC_LSB_1dB0 0 + +/* Register 521 CPCAP_REG_RXCOA --- Codec to Output Amp Switches */ +#define CPCAP_BIT_PGA_CDC_EN 10 +#define CPCAP_BIT_CDC_SW 9 +#define CPCAP_BIT_PGA_OUTR_USBDP_CDC_SW 8 +#define CPCAP_BIT_PGA_OUTL_USBDN_CDC_SW 7 +#define CPCAP_BIT_ALEFT_HS_CDC_SW 6 +#define CPCAP_BIT_ARIGHT_HS_CDC_SW 5 +#define CPCAP_BIT_A4_LINEOUT_L_CDC_SW 4 +#define CPCAP_BIT_A4_LINEOUT_R_CDC_SW 3 +#define CPCAP_BIT_A2_LDSP_L_CDC_SW 2 +#define CPCAP_BIT_A2_LDSP_R_CDC_SW 1 +#define CPCAP_BIT_A1_EAR_CDC_SW 0 + +/* Register 522 CPCAP_REG_RXSDOA --- RX Stereo DAC to Output Amp Switches */ +#define CPCAP_BIT_PGA_DAC_EN 12 +#define CPCAP_BIT_ST_DAC_SW 11 +#define CPCAP_BIT_MONO_DAC1 10 +#define CPCAP_BIT_MONO_DAC0 9 +#define CPCAP_BIT_PGA_OUTR_USBDP_DAC_SW 8 +#define CPCAP_BIT_PGA_OUTL_USBDN_DAC_SW 7 +#define CPCAP_BIT_ALEFT_HS_DAC_SW 6 +#define CPCAP_BIT_ARIGHT_HS_DAC_SW 5 +#define CPCAP_BIT_A4_LINEOUT_L_DAC_SW 4 +#define CPCAP_BIT_A4_LINEOUT_R_DAC_SW 3 +#define CPCAP_BIT_A2_LDSP_L_DAC_SW 2 +#define CPCAP_BIT_A2_LDSP_R_DAC_SW 1 +#define CPCAP_BIT_A1_EAR_DAC_SW 0 + +/* Register 523 CPCAP_REG_RXEPOA --- RX External PGA to Output Amp Switches */ +#define CPCAP_BIT_PGA_EXT_L_EN 14 +#define CPCAP_BIT_PGA_EXT_R_EN 13 +#define CPCAP_BIT_PGA_IN_L_SW 12 +#define CPCAP_BIT_PGA_IN_R_SW 11 +#define CPCAP_BIT_MONO_EXT1 10 +#define CPCAP_BIT_MONO_EXT0 9 +#define CPCAP_BIT_PGA_OUTR_USBDP_EXT_SW 8 +#define CPCAP_BIT_PGA_OUTL_USBDN_EXT_SW 7 +#define CPCAP_BIT_ALEFT_HS_EXT_SW 6 +#define CPCAP_BIT_ARIGHT_HS_EXT_SW 5 +#define CPCAP_BIT_A4_LINEOUT_L_EXT_SW 4 +#define CPCAP_BIT_A4_LINEOUT_R_EXT_SW 3 +#define CPCAP_BIT_A2_LDSP_L_EXT_SW 2 +#define CPCAP_BIT_A2_LDSP_R_EXT_SW 1 +#define CPCAP_BIT_A1_EAR_EXT_SW 0 + +/* Register 525 CPCAP_REG_A2LA --- SPK Amplifier and Clock Config for Headset */ +#define CPCAP_BIT_NCP_CLK_SYNC 7 +#define CPCAP_BIT_A2_CLK_SYNC 6 +#define CPCAP_BIT_A2_FREE_RUN 5 +#define CPCAP_BIT_A2_CLK2 4 +#define CPCAP_BIT_A2_CLK1 3 +#define CPCAP_BIT_A2_CLK0 2 +#define CPCAP_BIT_A2_CLK_IN 1 +#define CPCAP_BIT_A2_CONFIG 0 + +#define SLEEP_ACTIVATE_POWER 2 +#define CLOCK_TREE_RESET_TIME 1 + +/* constants for ST delay workaround */ +#define STM_STDAC_ACTIVATE_RAMP_TIME 1 +#define STM_STDAC_EN_TEST_PRE 0x090C +#define STM_STDAC_EN_TEST_POST 0x0000 +#define STM_STDAC_EN_ST_TEST1_PRE 0x2400 +#define STM_STDAC_EN_ST_TEST1_POST 0x0400 + +struct cpcap_reg_info { + u16 reg; + u16 mask; + u16 val; +}; + +static const struct cpcap_reg_info cpcap_default_regs[] = { + { CPCAP_REG_CC, 0xFFFF, 0x0000 }, + { CPCAP_REG_CC, 0xFFFF, 0x0000 }, + { CPCAP_REG_CDI, 0xBFFF, 0x0000 }, + { CPCAP_REG_SDAC, 0x0FFF, 0x0000 }, + { CPCAP_REG_SDACDI, 0x3FFF, 0x0000 }, + { CPCAP_REG_TXI, 0x0FDF, 0x0000 }, + { CPCAP_REG_TXMP, 0x0FFF, 0x0400 }, + { CPCAP_REG_RXOA, 0x01FF, 0x0000 }, + { CPCAP_REG_RXVC, 0xFF3C, 0x0000 }, + { CPCAP_REG_RXCOA, 0x07FF, 0x0000 }, + { CPCAP_REG_RXSDOA, 0x1FFF, 0x0000 }, + { CPCAP_REG_RXEPOA, 0x7FFF, 0x0000 }, + { CPCAP_REG_A2LA, BIT(CPCAP_BIT_A2_FREE_RUN), + BIT(CPCAP_BIT_A2_FREE_RUN) }, +}; + +enum cpcap_dai { + CPCAP_DAI_HIFI, + CPCAP_DAI_VOICE, +}; + +struct cpcap_audio { + struct snd_soc_codec *codec; + struct regmap *regmap; + + u16 vendor; + + int codec_clk_id; + int codec_freq; + int codec_format; +}; + +static int cpcap_st_workaround(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); + struct cpcap_audio *cpcap = snd_soc_codec_get_drvdata(codec); + int err = 0; + + /* Only CPCAP from ST requires workaround */ + if (cpcap->vendor != CPCAP_VENDOR_ST) + return 0; + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + err = regmap_write(cpcap->regmap, CPCAP_REG_TEST, + STM_STDAC_EN_TEST_PRE); + if (err) + return err; + err = regmap_write(cpcap->regmap, CPCAP_REG_ST_TEST1, + STM_STDAC_EN_ST_TEST1_PRE); + break; + case SND_SOC_DAPM_POST_PMU: + msleep(STM_STDAC_ACTIVATE_RAMP_TIME); + + err = regmap_write(cpcap->regmap, CPCAP_REG_ST_TEST1, + STM_STDAC_EN_ST_TEST1_POST); + if (err) + return err; + err = regmap_write(cpcap->regmap, CPCAP_REG_TEST, + STM_STDAC_EN_TEST_POST); + break; + default: + break; + } + + return err; +} + +/* Capture Gain Control: 0dB to 31dB in 1dB steps */ +static const DECLARE_TLV_DB_SCALE(mic_gain_tlv, 0, 100, 0); + +/* Playback Gain Control: -33dB to 12dB in 3dB steps */ +static const DECLARE_TLV_DB_SCALE(vol_tlv, -3300, 300, 0); + +static const struct snd_kcontrol_new cpcap_snd_controls[] = { + /* Playback Gain */ + SOC_SINGLE_TLV("HiFi Playback Volume", + CPCAP_REG_RXVC, CPCAP_BIT_VOL_DAC0, 0xF, 0, vol_tlv), + SOC_SINGLE_TLV("Voice Playback Volume", + CPCAP_REG_RXVC, CPCAP_BIT_VOL_CDC0, 0xF, 0, vol_tlv), + SOC_SINGLE_TLV("Ext Playback Volume", + CPCAP_REG_RXVC, CPCAP_BIT_VOL_EXT0, 0xF, 0, vol_tlv), + + /* Capture Gain */ + SOC_SINGLE_TLV("Mic1 Capture Volume", + CPCAP_REG_TXMP, CPCAP_BIT_MIC1_GAIN_0, 0x1F, 0, mic_gain_tlv), + SOC_SINGLE_TLV("Mic2 Capture Volume", + CPCAP_REG_TXMP, CPCAP_BIT_MIC2_GAIN_0, 0x1F, 0, mic_gain_tlv), + + /* Phase Invert */ + SOC_SINGLE("Hifi Left Phase Invert Switch", + CPCAP_REG_RXSDOA, CPCAP_BIT_MONO_DAC0, 1, 0), + SOC_SINGLE("Ext Left Phase Invert Switch", + CPCAP_REG_RXEPOA, CPCAP_BIT_MONO_EXT0, 1, 0), +}; + +static const char * const cpcap_out_mux_texts[] = { + "Off", "Voice", "HiFi", "Ext" +}; + +static const char * const cpcap_in_right_mux_texts[] = { + "Off", "Mic 1", "Headset Mic", "EMU Mic", "Ext Right" +}; + +static const char * const cpcap_in_left_mux_texts[] = { + "Off", "Mic 2", "Ext Left" +}; + +/* + * input muxes use unusual register layout, so that we need to use custom + * getter/setter methods + */ +static SOC_ENUM_SINGLE_EXT_DECL(cpcap_input_left_mux_enum, + cpcap_in_left_mux_texts); +static SOC_ENUM_SINGLE_EXT_DECL(cpcap_input_right_mux_enum, + cpcap_in_right_mux_texts); + +/* + * mux uses same bit in CPCAP_REG_RXCOA, CPCAP_REG_RXSDOA & CPCAP_REG_RXEPOA; + * even though the register layout makes it look like a mixer, this is a mux. + * Enabling multiple inputs will result in no audio being forwarded. + */ +static SOC_ENUM_SINGLE_DECL(cpcap_earpiece_mux_enum, 0, 0, cpcap_out_mux_texts); +static SOC_ENUM_SINGLE_DECL(cpcap_spkr_r_mux_enum, 0, 1, cpcap_out_mux_texts); +static SOC_ENUM_SINGLE_DECL(cpcap_spkr_l_mux_enum, 0, 2, cpcap_out_mux_texts); +static SOC_ENUM_SINGLE_DECL(cpcap_line_r_mux_enum, 0, 3, cpcap_out_mux_texts); +static SOC_ENUM_SINGLE_DECL(cpcap_line_l_mux_enum, 0, 4, cpcap_out_mux_texts); +static SOC_ENUM_SINGLE_DECL(cpcap_hs_r_mux_enum, 0, 5, cpcap_out_mux_texts); +static SOC_ENUM_SINGLE_DECL(cpcap_hs_l_mux_enum, 0, 6, cpcap_out_mux_texts); +static SOC_ENUM_SINGLE_DECL(cpcap_emu_l_mux_enum, 0, 7, cpcap_out_mux_texts); +static SOC_ENUM_SINGLE_DECL(cpcap_emu_r_mux_enum, 0, 8, cpcap_out_mux_texts); + +static int cpcap_output_mux_get_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_soc_dapm_kcontrol_codec(kcontrol); + struct cpcap_audio *cpcap = snd_soc_codec_get_drvdata(codec); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int shift = e->shift_l; + int reg_voice, reg_hifi, reg_ext, status; + int err; + + err = regmap_read(cpcap->regmap, CPCAP_REG_RXCOA, ®_voice); + if (err) + return err; + err = regmap_read(cpcap->regmap, CPCAP_REG_RXSDOA, ®_hifi); + if (err) + return err; + err = regmap_read(cpcap->regmap, CPCAP_REG_RXEPOA, ®_ext); + if (err) + return err; + + reg_voice = (reg_voice >> shift) & 1; + reg_hifi = (reg_hifi >> shift) & 1; + reg_ext = (reg_ext >> shift) & 1; + status = reg_ext << 2 | reg_hifi << 1 | reg_voice; + + switch (status) { + case 0x04: + ucontrol->value.enumerated.item[0] = 3; + break; + case 0x02: + ucontrol->value.enumerated.item[0] = 2; + break; + case 0x01: + ucontrol->value.enumerated.item[0] = 1; + break; + default: + ucontrol->value.enumerated.item[0] = 0; + break; + } + + return 0; +} + +static int cpcap_output_mux_put_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_soc_dapm_kcontrol_codec(kcontrol); + struct cpcap_audio *cpcap = snd_soc_codec_get_drvdata(codec); + struct snd_soc_dapm_context *dapm = + snd_soc_dapm_kcontrol_dapm(kcontrol); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int muxval = ucontrol->value.enumerated.item[0]; + unsigned int mask = BIT(e->shift_l); + u16 reg_voice = 0x00, reg_hifi = 0x00, reg_ext = 0x00; + int err; + + switch (muxval) { + case 1: + reg_voice = mask; + break; + case 2: + reg_hifi = mask; + break; + case 3: + reg_ext = mask; + break; + default: + break; + } + + err = regmap_update_bits(cpcap->regmap, CPCAP_REG_RXCOA, + mask, reg_voice); + if (err) + return err; + err = regmap_update_bits(cpcap->regmap, CPCAP_REG_RXSDOA, + mask, reg_hifi); + if (err) + return err; + err = regmap_update_bits(cpcap->regmap, CPCAP_REG_RXEPOA, + mask, reg_ext); + if (err) + return err; + + snd_soc_dapm_mux_update_power(dapm, kcontrol, muxval, e, NULL); + + return 0; +} + +static int cpcap_input_right_mux_get_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_soc_dapm_kcontrol_codec(kcontrol); + struct cpcap_audio *cpcap = snd_soc_codec_get_drvdata(codec); + int regval, mask; + int err; + + err = regmap_read(cpcap->regmap, CPCAP_REG_TXI, ®val); + if (err) + return err; + + mask = 0; + mask |= BIT(CPCAP_BIT_MIC1_MUX); + mask |= BIT(CPCAP_BIT_HS_MIC_MUX); + mask |= BIT(CPCAP_BIT_EMU_MIC_MUX); + mask |= BIT(CPCAP_BIT_RX_R_ENCODE); + + switch (regval & mask) { + case BIT(CPCAP_BIT_RX_R_ENCODE): + ucontrol->value.enumerated.item[0] = 4; + break; + case BIT(CPCAP_BIT_EMU_MIC_MUX): + ucontrol->value.enumerated.item[0] = 3; + break; + case BIT(CPCAP_BIT_HS_MIC_MUX): + ucontrol->value.enumerated.item[0] = 2; + break; + case BIT(CPCAP_BIT_MIC1_MUX): + ucontrol->value.enumerated.item[0] = 1; + break; + default: + ucontrol->value.enumerated.item[0] = 0; + break; + } + + return 0; +} + +static int cpcap_input_right_mux_put_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_soc_dapm_kcontrol_codec(kcontrol); + struct cpcap_audio *cpcap = snd_soc_codec_get_drvdata(codec); + struct snd_soc_dapm_context *dapm = + snd_soc_dapm_kcontrol_dapm(kcontrol); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int muxval = ucontrol->value.enumerated.item[0]; + int regval = 0, mask; + int err; + + mask = 0; + mask |= BIT(CPCAP_BIT_MIC1_MUX); + mask |= BIT(CPCAP_BIT_HS_MIC_MUX); + mask |= BIT(CPCAP_BIT_EMU_MIC_MUX); + mask |= BIT(CPCAP_BIT_RX_R_ENCODE); + + switch (muxval) { + case 1: + regval = BIT(CPCAP_BIT_MIC1_MUX); + break; + case 2: + regval = BIT(CPCAP_BIT_HS_MIC_MUX); + break; + case 3: + regval = BIT(CPCAP_BIT_EMU_MIC_MUX); + break; + case 4: + regval = BIT(CPCAP_BIT_RX_R_ENCODE); + break; + default: + break; + } + + err = regmap_update_bits(cpcap->regmap, CPCAP_REG_TXI, + mask, regval); + if (err) + return err; + + snd_soc_dapm_mux_update_power(dapm, kcontrol, muxval, e, NULL); + + return 0; +} + +static int cpcap_input_left_mux_get_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_soc_dapm_kcontrol_codec(kcontrol); + struct cpcap_audio *cpcap = snd_soc_codec_get_drvdata(codec); + int regval, mask; + int err; + + err = regmap_read(cpcap->regmap, CPCAP_REG_TXI, ®val); + if (err) + return err; + + mask = 0; + mask |= BIT(CPCAP_BIT_MIC2_MUX); + mask |= BIT(CPCAP_BIT_RX_L_ENCODE); + + switch (regval & mask) { + case BIT(CPCAP_BIT_RX_L_ENCODE): + ucontrol->value.enumerated.item[0] = 2; + break; + case BIT(CPCAP_BIT_MIC2_MUX): + ucontrol->value.enumerated.item[0] = 1; + break; + default: + ucontrol->value.enumerated.item[0] = 0; + break; + } + + return 0; +} + +static int cpcap_input_left_mux_put_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_soc_dapm_kcontrol_codec(kcontrol); + struct cpcap_audio *cpcap = snd_soc_codec_get_drvdata(codec); + struct snd_soc_dapm_context *dapm = + snd_soc_dapm_kcontrol_dapm(kcontrol); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int muxval = ucontrol->value.enumerated.item[0]; + int regval = 0, mask; + int err; + + mask = 0; + mask |= BIT(CPCAP_BIT_MIC2_MUX); + mask |= BIT(CPCAP_BIT_RX_L_ENCODE); + + switch (muxval) { + case 1: + regval = BIT(CPCAP_BIT_MIC2_MUX); + break; + case 2: + regval = BIT(CPCAP_BIT_RX_L_ENCODE); + break; + default: + break; + } + + err = regmap_update_bits(cpcap->regmap, CPCAP_REG_TXI, + mask, regval); + if (err) + return err; + + snd_soc_dapm_mux_update_power(dapm, kcontrol, muxval, e, NULL); + + return 0; +} + +static const struct snd_kcontrol_new cpcap_input_left_mux = + SOC_DAPM_ENUM_EXT("Input Left", cpcap_input_left_mux_enum, + cpcap_input_left_mux_get_enum, + cpcap_input_left_mux_put_enum); +static const struct snd_kcontrol_new cpcap_input_right_mux = + SOC_DAPM_ENUM_EXT("Input Right", cpcap_input_right_mux_enum, + cpcap_input_right_mux_get_enum, + cpcap_input_right_mux_put_enum); +static const struct snd_kcontrol_new cpcap_emu_left_mux = + SOC_DAPM_ENUM_EXT("EMU Left", cpcap_emu_l_mux_enum, + cpcap_output_mux_get_enum, cpcap_output_mux_put_enum); +static const struct snd_kcontrol_new cpcap_emu_right_mux = + SOC_DAPM_ENUM_EXT("EMU Right", cpcap_emu_r_mux_enum, + cpcap_output_mux_get_enum, cpcap_output_mux_put_enum); +static const struct snd_kcontrol_new cpcap_hs_left_mux = + SOC_DAPM_ENUM_EXT("Headset Left", cpcap_hs_l_mux_enum, + cpcap_output_mux_get_enum, cpcap_output_mux_put_enum); +static const struct snd_kcontrol_new cpcap_hs_right_mux = + SOC_DAPM_ENUM_EXT("Headset Right", cpcap_hs_r_mux_enum, + cpcap_output_mux_get_enum, cpcap_output_mux_put_enum); +static const struct snd_kcontrol_new cpcap_line_left_mux = + SOC_DAPM_ENUM_EXT("Line Left", cpcap_line_l_mux_enum, + cpcap_output_mux_get_enum, cpcap_output_mux_put_enum); +static const struct snd_kcontrol_new cpcap_line_right_mux = + SOC_DAPM_ENUM_EXT("Line Right", cpcap_line_r_mux_enum, + cpcap_output_mux_get_enum, cpcap_output_mux_put_enum); +static const struct snd_kcontrol_new cpcap_speaker_left_mux = + SOC_DAPM_ENUM_EXT("Speaker Left", cpcap_spkr_l_mux_enum, + cpcap_output_mux_get_enum, cpcap_output_mux_put_enum); +static const struct snd_kcontrol_new cpcap_speaker_right_mux = + SOC_DAPM_ENUM_EXT("Speaker Right", cpcap_spkr_r_mux_enum, + cpcap_output_mux_get_enum, cpcap_output_mux_put_enum); +static const struct snd_kcontrol_new cpcap_earpiece_mux = + SOC_DAPM_ENUM_EXT("Earpiece", cpcap_earpiece_mux_enum, + cpcap_output_mux_get_enum, cpcap_output_mux_put_enum); + +static const struct snd_kcontrol_new cpcap_hifi_mono_mixer_controls[] = { + SOC_DAPM_SINGLE("HiFi Mono Playback Switch", + CPCAP_REG_RXSDOA, CPCAP_BIT_MONO_DAC1, 1, 0), +}; +static const struct snd_kcontrol_new cpcap_ext_mono_mixer_controls[] = { + SOC_DAPM_SINGLE("Ext Mono Playback Switch", + CPCAP_REG_RXEPOA, CPCAP_BIT_MONO_EXT0, 1, 0), +}; + +static const struct snd_kcontrol_new cpcap_extr_mute_control = + SOC_DAPM_SINGLE("Switch", + CPCAP_REG_RXEPOA, CPCAP_BIT_PGA_IN_R_SW, 1, 0); +static const struct snd_kcontrol_new cpcap_extl_mute_control = + SOC_DAPM_SINGLE("Switch", + CPCAP_REG_RXEPOA, CPCAP_BIT_PGA_IN_L_SW, 1, 0); + +static const struct snd_kcontrol_new cpcap_voice_loopback = + SOC_DAPM_SINGLE("Switch", + CPCAP_REG_TXI, CPCAP_BIT_DLM, 1, 0); + +static const struct snd_soc_dapm_widget cpcap_dapm_widgets[] = { + /* DAIs */ + SND_SOC_DAPM_AIF_IN("HiFi RX", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("Voice RX", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("Voice TX", NULL, 0, SND_SOC_NOPM, 0, 0), + + /* Power Supply */ + SND_SOC_DAPM_REGULATOR_SUPPLY("VAUDIO", SLEEP_ACTIVATE_POWER, 0), + + /* Highpass Filters */ + SND_SOC_DAPM_REG(snd_soc_dapm_pga, "Highpass Filter RX", + CPCAP_REG_CC, CPCAP_BIT_AUDIHPF_0, 0x3, 0x3, 0x0), + SND_SOC_DAPM_REG(snd_soc_dapm_pga, "Highpass Filter TX", + CPCAP_REG_CC, CPCAP_BIT_AUDOHPF_0, 0x3, 0x3, 0x0), + + /* Clocks */ + SND_SOC_DAPM_SUPPLY("HiFi DAI Clock", + CPCAP_REG_SDACDI, CPCAP_BIT_ST_CLK_EN, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("Voice DAI Clock", + CPCAP_REG_CDI, CPCAP_BIT_CDC_CLK_EN, 0, NULL, 0), + + /* Microphone Bias */ + SND_SOC_DAPM_SUPPLY("MIC1R Bias", + CPCAP_REG_TXI, CPCAP_BIT_MB_ON1R, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("MIC1L Bias", + CPCAP_REG_TXI, CPCAP_BIT_MB_ON1L, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("MIC2 Bias", + CPCAP_REG_TXI, CPCAP_BIT_MB_ON2, 0, NULL, 0), + + /* Inputs */ + SND_SOC_DAPM_INPUT("MICR"), + SND_SOC_DAPM_INPUT("HSMIC"), + SND_SOC_DAPM_INPUT("EMUMIC"), + SND_SOC_DAPM_INPUT("MICL"), + SND_SOC_DAPM_INPUT("EXTR"), + SND_SOC_DAPM_INPUT("EXTL"), + + /* Capture Route */ + SND_SOC_DAPM_MUX("Right Capture Route", + SND_SOC_NOPM, 0, 0, &cpcap_input_right_mux), + SND_SOC_DAPM_MUX("Left Capture Route", + SND_SOC_NOPM, 0, 0, &cpcap_input_left_mux), + + /* Capture PGAs */ + SND_SOC_DAPM_PGA("Microphone 1 PGA", + CPCAP_REG_TXI, CPCAP_BIT_MIC1_PGA_EN, 0, NULL, 0), + SND_SOC_DAPM_PGA("Microphone 2 PGA", + CPCAP_REG_TXI, CPCAP_BIT_MIC2_PGA_EN, 0, NULL, 0), + + /* ADC */ + SND_SOC_DAPM_ADC("ADC Right", NULL, + CPCAP_REG_CC, CPCAP_BIT_MIC1_CDC_EN, 0), + SND_SOC_DAPM_ADC("ADC Left", NULL, + CPCAP_REG_CC, CPCAP_BIT_MIC2_CDC_EN, 0), + + /* DAC */ + SND_SOC_DAPM_DAC_E("DAC HiFi", NULL, + CPCAP_REG_SDAC, CPCAP_BIT_ST_DAC_EN, 0, + cpcap_st_workaround, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_DAC_E("DAC Voice", NULL, + CPCAP_REG_CC, CPCAP_BIT_CDC_EN_RX, 0, + cpcap_st_workaround, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU), + + /* Playback PGA */ + SND_SOC_DAPM_PGA("HiFi PGA", + CPCAP_REG_RXSDOA, CPCAP_BIT_PGA_DAC_EN, 0, NULL, 0), + SND_SOC_DAPM_PGA("Voice PGA", + CPCAP_REG_RXCOA, CPCAP_BIT_PGA_CDC_EN, 0, NULL, 0), + SND_SOC_DAPM_PGA_E("Ext Right PGA", + CPCAP_REG_RXEPOA, CPCAP_BIT_PGA_EXT_R_EN, 0, + NULL, 0, + cpcap_st_workaround, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_PGA_E("Ext Left PGA", + CPCAP_REG_RXEPOA, CPCAP_BIT_PGA_EXT_L_EN, 0, + NULL, 0, + cpcap_st_workaround, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU), + + /* Playback Switch */ + SND_SOC_DAPM_SWITCH("Ext Right Enable", SND_SOC_NOPM, 0, 0, + &cpcap_extr_mute_control), + SND_SOC_DAPM_SWITCH("Ext Left Enable", SND_SOC_NOPM, 0, 0, + &cpcap_extl_mute_control), + + /* Loopback Switch */ + SND_SOC_DAPM_SWITCH("Voice Loopback", SND_SOC_NOPM, 0, 0, + &cpcap_voice_loopback), + + /* Mono Mixer */ + SOC_MIXER_ARRAY("HiFi Mono Left Mixer", SND_SOC_NOPM, 0, 0, + cpcap_hifi_mono_mixer_controls), + SOC_MIXER_ARRAY("HiFi Mono Right Mixer", SND_SOC_NOPM, 0, 0, + cpcap_hifi_mono_mixer_controls), + SOC_MIXER_ARRAY("Ext Mono Left Mixer", SND_SOC_NOPM, 0, 0, + cpcap_ext_mono_mixer_controls), + SOC_MIXER_ARRAY("Ext Mono Right Mixer", SND_SOC_NOPM, 0, 0, + cpcap_ext_mono_mixer_controls), + + /* Output Routes */ + SND_SOC_DAPM_MUX("Earpiece Playback Route", SND_SOC_NOPM, 0, 0, + &cpcap_earpiece_mux), + SND_SOC_DAPM_MUX("Speaker Right Playback Route", SND_SOC_NOPM, 0, 0, + &cpcap_speaker_right_mux), + SND_SOC_DAPM_MUX("Speaker Left Playback Route", SND_SOC_NOPM, 0, 0, + &cpcap_speaker_left_mux), + SND_SOC_DAPM_MUX("Lineout Right Playback Route", SND_SOC_NOPM, 0, 0, + &cpcap_line_right_mux), + SND_SOC_DAPM_MUX("Lineout Left Playback Route", SND_SOC_NOPM, 0, 0, + &cpcap_line_left_mux), + SND_SOC_DAPM_MUX("Headset Right Playback Route", SND_SOC_NOPM, 0, 0, + &cpcap_hs_right_mux), + SND_SOC_DAPM_MUX("Headset Left Playback Route", SND_SOC_NOPM, 0, 0, + &cpcap_hs_left_mux), + SND_SOC_DAPM_MUX("EMU Right Playback Route", SND_SOC_NOPM, 0, 0, + &cpcap_emu_right_mux), + SND_SOC_DAPM_MUX("EMU Left Playback Route", SND_SOC_NOPM, 0, 0, + &cpcap_emu_left_mux), + + /* Output Amplifier */ + SND_SOC_DAPM_PGA("Earpiece PGA", + CPCAP_REG_RXOA, CPCAP_BIT_A1_EAR_EN, 0, NULL, 0), + SND_SOC_DAPM_PGA("Speaker Right PGA", + CPCAP_REG_RXOA, CPCAP_BIT_A2_LDSP_R_EN, 0, NULL, 0), + SND_SOC_DAPM_PGA("Speaker Left PGA", + CPCAP_REG_RXOA, CPCAP_BIT_A2_LDSP_L_EN, 0, NULL, 0), + SND_SOC_DAPM_PGA("Lineout Right PGA", + CPCAP_REG_RXOA, CPCAP_BIT_A4_LINEOUT_R_EN, 0, NULL, 0), + SND_SOC_DAPM_PGA("Lineout Left PGA", + CPCAP_REG_RXOA, CPCAP_BIT_A4_LINEOUT_L_EN, 0, NULL, 0), + SND_SOC_DAPM_PGA("Headset Right PGA", + CPCAP_REG_RXOA, CPCAP_BIT_HS_R_EN, 0, NULL, 0), + SND_SOC_DAPM_PGA("Headset Left PGA", + CPCAP_REG_RXOA, CPCAP_BIT_HS_L_EN, 0, NULL, 0), + SND_SOC_DAPM_PGA("EMU Right PGA", + CPCAP_REG_RXOA, CPCAP_BIT_EMU_SPKR_R_EN, 0, NULL, 0), + SND_SOC_DAPM_PGA("EMU Left PGA", + CPCAP_REG_RXOA, CPCAP_BIT_EMU_SPKR_L_EN, 0, NULL, 0), + + /* Headet Charge Pump */ + SND_SOC_DAPM_SUPPLY("Headset Charge Pump", + CPCAP_REG_RXOA, CPCAP_BIT_ST_HS_CP_EN, 0, NULL, 0), + + /* Outputs */ + SND_SOC_DAPM_OUTPUT("EP"), + SND_SOC_DAPM_OUTPUT("SPKR"), + SND_SOC_DAPM_OUTPUT("SPKL"), + SND_SOC_DAPM_OUTPUT("LINER"), + SND_SOC_DAPM_OUTPUT("LINEL"), + SND_SOC_DAPM_OUTPUT("HSR"), + SND_SOC_DAPM_OUTPUT("HSL"), + SND_SOC_DAPM_OUTPUT("EMUR"), + SND_SOC_DAPM_OUTPUT("EMUL"), +}; + +static const struct snd_soc_dapm_route intercon[] = { + /* Power Supply */ + {"HiFi PGA", NULL, "VAUDIO"}, + {"Voice PGA", NULL, "VAUDIO"}, + {"Ext Right PGA", NULL, "VAUDIO"}, + {"Ext Left PGA", NULL, "VAUDIO"}, + {"Microphone 1 PGA", NULL, "VAUDIO"}, + {"Microphone 2 PGA", NULL, "VAUDIO"}, + + /* Stream -> AIF */ + {"HiFi RX", NULL, "HiFi Playback"}, + {"Voice RX", NULL, "Voice Playback"}, + {"Voice Capture", NULL, "Voice TX"}, + + /* AIF clocks */ + {"HiFi RX", NULL, "HiFi DAI Clock"}, + {"Voice RX", NULL, "Voice DAI Clock"}, + {"Voice TX", NULL, "Voice DAI Clock"}, + + /* Digital Loopback */ + {"Voice Loopback", "Switch", "Voice TX"}, + {"Voice RX", NULL, "Voice Loopback"}, + + /* Highpass Filters */ + {"Highpass Filter RX", NULL, "Voice RX"}, + {"Voice TX", NULL, "Highpass Filter TX"}, + + /* AIF -> DAC mapping */ + {"DAC HiFi", NULL, "HiFi RX"}, + {"DAC Voice", NULL, "Highpass Filter RX"}, + + /* DAC -> PGA */ + {"HiFi PGA", NULL, "DAC HiFi"}, + {"Voice PGA", NULL, "DAC Voice"}, + + /* Ext Input -> PGA */ + {"Ext Right PGA", NULL, "EXTR"}, + {"Ext Left PGA", NULL, "EXTL"}, + + /* Ext PGA -> Ext Playback Switch */ + {"Ext Right Enable", "Switch", "Ext Right PGA"}, + {"Ext Left Enable", "Switch", "Ext Left PGA"}, + + /* HiFi PGA -> Mono Mixer */ + {"HiFi Mono Left Mixer", NULL, "HiFi PGA"}, + {"HiFi Mono Left Mixer", "HiFi Mono Playback Switch", "HiFi PGA"}, + {"HiFi Mono Right Mixer", NULL, "HiFi PGA"}, + {"HiFi Mono Right Mixer", "HiFi Mono Playback Switch", "HiFi PGA"}, + + /* Ext Playback Switch -> Ext Mono Mixer */ + {"Ext Mono Right Mixer", NULL, "Ext Right Enable"}, + {"Ext Mono Right Mixer", "Ext Mono Playback Switch", "Ext Left Enable"}, + {"Ext Mono Left Mixer", NULL, "Ext Left Enable"}, + {"Ext Mono Left Mixer", "Ext Mono Playback Switch", "Ext Right Enable"}, + + /* HiFi Mono Mixer -> Output Route */ + {"Earpiece Playback Route", "HiFi", "HiFi Mono Right Mixer"}, + {"Speaker Right Playback Route", "HiFi", "HiFi Mono Right Mixer"}, + {"Speaker Left Playback Route", "HiFi", "HiFi Mono Left Mixer"}, + {"Lineout Right Playback Route", "HiFi", "HiFi Mono Right Mixer"}, + {"Lineout Left Playback Route", "HiFi", "HiFi Mono Left Mixer"}, + {"Headset Right Playback Route", "HiFi", "HiFi Mono Right Mixer"}, + {"Headset Left Playback Route", "HiFi", "HiFi Mono Left Mixer"}, + {"EMU Right Playback Route", "HiFi", "HiFi Mono Right Mixer"}, + {"EMU Left Playback Route", "HiFi", "HiFi Mono Left Mixer"}, + + /* Voice PGA -> Output Route */ + {"Earpiece Playback Route", "Voice", "Voice PGA"}, + {"Speaker Right Playback Route", "Voice", "Voice PGA"}, + {"Speaker Left Playback Route", "Voice", "Voice PGA"}, + {"Lineout Right Playback Route", "Voice", "Voice PGA"}, + {"Lineout Left Playback Route", "Voice", "Voice PGA"}, + {"Headset Right Playback Route", "Voice", "Voice PGA"}, + {"Headset Left Playback Route", "Voice", "Voice PGA"}, + {"EMU Right Playback Route", "Voice", "Voice PGA"}, + {"EMU Left Playback Route", "Voice", "Voice PGA"}, + + /* Ext Mono Mixer -> Output Route */ + {"Earpiece Playback Route", "Ext", "Ext Mono Right Mixer"}, + {"Speaker Right Playback Route", "Ext", "Ext Mono Right Mixer"}, + {"Speaker Left Playback Route", "Ext", "Ext Mono Left Mixer"}, + {"Lineout Right Playback Route", "Ext", "Ext Mono Right Mixer"}, + {"Lineout Left Playback Route", "Ext", "Ext Mono Left Mixer"}, + {"Headset Right Playback Route", "Ext", "Ext Mono Right Mixer"}, + {"Headset Left Playback Route", "Ext", "Ext Mono Left Mixer"}, + {"EMU Right Playback Route", "Ext", "Ext Mono Right Mixer"}, + {"EMU Left Playback Route", "Ext", "Ext Mono Left Mixer"}, + + /* Output Route -> Output Amplifier */ + {"Earpiece PGA", NULL, "Earpiece Playback Route"}, + {"Speaker Right PGA", NULL, "Speaker Right Playback Route"}, + {"Speaker Left PGA", NULL, "Speaker Left Playback Route"}, + {"Lineout Right PGA", NULL, "Lineout Right Playback Route"}, + {"Lineout Left PGA", NULL, "Lineout Left Playback Route"}, + {"Headset Right PGA", NULL, "Headset Right Playback Route"}, + {"Headset Left PGA", NULL, "Headset Left Playback Route"}, + {"EMU Right PGA", NULL, "EMU Right Playback Route"}, + {"EMU Left PGA", NULL, "EMU Left Playback Route"}, + + /* Output Amplifier -> Output */ + {"EP", NULL, "Earpiece PGA"}, + {"SPKR", NULL, "Speaker Right PGA"}, + {"SPKL", NULL, "Speaker Left PGA"}, + {"LINER", NULL, "Lineout Right PGA"}, + {"LINEL", NULL, "Lineout Left PGA"}, + {"HSR", NULL, "Headset Right PGA"}, + {"HSL", NULL, "Headset Left PGA"}, + {"EMUR", NULL, "EMU Right PGA"}, + {"EMUL", NULL, "EMU Left PGA"}, + + /* Headset Charge Pump -> Headset */ + {"HSR", NULL, "Headset Charge Pump"}, + {"HSL", NULL, "Headset Charge Pump"}, + + /* Mic -> Mic Route */ + {"Right Capture Route", "Mic 1", "MICR"}, + {"Right Capture Route", "Headset Mic", "HSMIC"}, + {"Right Capture Route", "EMU Mic", "EMUMIC"}, + {"Right Capture Route", "Ext Right", "EXTR"}, + {"Left Capture Route", "Mic 2", "MICL"}, + {"Left Capture Route", "Ext Left", "EXTL"}, + + /* Input Route -> Microphone PGA */ + {"Microphone 1 PGA", NULL, "Right Capture Route"}, + {"Microphone 2 PGA", NULL, "Left Capture Route"}, + + /* Microphone PGA -> ADC */ + {"ADC Right", NULL, "Microphone 1 PGA"}, + {"ADC Left", NULL, "Microphone 2 PGA"}, + + /* ADC -> Stream */ + {"Highpass Filter TX", NULL, "ADC Right"}, + {"Highpass Filter TX", NULL, "ADC Left"}, + + /* Mic Bias */ + {"MICL", NULL, "MIC1L Bias"}, + {"MICR", NULL, "MIC1R Bias"}, +}; + +static int cpcap_set_sysclk(struct cpcap_audio *cpcap, enum cpcap_dai dai, + int clk_id, int freq) +{ + u16 clkfreqreg, clkfreqshift; + u16 clkfreqmask, clkfreqval; + u16 clkidreg, clkidshift; + u16 mask, val; + int err; + + switch (dai) { + case CPCAP_DAI_HIFI: + clkfreqreg = CPCAP_REG_SDAC; + clkfreqshift = CPCAP_BIT_ST_DAC_CLK0; + clkidreg = CPCAP_REG_SDACDI; + clkidshift = CPCAP_BIT_ST_DAC_CLK_IN_SEL; + break; + case CPCAP_DAI_VOICE: + clkfreqreg = CPCAP_REG_CC; + clkfreqshift = CPCAP_BIT_CDC_CLK0; + clkidreg = CPCAP_REG_CDI; + clkidshift = CPCAP_BIT_CLK_IN_SEL; + break; + default: + dev_err(cpcap->codec->dev, "invalid DAI: %d", dai); + return -EINVAL; + } + + /* setup clk id */ + if (clk_id < 0 || clk_id > 1) { + dev_err(cpcap->codec->dev, "invalid clk id %d", clk_id); + return -EINVAL; + } + err = regmap_update_bits(cpcap->regmap, clkidreg, BIT(clkidshift), + clk_id ? BIT(clkidshift) : 0); + if (err) + return err; + + /* enable PLL for Voice DAI */ + if (dai == CPCAP_DAI_VOICE) { + mask = BIT(CPCAP_BIT_CDC_PLL_SEL); + val = BIT(CPCAP_BIT_CDC_PLL_SEL); + err = regmap_update_bits(cpcap->regmap, CPCAP_REG_CDI, + mask, val); + if (err) + return err; + } + + /* setup frequency */ + clkfreqmask = 0x7 << clkfreqshift; + switch (freq) { + case 15360000: + clkfreqval = 0x01 << clkfreqshift; + break; + case 16800000: + clkfreqval = 0x02 << clkfreqshift; + break; + case 19200000: + clkfreqval = 0x03 << clkfreqshift; + break; + case 26000000: + clkfreqval = 0x04 << clkfreqshift; + break; + case 33600000: + clkfreqval = 0x05 << clkfreqshift; + break; + case 38400000: + clkfreqval = 0x06 << clkfreqshift; + break; + default: + dev_err(cpcap->codec->dev, "unsupported freq %u", freq); + return -EINVAL; + } + + err = regmap_update_bits(cpcap->regmap, clkfreqreg, + clkfreqmask, clkfreqval); + if (err) + return err; + + if (dai == CPCAP_DAI_VOICE) { + cpcap->codec_clk_id = clk_id; + cpcap->codec_freq = freq; + } + + return 0; +} + +static int cpcap_set_samprate(struct cpcap_audio *cpcap, enum cpcap_dai dai, + int samplerate) +{ + struct snd_soc_codec *codec = cpcap->codec; + u16 sampreg, sampmask, sampshift, sampval, sampreset; + int err, sampreadval; + + switch (dai) { + case CPCAP_DAI_HIFI: + sampreg = CPCAP_REG_SDAC; + sampshift = CPCAP_BIT_ST_SR0; + sampreset = BIT(CPCAP_BIT_DF_RESET_ST_DAC) | + BIT(CPCAP_BIT_ST_CLOCK_TREE_RESET); + break; + case CPCAP_DAI_VOICE: + sampreg = CPCAP_REG_CC; + sampshift = CPCAP_BIT_CDC_SR0; + sampreset = BIT(CPCAP_BIT_DF_RESET) | + BIT(CPCAP_BIT_CDC_CLOCK_TREE_RESET); + break; + default: + dev_err(codec->dev, "invalid DAI: %d", dai); + return -EINVAL; + } + + sampmask = 0xF << sampshift | sampreset; + switch (samplerate) { + case 48000: + sampval = 0x8 << sampshift; + break; + case 44100: + sampval = 0x7 << sampshift; + break; + case 32000: + sampval = 0x6 << sampshift; + break; + case 24000: + sampval = 0x5 << sampshift; + break; + case 22050: + sampval = 0x4 << sampshift; + break; + case 16000: + sampval = 0x3 << sampshift; + break; + case 12000: + sampval = 0x2 << sampshift; + break; + case 11025: + sampval = 0x1 << sampshift; + break; + case 8000: + sampval = 0x0 << sampshift; + break; + default: + dev_err(codec->dev, "unsupported samplerate %d", samplerate); + return -EINVAL; + } + err = regmap_update_bits(cpcap->regmap, sampreg, + sampmask, sampval | sampreset); + if (err) + return err; + + /* Wait for clock tree reset to complete */ + mdelay(CLOCK_TREE_RESET_TIME); + + err = regmap_read(cpcap->regmap, sampreg, &sampreadval); + if (err) + return err; + + if (sampreadval & sampreset) { + dev_err(codec->dev, "reset self-clear failed: %04x", + sampreadval); + return -EIO; + } + + return 0; +} + +static int cpcap_hifi_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct cpcap_audio *cpcap = snd_soc_codec_get_drvdata(codec); + int rate = params_rate(params); + + dev_dbg(codec->dev, "HiFi setup HW params: rate=%d", rate); + return cpcap_set_samprate(cpcap, CPCAP_DAI_HIFI, rate); +} + +static int cpcap_hifi_set_dai_sysclk(struct snd_soc_dai *codec_dai, int clk_id, + unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct cpcap_audio *cpcap = snd_soc_codec_get_drvdata(codec); + struct device *dev = codec->dev; + + dev_dbg(dev, "HiFi setup sysclk: clk_id=%u, freq=%u", clk_id, freq); + return cpcap_set_sysclk(cpcap, CPCAP_DAI_HIFI, clk_id, freq); +} + +static int cpcap_hifi_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct cpcap_audio *cpcap = snd_soc_codec_get_drvdata(codec); + struct device *dev = codec->dev; + static const u16 reg = CPCAP_REG_SDACDI; + static const u16 mask = + BIT(CPCAP_BIT_SMB_ST_DAC) | + BIT(CPCAP_BIT_ST_CLK_INV) | + BIT(CPCAP_BIT_ST_FS_INV) | + BIT(CPCAP_BIT_ST_DIG_AUD_FS0) | + BIT(CPCAP_BIT_ST_DIG_AUD_FS1) | + BIT(CPCAP_BIT_ST_L_TIMESLOT0) | + BIT(CPCAP_BIT_ST_L_TIMESLOT1) | + BIT(CPCAP_BIT_ST_L_TIMESLOT2) | + BIT(CPCAP_BIT_ST_R_TIMESLOT0) | + BIT(CPCAP_BIT_ST_R_TIMESLOT1) | + BIT(CPCAP_BIT_ST_R_TIMESLOT2); + u16 val = 0x0000; + + dev_dbg(dev, "HiFi setup dai format (%08x)", fmt); + + /* + * "HiFi Playback" should always be configured as + * SND_SOC_DAIFMT_CBM_CFM - codec clk & frm master + * SND_SOC_DAIFMT_I2S - I2S mode + */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + val &= ~BIT(CPCAP_BIT_SMB_ST_DAC); + break; + default: + dev_err(dev, "HiFi dai fmt failed: CPCAP should be master"); + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_IB_IF: + val |= BIT(CPCAP_BIT_ST_FS_INV); + val |= BIT(CPCAP_BIT_ST_CLK_INV); + break; + case SND_SOC_DAIFMT_IB_NF: + val &= ~BIT(CPCAP_BIT_ST_FS_INV); + val |= BIT(CPCAP_BIT_ST_CLK_INV); + break; + case SND_SOC_DAIFMT_NB_IF: + val |= BIT(CPCAP_BIT_ST_FS_INV); + val &= ~BIT(CPCAP_BIT_ST_CLK_INV); + break; + case SND_SOC_DAIFMT_NB_NF: + val &= ~BIT(CPCAP_BIT_ST_FS_INV); + val &= ~BIT(CPCAP_BIT_ST_CLK_INV); + break; + default: + dev_err(dev, "HiFi dai fmt failed: unsupported clock invert mode"); + return -EINVAL; + } + + if (val & BIT(CPCAP_BIT_ST_CLK_INV)) + val &= ~BIT(CPCAP_BIT_ST_CLK_INV); + else + val |= BIT(CPCAP_BIT_ST_CLK_INV); + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + val |= BIT(CPCAP_BIT_ST_DIG_AUD_FS0); + val |= BIT(CPCAP_BIT_ST_DIG_AUD_FS1); + break; + default: + /* 01 - 4 slots network mode */ + val |= BIT(CPCAP_BIT_ST_DIG_AUD_FS0); + val &= ~BIT(CPCAP_BIT_ST_DIG_AUD_FS1); + /* L on slot 1 */ + val |= BIT(CPCAP_BIT_ST_L_TIMESLOT0); + break; + } + + dev_dbg(dev, "HiFi dai format: val=%04x", val); + return regmap_update_bits(cpcap->regmap, reg, mask, val); +} + +static int cpcap_hifi_set_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + struct cpcap_audio *cpcap = snd_soc_codec_get_drvdata(codec); + static const u16 reg = CPCAP_REG_RXSDOA; + static const u16 mask = BIT(CPCAP_BIT_ST_DAC_SW); + u16 val; + + if (mute) + val = 0; + else + val = BIT(CPCAP_BIT_ST_DAC_SW); + + dev_dbg(codec->dev, "HiFi mute: %d", mute); + return regmap_update_bits(cpcap->regmap, reg, mask, val); +} + +static const struct snd_soc_dai_ops cpcap_dai_hifi_ops = { + .hw_params = cpcap_hifi_hw_params, + .set_sysclk = cpcap_hifi_set_dai_sysclk, + .set_fmt = cpcap_hifi_set_dai_fmt, + .digital_mute = cpcap_hifi_set_mute, +}; + +static int cpcap_voice_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct device *dev = codec->dev; + struct cpcap_audio *cpcap = snd_soc_codec_get_drvdata(codec); + static const u16 reg_cdi = CPCAP_REG_CDI; + int rate = params_rate(params); + int channels = params_channels(params); + int direction = substream->stream; + u16 val, mask; + int err; + + dev_dbg(dev, "Voice setup HW params: rate=%d, direction=%d, chan=%d", + rate, direction, channels); + + err = cpcap_set_samprate(cpcap, CPCAP_DAI_VOICE, rate); + if (err) + return err; + + if (direction == SNDRV_PCM_STREAM_CAPTURE) { + mask = 0x0000; + mask |= CPCAP_BIT_MIC1_RX_TIMESLOT0; + mask |= CPCAP_BIT_MIC1_RX_TIMESLOT1; + mask |= CPCAP_BIT_MIC1_RX_TIMESLOT2; + mask |= CPCAP_BIT_MIC2_TIMESLOT0; + mask |= CPCAP_BIT_MIC2_TIMESLOT1; + mask |= CPCAP_BIT_MIC2_TIMESLOT2; + val = 0x0000; + if (channels >= 2) + val = BIT(CPCAP_BIT_MIC1_RX_TIMESLOT0); + err = regmap_update_bits(cpcap->regmap, reg_cdi, mask, val); + if (err) + return err; + } + + return 0; +} + +static int cpcap_voice_set_dai_sysclk(struct snd_soc_dai *codec_dai, int clk_id, + unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct cpcap_audio *cpcap = snd_soc_codec_get_drvdata(codec); + + dev_dbg(codec->dev, "Voice setup sysclk: clk_id=%u, freq=%u", + clk_id, freq); + return cpcap_set_sysclk(cpcap, CPCAP_DAI_VOICE, clk_id, freq); +} + +static int cpcap_voice_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct cpcap_audio *cpcap = snd_soc_codec_get_drvdata(codec); + static const u16 mask = BIT(CPCAP_BIT_SMB_CDC) | + BIT(CPCAP_BIT_CLK_INV) | + BIT(CPCAP_BIT_FS_INV) | + BIT(CPCAP_BIT_CDC_DIG_AUD_FS0) | + BIT(CPCAP_BIT_CDC_DIG_AUD_FS1); + u16 val = 0x0000; + int err; + + dev_dbg(codec->dev, "Voice setup dai format (%08x)", fmt); + + /* + * "Voice Playback" and "Voice Capture" should always be + * configured as SND_SOC_DAIFMT_CBM_CFM - codec clk & frm + * master + */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + val &= ~BIT(CPCAP_BIT_SMB_CDC); + break; + default: + dev_err(codec->dev, "Voice dai fmt failed: CPCAP should be the master"); + val &= ~BIT(CPCAP_BIT_SMB_CDC); + break; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_IB_IF: + val |= BIT(CPCAP_BIT_CLK_INV); + val |= BIT(CPCAP_BIT_FS_INV); + break; + case SND_SOC_DAIFMT_IB_NF: + val |= BIT(CPCAP_BIT_CLK_INV); + val &= ~BIT(CPCAP_BIT_FS_INV); + break; + case SND_SOC_DAIFMT_NB_IF: + val &= ~BIT(CPCAP_BIT_CLK_INV); + val |= BIT(CPCAP_BIT_FS_INV); + break; + case SND_SOC_DAIFMT_NB_NF: + val &= ~BIT(CPCAP_BIT_CLK_INV); + val &= ~BIT(CPCAP_BIT_FS_INV); + break; + default: + dev_err(codec->dev, "Voice dai fmt failed: unsupported clock invert mode"); + break; + } + + if (val & BIT(CPCAP_BIT_CLK_INV)) + val &= ~BIT(CPCAP_BIT_CLK_INV); + else + val |= BIT(CPCAP_BIT_CLK_INV); + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + /* 11 - true I2S mode */ + val |= BIT(CPCAP_BIT_CDC_DIG_AUD_FS0); + val |= BIT(CPCAP_BIT_CDC_DIG_AUD_FS1); + break; + default: + /* 4 timeslots network mode */ + val |= BIT(CPCAP_BIT_CDC_DIG_AUD_FS0); + val &= ~BIT(CPCAP_BIT_CDC_DIG_AUD_FS1); + break; + } + + dev_dbg(codec->dev, "Voice dai format: val=%04x", val); + err = regmap_update_bits(cpcap->regmap, CPCAP_REG_CDI, mask, val); + if (err) + return err; + + cpcap->codec_format = val; + return 0; +} + +static int cpcap_voice_set_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + struct cpcap_audio *cpcap = snd_soc_codec_get_drvdata(codec); + static const u16 reg = CPCAP_REG_RXCOA; + static const u16 mask = BIT(CPCAP_BIT_CDC_SW); + u16 val; + + if (mute) + val = 0; + else + val = BIT(CPCAP_BIT_CDC_SW); + + dev_dbg(codec->dev, "Voice mute: %d", mute); + return regmap_update_bits(cpcap->regmap, reg, mask, val); +}; + +static const struct snd_soc_dai_ops cpcap_dai_voice_ops = { + .hw_params = cpcap_voice_hw_params, + .set_sysclk = cpcap_voice_set_dai_sysclk, + .set_fmt = cpcap_voice_set_dai_fmt, + .digital_mute = cpcap_voice_set_mute, +}; + +static struct snd_soc_dai_driver cpcap_dai[] = { +{ + .id = 0, + .name = "cpcap-hifi", + .playback = { + .stream_name = "HiFi Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FORMAT_S24_LE, + }, + .ops = &cpcap_dai_hifi_ops, +}, +{ + .id = 1, + .name = "cpcap-voice", + .playback = { + .stream_name = "Voice Playback", + .channels_min = 1, + .channels_max = 1, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "Voice Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &cpcap_dai_voice_ops, +}, +}; + +static int cpcap_dai_mux(struct cpcap_audio *cpcap, bool swap_dai_configuration) +{ + u16 hifi_val, voice_val; + u16 hifi_mask = BIT(CPCAP_BIT_DIG_AUD_IN_ST_DAC); + u16 voice_mask = BIT(CPCAP_BIT_DIG_AUD_IN); + int err; + + + + if (!swap_dai_configuration) { + /* Codec on DAI0, HiFi on DAI1 */ + voice_val = 0; + hifi_val = hifi_mask; + } else { + /* Codec on DAI1, HiFi on DAI0 */ + voice_val = voice_mask; + hifi_val = 0; + } + + err = regmap_update_bits(cpcap->regmap, CPCAP_REG_CDI, + voice_mask, voice_val); + if (err) + return err; + + err = regmap_update_bits(cpcap->regmap, CPCAP_REG_SDACDI, + hifi_mask, hifi_val); + if (err) + return err; + + return 0; +} + +static int cpcap_audio_reset(struct snd_soc_codec *codec, + bool swap_dai_configuration) +{ + struct cpcap_audio *cpcap = snd_soc_codec_get_drvdata(codec); + int i, err = 0; + + dev_dbg(codec->dev, "init audio codec"); + + for (i = 0; i < ARRAY_SIZE(cpcap_default_regs); i++) { + err = regmap_update_bits(cpcap->regmap, + cpcap_default_regs[i].reg, + cpcap_default_regs[i].mask, + cpcap_default_regs[i].val); + if (err) + return err; + } + + /* setup default settings */ + err = cpcap_dai_mux(cpcap, swap_dai_configuration); + if (err) + return err; + + err = cpcap_set_sysclk(cpcap, CPCAP_DAI_HIFI, 0, 26000000); + if (err) + return err; + err = cpcap_set_sysclk(cpcap, CPCAP_DAI_VOICE, 0, 26000000); + if (err) + return err; + + err = cpcap_set_samprate(cpcap, CPCAP_DAI_HIFI, 48000); + if (err) + return err; + + err = cpcap_set_samprate(cpcap, CPCAP_DAI_VOICE, 48000); + if (err) + return err; + + return 0; +} + +static int cpcap_soc_probe(struct snd_soc_codec *codec) +{ + struct cpcap_audio *cpcap; + int err; + + cpcap = devm_kzalloc(codec->dev, sizeof(*cpcap), GFP_KERNEL); + if (!cpcap) + return -ENOMEM; + snd_soc_codec_set_drvdata(codec, cpcap); + cpcap->codec = codec; + + cpcap->regmap = dev_get_regmap(codec->dev->parent, NULL); + if (!cpcap->regmap) + return -ENODEV; + snd_soc_codec_init_regmap(codec, cpcap->regmap); + + err = cpcap_get_vendor(codec->dev, cpcap->regmap, &cpcap->vendor); + if (err) + return err; + + return cpcap_audio_reset(codec, false); +} + +static struct snd_soc_codec_driver soc_codec_dev_cpcap = { + .probe = cpcap_soc_probe, + + .component_driver = { + .controls = cpcap_snd_controls, + .num_controls = ARRAY_SIZE(cpcap_snd_controls), + .dapm_widgets = cpcap_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(cpcap_dapm_widgets), + .dapm_routes = intercon, + .num_dapm_routes = ARRAY_SIZE(intercon), + }, +}; + +static int cpcap_codec_probe(struct platform_device *pdev) +{ + struct device_node *codec_node = + of_get_child_by_name(pdev->dev.parent->of_node, "audio-codec"); + + pdev->dev.of_node = codec_node; + + return snd_soc_register_codec(&pdev->dev, &soc_codec_dev_cpcap, + cpcap_dai, ARRAY_SIZE(cpcap_dai)); +} + +static int cpcap_codec_remove(struct platform_device *pdev) +{ + snd_soc_unregister_codec(&pdev->dev); + return 0; +} + +static struct platform_driver cpcap_codec_driver = { + .probe = cpcap_codec_probe, + .remove = cpcap_codec_remove, + .driver = { + .name = "cpcap-codec", + }, +}; +module_platform_driver(cpcap_codec_driver); + +MODULE_ALIAS("platform:cpcap-codec"); +MODULE_DESCRIPTION("ASoC CPCAP codec driver"); +MODULE_AUTHOR("Sebastian Reichel"); +MODULE_LICENSE("GPL v2");
From: Sebastian Reichel sre@kernel.org
Add support for the audio-codec node by converting from devm_of_platform_populate() to devm_mfd_add_devices().
Signed-off-by: Sebastian Reichel sebastian.reichel@collabora.co.uk --- drivers/mfd/motorola-cpcap.c | 51 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-)
diff --git a/drivers/mfd/motorola-cpcap.c b/drivers/mfd/motorola-cpcap.c index d2cc1eabac05..f6c79a4ccb55 100644 --- a/drivers/mfd/motorola-cpcap.c +++ b/drivers/mfd/motorola-cpcap.c @@ -18,6 +18,7 @@ #include <linux/regmap.h> #include <linux/sysfs.h>
+#include <linux/mfd/core.h> #include <linux/mfd/motorola-cpcap.h> #include <linux/spi/spi.h>
@@ -216,6 +217,53 @@ static const struct regmap_config cpcap_regmap_config = { .val_format_endian = REGMAP_ENDIAN_LITTLE, };
+static const struct mfd_cell cpcap_mfd_devices[] = { + { + .name = "cpcap_adc", + .of_compatible = "motorola,mapphone-cpcap-adc", + }, { + .name = "cpcap_battery", + .of_compatible = "motorola,cpcap-battery", + }, { + .name = "cpcap-charger", + .of_compatible = "motorola,mapphone-cpcap-charger", + }, { + .name = "cpcap-regulator", + .of_compatible = "motorola,mapphone-cpcap-regulator", + }, { + .name = "cpcap-rtc", + .of_compatible = "motorola,cpcap-rtc", + }, { + .name = "cpcap-pwrbutton", + .of_compatible = "motorola,cpcap-pwrbutton", + }, { + .name = "cpcap-usb-phy", + .of_compatible = "motorola,mapphone-cpcap-usb-phy", + }, { + .name = "cpcap-led", + .id = 0, + .of_compatible = "motorola,cpcap-led-red", + }, { + .name = "cpcap-led", + .id = 1, + .of_compatible = "motorola,cpcap-led-green", + }, { + .name = "cpcap-led", + .id = 2, + .of_compatible = "motorola,cpcap-led-blue", + }, { + .name = "cpcap-led", + .id = 3, + .of_compatible = "motorola,cpcap-led-adl", + }, { + .name = "cpcap-led", + .id = 4, + .of_compatible = "motorola,cpcap-led-cp", + }, { + .name = "cpcap-codec", + } +}; + static int cpcap_probe(struct spi_device *spi) { const struct of_device_id *match; @@ -260,7 +308,8 @@ static int cpcap_probe(struct spi_device *spi) if (ret) return ret;
- return devm_of_platform_populate(&cpcap->spi->dev); + return devm_mfd_add_devices(&spi->dev, 0, cpcap_mfd_devices, + ARRAY_SIZE(cpcap_mfd_devices), NULL, 0, NULL); }
static struct spi_driver cpcap_driver = {
On Fri, 23 Feb 2018, Sebastian Reichel wrote:
From: Sebastian Reichel sre@kernel.org
Add support for the audio-codec node by converting from devm_of_platform_populate() to devm_mfd_add_devices().
Signed-off-by: Sebastian Reichel sebastian.reichel@collabora.co.uk
drivers/mfd/motorola-cpcap.c | 51 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-)
diff --git a/drivers/mfd/motorola-cpcap.c b/drivers/mfd/motorola-cpcap.c index d2cc1eabac05..f6c79a4ccb55 100644 --- a/drivers/mfd/motorola-cpcap.c +++ b/drivers/mfd/motorola-cpcap.c @@ -18,6 +18,7 @@ #include <linux/regmap.h> #include <linux/sysfs.h>
+#include <linux/mfd/core.h> #include <linux/mfd/motorola-cpcap.h> #include <linux/spi/spi.h>
@@ -216,6 +217,53 @@ static const struct regmap_config cpcap_regmap_config = { .val_format_endian = REGMAP_ENDIAN_LITTLE, };
+static const struct mfd_cell cpcap_mfd_devices[] = {
- {
.name = "cpcap_adc",
.of_compatible = "motorola,mapphone-cpcap-adc",
- }, {
.name = "cpcap_battery",
.of_compatible = "motorola,cpcap-battery",
- }, {
.name = "cpcap-charger",
.of_compatible = "motorola,mapphone-cpcap-charger",
- }, {
.name = "cpcap-regulator",
.of_compatible = "motorola,mapphone-cpcap-regulator",
- }, {
.name = "cpcap-rtc",
.of_compatible = "motorola,cpcap-rtc",
- }, {
.name = "cpcap-pwrbutton",
.of_compatible = "motorola,cpcap-pwrbutton",
- }, {
.name = "cpcap-usb-phy",
.of_compatible = "motorola,mapphone-cpcap-usb-phy",
- }, {
.name = "cpcap-led",
.id = 0,
.of_compatible = "motorola,cpcap-led-red",
- }, {
.name = "cpcap-led",
.id = 1,
.of_compatible = "motorola,cpcap-led-green",
- }, {
.name = "cpcap-led",
.id = 2,
.of_compatible = "motorola,cpcap-led-blue",
- }, {
.name = "cpcap-led",
.id = 3,
.of_compatible = "motorola,cpcap-led-adl",
- }, {
.name = "cpcap-led",
.id = 4,
.of_compatible = "motorola,cpcap-led-cp",
- }, {
.name = "cpcap-codec",
- }
+};
With none of the entries containing platform_data /me wonders why you can't still use devm_of_platform_populate()?
static int cpcap_probe(struct spi_device *spi) { const struct of_device_id *match; @@ -260,7 +308,8 @@ static int cpcap_probe(struct spi_device *spi) if (ret) return ret;
- return devm_of_platform_populate(&cpcap->spi->dev);
- return devm_mfd_add_devices(&spi->dev, 0, cpcap_mfd_devices,
ARRAY_SIZE(cpcap_mfd_devices), NULL, 0, NULL);
}
static struct spi_driver cpcap_driver = {
Hi Lee,
On Wed, Mar 07, 2018 at 04:32:11PM +0000, Lee Jones wrote:
On Fri, 23 Feb 2018, Sebastian Reichel wrote:
+static const struct mfd_cell cpcap_mfd_devices[] = {
[...]
- }, {
.name = "cpcap-led",
.id = 4,
.of_compatible = "motorola,cpcap-led-cp",
- }, {
.name = "cpcap-codec",
- }
+};
With none of the entries containing platform_data /me wonders why you can't still use devm_of_platform_populate()?
Because devm_of_platform_populate works with compatible properties and cpcap-codec does not have one after I removed it for Mark.
-- Sebastian
static int cpcap_probe(struct spi_device *spi) { const struct of_device_id *match; @@ -260,7 +308,8 @@ static int cpcap_probe(struct spi_device *spi) if (ret) return ret;
- return devm_of_platform_populate(&cpcap->spi->dev);
- return devm_mfd_add_devices(&spi->dev, 0, cpcap_mfd_devices,
ARRAY_SIZE(cpcap_mfd_devices), NULL, 0, NULL);
}
static struct spi_driver cpcap_driver = {
-- Lee Jones Linaro Services Technical Lead Linaro.org │ Open source software for ARM SoCs Follow Linaro: Facebook | Twitter | Blog
On Thu, 08 Mar 2018, Sebastian Reichel wrote:
On Wed, Mar 07, 2018 at 04:32:11PM +0000, Lee Jones wrote:
On Fri, 23 Feb 2018, Sebastian Reichel wrote:
+static const struct mfd_cell cpcap_mfd_devices[] = {
[...]
- }, {
.name = "cpcap-led",
.id = 4,
.of_compatible = "motorola,cpcap-led-cp",
- }, {
.name = "cpcap-codec",
- }
+};
With none of the entries containing platform_data /me wonders why you can't still use devm_of_platform_populate()?
Because devm_of_platform_populate works with compatible properties and cpcap-codec does not have one after I removed it for Mark.
Sorry, I missed that conversation. Why was it removed?
static int cpcap_probe(struct spi_device *spi) { const struct of_device_id *match; @@ -260,7 +308,8 @@ static int cpcap_probe(struct spi_device *spi) if (ret) return ret;
- return devm_of_platform_populate(&cpcap->spi->dev);
- return devm_mfd_add_devices(&spi->dev, 0, cpcap_mfd_devices,
ARRAY_SIZE(cpcap_mfd_devices), NULL, 0, NULL);
}
static struct spi_driver cpcap_driver = {
Hi,
On Thu, Mar 08, 2018 at 09:53:15AM +0000, Lee Jones wrote:
On Thu, 08 Mar 2018, Sebastian Reichel wrote:
On Wed, Mar 07, 2018 at 04:32:11PM +0000, Lee Jones wrote:
On Fri, 23 Feb 2018, Sebastian Reichel wrote:
+static const struct mfd_cell cpcap_mfd_devices[] = {
[...]
- }, {
.name = "cpcap-led",
.id = 4,
.of_compatible = "motorola,cpcap-led-cp",
- }, {
.name = "cpcap-codec",
- }
+};
With none of the entries containing platform_data /me wonders why you can't still use devm_of_platform_populate()?
Because devm_of_platform_populate works with compatible properties and cpcap-codec does not have one after I removed it for Mark.
Sorry, I missed that conversation. Why was it removed?
I had it in PATCHv1-PATCHv4. It was removed, since Mark didn't want to have it in the DT ABI.
-- Sebastian
static int cpcap_probe(struct spi_device *spi) { const struct of_device_id *match; @@ -260,7 +308,8 @@ static int cpcap_probe(struct spi_device *spi) if (ret) return ret;
- return devm_of_platform_populate(&cpcap->spi->dev);
- return devm_mfd_add_devices(&spi->dev, 0, cpcap_mfd_devices,
ARRAY_SIZE(cpcap_mfd_devices), NULL, 0, NULL);
}
static struct spi_driver cpcap_driver = {
-- Lee Jones [李琼斯] Linaro Services Technical Lead Linaro.org │ Open source software for ARM SoCs Follow Linaro: Facebook | Twitter | Blog
On Thu, 08 Mar 2018, Sebastian Reichel wrote:
On Thu, Mar 08, 2018 at 09:53:15AM +0000, Lee Jones wrote:
On Thu, 08 Mar 2018, Sebastian Reichel wrote:
On Wed, Mar 07, 2018 at 04:32:11PM +0000, Lee Jones wrote:
On Fri, 23 Feb 2018, Sebastian Reichel wrote:
+static const struct mfd_cell cpcap_mfd_devices[] = {
[...]
- }, {
.name = "cpcap-led",
.id = 4,
.of_compatible = "motorola,cpcap-led-cp",
- }, {
.name = "cpcap-codec",
- }
+};
With none of the entries containing platform_data /me wonders why you can't still use devm_of_platform_populate()?
Because devm_of_platform_populate works with compatible properties and cpcap-codec does not have one after I removed it for Mark.
Sorry, I missed that conversation. Why was it removed?
I had it in PATCHv1-PATCHv4. It was removed, since Mark didn't want to have it in the DT ABI.
Right, but why? Is it not a hardware device? I think converting from devm_of_platform_populate() for one sub-device is a bit drastic.
On Thu, Mar 08, 2018 at 10:48:31AM +0000, Lee Jones wrote:
On Thu, 08 Mar 2018, Sebastian Reichel wrote:
I had it in PATCHv1-PATCHv4. It was removed, since Mark didn't want to have it in the DT ABI.
Right, but why? Is it not a hardware device? I think converting from devm_of_platform_populate() for one sub-device is a bit drastic.
It's not a separate physical device or IP and doesn't exist outside of the MFD, it's just how Linux is currently choosing to divide up the chip right now but that's totally open to change even in future versions of Linux. Clocks are a big issue with audio stuff, right now sections of the clock tree get handled in the CODEC driver but we're going to want to push them out to a clock driver so we're not reimplementing handling for clocks.
On Thu, 08 Mar 2018, Mark Brown wrote:
On Thu, Mar 08, 2018 at 10:48:31AM +0000, Lee Jones wrote:
On Thu, 08 Mar 2018, Sebastian Reichel wrote:
I had it in PATCHv1-PATCHv4. It was removed, since Mark didn't want to have it in the DT ABI.
Right, but why? Is it not a hardware device? I think converting from devm_of_platform_populate() for one sub-device is a bit drastic.
It's not a separate physical device or IP and doesn't exist outside of the MFD, it's just how Linux is currently choosing to divide up the chip right now but that's totally open to change even in future versions of Linux. Clocks are a big issue with audio stuff, right now sections of the clock tree get handled in the CODEC driver but we're going to want to push them out to a clock driver so we're not reimplementing handling for clocks.
How is the CODEC controlled? Does it have its own registers? I guess by "it's not a separate device or IP" you mean that it doesn't. But that begs the question, how does this then device differ from all the other devices (adc, battery, charger, regulator, rtc, pwrbutton, usb-phy and led)?
I'm asking, not to be awkward, but to avoid 50 lines of potentially unnecessary static code. If this is a real (sub-)device, even if it's part of a larger, single device (MFD) then there is no reason why it can't be represented as a single, albeit empty node.
On Fri, Mar 09, 2018 at 08:34:14AM +0000, Lee Jones wrote:
On Thu, 08 Mar 2018, Mark Brown wrote:
On Thu, Mar 08, 2018 at 10:48:31AM +0000, Lee Jones wrote:
On Thu, 08 Mar 2018, Sebastian Reichel wrote:
I had it in PATCHv1-PATCHv4. It was removed, since Mark didn't want to have it in the DT ABI.
Right, but why? Is it not a hardware device? I think converting from devm_of_platform_populate() for one sub-device is a bit drastic.
It's not a separate physical device or IP and doesn't exist outside of the MFD, it's just how Linux is currently choosing to divide up the chip right now but that's totally open to change even in future versions of Linux. Clocks are a big issue with audio stuff, right now sections of the clock tree get handled in the CODEC driver but we're going to want to push them out to a clock driver so we're not reimplementing handling for clocks.
How is the CODEC controlled? Does it have its own registers? I guess by "it's not a separate device or IP" you mean that it doesn't. But that begs the question, how does this then device differ from all the other devices (adc, battery, charger, regulator, rtc, pwrbutton, usb-phy and led)?
The audio codec goes from register 0x0800 - 0x0844, with the next register being defined @ 0x0a00. So it definetly has its own register range. I should note though, that 0x0800 is the audio regulator control. In Linux we handle this one in the regulator driver instead of in the audio driver. But this is mostly hidden in DT (the voltage ranges for VAUDIO are described in the regulator node now).
I'm asking, not to be awkward, but to avoid 50 lines of potentially unnecessary static code. If this is a real (sub-)device, even if it's part of a larger, single device (MFD) then there is no reason why it can't be represented as a single, albeit empty node.
We still have the node, so that it can be used with the ASoC graph binding. It just has no compatible value. Instead it is identified by the node's name.
-- Sebastian
On Fri, Mar 09, 2018 at 08:34:14AM +0000, Lee Jones wrote:
On Thu, 08 Mar 2018, Mark Brown wrote:
Linux. Clocks are a big issue with audio stuff, right now sections of the clock tree get handled in the CODEC driver but we're going to want to push them out to a clock driver so we're not reimplementing handling for clocks.
How is the CODEC controlled? Does it have its own registers? I guess by "it's not a separate device or IP" you mean that it doesn't. But that begs the question, how does this then device differ from all the other devices (adc, battery, charger, regulator, rtc, pwrbutton, usb-phy and led)?
I'm not convinced that this is a particularly good idea for the other functions but anyway... the big thing here is that in these devices the CODEC is generally not the level that the IP is created at, it's a collection of interlinked IPs which usually includes not only audio stuff but also some clocking stuff. The repeatable blocks that could get reused independently are generally a level down from the CODEC level, for example if you look at something like wm8994 there's a couple of identical FLL IPs which could make sense to enumerate individually in the DT. The top level generally doesn't get reused and is purely an encoding of what Linux is currently doing.
Regulators have a bit of this going on as well - you can see it in the wm831x series of drivers where the devices have a bunch of consistently defined regulators that are laid out in various configurations so we have one platform device per physical regulator (not sure if that ever got converted to DT).
Most of the other things you list are more at the level of the FLL where it's a single thing doing a single task that is likely to be repeated somewhere else as a block so there's more sense, though how often that actually happens can be questionable.
I'm asking, not to be awkward, but to avoid 50 lines of potentially unnecessary static code. If this is a real (sub-)device, even if it's part of a larger, single device (MFD) then there is no reason why it can't be represented as a single, albeit empty node.
It never used to be that complicated to define MFD functions? It was a data table and then a function call to register the table en masse. Certainly not anything it's worth defining an ABI to avoid.
* Mark Brown broonie@kernel.org [180309 12:41]:
On Fri, Mar 09, 2018 at 08:34:14AM +0000, Lee Jones wrote:
On Thu, 08 Mar 2018, Mark Brown wrote:
Linux. Clocks are a big issue with audio stuff, right now sections of the clock tree get handled in the CODEC driver but we're going to want to push them out to a clock driver so we're not reimplementing handling for clocks.
How is the CODEC controlled? Does it have its own registers? I guess by "it's not a separate device or IP" you mean that it doesn't. But that begs the question, how does this then device differ from all the other devices (adc, battery, charger, regulator, rtc, pwrbutton, usb-phy and led)?
I'm not convinced that this is a particularly good idea for the other functions but anyway... the big thing here is that in these devices the CODEC is generally not the level that the IP is created at, it's a collection of interlinked IPs which usually includes not only audio stuff but also some clocking stuff. The repeatable blocks that could get reused independently are generally a level down from the CODEC level, for example if you look at something like wm8994 there's a couple of identical FLL IPs which could make sense to enumerate individually in the DT. The top level generally doesn't get reused and is purely an encoding of what Linux is currently doing.
It seems that most of the components in the PMICs are just standard components packed into the PMIC with a control interface provided over I2C or SPI.
So using compatible for things like ADC, RTC and so on makes sense if eventually figure out which ones are shared across various drivers.
Sounds like audio is a bit more fuzzy like Mark describes above. And by not using a compatible for audio we can have things working while not establishing and ABI for something that might change in the future.
Regards,
Tony
Hi,
On Fri, Mar 09, 2018 at 07:11:53AM -0800, Tony Lindgren wrote:
- Mark Brown broonie@kernel.org [180309 12:41]:
On Fri, Mar 09, 2018 at 08:34:14AM +0000, Lee Jones wrote:
On Thu, 08 Mar 2018, Mark Brown wrote:
Linux. Clocks are a big issue with audio stuff, right now sections of the clock tree get handled in the CODEC driver but we're going to want to push them out to a clock driver so we're not reimplementing handling for clocks.
How is the CODEC controlled? Does it have its own registers? I guess by "it's not a separate device or IP" you mean that it doesn't. But that begs the question, how does this then device differ from all the other devices (adc, battery, charger, regulator, rtc, pwrbutton, usb-phy and led)?
I'm not convinced that this is a particularly good idea for the other functions but anyway... the big thing here is that in these devices the CODEC is generally not the level that the IP is created at, it's a collection of interlinked IPs which usually includes not only audio stuff but also some clocking stuff. The repeatable blocks that could get reused independently are generally a level down from the CODEC level, for example if you look at something like wm8994 there's a couple of identical FLL IPs which could make sense to enumerate individually in the DT. The top level generally doesn't get reused and is purely an encoding of what Linux is currently doing.
It seems that most of the components in the PMICs are just standard components packed into the PMIC with a control interface provided over I2C or SPI.
So using compatible for things like ADC, RTC and so on makes sense if eventually figure out which ones are shared across various drivers.
Sounds like audio is a bit more fuzzy like Mark describes above. And by not using a compatible for audio we can have things working while not establishing and ABI for something that might change in the future.
I would agree, if there was no ABI now, but we still have the ABI. Instead of specifying the compatible property, we now need to specify what the node name should look like (see my binding update). That's just a different ABI.
-- Sebastian
Hi,
On Thu, Mar 08, 2018 at 10:48:31AM +0000, Lee Jones wrote:
On Thu, 08 Mar 2018, Sebastian Reichel wrote:
On Thu, Mar 08, 2018 at 09:53:15AM +0000, Lee Jones wrote:
On Thu, 08 Mar 2018, Sebastian Reichel wrote:
On Wed, Mar 07, 2018 at 04:32:11PM +0000, Lee Jones wrote:
On Fri, 23 Feb 2018, Sebastian Reichel wrote:
+static const struct mfd_cell cpcap_mfd_devices[] = {
[...]
- }, {
.name = "cpcap-led",
.id = 4,
.of_compatible = "motorola,cpcap-led-cp",
- }, {
.name = "cpcap-codec",
- }
+};
With none of the entries containing platform_data /me wonders why you can't still use devm_of_platform_populate()?
Because devm_of_platform_populate works with compatible properties and cpcap-codec does not have one after I removed it for Mark.
Sorry, I missed that conversation. Why was it removed?
I had it in PATCHv1-PATCHv4. It was removed, since Mark didn't want to have it in the DT ABI.
Right, but why? Is it not a hardware device? I think converting from devm_of_platform_populate() for one sub-device is a bit drastic.
This must be answered by Mark. Personally I think it makes more sense to have the compatible, since all other cpcap sub-devices have them and it should be consistent IMHO. I changed it to avoid bikeshedding.
The previous discussion was here: https://patchwork.kernel.org/patch/10220035/
-- Sebastian
* Sebastian Reichel sebastian.reichel@collabora.co.uk [180308 09:47]:
Hi Lee,
On Wed, Mar 07, 2018 at 04:32:11PM +0000, Lee Jones wrote:
On Fri, 23 Feb 2018, Sebastian Reichel wrote:
+static const struct mfd_cell cpcap_mfd_devices[] = {
[...]
- }, {
.name = "cpcap-led",
.id = 4,
.of_compatible = "motorola,cpcap-led-cp",
- }, {
.name = "cpcap-codec",
- }
+};
With none of the entries containing platform_data /me wonders why you can't still use devm_of_platform_populate()?
Because devm_of_platform_populate works with compatible properties and cpcap-codec does not have one after I removed it for Mark.
How about keep devm_of_platform_populate() for the ones that already have compatible. Then add a table entry for cpcap-codec only and call devm_mfd_add_devices()?
Regards,
Tony
Hi,
On Thu, Mar 08, 2018 at 09:07:36AM -0800, Tony Lindgren wrote:
- Sebastian Reichel sebastian.reichel@collabora.co.uk [180308 09:47]:
Hi Lee,
On Wed, Mar 07, 2018 at 04:32:11PM +0000, Lee Jones wrote:
On Fri, 23 Feb 2018, Sebastian Reichel wrote:
+static const struct mfd_cell cpcap_mfd_devices[] = {
[...]
- }, {
.name = "cpcap-led",
.id = 4,
.of_compatible = "motorola,cpcap-led-cp",
- }, {
.name = "cpcap-codec",
- }
+};
With none of the entries containing platform_data /me wonders why you can't still use devm_of_platform_populate()?
Because devm_of_platform_populate works with compatible properties and cpcap-codec does not have one after I removed it for Mark.
How about keep devm_of_platform_populate() for the ones that already have compatible. Then add a table entry for cpcap-codec only and call devm_mfd_add_devices()?
This should work. I think it makes sense to wait for Rob Herring's feedback on the binding. My understanding is, that he has the last word on binding questions and I would like to avoid changing this back and forth.
-- Sebastian
On Fri, 09 Mar 2018, Sebastian Reichel wrote:
Hi,
On Thu, Mar 08, 2018 at 09:07:36AM -0800, Tony Lindgren wrote:
- Sebastian Reichel sebastian.reichel@collabora.co.uk [180308 09:47]:
Hi Lee,
On Wed, Mar 07, 2018 at 04:32:11PM +0000, Lee Jones wrote:
On Fri, 23 Feb 2018, Sebastian Reichel wrote:
+static const struct mfd_cell cpcap_mfd_devices[] = {
[...]
- }, {
.name = "cpcap-led",
.id = 4,
.of_compatible = "motorola,cpcap-led-cp",
- }, {
.name = "cpcap-codec",
- }
+};
With none of the entries containing platform_data /me wonders why you can't still use devm_of_platform_populate()?
Because devm_of_platform_populate works with compatible properties and cpcap-codec does not have one after I removed it for Mark.
How about keep devm_of_platform_populate() for the ones that already have compatible. Then add a table entry for cpcap-codec only and call devm_mfd_add_devices()?
This should work. I think it makes sense to wait for Rob Herring's feedback on the binding. My understanding is, that he has the last word on binding questions and I would like to avoid changing this back and forth.
I've been avoiding mix-and-matching mfd_*() and of_platform_*() APIs, else it gets too confusing for new developers.
*If* you make the switch to mfd_*() APIs (not preferable), then I would like to see of_platform_*() removed.
Add node for audio-codec to its DT file.
Signed-off-by: Sebastian Reichel sebastian.reichel@collabora.co.uk --- arch/arm/boot/dts/motorola-cpcap-mapphone.dtsi | 13 +++++++++++++ 1 file changed, 13 insertions(+)
diff --git a/arch/arm/boot/dts/motorola-cpcap-mapphone.dtsi b/arch/arm/boot/dts/motorola-cpcap-mapphone.dtsi index 4d61e5b1334a..ddc7a7bb33c0 100644 --- a/arch/arm/boot/dts/motorola-cpcap-mapphone.dtsi +++ b/arch/arm/boot/dts/motorola-cpcap-mapphone.dtsi @@ -68,6 +68,19 @@ }; };
+ cpcap_audio: audio-codec { + #sound-dai-cells = <1>; + + port@0 { + cpcap_audio_codec0: endpoint { + }; + }; + port@1 { + cpcap_audio_codec1: endpoint { + }; + }; + }; + cpcap_rtc: rtc { compatible = "motorola,cpcap-rtc";
Add sound support to Motorola Droid 4 using simple-soundcard and CPCAP's audio codec. This does not yet correctly represent the whole audio routing, since McBSP3 is also connected to Bluetooth and MDM6600 modem (and probably also 4G modem). These extra DAI links are not yet supported and have not been tested.
Signed-off-by: Sebastian Reichel sebastian.reichel@collabora.co.uk --- arch/arm/boot/dts/omap4-droid4-xt894.dts | 78 ++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+)
diff --git a/arch/arm/boot/dts/omap4-droid4-xt894.dts b/arch/arm/boot/dts/omap4-droid4-xt894.dts index e11a24397163..fc30d375883a 100644 --- a/arch/arm/boot/dts/omap4-droid4-xt894.dts +++ b/arch/arm/boot/dts/omap4-droid4-xt894.dts @@ -118,6 +118,26 @@
}; }; + + soundcard { + compatible = "audio-graph-card"; + label = "Droid 4 Audio"; + + simple-graph-card,widgets = + "Speaker", "Earpiece", + "Speaker", "Loudspeaker", + "Headphone", "Headphone Jack", + "Microphone", "Internal Mic"; + + simple-graph-card,routing = + "Earpiece", "EP", + "Loudspeaker", "SPKR", + "Headphone Jack", "HSL", + "Headphone Jack", "HSR", + "MICR", "Internal Mic"; + + dais = <&mcbsp2_port>, <&mcbsp3_port>; + }; };
&dss { @@ -515,6 +535,24 @@ OMAP4_IOPAD(0x112, PIN_OUTPUT_PULLUP | MUX_MODE5) /* uart4_rts */ >; }; + + mcbsp2_pins: pinmux_mcbsp2_pins { + pinctrl-single,pins = < + OMAP4_IOPAD(0x0f6, PIN_INPUT | MUX_MODE0) /* abe_mcbsp2_clkx */ + OMAP4_IOPAD(0x0f8, PIN_INPUT | MUX_MODE0) /* abe_mcbsp2_dr */ + OMAP4_IOPAD(0x0fa, PIN_OUTPUT | MUX_MODE0) /* abe_mcbsp2_dx */ + OMAP4_IOPAD(0x0fc, PIN_INPUT | MUX_MODE0) /* abe_mcbsp2_fsx */ + >; + }; + + mcbsp3_pins: pinmux_mcbsp3_pins { + pinctrl-single,pins = < + OMAP4_IOPAD(0x106, PIN_INPUT | MUX_MODE1) /* abe_mcbsp3_dr */ + OMAP4_IOPAD(0x108, PIN_OUTPUT | MUX_MODE1) /* abe_mcbsp3_dx */ + OMAP4_IOPAD(0x10a, PIN_INPUT | MUX_MODE1) /* abe_mcbsp3_clkx */ + OMAP4_IOPAD(0x10c, PIN_INPUT | MUX_MODE1) /* abe_mcbsp3_fsx */ + >; + }; };
&omap4_pmx_wkup { @@ -600,3 +638,43 @@ "0", "0", "1"; }; }; + +&mcbsp2 { + #sound-dai-cells = <0>; + pinctrl-names = "default"; + pinctrl-0 = <&mcbsp2_pins>; + status = "okay"; + + mcbsp2_port: port { + cpu_dai2: endpoint { + dai-format = "i2s"; + remote-endpoint = <&cpcap_audio_codec0>; + frame-master = <&cpcap_audio_codec0>; + bitclock-master = <&cpcap_audio_codec0>; + }; + }; +}; + +&mcbsp3 { + #sound-dai-cells = <0>; + pinctrl-names = "default"; + pinctrl-0 = <&mcbsp3_pins>; + status = "okay"; + + mcbsp3_port: port { + cpu_dai3: endpoint { + dai-format = "dsp_a"; + frame-master = <&cpcap_audio_codec1>; + bitclock-master = <&cpcap_audio_codec1>; + remote-endpoint = <&cpcap_audio_codec1>; + }; + }; +}; + +&cpcap_audio_codec0 { + remote-endpoint = <&cpu_dai2>; +}; + +&cpcap_audio_codec1 { + remote-endpoint = <&cpu_dai3>; +};
Hi!
Add sound support to Motorola Droid 4 using simple-soundcard and CPCAP's audio codec. This does not yet correctly represent the whole audio routing, since McBSP3 is also connected to Bluetooth and MDM6600 modem (and probably also 4G modem). These extra DAI links are not yet supported and have not been tested.
I applied the series on top v4.16-rc5, but I can't seem to get it to work:
root@devuan:/sys/devices/platform/usb-phy@1# cat /proc/asound/cards 0 [H58006000encode]: HDMI_58006000_e - HDMI 58006000.encoder HDMI 58006000.encoder root@devuan:/sys/devices/platform/usb-phy@1# [ 10.819122] ALSA device list: [ 10.832641] #0: HDMI 58006000.encoder [ 10.842407] Waiting 10 sec before mounting root device...
I think I should have the required options enabled...
CONFIG_SND_OMAP_SOC=y CONFIG_SND_OMAP_SOC_DMIC=y CONFIG_SND_OMAP_SOC_MCBSP=y CONFIG_SND_OMAP_SOC_MCPDM=y CONFIG_SND_OMAP_SOC_HDMI_AUDIO=y # CONFIG_SND_OMAP_SOC_RX51 is not set # CONFIG_SND_OMAP_SOC_N9 is not set CONFIG_SND_OMAP_SOC_OMAP_TWL4030=y CONFIG_SND_OMAP_SOC_OMAP_ABE_TWL6040=y # CONFIG_SND_SOC_IMG is not set
# # STMicroelectronics STM32 SOC audio support # # CONFIG_SND_SOC_XTFPGA_I2S is not set # CONFIG_ZX_TDM is not set CONFIG_SND_SOC_I2C_AND_SPI=y CONFIG_SND_SOC_CPCAP=y
Any ideas?
Thanks, Pavel
Hi Pavel,
On Thu, Mar 22, 2018 at 09:48:05PM +0100, Pavel Machek wrote:
Hi!
Add sound support to Motorola Droid 4 using simple-soundcard and CPCAP's audio codec. This does not yet correctly represent the whole audio routing, since McBSP3 is also connected to Bluetooth and MDM6600 modem (and probably also 4G modem). These extra DAI links are not yet supported and have not been tested.
I applied the series on top v4.16-rc5, but I can't seem to get it to work:
root@devuan:/sys/devices/platform/usb-phy@1# cat /proc/asound/cards 0 [H58006000encode]: HDMI_58006000_e - HDMI 58006000.encoder HDMI 58006000.encoder root@devuan:/sys/devices/platform/usb-phy@1# [ 10.819122] ALSA device list: [ 10.832641] #0: HDMI 58006000.encoder [ 10.842407] Waiting 10 sec before mounting root device...
I think I should have the required options enabled...
CONFIG_SND_OMAP_SOC=y CONFIG_SND_OMAP_SOC_DMIC=y CONFIG_SND_OMAP_SOC_MCBSP=y CONFIG_SND_OMAP_SOC_MCPDM=y CONFIG_SND_OMAP_SOC_HDMI_AUDIO=y
That's the SoC (OMAP) side.
# CONFIG_SND_OMAP_SOC_RX51 is not set # CONFIG_SND_OMAP_SOC_N9 is not set CONFIG_SND_OMAP_SOC_OMAP_TWL4030=y CONFIG_SND_OMAP_SOC_OMAP_ABE_TWL6040=y
That's not needed (but does not hurt). The Droid 4 has no TWL companion chip and uses CPCAP instead.
# CONFIG_SND_SOC_IMG is not set
# # STMicroelectronics STM32 SOC audio support # # CONFIG_SND_SOC_XTFPGA_I2S is not set # CONFIG_ZX_TDM is not set CONFIG_SND_SOC_I2C_AND_SPI=y CONFIG_SND_SOC_CPCAP=y
That's the codec side.
Any ideas?
You probably did not enable the soundcard driver, that binds against "audio-graph-card" compatible and connects the codec driver with the SoC driver: CONFIG_SND_AUDIO_GRAPH_CARD
-- Sebastian
Hi!
# CONFIG_SND_OMAP_SOC_RX51 is not set # CONFIG_SND_OMAP_SOC_N9 is not set CONFIG_SND_OMAP_SOC_OMAP_TWL4030=y CONFIG_SND_OMAP_SOC_OMAP_ABE_TWL6040=y
That's not needed (but does not hurt). The Droid 4 has no TWL companion chip and uses CPCAP instead.
Aha, thanks. I was doing wild experiments to get something that boots... I turned them off.
Any ideas?
You probably did not enable the soundcard driver, that binds against "audio-graph-card" compatible and connects the codec driver with the SoC driver: CONFIG_SND_AUDIO_GRAPH_CARD
Ok, that certainly changed things; I am now getting this:
Mar 23 08:39:17 devuan kernel: [ 11.633605] asoc-audio-graph-card soundcard: GPIO lookup for consumer pa Mar 23 08:39:17 devuan kernel: [ 11.644958] asoc-audio-graph-card soundcard: using device tr ee for GPIO lookup Mar 23 08:39:17 devuan kernel: [ 11.656677] of_get_named_gpiod_flags: can't parse 'pa-gpios' property of node '/soundcard[0]' Mar 23 08:39:17 devuan kernel: [ 11.669738] of_get_named_gpiod_flags: can't parse 'pa-gpio' property of node '/soundcard[0]' Mar 23 08:39:17 devuan kernel: [ 11.682617] asoc-audio-graph-card soundcard: using lookup ta bles for GPIO lookup Mar 23 08:39:17 devuan kernel: [ 11.694427] asoc-audio-graph-card soundcard: lookup for GPIO pa failed
Indeed the dts does not seem to contain any gpios
soundcard { compatible = "audio-graph-card"; label = "Droid 4 Audio";
simple-graph-card,widgets = "Speaker", "Earpiece", "Speaker", "Loudspeaker", "Headphone", "Headphone Jack", "Microphone", "Internal Mic";
simple-graph-card,routing = "Earpiece", "EP", "Loudspeaker", "SPKR", "Headphone Jack", "HSL", "Headphone Jack", "HSR", "MICR", "Internal Mic";
dais = <&mcbsp2_port>, <&mcbsp3_port>; };
Any more ideas? :-)
Thanks,
Pavel
Hi,
On Fri, Mar 23, 2018 at 11:09:30AM +0100, Pavel Machek wrote:
Hi!
# CONFIG_SND_OMAP_SOC_RX51 is not set # CONFIG_SND_OMAP_SOC_N9 is not set CONFIG_SND_OMAP_SOC_OMAP_TWL4030=y CONFIG_SND_OMAP_SOC_OMAP_ABE_TWL6040=y
That's not needed (but does not hurt). The Droid 4 has no TWL companion chip and uses CPCAP instead.
Aha, thanks. I was doing wild experiments to get something that boots... I turned them off.
Any ideas?
You probably did not enable the soundcard driver, that binds against "audio-graph-card" compatible and connects the codec driver with the SoC driver: CONFIG_SND_AUDIO_GRAPH_CARD
Ok, that certainly changed things; I am now getting this:
Mar 23 08:39:17 devuan kernel: [ 11.633605] asoc-audio-graph-card soundcard: GPIO lookup for consumer pa Mar 23 08:39:17 devuan kernel: [ 11.644958] asoc-audio-graph-card soundcard: using device tr ee for GPIO lookup Mar 23 08:39:17 devuan kernel: [ 11.656677] of_get_named_gpiod_flags: can't parse 'pa-gpios' property of node '/soundcard[0]' Mar 23 08:39:17 devuan kernel: [ 11.669738] of_get_named_gpiod_flags: can't parse 'pa-gpio' property of node '/soundcard[0]' Mar 23 08:39:17 devuan kernel: [ 11.682617] asoc-audio-graph-card soundcard: using lookup ta bles for GPIO lookup Mar 23 08:39:17 devuan kernel: [ 11.694427] asoc-audio-graph-card soundcard: lookup for GPIO pa failed
That's not an error, but just some debug messages. Consider disabling CONFIG_DEBUG_GPIO to get rid of them.
Indeed the dts does not seem to contain any gpios
It's optional and does not exist on Droid 4.
soundcard { compatible = "audio-graph-card"; label = "Droid 4 Audio"; simple-graph-card,widgets = "Speaker", "Earpiece", "Speaker", "Loudspeaker", "Headphone", "Headphone Jack", "Microphone", "Internal Mic"; simple-graph-card,routing = "Earpiece", "EP", "Loudspeaker", "SPKR", "Headphone Jack", "HSL", "Headphone Jack", "HSR", "MICR", "Internal Mic"; dais = <&mcbsp2_port>, <&mcbsp3_port>;
};
Any more ideas? :-)
Make sure, that the device was not proped properly (cat /proc/asound/cards) and post the real error? :)
-- Sebastian
Hi!
Any ideas?
You probably did not enable the soundcard driver, that binds against "audio-graph-card" compatible and connects the codec driver with the SoC driver: CONFIG_SND_AUDIO_GRAPH_CARD
Ok, that certainly changed things; I am now getting this:
Mar 23 08:39:17 devuan kernel: [ 11.633605] asoc-audio-graph-card soundcard: GPIO lookup for consumer pa Mar 23 08:39:17 devuan kernel: [ 11.644958] asoc-audio-graph-card soundcard: using device tr ee for GPIO lookup Mar 23 08:39:17 devuan kernel: [ 11.656677] of_get_named_gpiod_flags: can't parse 'pa-gpios' property of node '/soundcard[0]' Mar 23 08:39:17 devuan kernel: [ 11.669738] of_get_named_gpiod_flags: can't parse 'pa-gpio' property of node '/soundcard[0]' Mar 23 08:39:17 devuan kernel: [ 11.682617] asoc-audio-graph-card soundcard: using lookup ta bles for GPIO lookup Mar 23 08:39:17 devuan kernel: [ 11.694427] asoc-audio-graph-card soundcard: lookup for GPIO pa failed
That's not an error, but just some debug messages. Consider disabling CONFIG_DEBUG_GPIO to get rid of them.
Indeed the dts does not seem to contain any gpios
It's optional and does not exist on Droid 4.
soundcard { compatible = "audio-graph-card"; label = "Droid 4 Audio"; simple-graph-card,widgets = "Speaker", "Earpiece", "Speaker", "Loudspeaker", "Headphone", "Headphone Jack", "Microphone", "Internal Mic"; simple-graph-card,routing = "Earpiece", "EP", "Loudspeaker", "SPKR", "Headphone Jack", "HSL", "Headphone Jack", "HSR", "MICR", "Internal Mic"; dais = <&mcbsp2_port>, <&mcbsp3_port>;
};
Any more ideas? :-)
Make sure, that the device was not proped properly (cat /proc/asound/cards) and post the real error? :)
Unfortunately, that are last messages from asoc-audio-graph-card
Mar 23 08:39:17 devuan kernel: [ 11.694427] asoc-audio-graph-card soundcard: lookup for GPIO pa failed
and no, it is not detected:
root@devuan:/home/user# cat /proc/asound/cards 0 [H58006000encode]: HDMI_58006000_e - HDMI 58006000.encoder HDMI 58006000.encoder root@devuan:/home/user#
Hmm. Is it possible that EPROBEDEFFER is playing with me again?
Pavel
Hi!
Make sure, that the device was not proped properly (cat /proc/asound/cards) and post the real error? :)
Standby... looks like I cleaned up too much of "unused" config.
Hi!
That's not an error, but just some debug messages. Consider disabling CONFIG_DEBUG_GPIO to get rid of them.
Indeed the dts does not seem to contain any gpios
It's optional and does not exist on Droid 4.
soundcard {
...
};
Any more ideas? :-)
Make sure, that the device was not proped properly (cat /proc/asound/cards) and post the real error? :)
Thanks, it is indeed a config problem. When I disabled "unrelated" options, these got disabled, too:
They can be only selected, and there's nothing to select them in Droid 4 case. Pavel
diff --git a/sound/soc/omap/Kconfig b/sound/soc/omap/Kconfig index 2772414..7860b6e 100644 --- a/sound/soc/omap/Kconfig +++ b/sound/soc/omap/Kconfig @@ -4,13 +4,13 @@ config SND_OMAP_SOC select SND_DMAENGINE_PCM
config SND_OMAP_SOC_DMIC - tristate + tristate "dmic"
config SND_OMAP_SOC_MCBSP - tristate + tristate "mcbsp"
config SND_OMAP_SOC_MCPDM - tristate + tristate "mcpdm"
config SND_OMAP_SOC_HDMI_AUDIO tristate "HDMI audio support for OMAP4+ based SoCs"
Hi!
Any more ideas? :-)
Make sure, that the device was not proped properly (cat /proc/asound/cards) and post the real error? :)
Oh and, audio now works for me, so
Tested-by: Pavel Machek pavel@ucw.cz
for the series.
Thanks, Pavel
Hi!
Do you have any ideas what needs to be done for voice calls support? I can talk to the modem and start a call.
Then something like this (untested!) is certainly needed. Probably more...
Thanks, Pavel
diff --git a/sound/soc/codecs/cpcap.c b/sound/soc/codecs/cpcap.c index aedb267..7646f68 100644 --- a/sound/soc/codecs/cpcap.c +++ b/sound/soc/codecs/cpcap.c @@ -1280,6 +1280,60 @@ static int cpcap_voice_hw_params(struct snd_pcm_substream *substream, return 0; }
+static int cpcap_incall_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct device *dev = codec->dev; + struct cpcap_audio *cpcap = snd_soc_codec_get_drvdata(codec); + static const u16 reg_cdi = CPCAP_REG_CDI; + int rate = params_rate(params); + int channels = params_channels(params); + int direction = substream->stream; + u16 val, mask; + int err; + + dev_dbg(dev, "Incall setup HW params: rate=%d, direction=%d, chan=%d", + rate, direction, channels); + + /* codec, 1 in original code is CPCAP_REG_CC + codec, 2 is CPCAP_REG_CDI + codec, 5 is CPCAP_REG_TXI */ + + if (/* cpcap->codec_strm_cnt == */ 1) { + /* + if (pdata->voice_type != VOICE_TYPE_QC) + printk("FIXME: Only MDM6600 support is implemented here.\n"); + */ + + err = regmap_update_bits(cpcap->regmap, CPCAP_REG_CDI, 0xffff, 0xAE02); + if (err) printk("cpcap error %d\n", __LINE__); + + err = regmap_update_bits(cpcap->regmap, CPCAP_REG_CC, 0xffff, 0x6120); + if (err) printk("cpcap error %d\n", __LINE__); + + err = cpcap_set_samprate(cpcap, CPCAP_DAI_VOICE, rate); + printk("Configured stream\n"); + } + + /* (direction == SNDRV_PCM_STREAM_CAPTURE) ?? */ + if (substream->stream) { /* up link */ + unsigned int set = CPCAP_BIT_AUDIHPF_1 | CPCAP_BIT_AUDIHPF_0; + err = regmap_update_bits(cpcap->regmap, CPCAP_REG_CC, set, set); + if (err) printk("cpcap error %d\n", __LINE__); + + set = CPCAP_BIT_MB_ON1L | CPCAP_BIT_MB_ON1R; + err = regmap_update_bits(cpcap->regmap, CPCAP_REG_TXI, set, set); + } else { /* down link */ + unsigned int set = CPCAP_BIT_AUDOHPF_1 | CPCAP_BIT_AUDOHPF_0; + err = regmap_update_bits(cpcap->regmap, CPCAP_REG_CC, set, set); + if (err) printk("cpcap error %d\n", __LINE__); + } + + return err; +} + static int cpcap_voice_set_dai_sysclk(struct snd_soc_dai *codec_dai, int clk_id, unsigned int freq, int dir) { @@ -1394,6 +1448,14 @@ static const struct snd_soc_dai_ops cpcap_dai_voice_ops = { .digital_mute = cpcap_voice_set_mute, };
+static const struct snd_soc_dai_ops cpcap_dai_incall_ops = { + /* FIXME */ + .hw_params = cpcap_incall_hw_params, + .set_sysclk = cpcap_voice_set_dai_sysclk, + .set_fmt = cpcap_voice_set_dai_fmt, + .digital_mute = cpcap_voice_set_mute, +}; + static struct snd_soc_dai_driver cpcap_dai[] = { { .id = 0, @@ -1426,6 +1488,26 @@ static struct snd_soc_dai_driver cpcap_dai[] = { }, .ops = &cpcap_dai_voice_ops, }, +{ + .id = 2, + .name = "cpcap in-call", + .playback = { + .stream_name = "InCall DL", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &cpcap_dai_incall_ops, +}, +/* FIXME: this misses bt-call, cpcap bt, BPvoice, FM */ };
static int cpcap_dai_mux(struct cpcap_audio *cpcap, bool swap_dai_configuration)
Hi,
On Mon, Mar 26, 2018 at 04:16:38PM +0200, Pavel Machek wrote:
Do you have any ideas what needs to be done for voice calls support?
Sure.
I can talk to the modem and start a call.
Doing an AT query is the easy part :)
Then something like this (untested!) is certainly needed. Probably more...
I intentionally left this part out. The CPCAP codec has two DAIs and not 3+. The code you just added is a hack from Motorola. Their driver is full of hacks and it's obvious its author(s) did not fully understand the ASoC APIs.
My understanding is, that we only need to replace the audio-graph-card driver to something more complex. The VOICE DAI needs to be configured differently based on the use case:
call: * mode=i2s * freq=26000000 * samprate=16000 or samprate=8000
bt voice: * mode="4 timeslots network" * freq=26000000 * samprate=8000 * CPCAP_BIT_MIC2_TIMESLOT1
bt call: * mode="4 timeslots network" * freq=19200000 * samprate=8000 * CPCAP_BIT_MIC2_TIMESLOT1
"audio-graph-card" is not capable of doing this. I was planning to try "audio-graph-scu-card" in combination with adding codecs for the modem and BT to DT. If that does not work we need a Droid 4 specific soundcard driver.
Either way "audio-graph-card" is not the correct driver for D4. I added it nevertheless, since it gets audio working for now and there is no risk of DT breakage. Old *.dtb will continue to work with the "audio-graph-card" even after we switch to something else.
-- Sebastian
Thanks, Pavel
diff --git a/sound/soc/codecs/cpcap.c b/sound/soc/codecs/cpcap.c index aedb267..7646f68 100644 --- a/sound/soc/codecs/cpcap.c +++ b/sound/soc/codecs/cpcap.c @@ -1280,6 +1280,60 @@ static int cpcap_voice_hw_params(struct snd_pcm_substream *substream, return 0; }
+static int cpcap_incall_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
+{
- struct snd_soc_codec *codec = dai->codec;
- struct device *dev = codec->dev;
- struct cpcap_audio *cpcap = snd_soc_codec_get_drvdata(codec);
- static const u16 reg_cdi = CPCAP_REG_CDI;
- int rate = params_rate(params);
- int channels = params_channels(params);
- int direction = substream->stream;
- u16 val, mask;
- int err;
- dev_dbg(dev, "Incall setup HW params: rate=%d, direction=%d, chan=%d",
rate, direction, channels);
- /* codec, 1 in original code is CPCAP_REG_CC
codec, 2 is CPCAP_REG_CDI
codec, 5 is CPCAP_REG_TXI */
- if (/* cpcap->codec_strm_cnt == */ 1) {
/*
if (pdata->voice_type != VOICE_TYPE_QC)
printk("FIXME: Only MDM6600 support is implemented here.\n");
*/
err = regmap_update_bits(cpcap->regmap, CPCAP_REG_CDI, 0xffff, 0xAE02);
if (err) printk("cpcap error %d\n", __LINE__);
err = regmap_update_bits(cpcap->regmap, CPCAP_REG_CC, 0xffff, 0x6120);
if (err) printk("cpcap error %d\n", __LINE__);
err = cpcap_set_samprate(cpcap, CPCAP_DAI_VOICE, rate);
printk("Configured stream\n");
- }
- /* (direction == SNDRV_PCM_STREAM_CAPTURE) ?? */
- if (substream->stream) { /* up link */
unsigned int set = CPCAP_BIT_AUDIHPF_1 | CPCAP_BIT_AUDIHPF_0;
err = regmap_update_bits(cpcap->regmap, CPCAP_REG_CC, set, set);
if (err) printk("cpcap error %d\n", __LINE__);
set = CPCAP_BIT_MB_ON1L | CPCAP_BIT_MB_ON1R;
err = regmap_update_bits(cpcap->regmap, CPCAP_REG_TXI, set, set);
- } else { /* down link */
unsigned int set = CPCAP_BIT_AUDOHPF_1 | CPCAP_BIT_AUDOHPF_0;
err = regmap_update_bits(cpcap->regmap, CPCAP_REG_CC, set, set);
if (err) printk("cpcap error %d\n", __LINE__);
- }
- return err;
+}
static int cpcap_voice_set_dai_sysclk(struct snd_soc_dai *codec_dai, int clk_id, unsigned int freq, int dir) { @@ -1394,6 +1448,14 @@ static const struct snd_soc_dai_ops cpcap_dai_voice_ops = { .digital_mute = cpcap_voice_set_mute, };
+static const struct snd_soc_dai_ops cpcap_dai_incall_ops = {
- /* FIXME */
- .hw_params = cpcap_incall_hw_params,
- .set_sysclk = cpcap_voice_set_dai_sysclk,
- .set_fmt = cpcap_voice_set_dai_fmt,
- .digital_mute = cpcap_voice_set_mute,
+};
static struct snd_soc_dai_driver cpcap_dai[] = { { .id = 0, @@ -1426,6 +1488,26 @@ static struct snd_soc_dai_driver cpcap_dai[] = { }, .ops = &cpcap_dai_voice_ops, }, +{
- .id = 2,
- .name = "cpcap in-call",
- .playback = {
.stream_name = "InCall DL",
.channels_min = 1,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
- },
- .capture = {
.stream_name = "Capture",
.channels_min = 1,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
- },
- .ops = &cpcap_dai_incall_ops,
+}, +/* FIXME: this misses bt-call, cpcap bt, BPvoice, FM */ };
static int cpcap_dai_mux(struct cpcap_audio *cpcap, bool swap_dai_configuration)
-- (english) http://www.livejournal.com/~pavelmachek (cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html
Hi!
On Mon, Mar 26, 2018 at 04:16:38PM +0200, Pavel Machek wrote:
Do you have any ideas what needs to be done for voice calls support?
Sure.
:-).
I can talk to the modem and start a call.
Doing an AT query is the easy part :)
Well, yes. Doing it right (ofono, etc) is not that easy.
Then something like this (untested!) is certainly needed. Probably more...
I intentionally left this part out. The CPCAP codec has two DAIs and not 3+. The code you just added is a hack from Motorola. Their driver is full of hacks and it's obvious its author(s) did not fully understand the ASoC APIs.
Ok.
My understanding is, that we only need to replace the audio-graph-card driver to something more complex. The VOICE DAI needs to be configured differently based on the use case:
call:
- mode=i2s
- freq=26000000
- samprate=16000 or samprate=8000
Ok, lets ignore bluetooth for now. There is "normal" mode, and then there's "call" mode, right? Where's "normal" mode configured?
Could we simply always configure the VOICE DAI in the call mode? Yes, it would limit samplerates to 8 or 16k, but .. that's not too important limitation for voice codec...?
"audio-graph-card" is not capable of doing this. I was planning to try "audio-graph-scu-card" in combination with adding codecs for the modem and BT to DT. If that does not work we need a Droid 4 specific soundcard driver.
Either way "audio-graph-card" is not the correct driver for D4. I added it nevertheless, since it gets audio working for now and there is no risk of DT breakage. Old *.dtb will continue to work with the "audio-graph-card" even after we switch to something else.
Well, you'll still cause regressions when you change the dts, as people will have wrong .config for new dts.
Best regards, Pavel
Hi,
On Mon, Mar 26, 2018 at 10:31:54PM +0200, Pavel Machek wrote:
Hi!
On Mon, Mar 26, 2018 at 04:16:38PM +0200, Pavel Machek wrote:
Do you have any ideas what needs to be done for voice calls support?
Sure.
:-).
I can talk to the modem and start a call.
Doing an AT query is the easy part :)
Well, yes. Doing it right (ofono, etc) is not that easy.
I agree. That can be worked on independently.
Then something like this (untested!) is certainly needed. Probably more...
I intentionally left this part out. The CPCAP codec has two DAIs and not 3+. The code you just added is a hack from Motorola. Their driver is full of hacks and it's obvious its author(s) did not fully understand the ASoC APIs.
Ok.
My understanding is, that we only need to replace the audio-graph-card driver to something more complex. The VOICE DAI needs to be configured differently based on the use case:
call:
- mode=i2s
- freq=26000000
- samprate=16000 or samprate=8000
Ok, lets ignore bluetooth for now. There is "normal" mode, and then there's "call" mode, right? Where's "normal" mode configured?
mcbsp3_port: port { cpu_dai3: endpoint { dai-format = "dsp_a"; frame-master = <&cpcap_audio_codec1>; bitclock-master = <&cpcap_audio_codec1>; remote-endpoint = <&cpcap_audio_codec1>; }; };
Could we simply always configure the VOICE DAI in the call mode? Yes, it would limit samplerates to 8 or 16k, but .. that's not too important limitation for voice codec...?
I think we could configure "normal" mode to use the same settings as call mode. But ASoC will disable CPCAP, since there will be no active user without ASoC knowing about the modem.
"audio-graph-card" is not capable of doing this. I was planning to try "audio-graph-scu-card" in combination with adding codecs for the modem and BT to DT. If that does not work we need a Droid 4 specific soundcard driver.
Either way "audio-graph-card" is not the correct driver for D4. I added it nevertheless, since it gets audio working for now and there is no risk of DT breakage. Old *.dtb will continue to work with the "audio-graph-card" even after we switch to something else.
Well, you'll still cause regressions when you change the dts, as people will have wrong .config for new dts.
That's correct, the .config needs to change. I think that's ok, because the only alternative is having no audio support at all until the soundcard driver is ready. This means, that you also need to change .config once its ready (in case you want to use it).
-- Sebastian
Hi!
My understanding is, that we only need to replace the audio-graph-card driver to something more complex. The VOICE DAI needs to be configured differently based on the use case:
call:
- mode=i2s
- freq=26000000
- samprate=16000 or samprate=8000
Ok, lets ignore bluetooth for now. There is "normal" mode, and then there's "call" mode, right? Where's "normal" mode configured?
mcbsp3_port: port { cpu_dai3: endpoint { dai-format = "dsp_a"; frame-master = <&cpcap_audio_codec1>; bitclock-master = <&cpcap_audio_codec1>; remote-endpoint = <&cpcap_audio_codec1>; }; };
Hmm. Can't tell mode and freq here easily. Does it default to i2s / 26000000?
Could we simply always configure the VOICE DAI in the call mode? Yes, it would limit samplerates to 8 or 16k, but .. that's not too important limitation for voice codec...?
I think we could configure "normal" mode to use the same settings as call mode. But ASoC will disable CPCAP, since there will be no active user without ASoC knowing about the modem.
Ok, but then we can just start playback/recording with the call, and everything should work, no?
But Motorola code seems to do different magic according to modem type, so things may not be as easy.
if (pdata->voice_type == VOICE_TYPE_STE) { /* STE_M570 */ ret = cpcap_audio_reg_write(codec, 2, 0xAE06); if (rate == 16000) ret |= cpcap_audio_reg_write(codec, 1, 0x8720); else ret |= cpcap_audio_reg_write(codec, 1, 0x8120); } else if (pdata->voice_type == VOICE_TYPE_QC) { /* MDM6600 */ ret = cpcap_audio_reg_write(codec, 2, 0xAE02); if (rate == 16000) { ret |= cpcap_audio_reg_write(codec, 1, 0x6720); } else { ret |= cpcap_audio_reg_write(codec, 1, 0x6120); }
"audio-graph-card" is not capable of doing this. I was planning to try "audio-graph-scu-card" in combination with adding codecs for the modem and BT to DT. If that does not work we need a Droid 4 specific soundcard driver.
Either way "audio-graph-card" is not the correct driver for D4. I added it nevertheless, since it gets audio working for now and there is no risk of DT breakage. Old *.dtb will continue to work with the "audio-graph-card" even after we switch to something else.
Well, you'll still cause regressions when you change the dts, as people will have wrong .config for new dts.
That's correct, the .config needs to change. I think that's ok, because the only alternative is having no audio support at all until the soundcard driver is ready. This means, that you also need to change .config once its ready (in case you want to use it).
Ok, another problem seems to be that spaces in audio device name seem to confuse the userland.
Aha, here:
user@devuan:~$ cat /proc/asound/cards 0 [H58006000encode]: HDMI_58006000_e - HDMI 58006000.encoder HDMI 58006000.encoder 1 [Audio ]: Droid_4_Audio - Droid 4 Audio Droid 4 Audio See how the card is refered simply as "Audio"? Same thing then happens in alsa "state" file:
state.Audio { control.1 {
.
Best regards,
Pavel
* Pavel Machek pavel@ucw.cz [180327 20:42]:
But Motorola code seems to do different magic according to modem type, so things may not be as easy.
if (pdata->voice_type == VOICE_TYPE_STE) { /* STE_M570 */ ret = cpcap_audio_reg_write(codec, 2, 0xAE06); if (rate == 16000) ret |= cpcap_audio_reg_write(codec, 1, 0x8720); else ret |= cpcap_audio_reg_write(codec, 1, 0x8120); } else if (pdata->voice_type == VOICE_TYPE_QC) { /* MDM6600 */ ret = cpcap_audio_reg_write(codec, 2, 0xAE02); if (rate == 16000) { ret |= cpcap_audio_reg_write(codec, 1, 0x6720); } else { ret |= cpcap_audio_reg_write(codec, 1, 0x6120); }
The parts not related to mdm6600 can be ignored until somebody gets some device other modems working with mainline kernel.
And if there are any refrerences to W3GLTE, or wrigley, it supposedly never worked for voice calls and only data.
Regards,
Tony
On Mon, Mar 26, 2018 at 05:58:28PM +0200, Sebastian Reichel wrote:
My understanding is, that we only need to replace the audio-graph-card driver to something more complex. The VOICE DAI needs to be configured differently based on the use case:
No, this is exactly the sort of use case with multiple DAIs that the graph card is intended to enable over the old simple-card.
"audio-graph-card" is not capable of doing this. I was planning to try "audio-graph-scu-card" in combination with adding codecs for the modem and BT to DT. If that does not work we need a Droid 4 specific soundcard driver.
That's just the audio graph card with some hacks for working with a DPCM based SoC driver set.
Hi Mark,
On Tue, Mar 27, 2018 at 08:14:41PM +0800, Mark Brown wrote:
On Mon, Mar 26, 2018 at 05:58:28PM +0200, Sebastian Reichel wrote:
My understanding is, that we only need to replace the audio-graph-card driver to something more complex. The VOICE DAI needs to be configured differently based on the use case:
No, this is exactly the sort of use case with multiple DAIs that the graph card is intended to enable over the old simple-card.
Oh nice. So the Droid 4 hardware wiring looks like this (at least according to my understanding):
+----------+ +-------------+ | OMAP4 | | CPCAP | | | | | | [McBSP2] | <-----> | [HiFi DAI] | | | | | | [McBSP3] | <--+--> | [Voice DAI] | | | | | | +----------+ | +-------------+ | +----------+ | +-------------+ | MDM6600 | | | WL1285 | | | | | | | [DAI] | <--+--> | [DAI] | | | | | +----------+ +-------------+
Legend: OMAP4 = SoC running Linux CPCAP = Audio codec MDM6600 = Baseband WL1285 = Bluetooth
Re-reading the audio-graph-card binding document I still don't see how the network (OMAP.McBSP3, CPCAP.Voice, MDM6600, WL1285) is supposed to look like. It seems to expect point-to-point DAI connections.
"audio-graph-card" is not capable of doing this. I was planning to try "audio-graph-scu-card" in combination with adding codecs for the modem and BT to DT. If that does not work we need a Droid 4 specific soundcard driver.
That's just the audio graph card with some hacks for working with a DPCM based SoC driver set.
Ok.
-- Sebastian
On Wed, Mar 28, 2018 at 12:22:37AM +0200, Sebastian Reichel wrote:
On Tue, Mar 27, 2018 at 08:14:41PM +0800, Mark Brown wrote:
No, this is exactly the sort of use case with multiple DAIs that the graph card is intended to enable over the old simple-card.
+----------+ +-------------+ | OMAP4 | | CPCAP | | | | | | [McBSP2] | <-----> | [HiFi DAI] | | | | | | [McBSP3] | <--+--> | [Voice DAI] | | | | | | +----------+ | +-------------+ | +----------+ | +-------------+ | MDM6600 | | | WL1285 | | | | | | | [DAI] | <--+--> | [DAI] | | | | | +----------+ +-------------+
Legend: OMAP4 = SoC running Linux CPCAP = Audio codec MDM6600 = Baseband WL1285 = Bluetooth
Re-reading the audio-graph-card binding document I still don't see how the network (OMAP.McBSP3, CPCAP.Voice, MDM6600, WL1285) is supposed to look like. It seems to expect point-to-point DAI connections.
Ugh, a TDM mux? That's really unusual and not particularly supported yet, you'd need to extend the graph card to do it. It's where things should end up for a generic card though.
Hi,
On Wed, Mar 28, 2018 at 10:29:10AM +0800, Mark Brown wrote:
On Wed, Mar 28, 2018 at 12:22:37AM +0200, Sebastian Reichel wrote:
On Tue, Mar 27, 2018 at 08:14:41PM +0800, Mark Brown wrote:
No, this is exactly the sort of use case with multiple DAIs that the graph card is intended to enable over the old simple-card.
+----------+ +-------------+ | OMAP4 | | CPCAP | | | | | | [McBSP2] | <-----> | [HiFi DAI] | | | | | | [McBSP3] | <--+--> | [Voice DAI] | | | | | | +----------+ | +-------------+ | +----------+ | +-------------+ | MDM6600 | | | WL1285 | | | | | | | [DAI] | <--+--> | [DAI] | | | | | +----------+ +-------------+
Legend: OMAP4 = SoC running Linux CPCAP = Audio codec MDM6600 = Baseband WL1285 = Bluetooth
Re-reading the audio-graph-card binding document I still don't see how the network (OMAP.McBSP3, CPCAP.Voice, MDM6600, WL1285) is supposed to look like. It seems to expect point-to-point DAI connections.
Ugh, a TDM mux?
Yes, at least that's how I understood Motorola's code.
That's really unusual and not particularly supported yet, you'd need to extend the graph card to do it. It's where things should end up for a generic card though.
Motorola's driver provided the following modes:
OMAP4 <-> CPCAP (voice recording) MDM6600 <-> CPCAP (voice call, CPU not involved) OMAP4 <-> WL1285 (bluetooth HFP/HSP) MDM6600 <-> WL1285 (bluetooth voice call)
In case of the last two variants, the bus clock is provided by CPCAP, so it needs to be enabled for any audio stream. I suppose the codec <-> codec as part of TDM is out of scope for the graph card and we need a Droid 4 specific card driver?
-- Sebastian
Hi,
* Sebastian Reichel sebastian.reichel@collabora.co.uk [180328 14:03]:
Hi,
On Wed, Mar 28, 2018 at 10:29:10AM +0800, Mark Brown wrote:
On Wed, Mar 28, 2018 at 12:22:37AM +0200, Sebastian Reichel wrote:
On Tue, Mar 27, 2018 at 08:14:41PM +0800, Mark Brown wrote:
No, this is exactly the sort of use case with multiple DAIs that the graph card is intended to enable over the old simple-card.
+----------+ +-------------+ | OMAP4 | | CPCAP | | | | | | [McBSP2] | <-----> | [HiFi DAI] | | | | | | [McBSP3] | <--+--> | [Voice DAI] | | | | | | +----------+ | +-------------+ | +----------+ | +-------------+ | MDM6600 | | | WL1285 | | | | | | | [DAI] | <--+--> | [DAI] | | | | | +----------+ +-------------+
Legend: OMAP4 = SoC running Linux CPCAP = Audio codec MDM6600 = Baseband WL1285 = Bluetooth
Re-reading the audio-graph-card binding document I still don't see how the network (OMAP.McBSP3, CPCAP.Voice, MDM6600, WL1285) is supposed to look like. It seems to expect point-to-point DAI connections.
Ugh, a TDM mux?
Yes, at least that's how I understood Motorola's code.
Hmm is there some active component doing the muxing then? Maybe the "AT+CMUT=0" part below?
That's really unusual and not particularly supported yet, you'd need to extend the graph card to do it. It's where things should end up for a generic card though.
Motorola's driver provided the following modes:
OMAP4 <-> CPCAP (voice recording) MDM6600 <-> CPCAP (voice call, CPU not involved) OMAP4 <-> WL1285 (bluetooth HFP/HSP) MDM6600 <-> WL1285 (bluetooth voice call)
In case of the last two variants, the bus clock is provided by CPCAP, so it needs to be enabled for any audio stream. I suppose the codec <-> codec as part of TDM is out of scope for the graph card and we need a Droid 4 specific card driver?
Hmm well I got audio call hacked to work as a proof of concept hack, see below. Maybe it can be used to verify some of the assumptions above.
Then.. To split the work a bit, can you guys maybe try to decode the cpcap register values and try to do a proper ASoC driver patch?
Meanwhile, I can try to make voice calls more reproducable with qmi or MM for example instead of just n_gsm.. And then I'll try to fix my n_gsm pile of hacks for posting..
Cheers,
Tony
8< --------------------------
From tony Mon Sep 17 00:00:00 2001
From: Tony Lindgren tony@atomide.com Date: Wed, 28 Mar 2018 08:29:38 -0700 Subject: [PATCH] NOT FOR MERGING: Quick hack for droid 4 mdm6600 voice call
Here's quick hack to allow making a voice call on mdm6600 based on diffing the cpcap registers in Android. The patch just keeps overwriting the cpcap values every second so it's nowhere near usable for merging, just a test patch.
Looks like the cpcap register changes during a speaker phone audio call are:
@@ -510,17 +510,17 @@ 07f4: 0000 07f8: 0000 07fc: 0000 -0800: 0065 -0804: 0000 -0808: 0040 +0800: 0025 # CPCAP_REG_VAUDIOC VAUDIO Control +0804: 60cf # CPCAP_REG_CC Codec Control, moto cpcap.c:1337 sets 0x0093? +0808: ae0a # CPCAP_REG_CDI Codec Digital Interface 080c: 0000 0810: 0004 -0814: 0804 -0818: 079c -081c: 0000 -0820: 0924 -0824: 0000 -0828: 0000 +0814: 0cc0 # CPCAP_REG_TXI TX Inputs, moto cpcap.c:1340 sets 0x0CC6? +0818: 0610 # CPCAP_REG_TXMP TX MIC PGA's, moto cpcap.c:1343 sets 0x0273? +081c: 0006 # CPCAP_REG_RXOA RX Output Amplifiers +0820: 0b2c # CPCAP_REG_RXVC RX Volume Control +0824: 0606 # CPCAP_REG_RXCOA RX Codec to Output Amps +0828: 0600 # CPCAP_REG_RXSDOA RX Stereo DAC to Output Amps 082c: 0400 0830: 0000 0834: 0030
I wonder if mdm6600 is the i2s master during the voice call?
Then using the n_gsm ts 27.010 uart mux, I dial:
./ngsm-rw 1 "AT+CFUN=1" # connect to network U0001+CFUN:OK ./ngsm-rw 2 "AT+CMUT=0" # unmute speaker over ch2, do this over qmi? U0001+CMUT:OK ./ngsm-rw 1 "ATD#123" # dial number U0001D:OK
And I do hear a voice talking over the speakerphone :) Sorry have not tested the mic yet..
FYI, the ngsm-rw script I use is just:
#!/bin/sh
if [ "${1}" == "" ]; then echo "Usage: $0 port command" exit 1 fi
port=${1} command=${2}
exec 3<>/dev/gsmtty${port} printf "U0001%s\r\0" ${command} >&3 read result <&3 exec 3>&- exec 3<&-
echo ${result}
My n_gsm patches are not quite ready yet sorry.. But meanwhile hopefully this can be somehow also done using qmi. At least "AT+CMUT=0" fails over ttyUSB4. --- sound/soc/codecs/cpcap.c | 70 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-)
diff --git a/sound/soc/codecs/cpcap.c b/sound/soc/codecs/cpcap.c --- a/sound/soc/codecs/cpcap.c +++ b/sound/soc/codecs/cpcap.c @@ -251,6 +251,8 @@ struct cpcap_audio { int codec_clk_id; int codec_freq; int codec_format; + + struct delayed_work work; };
static int cpcap_st_workaround(struct snd_soc_dapm_widget *w, @@ -1500,6 +1502,57 @@ static int cpcap_audio_reset(struct snd_soc_component *component, return 0; }
+static void cpcap_soc_work(struct work_struct *work) +{ + struct cpcap_audio *cpcap = container_of(work, + struct cpcap_audio, + work.work); + struct device *dev = cpcap->component->dev; + int error; + + dev_info(dev, "Somebody do a proper driver please %s\n", __func__); + + error = regmap_update_bits(cpcap->regmap, CPCAP_REG_VAUDIOC, + 0xffff, 0x0025); + if (error) + goto out; + error = regmap_update_bits(cpcap->regmap, CPCAP_REG_CC, + 0xffff, 0x60cf); + if (error) + goto out; + error = regmap_update_bits(cpcap->regmap, CPCAP_REG_CDI, + 0xffff, 0xae0a); + if (error) + goto out; + error = regmap_update_bits(cpcap->regmap, CPCAP_REG_TXI, + 0xffff, 0x0cc0); + if (error) + goto out; + error = regmap_update_bits(cpcap->regmap, CPCAP_REG_TXMP, + 0xffff, 0x0610); + if (error) + goto out; + error = regmap_update_bits(cpcap->regmap, CPCAP_REG_RXOA, + 0xffff, 0x0006); + if (error) + goto out; + error = regmap_update_bits(cpcap->regmap, CPCAP_REG_RXVC, + 0xffff, 0x0b2c); + if (error) + goto out; + error = regmap_update_bits(cpcap->regmap, CPCAP_REG_RXCOA, + 0xffff, 0x0606); + if (error) + goto out; + error = regmap_update_bits(cpcap->regmap, CPCAP_REG_RXSDOA, + 0xffff, 0x0600); + if (error) + goto out; + +out: + schedule_delayed_work(&cpcap->work, msecs_to_jiffies(1000)); +} + static int cpcap_soc_probe(struct snd_soc_component *component) { struct cpcap_audio *cpcap; @@ -1520,11 +1573,26 @@ static int cpcap_soc_probe(struct snd_soc_component *component) if (err) return err;
- return cpcap_audio_reset(component, false); + err = cpcap_audio_reset(component, false); + if (err) + return err; + + INIT_DELAYED_WORK(&cpcap->work, cpcap_soc_work); + schedule_delayed_work(&cpcap->work, msecs_to_jiffies(1000)); + + return 0; +} + +static void cpcap_soc_remove(struct snd_soc_component *component) +{ + struct cpcap_audio *cpcap = snd_soc_component_get_drvdata(component); + + cancel_delayed_work_sync(&cpcap->work); }
static struct snd_soc_component_driver soc_codec_dev_cpcap = { .probe = cpcap_soc_probe, + .remove = cpcap_soc_remove, .controls = cpcap_snd_controls, .num_controls = ARRAY_SIZE(cpcap_snd_controls), .dapm_widgets = cpcap_dapm_widgets,
Hi,
On Wed, Mar 28, 2018 at 06:45:07PM -0700, Tony Lindgren wrote:
Hi,
- Sebastian Reichel sebastian.reichel@collabora.co.uk [180328 14:03]:
Hi,
On Wed, Mar 28, 2018 at 10:29:10AM +0800, Mark Brown wrote:
On Wed, Mar 28, 2018 at 12:22:37AM +0200, Sebastian Reichel wrote:
On Tue, Mar 27, 2018 at 08:14:41PM +0800, Mark Brown wrote:
No, this is exactly the sort of use case with multiple DAIs that the graph card is intended to enable over the old simple-card.
+----------+ +-------------+ | OMAP4 | | CPCAP | | | | | | [McBSP2] | <-----> | [HiFi DAI] | | | | | | [McBSP3] | <--+--> | [Voice DAI] | | | | | | +----------+ | +-------------+ | +----------+ | +-------------+ | MDM6600 | | | WL1285 | | | | | | | [DAI] | <--+--> | [DAI] | | | | | +----------+ +-------------+
Legend: OMAP4 = SoC running Linux CPCAP = Audio codec MDM6600 = Baseband WL1285 = Bluetooth
Re-reading the audio-graph-card binding document I still don't see how the network (OMAP.McBSP3, CPCAP.Voice, MDM6600, WL1285) is supposed to look like. It seems to expect point-to-point DAI connections.
Ugh, a TDM mux?
Yes, at least that's how I understood Motorola's code.
Hmm is there some active component doing the muxing then? Maybe the "AT+CMUT=0" part below?
I don't think, that there is a special hardware mux. I think each device is configured to use a proper timeslot and/or is being used exclusively.
That's really unusual and not particularly supported yet, you'd need to extend the graph card to do it. It's where things should end up for a generic card though.
Motorola's driver provided the following modes:
OMAP4 <-> CPCAP (voice recording) MDM6600 <-> CPCAP (voice call, CPU not involved) OMAP4 <-> WL1285 (bluetooth HFP/HSP) MDM6600 <-> WL1285 (bluetooth voice call)
In case of the last two variants, the bus clock is provided by CPCAP, so it needs to be enabled for any audio stream. I suppose the codec <-> codec as part of TDM is out of scope for the graph card and we need a Droid 4 specific card driver?
Hmm well I got audio call hacked to work as a proof of concept hack, see below. Maybe it can be used to verify some of the assumptions above.
Your proof of concept verifies the assumption, that the modem is connected to the CPCAP voice DAI. This patchset is a proof, that the voice DAI is connected to OMAP. So we can tell for sure, that this is not a common direct DAI-to-DAI connection.
Then.. To split the work a bit, can you guys maybe try to decode the cpcap register values and try to do a proper ASoC driver patch?
Meanwhile, I can try to make voice calls more reproducable with qmi or MM for example instead of just n_gsm.. And then I'll try to fix my n_gsm pile of hacks for posting..
Cheers,
Tony
8< -------------------------- From tony Mon Sep 17 00:00:00 2001 From: Tony Lindgren tony@atomide.com Date: Wed, 28 Mar 2018 08:29:38 -0700 Subject: [PATCH] NOT FOR MERGING: Quick hack for droid 4 mdm6600 voice call
Here's quick hack to allow making a voice call on mdm6600 based on diffing the cpcap registers in Android. The patch just keeps overwriting the cpcap values every second so it's nowhere near usable for merging, just a test patch.
Looks like the cpcap register changes during a speaker phone audio call are:
@@ -510,17 +510,17 @@ 07f4: 0000 07f8: 0000 07fc: 0000 -0800: 0065 -0804: 0000 -0808: 0040 +0800: 0025 # CPCAP_REG_VAUDIOC VAUDIO Control
enable vaudio (obviously required :))
+0804: 60cf # CPCAP_REG_CC Codec Control, moto cpcap.c:1337 sets 0x0093?
0x6000 => clkfreq=19200000
The following bits are automatically set via DAPM by cpcap codec, once it is used:
0x00c0 => "ADC Left" + "DAC Voice" 0x000f => "Highpass Filter TX" + "Highpass Filter RX"
+0808: ae0a # CPCAP_REG_CDI Codec Digital Interface
0xa000 => enable PLL & use clock 1
This should be used by default for VOICE DAI.
0x0e00 => "Voice DAI Clock"=1 (handled by DAPM) , mode=I2S 0x000a => CPCAP_BIT_CLK_INV | CPCAP_BIT_MIC1_RX_TIMESLOT0
080c: 0000 0810: 0004 -0814: 0804 -0818: 079c -081c: 0000 -0820: 0924 -0824: 0000 -0828: 0000 +0814: 0cc0 # CPCAP_REG_TXI TX Inputs, moto cpcap.c:1340 sets 0x0CC6? +0818: 0610 # CPCAP_REG_TXMP TX MIC PGA's, moto cpcap.c:1343 sets 0x0273? +081c: 0006 # CPCAP_REG_RXOA RX Output Amplifiers +0820: 0b2c # CPCAP_REG_RXVC RX Volume Control +0824: 0606 # CPCAP_REG_RXCOA RX Codec to Output Amps +0828: 0600 # CPCAP_REG_RXSDOA RX Stereo DAC to Output Amps
This configures the loudspeaker, mics and volume and enables the required clocks/DACs/... This is already covered by the cpcap codec driver. You just need to configure everything correctly in alsamixer.
082c: 0400 0830: 0000 0834: 0030
I wonder if mdm6600 is the i2s master during the voice call?
I think cpcap is always the clock and frame master, but I think mdm6600 is the remote side and OMAP is not involved at all.
-- Sebastian
Then using the n_gsm ts 27.010 uart mux, I dial:
./ngsm-rw 1 "AT+CFUN=1" # connect to network U0001+CFUN:OK ./ngsm-rw 2 "AT+CMUT=0" # unmute speaker over ch2, do this over qmi? U0001+CMUT:OK ./ngsm-rw 1 "ATD#123" # dial number U0001D:OK
And I do hear a voice talking over the speakerphone :) Sorry have not tested the mic yet..
FYI, the ngsm-rw script I use is just:
#!/bin/sh
if [ "${1}" == "" ]; then echo "Usage: $0 port command" exit 1 fi
port=${1} command=${2}
exec 3<>/dev/gsmtty${port} printf "U0001%s\r\0" ${command} >&3 read result <&3 exec 3>&- exec 3<&-
echo ${result}
My n_gsm patches are not quite ready yet sorry.. But meanwhile hopefully this can be somehow also done using qmi. At least "AT+CMUT=0" fails over ttyUSB4.
sound/soc/codecs/cpcap.c | 70 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-)
diff --git a/sound/soc/codecs/cpcap.c b/sound/soc/codecs/cpcap.c --- a/sound/soc/codecs/cpcap.c +++ b/sound/soc/codecs/cpcap.c @@ -251,6 +251,8 @@ struct cpcap_audio { int codec_clk_id; int codec_freq; int codec_format;
- struct delayed_work work;
};
static int cpcap_st_workaround(struct snd_soc_dapm_widget *w, @@ -1500,6 +1502,57 @@ static int cpcap_audio_reset(struct snd_soc_component *component, return 0; }
+static void cpcap_soc_work(struct work_struct *work) +{
- struct cpcap_audio *cpcap = container_of(work,
struct cpcap_audio,
work.work);
- struct device *dev = cpcap->component->dev;
- int error;
- dev_info(dev, "Somebody do a proper driver please %s\n", __func__);
- error = regmap_update_bits(cpcap->regmap, CPCAP_REG_VAUDIOC,
0xffff, 0x0025);
- if (error)
goto out;
- error = regmap_update_bits(cpcap->regmap, CPCAP_REG_CC,
0xffff, 0x60cf);
- if (error)
goto out;
- error = regmap_update_bits(cpcap->regmap, CPCAP_REG_CDI,
0xffff, 0xae0a);
- if (error)
goto out;
- error = regmap_update_bits(cpcap->regmap, CPCAP_REG_TXI,
0xffff, 0x0cc0);
- if (error)
goto out;
- error = regmap_update_bits(cpcap->regmap, CPCAP_REG_TXMP,
0xffff, 0x0610);
- if (error)
goto out;
- error = regmap_update_bits(cpcap->regmap, CPCAP_REG_RXOA,
0xffff, 0x0006);
- if (error)
goto out;
- error = regmap_update_bits(cpcap->regmap, CPCAP_REG_RXVC,
0xffff, 0x0b2c);
- if (error)
goto out;
- error = regmap_update_bits(cpcap->regmap, CPCAP_REG_RXCOA,
0xffff, 0x0606);
- if (error)
goto out;
- error = regmap_update_bits(cpcap->regmap, CPCAP_REG_RXSDOA,
0xffff, 0x0600);
- if (error)
goto out;
+out:
- schedule_delayed_work(&cpcap->work, msecs_to_jiffies(1000));
+}
static int cpcap_soc_probe(struct snd_soc_component *component) { struct cpcap_audio *cpcap; @@ -1520,11 +1573,26 @@ static int cpcap_soc_probe(struct snd_soc_component *component) if (err) return err;
- return cpcap_audio_reset(component, false);
- err = cpcap_audio_reset(component, false);
- if (err)
return err;
- INIT_DELAYED_WORK(&cpcap->work, cpcap_soc_work);
- schedule_delayed_work(&cpcap->work, msecs_to_jiffies(1000));
- return 0;
+}
+static void cpcap_soc_remove(struct snd_soc_component *component) +{
- struct cpcap_audio *cpcap = snd_soc_component_get_drvdata(component);
- cancel_delayed_work_sync(&cpcap->work);
}
static struct snd_soc_component_driver soc_codec_dev_cpcap = { .probe = cpcap_soc_probe,
- .remove = cpcap_soc_remove, .controls = cpcap_snd_controls, .num_controls = ARRAY_SIZE(cpcap_snd_controls), .dapm_widgets = cpcap_dapm_widgets,
-- 2.16.3
* Sebastian Reichel sebastian.reichel@collabora.co.uk [180329 13:37]:
Hi,
On Wed, Mar 28, 2018 at 06:45:07PM -0700, Tony Lindgren wrote:
Hi,
- Sebastian Reichel sebastian.reichel@collabora.co.uk [180328 14:03]:
Hi,
On Wed, Mar 28, 2018 at 10:29:10AM +0800, Mark Brown wrote:
On Wed, Mar 28, 2018 at 12:22:37AM +0200, Sebastian Reichel wrote:
On Tue, Mar 27, 2018 at 08:14:41PM +0800, Mark Brown wrote:
No, this is exactly the sort of use case with multiple DAIs that the graph card is intended to enable over the old simple-card.
+----------+ +-------------+ | OMAP4 | | CPCAP | | | | | | [McBSP2] | <-----> | [HiFi DAI] | | | | | | [McBSP3] | <--+--> | [Voice DAI] | | | | | | +----------+ | +-------------+ | +----------+ | +-------------+ | MDM6600 | | | WL1285 | | | | | | | [DAI] | <--+--> | [DAI] | | | | | +----------+ +-------------+
Legend: OMAP4 = SoC running Linux CPCAP = Audio codec MDM6600 = Baseband WL1285 = Bluetooth
Re-reading the audio-graph-card binding document I still don't see how the network (OMAP.McBSP3, CPCAP.Voice, MDM6600, WL1285) is supposed to look like. It seems to expect point-to-point DAI connections.
Ugh, a TDM mux?
Yes, at least that's how I understood Motorola's code.
Hmm is there some active component doing the muxing then? Maybe the "AT+CMUT=0" part below?
I don't think, that there is a special hardware mux. I think each device is configured to use a proper timeslot and/or is being used exclusively.
OK. I wonder what "AT+CMUT=0" on mdm6600 then does? If a voice call is requested and mdm6600 only has one i2s output it seems kind of unnecesary :)
That's really unusual and not particularly supported yet, you'd need to extend the graph card to do it. It's where things should end up for a generic card though.
Motorola's driver provided the following modes:
OMAP4 <-> CPCAP (voice recording) MDM6600 <-> CPCAP (voice call, CPU not involved) OMAP4 <-> WL1285 (bluetooth HFP/HSP) MDM6600 <-> WL1285 (bluetooth voice call)
In case of the last two variants, the bus clock is provided by CPCAP, so it needs to be enabled for any audio stream. I suppose the codec <-> codec as part of TDM is out of scope for the graph card and we need a Droid 4 specific card driver?
Hmm well I got audio call hacked to work as a proof of concept hack, see below. Maybe it can be used to verify some of the assumptions above.
Your proof of concept verifies the assumption, that the modem is connected to the CPCAP voice DAI. This patchset is a proof, that the voice DAI is connected to OMAP. So we can tell for sure, that this is not a common direct DAI-to-DAI connection.
OK
@@ -510,17 +510,17 @@ 07f4: 0000 07f8: 0000 07fc: 0000 -0800: 0065 -0804: 0000 -0808: 0040 +0800: 0025 # CPCAP_REG_VAUDIOC VAUDIO Control
enable vaudio (obviously required :))
+0804: 60cf # CPCAP_REG_CC Codec Control, moto cpcap.c:1337 sets 0x0093?
0x6000 => clkfreq=19200000
The following bits are automatically set via DAPM by cpcap codec, once it is used:
0x00c0 => "ADC Left" + "DAC Voice" 0x000f => "Highpass Filter TX" + "Highpass Filter RX"
+0808: ae0a # CPCAP_REG_CDI Codec Digital Interface
0xa000 => enable PLL & use clock 1
This should be used by default for VOICE DAI.
0x0e00 => "Voice DAI Clock"=1 (handled by DAPM) , mode=I2S 0x000a => CPCAP_BIT_CLK_INV | CPCAP_BIT_MIC1_RX_TIMESLOT0
080c: 0000 0810: 0004 -0814: 0804 -0818: 079c -081c: 0000 -0820: 0924 -0824: 0000 -0828: 0000 +0814: 0cc0 # CPCAP_REG_TXI TX Inputs, moto cpcap.c:1340 sets 0x0CC6? +0818: 0610 # CPCAP_REG_TXMP TX MIC PGA's, moto cpcap.c:1343 sets 0x0273? +081c: 0006 # CPCAP_REG_RXOA RX Output Amplifiers +0820: 0b2c # CPCAP_REG_RXVC RX Volume Control +0824: 0606 # CPCAP_REG_RXCOA RX Codec to Output Amps +0828: 0600 # CPCAP_REG_RXSDOA RX Stereo DAC to Output Amps
This configures the loudspeaker, mics and volume and enables the required clocks/DACs/... This is already covered by the cpcap codec driver. You just need to configure everything correctly in alsamixer.
082c: 0400 0830: 0000 0834: 0030
I wonder if mdm6600 is the i2s master during the voice call?
I think cpcap is always the clock and frame master, but I think mdm6600 is the remote side and OMAP is not involved at all.
OK. So could it be just an alsamixer on/off toggle then for "Modem" or something similar?
Then using the n_gsm ts 27.010 uart mux, I dial:
./ngsm-rw 1 "AT+CFUN=1" # connect to network U0001+CFUN:OK ./ngsm-rw 2 "AT+CMUT=0" # unmute speaker over ch2, do this over qmi? U0001+CMUT:OK ./ngsm-rw 1 "ATD#123" # dial number U0001D:OK
There's a typo above, it should be just ATD123 where 123 is the number.
I was just doing few test calls to robots. Payback time for all the robocalls, you know! :)
And I do hear a voice talking over the speakerphone :) Sorry have not tested the mic yet..
And calling a person I can hear the other end but the mic is not working. So maybe I need to tweak the alsamixer settings too for mic?
Regards,
Tony
Hi,
On Thu, Mar 29, 2018 at 06:59:04AM -0700, Tony Lindgren wrote:
I think cpcap is always the clock and frame master, but I think mdm6600 is the remote side and OMAP is not involved at all.
OK. So could it be just an alsamixer on/off toggle then for "Modem" or something similar?
I think so. We might want to have an "Mode" enum instead, though. That can be extended, once we unlock the other modes (bluetooth, bluetooth call).
Then using the n_gsm ts 27.010 uart mux, I dial:
./ngsm-rw 1 "AT+CFUN=1" # connect to network U0001+CFUN:OK ./ngsm-rw 2 "AT+CMUT=0" # unmute speaker over ch2, do this over qmi? U0001+CMUT:OK ./ngsm-rw 1 "ATD#123" # dial number U0001D:OK
There's a typo above, it should be just ATD123 where 123 is the number.
I was just doing few test calls to robots. Payback time for all the robocalls, you know! :)
And I do hear a voice talking over the speakerphone :) Sorry have not tested the mic yet..
And calling a person I can hear the other end but the mic is not working. So maybe I need to tweak the alsamixer settings too for mic?
Your override kills most settings from alsamixer. You can try to just override CPCAP_REG_VAUDIOC, CPCAP_REG_CC and CPCAP_REG_CDI and setup everything else with alsamixer.
-- Sebastian
* Sebastian Reichel sebastian.reichel@collabora.co.uk [180329 15:47]:
Hi,
On Thu, Mar 29, 2018 at 06:59:04AM -0700, Tony Lindgren wrote:
I think cpcap is always the clock and frame master, but I think mdm6600 is the remote side and OMAP is not involved at all.
OK. So could it be just an alsamixer on/off toggle then for "Modem" or something similar?
I think so. We might want to have an "Mode" enum instead, though. That can be extended, once we unlock the other modes (bluetooth, bluetooth call).
OK. Seems in addition to the "Mode" enum, we also need some other switch for modem on and off toggle?
I guess most people want to save the preferred "Mode" enum, then for the duration of the call enable the selected mode.
And I guess the mute is already there for the mic with 'm' in alsamixer for conf calls :)
Then using the n_gsm ts 27.010 uart mux, I dial:
./ngsm-rw 1 "AT+CFUN=1" # connect to network U0001+CFUN:OK ./ngsm-rw 2 "AT+CMUT=0" # unmute speaker over ch2, do this over qmi? U0001+CMUT:OK ./ngsm-rw 1 "ATD#123" # dial number U0001D:OK
There's a typo above, it should be just ATD123 where 123 is the number.
I was just doing few test calls to robots. Payback time for all the robocalls, you know! :)
And I do hear a voice talking over the speakerphone :) Sorry have not tested the mic yet..
And calling a person I can hear the other end but the mic is not working. So maybe I need to tweak the alsamixer settings too for mic?
Your override kills most settings from alsamixer. You can try to just override CPCAP_REG_VAUDIOC, CPCAP_REG_CC and CPCAP_REG_CDI and setup everything else with alsamixer.
OK I'll try to narrow it down and play with the mic settings tonight or over the weekend.
Regards,
Tony
Hi,
On Thu, Mar 29, 2018 at 09:06:11AM -0700, Tony Lindgren wrote:
- Sebastian Reichel sebastian.reichel@collabora.co.uk [180329 15:47]:
Hi,
On Thu, Mar 29, 2018 at 06:59:04AM -0700, Tony Lindgren wrote:
I think cpcap is always the clock and frame master, but I think mdm6600 is the remote side and OMAP is not involved at all.
OK. So could it be just an alsamixer on/off toggle then for "Modem" or something similar?
I think so. We might want to have an "Mode" enum instead, though. That can be extended, once we unlock the other modes (bluetooth, bluetooth call).
OK. Seems in addition to the "Mode" enum, we also need some other switch for modem on and off toggle?
I don't think so. DAPM should automatically power on/off the modem if there is a valid path from some CPCAP output/input. Since the mode enum will "disconnect" the modem in the DAPM graph, it should be enabled/disabled automatically.
I guess most people want to save the preferred "Mode" enum, then for the duration of the call enable the selected mode.
And I guess the mute is already there for the mic with 'm' in alsamixer for conf calls :)
Yes.
-- Sebastian
Hi!
Your override kills most settings from alsamixer. You can try to just override CPCAP_REG_VAUDIOC, CPCAP_REG_CC and CPCAP_REG_CDI and setup everything else with alsamixer.
OK I'll try to narrow it down and play with the mic settings tonight or over the weekend.
No, just the three+alsamixer will not work. You also need to
814: |= 400
824: |= 400 CPCAP_REG_RXCOA : CPCAP_BIT_PGA_CDC_EN 81c: |= 006 CPCAP_REG_RXOA : CPCAP_BIT_A2_LDSP_L_EN / CPCAP_BIT_A2_LDSP_R_EN
You can set those using sudo aplay -D plughw:CARD=Audio,DEV=1 /usr/share/sounds/alsa/Front_Left.wav ... but that will do bad stuff to the rest of the bits.
You might find this useful:
watch 'cat /sys/kernel/debug/regmap/spi0.0/registers | grep "0800|0804|0808|0814|0818|081c|0820|0824|0828"'
Good luck, I need some sleep,
Pavel
Hi!
And I do hear a voice talking over the speakerphone :) Sorry have not tested the mic yet..
And calling a person I can hear the other end but the mic is not working. So maybe I need to tweak the alsamixer settings too for mic?
Your override kills most settings from alsamixer. You can try to just override CPCAP_REG_VAUDIOC, CPCAP_REG_CC and CPCAP_REG_CDI and setup everything else with alsamixer.
I tried that, and could not get it to work; will try some more.
Is there easy way to get the register dump?
Thanks, Pavel
* Pavel Machek pavel@ucw.cz [180329 16:38]:
Hi!
And I do hear a voice talking over the speakerphone :) Sorry have not tested the mic yet..
And calling a person I can hear the other end but the mic is not working. So maybe I need to tweak the alsamixer settings too for mic?
Your override kills most settings from alsamixer. You can try to just override CPCAP_REG_VAUDIOC, CPCAP_REG_CC and CPCAP_REG_CDI and setup everything else with alsamixer.
I tried that, and could not get it to work; will try some more.
Is there easy way to get the register dump?
For mainline kernel:
# cat /sys/kernel/debug/regmap/spi0.0/registers
For Android v3.0.8 kernel, see cpcaprw on github.
Regards,
Tony
On Thu 2018-03-29 09:41:13, Tony Lindgren wrote:
- Pavel Machek pavel@ucw.cz [180329 16:38]:
Hi!
And I do hear a voice talking over the speakerphone :) Sorry have not tested the mic yet..
And calling a person I can hear the other end but the mic is not working. So maybe I need to tweak the alsamixer settings too for mic?
Your override kills most settings from alsamixer. You can try to just override CPCAP_REG_VAUDIOC, CPCAP_REG_CC and CPCAP_REG_CDI and setup everything else with alsamixer.
I tried that, and could not get it to work; will try some more.
Is there easy way to get the register dump?
For mainline kernel:
# cat /sys/kernel/debug/regmap/spi0.0/registers
For Android v3.0.8 kernel, see cpcaprw on github.
Thanks. I got call working including outgoing audio: in capture settings, right->mic 1, Mic1 + Mic2 in alsamixer -> 100%. But I had the other phone muted, so I don't yet know if such call would be of usable quality.
Thanks! Pavel
--- tony.as 2018-03-29 18:29:41.715742372 +0000 +++ mic.as 2018-03-29 18:33:40.910759649 +0000 @@ -258,8 +258,8 @@ 0404: 0000 0408: 0000 040c: 0000 -0410: 0014 -0414: 0104 +0410: 0004 +0414: 0105 0418: 000e 041c: 00ff 0420: 44d3 @@ -515,8 +515,8 @@ 0808: ae0a 080c: 0078 0810: 003c -0814: 0cc0 -0818: 0610 +0814: 0cc4 +0818: 061f 081c: 0006 0820: 0b2c 0824: 0606 @@ -641,13 +641,13 @@ 0a00: 0000 0a04: 23b6 0a08: 002b -0a0c: 1530 +0a0c: 18ec 0a10: 0000 -0a14: 4e54 -0a18: 0036 +0a14: 6a90 +0a18: 0040 0a1c: 0000 0a20: 0000 -0a24: 0738 +0a24: 0b40 0a28: 0000 0a2c: 0000 0a30: 0000 @@ -769,12 +769,12 @@ 0c00: 9004 0c04: 0136 0c08: 0000 -0c0c: 02fb +0c0c: 02fc 0c10: 01da -0c14: 015b -0c18: 0300 -0c1c: 0271 -0c20: 01e5 +0c14: 015a +0c18: 02fe +0c1c: 0270 +0c20: 01f5 0c24: 0000 0c28: 0204 0c2c: 0205
* Pavel Machek pavel@ucw.cz [180329 18:41]:
Thanks. I got call working including outgoing audio: in capture settings, right->mic 1, Mic1 + Mic2 in alsamixer -> 100%. But I had the other phone muted, so I don't yet know if such call would be of usable quality.
Great, good to hear that :)
Tony
--- tony.as 2018-03-29 18:29:41.715742372 +0000 +++ mic.as 2018-03-29 18:33:40.910759649 +0000 @@ -258,8 +258,8 @@ 0404: 0000 0408: 0000 040c: 0000 -0410: 0014 -0414: 0104 +0410: 0004 +0414: 0105 0418: 000e 041c: 00ff 0420: 44d3 @@ -515,8 +515,8 @@ 0808: ae0a 080c: 0078 0810: 003c -0814: 0cc0 -0818: 0610 +0814: 0cc4 +0818: 061f 081c: 0006 0820: 0b2c 0824: 0606 @@ -641,13 +641,13 @@ 0a00: 0000 0a04: 23b6 0a08: 002b -0a0c: 1530 +0a0c: 18ec 0a10: 0000 -0a14: 4e54 -0a18: 0036 +0a14: 6a90 +0a18: 0040 0a1c: 0000 0a20: 0000 -0a24: 0738 +0a24: 0b40 0a28: 0000 0a2c: 0000 0a30: 0000 @@ -769,12 +769,12 @@ 0c00: 9004 0c04: 0136 0c08: 0000 -0c0c: 02fb +0c0c: 02fc 0c10: 01da -0c14: 015b -0c18: 0300 -0c1c: 0271 -0c20: 01e5 +0c14: 015a +0c18: 02fe +0c1c: 0270 +0c20: 01f5 0c24: 0000 0c28: 0204 0c2c: 0205
On Thu 2018-03-29 14:56:13, Tony Lindgren wrote:
- Pavel Machek pavel@ucw.cz [180329 18:41]:
Thanks. I got call working including outgoing audio: in capture settings, right->mic 1, Mic1 + Mic2 in alsamixer -> 100%. But I had the other phone muted, so I don't yet know if such call would be of usable quality.
Great, good to hear that :)
I also got ofonod to work, with rather crazy hacks. But I now have incoming/outgoing calls with GUI :-).
Pavel
On 30/03/18 12:37, Pavel Machek wrote:
On Thu 2018-03-29 14:56:13, Tony Lindgren wrote:
- Pavel Machek pavel@ucw.cz [180329 18:41]:
Thanks. I got call working including outgoing audio: in capture settings, right->mic 1, Mic1 + Mic2 in alsamixer -> 100%. But I had the other phone muted, so I don't yet know if such call would be of usable quality.
Great, good to hear that :)
I also got ofonod to work, with rather crazy hacks. But I now have incoming/outgoing calls with GUI :-).
Would you mind sharing those hacks - I would like to play around with ofonod as well. Maybe I can help with a way forward.
Cheers, Merlijn
* Merlijn Wajer merlijn@wizzup.org [180330 13:09]:
On 30/03/18 12:37, Pavel Machek wrote:
On Thu 2018-03-29 14:56:13, Tony Lindgren wrote:
- Pavel Machek pavel@ucw.cz [180329 18:41]:
Thanks. I got call working including outgoing audio: in capture settings, right->mic 1, Mic1 + Mic2 in alsamixer -> 100%. But I had the other phone muted, so I don't yet know if such call would be of usable quality.
Great, good to hear that :)
I also got ofonod to work, with rather crazy hacks. But I now have incoming/outgoing calls with GUI :-).
Would you mind sharing those hacks - I would like to play around with ofonod as well. Maybe I can help with a way forward.
Yeah that would be fun to play with :)
Below is a patch that works better at least for ModemManager, does anybody have ideas what the unused three ports might be?
I wonder if ofono can use /dev/ttyUSB0 CQDM port?
Regards,
Tony
8< -------------------------
From tony Mon Sep 17 00:00:00 2001
From: Tony Lindgren tony@atomide.com Date: Sun, 19 Nov 2017 19:55:56 -0800 Subject: [PATCH] USB: qcaux: Add droid 4 mdm6600 modem
We have five USB ports on mdm6600 modem and does not have the same layout as layout as Gobi 1K/2K/etc devices listed in qcserial.c
So we should not be adding them to qcserial.c but instead use qcaux.c or option.c as noted by Dan Williams dcbw@redhat.com.
The ttyUSB ports detected by ModemManager are:
ttyUSB0 CQDM-capable ttyUSB1 no response ttyUSB2 no response ttyUSB3 no response ttyUSB4 AT-capable
Note that it takes a while for ModemManager to start while it's detecting the ports, so we may want to eventually limit the ports.
So far no luck finding out what the unused three ports are, maybe there is NMEA there if somebody knows how to enable it.
Signed-off-by: Tony Lingren tony@atomide.com --- drivers/usb/serial/qcaux.c | 1 + 1 file changed, 1 insertion(+)
diff --git a/drivers/usb/serial/qcaux.c b/drivers/usb/serial/qcaux.c --- a/drivers/usb/serial/qcaux.c +++ b/drivers/usb/serial/qcaux.c @@ -66,6 +66,7 @@ static const struct usb_device_id id_table[] = { { USB_VENDOR_AND_INTERFACE_INFO(UTSTARCOM_VENDOR_ID, 0xff, 0xfe, 0xff) }, /* WMC */ { USB_VENDOR_AND_INTERFACE_INFO(UTSTARCOM_VENDOR_ID, 0xff, 0xff, 0xff) }, /* DIAG */ { USB_DEVICE_AND_INTERFACE_INFO(0x1fac, 0x0151, 0xff, 0xff, 0xff) }, + { USB_DEVICE_AND_INTERFACE_INFO(0x22b8, 0x2a70, 0xff, 0xff, 0xff) }, { }, }; MODULE_DEVICE_TABLE(usb, id_table);
Hi
On Fri., 30 Mar. 2018, 11:22 pm Tony Lindgren, tony@atomide.com wrote:
- Merlijn Wajer merlijn@wizzup.org [180330 13:09]:
On 30/03/18 12:37, Pavel Machek wrote:
On Thu 2018-03-29 14:56:13, Tony Lindgren wrote:
- Pavel Machek pavel@ucw.cz [180329 18:41]:
Thanks. I got call working including outgoing audio: in capture settings, right->mic 1, Mic1 + Mic2 in alsamixer -> 100%. But I had the other phone muted, so I don't yet know if such call would be of usable quality.
Great, good to hear that :)
I also got ofonod to work, with rather crazy hacks. But I now have incoming/outgoing calls with GUI :-).
Would you mind sharing those hacks - I would like to play around with ofonod as well. Maybe I can help with a way forward.
Yeah that would be fun to play with :)
Below is a patch that works better at least for ModemManager, does anybody have ideas what the unused three ports might be?
I wonder if ofono can use /dev/ttyUSB0 CQDM port?
One of them can be used proprably to bridge debug for modem certification. This is usually disable for production device. The other could used for unsolicited response of GPS function if exists in this modem
Michael
Regards,
Tony
8< ------------------------- From tony Mon Sep 17 00:00:00 2001 From: Tony Lindgren tony@atomide.com Date: Sun, 19 Nov 2017 19:55:56 -0800 Subject: [PATCH] USB: qcaux: Add droid 4 mdm6600 modem
We have five USB ports on mdm6600 modem and does not have the same layout as layout as Gobi 1K/2K/etc devices listed in qcserial.c
So we should not be adding them to qcserial.c but instead use qcaux.c or option.c as noted by Dan Williams dcbw@redhat.com.
The ttyUSB ports detected by ModemManager are:
ttyUSB0 CQDM-capable ttyUSB1 no response ttyUSB2 no response ttyUSB3 no response ttyUSB4 AT-capable
Note that it takes a while for ModemManager to start while it's detecting the ports, so we may want to eventually limit the ports.
So far no luck finding out what the unused three ports are, maybe there is NMEA there if somebody knows how to enable it.
Signed-off-by: Tony Lingren tony@atomide.com
drivers/usb/serial/qcaux.c | 1 + 1 file changed, 1 insertion(+)
diff --git a/drivers/usb/serial/qcaux.c b/drivers/usb/serial/qcaux.c --- a/drivers/usb/serial/qcaux.c +++ b/drivers/usb/serial/qcaux.c @@ -66,6 +66,7 @@ static const struct usb_device_id id_table[] = { { USB_VENDOR_AND_INTERFACE_INFO(UTSTARCOM_VENDOR_ID, 0xff, 0xfe, 0xff) }, /* WMC */ { USB_VENDOR_AND_INTERFACE_INFO(UTSTARCOM_VENDOR_ID, 0xff, 0xff, 0xff) }, /* DIAG */ { USB_DEVICE_AND_INTERFACE_INFO(0x1fac, 0x0151, 0xff, 0xff, 0xff) },
{ USB_DEVICE_AND_INTERFACE_INFO(0x22b8, 0x2a70, 0xff, 0xff, 0xff)
}, { }, }; MODULE_DEVICE_TABLE(usb, id_table); -- 2.16.3 _______________________________________________ Alsa-devel mailing list Alsa-devel@alsa-project.org http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
* Michael Nazzareno Trimarchi michael@amarulasolutions.com [180330 15:27]:
Hi
On Fri., 30 Mar. 2018, 11:22 pm Tony Lindgren, tony@atomide.com wrote:
- Merlijn Wajer merlijn@wizzup.org [180330 13:09]:
On 30/03/18 12:37, Pavel Machek wrote:
On Thu 2018-03-29 14:56:13, Tony Lindgren wrote:
- Pavel Machek pavel@ucw.cz [180329 18:41]:
Thanks. I got call working including outgoing audio: in capture settings, right->mic 1, Mic1 + Mic2 in alsamixer -> 100%. But I had the other phone muted, so I don't yet know if such call would be of usable quality.
Great, good to hear that :)
I also got ofonod to work, with rather crazy hacks. But I now have incoming/outgoing calls with GUI :-).
Would you mind sharing those hacks - I would like to play around with ofonod as well. Maybe I can help with a way forward.
Yeah that would be fun to play with :)
Below is a patch that works better at least for ModemManager, does anybody have ideas what the unused three ports might be?
I wonder if ofono can use /dev/ttyUSB0 CQDM port?
One of them can be used proprably to bridge debug for modem certification. This is usually disable for production device. The other could used for unsolicited response of GPS function if exists in this modem
OK so at least one of the ports is probably permanently disabled then.
GPS is there for sure, it can be accessed with at least mmcli with ModemManager running assuming modem instance 0:
$ mmcli -m 0 --enable $ mmcli -m 0 --location-enable-gps-raw $ sudo chmod a+r /dev/cdc-wdm0
Then configure gpsd to use /dev/cdc-wdm0.
But having a dedicated NMEA port would be nice. Any ideas how the GPS port might be enabled for ttyUSB, should it start just printing out data if GPS is enabled?
FYI, selecting the "BP HW Diag & Boot AP" option from bootloader with the volume buttons routes the whole modem to the OTG USB interface with the same ports available to PC so that's probably for cert use. If needed we can do this from Linux too by just by setting one of the USB PHY pins high with cpcap_usb_gpio_set_mode().
Regards,
Tony
* Tony Lindgren tony@atomide.com [180330 15:46]:
- Michael Nazzareno Trimarchi michael@amarulasolutions.com [180330 15:27]:
Hi
On Fri., 30 Mar. 2018, 11:22 pm Tony Lindgren, tony@atomide.com wrote:
- Merlijn Wajer merlijn@wizzup.org [180330 13:09]:
On 30/03/18 12:37, Pavel Machek wrote:
On Thu 2018-03-29 14:56:13, Tony Lindgren wrote:
- Pavel Machek pavel@ucw.cz [180329 18:41]:
> Thanks. I got call working including outgoing audio: in capture > settings, right->mic 1, Mic1 + Mic2 in alsamixer -> 100%. But I had > the other phone muted, so I don't yet know if such call would be of > usable quality.
Great, good to hear that :)
I also got ofonod to work, with rather crazy hacks. But I now have incoming/outgoing calls with GUI :-).
Would you mind sharing those hacks - I would like to play around with ofonod as well. Maybe I can help with a way forward.
Yeah that would be fun to play with :)
Below is a patch that works better at least for ModemManager, does anybody have ideas what the unused three ports might be?
I wonder if ofono can use /dev/ttyUSB0 CQDM port?
One of them can be used proprably to bridge debug for modem certification. This is usually disable for production device. The other could used for unsolicited response of GPS function if exists in this modem
OK so at least one of the ports is probably permanently disabled then.
GPS is there for sure, it can be accessed with at least mmcli with ModemManager running assuming modem instance 0:
$ mmcli -m 0 --enable $ mmcli -m 0 --location-enable-gps-raw $ sudo chmod a+r /dev/cdc-wdm0
Then configure gpsd to use /dev/cdc-wdm0.
But having a dedicated NMEA port would be nice. Any ideas how the GPS port might be enabled for ttyUSB, should it start just printing out data if GPS is enabled?
FYI, selecting the "BP HW Diag & Boot AP" option from bootloader with the volume buttons routes the whole modem to the OTG USB interface with the same ports available to PC so that's probably for cert use. If needed we can do this from Linux too by just by setting one of the USB PHY pins high with cpcap_usb_gpio_set_mode().
And after some searching the the three unknown ports seem to be NMEA, TCMD and MUX in some unknown order. No idea what the TCMD and MUX are here.
Looks like /dev/ttyUSB3 pretty much just echoes back the characters typed in.
Regards,
Tony
Hi!
Great, good to hear that :)
I also got ofonod to work, with rather crazy hacks. But I now have incoming/outgoing calls with GUI :-).
Would you mind sharing those hacks - I would like to play around with ofonod as well. Maybe I can help with a way forward.
Yeah that would be fun to play with :)
Ok, I thought I should clean them up first, but hey...
sudo emacs /etc/udev/rules.d/55-modem.rules KERNEL=="ttyUSB4", ENV{OFONO_DRIVER}="g1"
sudo udevadm trigger
And now, the crazy hack follows. Note I'm using AT interface -- which is probably not good idea.
Anyway, network/signal strength is detected, and calls seem to work.
diff --git a/drivers/atmodem/sms.c b/drivers/atmodem/sms.c index 68b89862..3a9f4bc0 100644 --- a/drivers/atmodem/sms.c +++ b/drivers/atmodem/sms.c @@ -440,6 +440,8 @@ static void at_cmt_notify(GAtResult *result, gpointer user_data) if (data->vendor != OFONO_VENDOR_SIMCOM) at_ack_delivery(sms);
+ return; + err: ofono_error("Unable to parse CMT notification"); } diff --git a/plugins/g1.c b/plugins/g1.c index d915a565..dd4e735d 100644 --- a/plugins/g1.c +++ b/plugins/g1.c @@ -60,7 +60,8 @@ static void g1_debug(const char *str, void *user_data) /* Detect hardware, and initialize if found */ static int g1_probe(struct ofono_modem *modem) { - DBG(""); + DBG("probing G1"); + DBG("probing G1 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
return 0; } @@ -97,14 +98,21 @@ static int g1_enable(struct ofono_modem *modem)
DBG("");
+ DBG("enabling G1 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + device = ofono_modem_get_string(modem, "Device"); - if (device == NULL) - return -EINVAL; + // if (device == NULL) + // return -EINVAL; + device = "/dev/ttyUSB4"; + + DBG("");
channel = g_at_tty_open(device, NULL); if (channel == NULL) return -EIO;
+ DBG(""); + syntax = g_at_syntax_new_gsm_permissive(); chat = g_at_chat_new(channel, syntax); g_io_channel_unref(channel); @@ -116,11 +124,14 @@ static int g1_enable(struct ofono_modem *modem) if (getenv("OFONO_AT_DEBUG")) g_at_chat_set_debug(chat, g1_debug, "");
+ DBG(""); ofono_modem_set_data(modem, chat);
+ DBG(""); /* ensure modem is in a known state; verbose on, echo/quiet off */ g_at_chat_send(chat, "ATE0Q0V1", NULL, NULL, NULL, NULL);
+ DBG(""); /* power up modem */ g_at_chat_send(chat, "AT+CFUN=1", NULL, cfun_set_on_cb, modem, NULL);
@@ -191,18 +202,56 @@ static void g1_post_sim(struct ofono_modem *modem) ofono_message_waiting_register(mw); }
+static void g1_post_online(struct ofono_modem *modem) +{ + DBG(); +} + +static void set_online_cb(gboolean ok, GAtResult *result, gpointer user_data) +{ + struct ofono_modem *modem = user_data; + GAtChat *chat = ofono_modem_get_data(modem); + + DBG(""); + + g_at_chat_unref(chat); + ofono_modem_set_data(modem, NULL); + + // if (ok) + // ofono_modem_set_online(modem, TRUE); +} + +static void g1_set_online(struct ofono_modem *modem, ofono_bool_t online, + ofono_modem_online_cb_t cb, void *user_data) +{ + GAtChat *chat = ofono_modem_get_data(modem); + char const *command = online ? "AT+CFUN=1" : "AT+CFUN=4"; + + DBG("modem %p %s", modem, online ? "online" : "offline"); + + if (g_at_chat_send(chat, command, NULL, + set_online_cb, modem, NULL) > 0) + return; + + //CALLBACK_WITH_FAILURE(cb, cbd->data); + +} + static struct ofono_modem_driver g1_driver = { .name = "g1", .probe = g1_probe, .remove = g1_remove, .enable = g1_enable, .disable = g1_disable, + // .set_online = g1_set_online, .pre_sim = g1_pre_sim, .post_sim = g1_post_sim, + .post_online = g1_post_online, };
static int g1_init(void) { + DBG("g1_init!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n"); return ofono_modem_driver_register(&g1_driver); }
diff --git a/plugins/udevng.c b/plugins/udevng.c index ff5d41af..3e0cdf81 100644 --- a/plugins/udevng.c +++ b/plugins/udevng.c @@ -1250,6 +1250,7 @@ static struct { { "cinterion", setup_serial_modem }, { "nokiacdma", setup_serial_modem }, { "sim900", setup_serial_modem }, + { "g1", setup_serial_modem }, { "wavecom", setup_wavecom }, { "tc65", setup_tc65 }, { "ehs6", setup_ehs6 }, @@ -1407,7 +1408,7 @@ static void add_serial_device(struct udev_device *dev)
mdev = get_serial_modem_device(dev); if (!mdev) { - DBG("Device is missing required OFONO_DRIVER property"); + //DBG("Device %s %s is missing required OFONO_DRIVER property", udev_device_get_devpath(mdev), udev_device_get_syspath(mdev)); return; }
@@ -1419,6 +1420,9 @@ static void add_serial_device(struct udev_device *dev)
devnode = udev_device_get_devnode(dev);
+ DBG("Got OFONO_DRIVER!!!! driver %s path %s\n", driver, devpath); + + if (!syspath || !devpath) return;
@@ -1578,8 +1582,6 @@ static struct { { "mbm", "cdc_ether", "0930" }, { "mbm", "cdc_ncm", "0930" }, { "hso", "hso" }, - { "gobi", "qmi_wwan" }, - { "gobi", "qcserial" }, { "sierra", "qmi_wwan", "1199" }, { "sierra", "qcserial", "1199" }, { "sierra", "sierra" }, @@ -1602,6 +1604,8 @@ static struct { { "telit", "cdc_acm", "1bc7", "0021" }, { "telitqmi", "qmi_wwan", "1bc7", "1201" }, { "telitqmi", "option", "1bc7", "1201" }, + { "telitqmi", "qmi_wwan", "22b8", "2a70" }, + { "telitqmi", "option", "22b8", "2a70" }, { "nokia", "option", "0421", "060e" }, { "nokia", "option", "0421", "0623" }, { "samsung", "option", "04e8", "6889" }, @@ -1717,10 +1721,12 @@ static void check_device(struct udev_device *device) return; }
+#if 0 if ((g_str_equal(bus, "usb") == TRUE) || (g_str_equal(bus, "usbmisc") == TRUE)) check_usb_device(device); else +#endif add_serial_device(device);
} @@ -1746,17 +1752,20 @@ static gboolean create_modem(gpointer key, gpointer value, gpointer user_data) return TRUE;
for (i = 0; driver_list[i].name; i++) { + DBG("comparing %s %s", driver_list[i].name, modem->driver); if (g_str_equal(driver_list[i].name, modem->driver) == FALSE) continue;
- if (driver_list[i].setup(modem) == TRUE) { + /* if (driver_list[i].setup(modem) == TRUE) */ { ofono_modem_set_string(modem->modem, "SystemPath", syspath); ofono_modem_register(modem->modem); + DBG("create modem is okay?"); return FALSE; } }
+ DBG("create modem is maybe not okay?"); return TRUE; }
@@ -1796,6 +1805,7 @@ static void enumerate_devices(struct udev *context) udev_enumerate_unref(enumerate);
g_hash_table_foreach_remove(modem_list, create_modem, NULL); + DBG("Enumerate devices ok?"); }
static struct udev *udev_ctx; @@ -1811,6 +1821,8 @@ static gboolean check_modem_list(gpointer user_data)
g_hash_table_foreach_remove(modem_list, create_modem, NULL);
+ DBG("Check modem list ok?"); + return FALSE; }
@@ -1820,6 +1832,8 @@ static gboolean udev_event(GIOChannel *channel, GIOCondition cond, struct udev_device *device; const char *action;
+ DBG("udev event"); + if (cond & (G_IO_ERR | G_IO_HUP | G_IO_NVAL)) { ofono_warn("Error with udev monitor channel"); udev_watch = 0; @@ -1838,11 +1852,14 @@ static gboolean udev_event(GIOChannel *channel, GIOCondition cond, if (udev_delay > 0) g_source_remove(udev_delay);
+ DBG("udev event add -> check"); check_device(device);
udev_delay = g_timeout_add_seconds(1, check_modem_list, NULL); - } else if (g_str_equal(action, "remove") == TRUE) + } else if (g_str_equal(action, "remove") == TRUE) { + DBG("udev event remove -> remove"); remove_device(device); + }
udev_device_unref(device);
@@ -1892,8 +1909,10 @@ static int detect_init(void) return -EIO; }
+ ofono_warn("detect_init..."); modem_list = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, destroy_modem); + ofono_warn("detect_init 2...");
udev_monitor_filter_add_match_subsystem_devtype(udev_mon, "tty", NULL); udev_monitor_filter_add_match_subsystem_devtype(udev_mon, "usb", NULL); diff --git a/src/modem.c b/src/modem.c index 0cee861f..d8dde772 100644 --- a/src/modem.c +++ b/src/modem.c @@ -603,12 +603,14 @@ static gboolean modem_has_sim(struct ofono_modem *modem)
static gboolean modem_is_always_online(struct ofono_modem *modem) { + DBG(); if (modem->driver->set_online == NULL) return TRUE;
if (ofono_modem_get_boolean(modem, "AlwaysOnline") == TRUE) return TRUE;
+ DBG("not always"); return FALSE; }
@@ -720,8 +722,10 @@ static void sim_state_watch(enum ofono_sim_state new_state, void *user) modem_change_state(modem, MODEM_STATE_OFFLINE);
/* Modem is always online, proceed to online state. */ - if (modem_is_always_online(modem) == TRUE) + if (modem_is_always_online(modem) == TRUE) { set_online(modem, TRUE); + modem->online = TRUE; + }
if (modem->online == TRUE) modem_change_state(modem, MODEM_STATE_ONLINE); @@ -1882,13 +1886,17 @@ struct ofono_modem *ofono_modem_create(const char *name, const char *type) else snprintf(path, sizeof(path), "/%s", name);
- if (!dbus_validate_path(path, NULL)) + if (!dbus_validate_path(path, NULL)) { + DBG("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!bad dbus path"); return NULL; + }
modem = g_try_new0(struct ofono_modem, 1);
- if (modem == NULL) + if (modem == NULL) { + DBG("!!!out of memory?!"); return modem; + }
modem->path = g_strdup(path); modem->driver_type = g_strdup(type); @@ -1900,6 +1908,7 @@ struct ofono_modem *ofono_modem_create(const char *name, const char *type) if (name == NULL) next_modem_id += 1;
+ DBG("Created new modem, path %s", path); return modem; }
diff --git a/src/network.c b/src/network.c index ae3175d4..700183c0 100644 --- a/src/network.c +++ b/src/network.c @@ -980,6 +980,7 @@ static DBusMessage *network_scan(DBusConnection *conn, { struct ofono_netreg *netreg = data;
+ DBG(); if (netreg->mode == NETWORK_REGISTRATION_MODE_AUTO_ONLY) return __ofono_error_access_denied(msg);
@@ -991,6 +992,7 @@ static DBusMessage *network_scan(DBusConnection *conn,
netreg->pending = dbus_message_ref(msg);
+ DBG(); netreg->driver->list_operators(netreg, operator_list_callback, netreg);
return NULL; diff --git a/src/voicecall.c b/src/voicecall.c index e4f6a4c0..2c637e58 100644 --- a/src/voicecall.c +++ b/src/voicecall.c @@ -1492,6 +1492,7 @@ static int voicecall_dial(struct ofono_voicecall *vc, const char *number, struct ofono_modem *modem = __ofono_atom_get_modem(vc->atom); struct ofono_phone_number ph;
+ DBG(""); if (g_slist_length(vc->call_list) >= MAX_VOICE_CALLS) return -EPERM;
On Fri 2018-03-30 15:07:24, Merlijn Wajer wrote:
On 30/03/18 12:37, Pavel Machek wrote:
On Thu 2018-03-29 14:56:13, Tony Lindgren wrote:
- Pavel Machek pavel@ucw.cz [180329 18:41]:
Thanks. I got call working including outgoing audio: in capture settings, right->mic 1, Mic1 + Mic2 in alsamixer -> 100%. But I had the other phone muted, so I don't yet know if such call would be of usable quality.
Great, good to hear that :)
I also got ofonod to work, with rather crazy hacks. But I now have incoming/outgoing calls with GUI :-).
Would you mind sharing those hacks - I would like to play around with ofonod as well. Maybe I can help with a way forward.
Okay. Here's less hacky version of the hack, but still using AT commands. You still need to set up udev, as described in the other mail.
And... it seems I can have a qmi connection, too, but that's topic for other email.
Good luck, Pavel
diff --git a/drivers/atmodem/sms.c b/drivers/atmodem/sms.c index 68b89862..3a9f4bc0 100644 --- a/drivers/atmodem/sms.c +++ b/drivers/atmodem/sms.c @@ -440,6 +440,8 @@ static void at_cmt_notify(GAtResult *result, gpointer user_data) if (data->vendor != OFONO_VENDOR_SIMCOM) at_ack_delivery(sms);
+ return; + err: ofono_error("Unable to parse CMT notification"); } diff --git a/plugins/udevng.c b/plugins/udevng.c index ff5d41af..a4b18488 100644 --- a/plugins/udevng.c +++ b/plugins/udevng.c @@ -233,10 +233,11 @@ static gboolean setup_gobi(struct modem_info *modem) } }
+ DBG("qmi=%s net=%s mdm=%s gps=%s diag=%s", qmi, net, mdm, gps, diag); + if (qmi == NULL || mdm == NULL || net == NULL) return FALSE;
- DBG("qmi=%s net=%s mdm=%s gps=%s diag=%s", qmi, net, mdm, gps, diag);
ofono_modem_set_string(modem->modem, "Device", qmi); ofono_modem_set_string(modem->modem, "Modem", mdm); @@ -1250,6 +1251,7 @@ static struct { { "cinterion", setup_serial_modem }, { "nokiacdma", setup_serial_modem }, { "sim900", setup_serial_modem }, + { "g1", setup_serial_modem }, { "wavecom", setup_wavecom }, { "tc65", setup_tc65 }, { "ehs6", setup_ehs6 }, @@ -1578,8 +1580,6 @@ static struct { { "mbm", "cdc_ether", "0930" }, { "mbm", "cdc_ncm", "0930" }, { "hso", "hso" }, - { "gobi", "qmi_wwan" }, - { "gobi", "qcserial" }, { "sierra", "qmi_wwan", "1199" }, { "sierra", "qcserial", "1199" }, { "sierra", "sierra" }, @@ -1602,6 +1602,8 @@ static struct { { "telit", "cdc_acm", "1bc7", "0021" }, { "telitqmi", "qmi_wwan", "1bc7", "1201" }, { "telitqmi", "option", "1bc7", "1201" }, + { "telitqmi", "qmi_wwan", "22b8", "2a70" }, + { "telitqmi", "option", "22b8", "2a70" }, { "nokia", "option", "0421", "060e" }, { "nokia", "option", "0421", "0623" }, { "samsung", "option", "04e8", "6889" }, @@ -1717,10 +1719,12 @@ static void check_device(struct udev_device *device) return; }
+#if 0 if ((g_str_equal(bus, "usb") == TRUE) || (g_str_equal(bus, "usbmisc") == TRUE)) check_usb_device(device); else +#endif add_serial_device(device);
} @@ -1749,14 +1753,17 @@ static gboolean create_modem(gpointer key, gpointer value, gpointer user_data) if (g_str_equal(driver_list[i].name, modem->driver) == FALSE) continue;
+ DBG("Attempting modem setup, driver %s", modem->driver); if (driver_list[i].setup(modem) == TRUE) { ofono_modem_set_string(modem->modem, "SystemPath", syspath); ofono_modem_register(modem->modem); return FALSE; } + DBG("Modem setup failed, driver %s", modem->driver); }
+ DBG("Modem setup failed or not in driver_list?"); return TRUE; }
On Fri 2018-03-30 19:50:50, Pavel Machek wrote:
On Fri 2018-03-30 15:07:24, Merlijn Wajer wrote:
On 30/03/18 12:37, Pavel Machek wrote:
On Thu 2018-03-29 14:56:13, Tony Lindgren wrote:
- Pavel Machek pavel@ucw.cz [180329 18:41]:
Thanks. I got call working including outgoing audio: in capture settings, right->mic 1, Mic1 + Mic2 in alsamixer -> 100%. But I had the other phone muted, so I don't yet know if such call would be of usable quality.
Great, good to hear that :)
I also got ofonod to work, with rather crazy hacks. But I now have incoming/outgoing calls with GUI :-).
Would you mind sharing those hacks - I would like to play around with ofonod as well. Maybe I can help with a way forward.
Okay. Here's less hacky version of the hack, but still using AT commands. You still need to set up udev, as described in the other mail.
And... it seems I can have a qmi connection, too, but that's topic for other email.
This, applied on top of previous patch, gives me some kind of qmi connection, AFAICT. I can enable/online a modem, but nothing else works.
LocationReporting is advertised but does not work. dial-number does not work either. But from list-modems, it looks like some kind of communication works... Pavel
user@devuan:/my/ofono/test$ sudo python2 ./list-modems [ /gobi_0 ] SystemPath = /sys/devices/platform/44000000.ocp/4a064000.usbhshost/4a064800.ohci/usb2/2-1 Features = sim gps Emergency = 0 Powered = 1 Lockdown = 0 Interfaces = org.ofono.SimManager org.ofono.LocationReporting org.ofono.VoiceCallManager Online = 1 Model = 196 Revision = M6600A-SCAUHSZ-3.1.3310T 1 [Jun 09 2011 17:00:00] Type = hardware Serial = 809DE736 Manufacturer = QUALCOMM INCORPORATED [ org.ofono.SimManager ] Present = 0 [ org.ofono.LocationReporting ] Type = nmea Enabled = 0 [ org.ofono.VoiceCallManager ] EmergencyNumbers = 08 000 999 110 112 911 118 119
user@devuan:/my/ofono/test$
diff --git a/doc/location-reporting-api.txt b/doc/location-reporting-api.txt index 21e346d4..ff0a35dc 100644 --- a/doc/location-reporting-api.txt +++ b/doc/location-reporting-api.txt @@ -13,7 +13,7 @@ Methods dict GetProperties() filedescriptor Request()
Asks to turn ON the NMEA stream and supplies the - gps device file descriptor. The external cliend should + gps device file descriptor. The external client should use the file descriptor to receive the NMEA data.
Possible Errors: [service].Error.InProgress diff --git a/plugins/udevng.c b/plugins/udevng.c index a4b18488..1c6a6679 100644 --- a/plugins/udevng.c +++ b/plugins/udevng.c @@ -234,9 +234,10 @@ static gboolean setup_gobi(struct modem_info *modem) }
DBG("qmi=%s net=%s mdm=%s gps=%s diag=%s", qmi, net, mdm, gps, diag); - +#if 0 if (qmi == NULL || mdm == NULL || net == NULL) return FALSE; +#endif
ofono_modem_set_string(modem->modem, "Device", qmi); @@ -1251,7 +1252,7 @@ static struct { { "cinterion", setup_serial_modem }, { "nokiacdma", setup_serial_modem }, { "sim900", setup_serial_modem }, - { "g1", setup_serial_modem }, + // { "g1", setup_serial_modem }, { "wavecom", setup_wavecom }, { "tc65", setup_tc65 }, { "ehs6", setup_ehs6 }, @@ -1602,8 +1603,8 @@ static struct { { "telit", "cdc_acm", "1bc7", "0021" }, { "telitqmi", "qmi_wwan", "1bc7", "1201" }, { "telitqmi", "option", "1bc7", "1201" }, - { "telitqmi", "qmi_wwan", "22b8", "2a70" }, - { "telitqmi", "option", "22b8", "2a70" }, + { "gobi", "qmi_wwan", "22b8", "2a70" }, + { "gobi", "option", "22b8", "2a70" }, { "nokia", "option", "0421", "060e" }, { "nokia", "option", "0421", "0623" }, { "samsung", "option", "04e8", "6889" }, @@ -1719,7 +1720,7 @@ static void check_device(struct udev_device *device) return; }
-#if 0 +#if 1 if ((g_str_equal(bus, "usb") == TRUE) || (g_str_equal(bus, "usbmisc") == TRUE)) check_usb_device(device);
Pavel
Hi!
Would you mind sharing those hacks - I would like to play around with ofonod as well. Maybe I can help with a way forward.
Okay. Here's less hacky version of the hack, but still using AT commands. You still need to set up udev, as described in the other mail.
And... it seems I can have a qmi connection, too, but that's topic for other email.
This, applied on top of previous patch, gives me some kind of qmi connection, AFAICT. I can enable/online a modem, but nothing else works.
LocationReporting is advertised but does not work. dial-number does not work either. But from list-modems, it looks like some kind of communication works...
Ok, strange.
So there's ofonod in the maemo-leste already.
user@devuan:~$ /usr/sbin/ofonod -v 1.22
...and it seems to somehow work with the droid 4, in default configuration, thinking it is "Gobi" modem.
user@devuan:/my/ofono$ sudo python2 test/list-modems [ /gobi_0 ] Features = sms net rat ussd sim gps Emergency = 0 Powered = 1 Lockdown = 0 Interfaces = org.ofono.SmartMessaging org.ofono.PushNotification org.ofono.MessageManager org.ofono.NetworkRegistration org.ofono.RadioSettings org.ofono.SupplementaryServices org.ofono.NetworkMonitor org.ofono.MessageWaiting org.ofono.AllowedAccessPoints org.ofono.SimManager org.ofono.LocationReporting org.ofono.VoiceCallManager Online = 1 Model = 196 Revision = M6600A-SCAUHSZ-3.1.3310T 1 [Jun 09 2011 17:00:00] Type = hardware ...
... and it seems to kind-of work. Even incoming SMSes work, which I could not get to work in AT mode. Otoh, voice calls do not, so...
Confused, Pavel
Hi!
Meanwhile, I can try to make voice calls more reproducable with qmi or MM for example instead of just n_gsm.. And then I'll try to fix my n_gsm pile of hacks for posting..
If you get something to work, that will be great.
If you get ofonod to work, that would be even better :-). [That's what Jolla uses, and that's what I have graphical clients for].
Oh and.. if you have reasonable windowing system, let me know.
Best regards, Pavel
* Pavel Machek pavel@ucw.cz [180329 14:11]:
Hi!
Meanwhile, I can try to make voice calls more reproducable with qmi or MM for example instead of just n_gsm.. And then I'll try to fix my n_gsm pile of hacks for posting..
If you get something to work, that will be great.
Yeah I need to at least fix a n_gsm message handling issue that currently causes retries and the retries have to be set artificially high right now to avoid timeouts.. Right now each at command takes about 30 seconds.. So I can't do any short calls :)
If you get ofonod to work, that would be even better :-). [That's what Jolla uses, and that's what I have graphical clients for].
OK, I'll stick to picocom, qmicli and mmcli for now :)
Oh and.. if you have reasonable windowing system, let me know.
I've been just using xorg and i3.
Regards,
Tony
Hi!
@@ -510,17 +510,17 @@ 07f4: 0000 07f8: 0000 07fc: 0000 -0800: 0065 -0804: 0000 -0808: 0040 +0800: 0025 # CPCAP_REG_VAUDIOC VAUDIO Control +0804: 60cf # CPCAP_REG_CC Codec Control, moto cpcap.c:1337 sets 0x0093? +0808: ae0a # CPCAP_REG_CDI Codec Digital Interface 080c: 0000 0810: 0004 -0814: 0804 -0818: 079c -081c: 0000 -0820: 0924 -0824: 0000 -0828: 0000 +0814: 0cc0 # CPCAP_REG_TXI TX Inputs, moto cpcap.c:1340 sets 0x0CC6? +0818: 0610 # CPCAP_REG_TXMP TX MIC PGA's, moto cpcap.c:1343 sets 0x0273? +081c: 0006 # CPCAP_REG_RXOA RX Output Amplifiers +0820: 0b2c # CPCAP_REG_RXVC RX Volume Control +0824: 0606 # CPCAP_REG_RXCOA RX Codec to Output Amps +0828: 0600 # CPCAP_REG_RXSDOA RX Stereo DAC to Output Amps 082c: 0400 0830: 0000 0834: 0030
I wonder if mdm6600 is the i2s master during the voice call?
Then using the n_gsm ts 27.010 uart mux, I dial:
./ngsm-rw 1 "AT+CFUN=1" # connect to network U0001+CFUN:OK ./ngsm-rw 2 "AT+CMUT=0" # unmute speaker over ch2, do this over qmi? U0001+CMUT:OK ./ngsm-rw 1 "ATD#123" # dial number U0001D:OK
And I do hear a voice talking over the speakerphone :) Sorry have not tested the mic yet..
Hehe, ok, that's fun! I don't need +CMUT=0. It gave me error but then the call worked, anyway... (I'm working with ttyUSB4).
I don't know how to hang the call up. ATH did not work for me.
And no, microphone does not work.
Thanks! Pavel
* Pavel Machek pavel@ucw.cz [180329 16:09]:
Then using the n_gsm ts 27.010 uart mux, I dial:
./ngsm-rw 1 "AT+CFUN=1" # connect to network U0001+CFUN:OK ./ngsm-rw 2 "AT+CMUT=0" # unmute speaker over ch2, do this over qmi? U0001+CMUT:OK ./ngsm-rw 1 "ATD#123" # dial number U0001D:OK
And I do hear a voice talking over the speakerphone :) Sorry have not tested the mic yet..
Hehe, ok, that's fun! I don't need +CMUT=0. It gave me error but then the call worked, anyway... (I'm working with ttyUSB4).
Oh interesting. So maybe Android is doing AT+CMUT=0 for some other modems and it's not really needed? Or you have settings preserved from warm boot from Android?
I did not have any luck with ttyUSB4 last night. Did you dial with ATD123; or ATD123? So is the semicolon really needed?
I don't know how to hang the call up. ATH did not work for me.
OK ATH works for me on n_gsm channel 1.
And no, microphone does not work.
Yeah so conf calls only for now.
Tony
On Thu 2018-03-29 09:34:50, Tony Lindgren wrote:
- Pavel Machek pavel@ucw.cz [180329 16:09]:
Then using the n_gsm ts 27.010 uart mux, I dial:
./ngsm-rw 1 "AT+CFUN=1" # connect to network U0001+CFUN:OK ./ngsm-rw 2 "AT+CMUT=0" # unmute speaker over ch2, do this over qmi? U0001+CMUT:OK ./ngsm-rw 1 "ATD#123" # dial number U0001D:OK
And I do hear a voice talking over the speakerphone :) Sorry have not tested the mic yet..
Hehe, ok, that's fun! I don't need +CMUT=0. It gave me error but then the call worked, anyway... (I'm working with ttyUSB4).
Oh interesting. So maybe Android is doing AT+CMUT=0 for some other modems and it's not really needed? Or you have settings preserved from warm boot from Android?
No idea. But I booted from recovery, and I had to power up modem using +CFUN...
I did not have any luck with ttyUSB4 last night. Did you dial with ATD123; or ATD123? So is the semicolon really needed?
AT+CFUN=1 OK ATD800123456 NO CARRIER ATD800123456; OK
I seem to need semicolon. And yes, I tried again after the call. Still says no carrier.
Hmm. And when calling test number, the modem failed:
[ 438.236755] Somebody do a proper driver please cpcap_soc_work [ 495.496032] phy-mapphone-mdm6600 usb-phy@1: modem status: 0 off [ 499.406005] phy-mapphone-mdm6600 usb-phy@1: modem status: 0 off [ 499.407318] usb 2-1: USB disconnect, device number 2 [ 499.424926] qcserial ttyUSB0: Qualcomm USB modem converter now disconnected from ttyUSB0 [ 499.434112] qcserial 2-1:1.0: device disconnected [ 499.443389] qcserial ttyUSB1: Qualcomm USB modem converter now disconnected from ttyUSB1 [ 499.451904] qcserial 2-1:1.1: device disconnected [ 499.460968] qcserial ttyUSB2: Qualcomm USB modem converter now disconnected from ttyUSB2 [ 499.469512] qcserial 2-1:1.2: device disconnected [ 499.478057] qcserial ttyUSB3: Qualcomm USB modem converter now disconnected from ttyUSB3 [ 499.486572] qcserial 2-1:1.3: device disconnected [ 499.504760] qcserial ttyUSB4: Qualcomm USB modem converter now disconnected from ttyUSB4 [ 499.513244] qcserial 2-1:1.4: device disconnected [ 499.519104] qmi_wwan 2-1:1.5 wwan0: unregister 'qmi_wwan' usb-4a064800.ohci-1, WWAN/QMI device [ 499.611511] qmi_wwan 2-1:1.6 wwan1: unregister 'qmi_wwan' usb-4a064800.ohci-1, WWAN/QMI device [ 499.676086] Somebody do a proper driver please cpcap_soc_work [ 499.688323] qmi_wwan 2-1:1.7 wwan2: unregister 'qmi_wwan' usb-4a064800.ohci-1, WWAN/QMI device [ 499.768859] qmi_wwan 2-1:1.8 wwan3: unregister 'qmi_wwan' usb-4a064800.ohci-1, WWAN/QMI device
That one will be fun to debug, I'm afraid. (Or does the message at 495.496032 mean that we turned it off?)
Best regards, Pavel
* Pavel Machek pavel@ucw.cz [180329 18:07]:
On Thu 2018-03-29 09:34:50, Tony Lindgren wrote:
- Pavel Machek pavel@ucw.cz [180329 16:09]:
Then using the n_gsm ts 27.010 uart mux, I dial:
./ngsm-rw 1 "AT+CFUN=1" # connect to network U0001+CFUN:OK ./ngsm-rw 2 "AT+CMUT=0" # unmute speaker over ch2, do this over qmi? U0001+CMUT:OK ./ngsm-rw 1 "ATD#123" # dial number U0001D:OK
And I do hear a voice talking over the speakerphone :) Sorry have not tested the mic yet..
Hehe, ok, that's fun! I don't need +CMUT=0. It gave me error but then the call worked, anyway... (I'm working with ttyUSB4).
Oh interesting. So maybe Android is doing AT+CMUT=0 for some other modems and it's not really needed? Or you have settings preserved from warm boot from Android?
No idea. But I booted from recovery, and I had to power up modem using +CFUN...
I did not have any luck with ttyUSB4 last night. Did you dial with ATD123; or ATD123? So is the semicolon really needed?
AT+CFUN=1 OK ATD800123456 NO CARRIER ATD800123456; OK
I seem to need semicolon. And yes, I tried again after the call. Still says no carrier.
OK thanks for confirming that.
Hmm. And when calling test number, the modem failed:
[ 438.236755] Somebody do a proper driver please cpcap_soc_work [ 495.496032] phy-mapphone-mdm6600 usb-phy@1: modem status: 0 off [ 499.406005] phy-mapphone-mdm6600 usb-phy@1: modem status: 0 off [ 499.407318] usb 2-1: USB disconnect, device number 2 [ 499.424926] qcserial ttyUSB0: Qualcomm USB modem converter now disconnected from ttyUSB0 [ 499.434112] qcserial 2-1:1.0: device disconnected [ 499.443389] qcserial ttyUSB1: Qualcomm USB modem converter now disconnected from ttyUSB1 [ 499.451904] qcserial 2-1:1.1: device disconnected [ 499.460968] qcserial ttyUSB2: Qualcomm USB modem converter now disconnected from ttyUSB2 [ 499.469512] qcserial 2-1:1.2: device disconnected [ 499.478057] qcserial ttyUSB3: Qualcomm USB modem converter now disconnected from ttyUSB3 [ 499.486572] qcserial 2-1:1.3: device disconnected [ 499.504760] qcserial ttyUSB4: Qualcomm USB modem converter now disconnected from ttyUSB4 [ 499.513244] qcserial 2-1:1.4: device disconnected [ 499.519104] qmi_wwan 2-1:1.5 wwan0: unregister 'qmi_wwan' usb-4a064800.ohci-1, WWAN/QMI device [ 499.611511] qmi_wwan 2-1:1.6 wwan1: unregister 'qmi_wwan' usb-4a064800.ohci-1, WWAN/QMI device [ 499.676086] Somebody do a proper driver please cpcap_soc_work [ 499.688323] qmi_wwan 2-1:1.7 wwan2: unregister 'qmi_wwan' usb-4a064800.ohci-1, WWAN/QMI device [ 499.768859] qmi_wwan 2-1:1.8 wwan3: unregister 'qmi_wwan' usb-4a064800.ohci-1, WWAN/QMI device
That one will be fun to debug, I'm afraid. (Or does the message at 495.496032 mean that we turned it off?)
Looks like mdm6600 either entered low-power mode, or hung? If it hung, it's internal watchdog might bring it back after a while.
If it entered low-power mode, in theory we should see the state change interrupt that's handled by phy-mapphone-mdm6600.c. I never managed to do that so far so maybe you figured it out with some command.
Regards,
Tony
* Tony Lindgren tony@atomide.com [180329 22:00]:
If it entered low-power mode, in theory we should see the state change interrupt that's handled by phy-mapphone-mdm6600.c. I never managed to do that so far so maybe you figured it out with some command.
And looks like the trigger to mdm6600 to enter low power states is done via USB:
# echo auto > /sys/devices/platform/44000000.ocp/4a064000.usbhshost/4a064800.ohci/usb1/1-1/power/level
Looks like with that and modem enabled I'm seeing 207mW power consumption on a and idle system with screen off on my power supply. Opening /dev/ttyUSB4 makes the power consumption go up to 502mW.. So now we need the wakeirqs and PM runtime to work for ohci and then the device can be used for few hours on batteries.
Regards,
Tony
Hi!
Hmm well I got audio call hacked to work as a proof of concept hack, see below. Maybe it can be used to verify some of the assumptions above.
Then.. To split the work a bit, can you guys maybe try to decode the cpcap register values and try to do a proper ASoC driver patch?
This is not proper patch yet, but it should be a step in that direction...
If someone knows how to express cleanly "active support for modem is the same as active audio playback", let me know....
Best regards, Pavel
diff --git a/sound/soc/codecs/cpcap.c b/sound/soc/codecs/cpcap.c index 3b53bd0..7aaa4db 100644 --- a/sound/soc/codecs/cpcap.c +++ b/sound/soc/codecs/cpcap.c @@ -221,18 +221,18 @@ struct cpcap_reg_info { };
static const struct cpcap_reg_info cpcap_default_regs[] = { - { CPCAP_REG_CC, 0xFFFF, 0x0000 }, - { CPCAP_REG_CC, 0xFFFF, 0x0000 }, - { CPCAP_REG_CDI, 0xBFFF, 0x0000 }, + { CPCAP_REG_CC, 0xFFFF, 0x60cf }, + { CPCAP_REG_CDI, 0xBFFF, 0xae0a }, { CPCAP_REG_SDAC, 0x0FFF, 0x0000 }, { CPCAP_REG_SDACDI, 0x3FFF, 0x0000 }, - { CPCAP_REG_TXI, 0x0FDF, 0x0000 }, - { CPCAP_REG_TXMP, 0x0FFF, 0x0400 }, - { CPCAP_REG_RXOA, 0x01FF, 0x0000 }, - { CPCAP_REG_RXVC, 0xFF3C, 0x0000 }, - { CPCAP_REG_RXCOA, 0x07FF, 0x0000 }, - { CPCAP_REG_RXSDOA, 0x1FFF, 0x0000 }, + { CPCAP_REG_TXI, 0x0FFF, 0x0CC0 }, + { CPCAP_REG_TXMP, 0x0FFF, 0x0610 }, + { CPCAP_REG_RXOA, 0x01FF, 0x0006 }, + { CPCAP_REG_RXVC, 0xFF3C, 0x0B2C }, + { CPCAP_REG_RXCOA, 0x07FF, 0x0606 }, + { CPCAP_REG_RXSDOA, 0x1FFF, 0x0600 }, { CPCAP_REG_RXEPOA, 0x7FFF, 0x0000 }, + { CPCAP_REG_VAUDIOC, 0xFFFF, 0x0025 }, { CPCAP_REG_A2LA, BIT(CPCAP_BIT_A2_FREE_RUN), BIT(CPCAP_BIT_A2_FREE_RUN) }, }; @@ -330,6 +330,11 @@ static const char * const cpcap_in_left_mux_texts[] = { "Off", "Mic 2", "Ext Left" };
+static const char * const cpcap_mode_texts[] = { + "Normal", "Call" +}; + + /* * input muxes use unusual register layout, so that we need to use custom * getter/setter methods @@ -354,6 +359,8 @@ static SOC_ENUM_SINGLE_DECL(cpcap_hs_l_mux_enum, 0, 6, cpcap_out_mux_texts); static SOC_ENUM_SINGLE_DECL(cpcap_emu_l_mux_enum, 0, 7, cpcap_out_mux_texts); static SOC_ENUM_SINGLE_DECL(cpcap_emu_r_mux_enum, 0, 8, cpcap_out_mux_texts);
+static SOC_ENUM_SINGLE_DECL(cpcap_mode_enum, 0, 9, cpcap_mode_texts); + static int cpcap_output_mux_get_enum(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { @@ -442,6 +449,86 @@ static int cpcap_output_mux_put_enum(struct snd_kcontrol *kcontrol, return 0; }
+static int mode; + +static int cpcap_mode_get_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_soc_dapm_kcontrol_codec(kcontrol); + struct cpcap_audio *cpcap = snd_soc_codec_get_drvdata(codec); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + int err; + + ucontrol->value.enumerated.item[0] = mode; + + return 0; +} + +static int cpcap_mode_put_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_soc_dapm_kcontrol_codec(kcontrol); + struct cpcap_audio *cpcap = snd_soc_codec_get_drvdata(codec); + struct snd_soc_dapm_context *dapm = + snd_soc_dapm_kcontrol_dapm(kcontrol); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int muxval = ucontrol->value.enumerated.item[0]; + unsigned int mask = BIT(e->shift_l); + int err; + + printk("Requested mode %d\n", muxval); + + mode = muxval; + + switch (muxval) { + case 1: + + err = regmap_update_bits(cpcap->regmap, CPCAP_REG_VAUDIOC, + 0xffff, 0x0025); // OK + if (err) + goto out; + err = regmap_update_bits(cpcap->regmap, CPCAP_REG_CC, + 0xffff, 0x60cf); // OK + if (err) + goto out; + err = regmap_update_bits(cpcap->regmap, CPCAP_REG_CDI, + 0xffff, 0xae0a); // OK + if (err) + goto out; + err = regmap_update_bits(cpcap->regmap, CPCAP_REG_TXI, + 0xffff, 0x0cc0); + if (err) + goto out; + + mask = 1 << CPCAP_BIT_PGA_CDC_EN | 0x200; + err = regmap_update_bits(cpcap->regmap, CPCAP_REG_RXCOA, + mask, mask); + if (err) + printk("error #3\n"); + + mask = 1 << CPCAP_BIT_A2_LDSP_L_EN | 1 << CPCAP_BIT_A2_LDSP_R_EN; + err = regmap_update_bits(cpcap->regmap, CPCAP_REG_RXOA, + mask, mask); + if (err) + printk("error #2\n"); + + err = regmap_update_bits(cpcap->regmap, 0x814, + 0x0400, 0x0400); + if (err) + printk("error #1\n"); + + default: + break; + } + + // FIXME +// snd_soc_dapm_mux_update_power(dapm, kcontrol, muxval, e, NULL); + + return 0; +out: printk("Something failed\n"); + return -EINVAL; +} + static int cpcap_input_right_mux_get_enum(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { @@ -630,6 +717,11 @@ static const struct snd_kcontrol_new cpcap_earpiece_mux = SOC_DAPM_ENUM_EXT("Earpiece", cpcap_earpiece_mux_enum, cpcap_output_mux_get_enum, cpcap_output_mux_put_enum);
+static const struct snd_kcontrol_new cpcap_mode = + SOC_DAPM_ENUM_EXT("Mode", cpcap_mode_enum, + cpcap_mode_get_enum, cpcap_mode_put_enum); + + static const struct snd_kcontrol_new cpcap_hifi_mono_mixer_controls[] = { SOC_DAPM_SINGLE("HiFi Mono Playback Switch", CPCAP_REG_RXSDOA, CPCAP_BIT_MONO_DAC1, 1, 0), @@ -771,6 +863,10 @@ static const struct snd_soc_dapm_widget cpcap_dapm_widgets[] = { SND_SOC_DAPM_MUX("EMU Left Playback Route", SND_SOC_NOPM, 0, 0, &cpcap_emu_left_mux),
+ + SND_SOC_DAPM_MUX("Mode", SND_SOC_NOPM, 0, 0, + &cpcap_mode), + /* Output Amplifier */ SND_SOC_DAPM_PGA("Earpiece PGA", CPCAP_REG_RXOA, CPCAP_BIT_A1_EAR_EN, 0, NULL, 0), @@ -816,6 +912,9 @@ static const struct snd_soc_dapm_route intercon[] = { {"Microphone 1 PGA", NULL, "VAUDIO"}, {"Microphone 2 PGA", NULL, "VAUDIO"},
+ /* FIXME */ + {"Mode", NULL, "Voice TX"}, + /* Stream -> AIF */ {"HiFi RX", NULL, "HiFi Playback"}, {"Voice RX", NULL, "Voice Playback"},
* Pavel Machek pavel@ucw.cz [180331 14:56]:
Hi!
Hmm well I got audio call hacked to work as a proof of concept hack, see below. Maybe it can be used to verify some of the assumptions above.
Then.. To split the work a bit, can you guys maybe try to decode the cpcap register values and try to do a proper ASoC driver patch?
This is not proper patch yet, but it should be a step in that direction...
Cool :) Microphone still does not work for me.. I tried tweaking the alsamixer settings but no mic. This is with cold boot with droid4-kexecboot if that might make a difference, we may have some register uninitialized somewhere. Any ideas?
FYI, I got one of the remaining n_gsm issues tracked down yesterday, at least one more bug to go. Things only work with n_gsm debug enabled right now for some reason..
Regards,
Tony
On Sat 2018-03-31 11:19:35, Tony Lindgren wrote:
- Pavel Machek pavel@ucw.cz [180331 14:56]:
Hi!
Hmm well I got audio call hacked to work as a proof of concept hack, see below. Maybe it can be used to verify some of the assumptions above.
Then.. To split the work a bit, can you guys maybe try to decode the cpcap register values and try to do a proper ASoC driver patch?
This is not proper patch yet, but it should be a step in that direction...
Cool :) Microphone still does not work for me.. I tried tweaking the alsamixer settings but no mic. This is with cold boot with droid4-kexecboot if that might make a difference, we may have some register uninitialized somewhere. Any ideas?
Ok, I was focusing on the speaker side.
alsamixer, tab to go to capture settings, set it to 37 37 Mic2 Mic1 should work, according to my notes, but not recently tested and not tested against real human.
I'll attempt to test it, but something in my userland shuts down system just after boot 60% of time, which is rather annoying.
FYI, I got one of the remaining n_gsm issues tracked down yesterday, at least one more bug to go. Things only work with n_gsm debug enabled right now for some reason..
I don't know why I need n_gsm, but I have my fingers crossed. :-).
Pavel
On Sat 2018-03-31 21:19:39, Pavel Machek wrote:
On Sat 2018-03-31 11:19:35, Tony Lindgren wrote:
- Pavel Machek pavel@ucw.cz [180331 14:56]:
Hi!
Hmm well I got audio call hacked to work as a proof of concept hack, see below. Maybe it can be used to verify some of the assumptions above.
Then.. To split the work a bit, can you guys maybe try to decode the cpcap register values and try to do a proper ASoC driver patch?
This is not proper patch yet, but it should be a step in that direction...
Cool :) Microphone still does not work for me.. I tried tweaking the alsamixer settings but no mic. This is with cold boot with droid4-kexecboot if that might make a difference, we may have some register uninitialized somewhere. Any ideas?
Ok, I was focusing on the speaker side.
alsamixer, tab to go to capture settings, set it to 37 37 Mic2 Mic1 should work, according to my notes, but not recently tested and not tested against real human.
I'll attempt to test it, but something in my userland shuts down system just after boot 60% of time, which is rather annoying.
Hmm. So I tried again, and setting Mic1 and back in the capture settings crashed the modem. Bang, disconnected from the USB.
Pavel
On Sat 2018-03-31 21:46:16, Pavel Machek wrote:
On Sat 2018-03-31 21:19:39, Pavel Machek wrote:
On Sat 2018-03-31 11:19:35, Tony Lindgren wrote:
- Pavel Machek pavel@ucw.cz [180331 14:56]:
Hi!
Hmm well I got audio call hacked to work as a proof of concept hack, see below. Maybe it can be used to verify some of the assumptions above.
Then.. To split the work a bit, can you guys maybe try to decode the cpcap register values and try to do a proper ASoC driver patch?
This is not proper patch yet, but it should be a step in that direction...
Cool :) Microphone still does not work for me.. I tried tweaking the alsamixer settings but no mic. This is with cold boot with droid4-kexecboot if that might make a difference, we may have some register uninitialized somewhere. Any ideas?
Ok, I was focusing on the speaker side.
alsamixer, tab to go to capture settings, set it to 37 37 Mic2 Mic1 should work, according to my notes, but not recently tested and not tested against real human.
I'll attempt to test it, but something in my userland shuts down system just after boot 60% of time, which is rather annoying.
Hmm. So I tried again, and setting Mic1 and back in the capture settings crashed the modem. Bang, disconnected from the USB.
Next try, and it worked this time.
_Before the call_, set mode to Normal and then Call. Then go to capture, and set 100 100 Mic2 Mic1. Then place a call,
AT+CFUN=1 OK ATD6;
If you change Mic1 setting, GSM modem will likely crash.
Good luck, Pavel
* Pavel Machek pavel@ucw.cz [180331 19:56]:
On Sat 2018-03-31 21:46:16, Pavel Machek wrote:
On Sat 2018-03-31 21:19:39, Pavel Machek wrote:
On Sat 2018-03-31 11:19:35, Tony Lindgren wrote:
Cool :) Microphone still does not work for me.. I tried tweaking the alsamixer settings but no mic. This is with cold boot with droid4-kexecboot if that might make a difference, we may have some register uninitialized somewhere. Any ideas?
Ok, I was focusing on the speaker side.
alsamixer, tab to go to capture settings, set it to 37 37 Mic2 Mic1 should work, according to my notes, but not recently tested and not tested against real human.
I'll attempt to test it, but something in my userland shuts down system just after boot 60% of time, which is rather annoying.
Hmm. So I tried again, and setting Mic1 and back in the capture settings crashed the modem. Bang, disconnected from the USB.
Next try, and it worked this time.
_Before the call_, set mode to Normal and then Call. Then go to capture, and set 100 100 Mic2 Mic1. Then place a call,
AT+CFUN=1 OK ATD6;
No luck with microphone here :( Using ttyUSB4, AT+CFUN=1 works, but ATD command on it just hangs the USB interface and I have to reload phy-mapphone-mdm6600 to reset the modem.
If you change Mic1 setting, GSM modem will likely crash.
Have not seen that one here :)
Regards,
Tony
On Sat 2018-03-31 16:43:14, Tony Lindgren wrote:
- Pavel Machek pavel@ucw.cz [180331 19:56]:
On Sat 2018-03-31 21:46:16, Pavel Machek wrote:
On Sat 2018-03-31 21:19:39, Pavel Machek wrote:
On Sat 2018-03-31 11:19:35, Tony Lindgren wrote:
Cool :) Microphone still does not work for me.. I tried tweaking the alsamixer settings but no mic. This is with cold boot with droid4-kexecboot if that might make a difference, we may have some register uninitialized somewhere. Any ideas?
Ok, I was focusing on the speaker side.
alsamixer, tab to go to capture settings, set it to 37 37 Mic2 Mic1 should work, according to my notes, but not recently tested and not tested against real human.
I'll attempt to test it, but something in my userland shuts down system just after boot 60% of time, which is rather annoying.
Hmm. So I tried again, and setting Mic1 and back in the capture settings crashed the modem. Bang, disconnected from the USB.
Next try, and it worked this time.
_Before the call_, set mode to Normal and then Call. Then go to capture, and set 100 100 Mic2 Mic1. Then place a call,
AT+CFUN=1 OK ATD6;
No luck with microphone here :( Using ttyUSB4, AT+CFUN=1 works, but ATD command on it just hangs the USB interface and I have to reload phy-mapphone-mdm6600 to reset the modem.
I realized where the difference is.
I have ofonod running there, one from maemo leste distribution. It manages to talk to the modem somehow. I guess that accounts for the difference.
I guess I should get it out of the picture.... Pavel
On Sat 2018-03-31 16:43:14, Tony Lindgren wrote:
- Pavel Machek pavel@ucw.cz [180331 19:56]:
On Sat 2018-03-31 21:46:16, Pavel Machek wrote:
On Sat 2018-03-31 21:19:39, Pavel Machek wrote:
On Sat 2018-03-31 11:19:35, Tony Lindgren wrote:
Cool :) Microphone still does not work for me.. I tried tweaking the alsamixer settings but no mic. This is with cold boot with droid4-kexecboot if that might make a difference, we may have some register uninitialized somewhere. Any ideas?
Ok, I was focusing on the speaker side.
alsamixer, tab to go to capture settings, set it to 37 37 Mic2 Mic1 should work, according to my notes, but not recently tested and not tested against real human.
I'll attempt to test it, but something in my userland shuts down system just after boot 60% of time, which is rather annoying.
Hmm. So I tried again, and setting Mic1 and back in the capture settings crashed the modem. Bang, disconnected from the USB.
Next try, and it worked this time.
_Before the call_, set mode to Normal and then Call. Then go to capture, and set 100 100 Mic2 Mic1. Then place a call,
AT+CFUN=1 OK ATD6;
No luck with microphone here :( Using ttyUSB4, AT+CFUN=1 works, but ATD command on it just hangs the USB interface and I have to reload phy-mapphone-mdm6600 to reset the modem.
Test call with real human worked (thanks to Rolf K.), I could hear him well but he reported call was very quiet. And that was with capture settings at 100%.
If you had a register dump from android with mics working, preferably not in speaker mode, perhaps I could try to figure it out?
Pavel
* Pavel Machek pavel@ucw.cz [180401 13:20]:
On Sat 2018-03-31 16:43:14, Tony Lindgren wrote:
- Pavel Machek pavel@ucw.cz [180331 19:56]:
On Sat 2018-03-31 21:46:16, Pavel Machek wrote:
On Sat 2018-03-31 21:19:39, Pavel Machek wrote:
On Sat 2018-03-31 11:19:35, Tony Lindgren wrote:
Cool :) Microphone still does not work for me.. I tried tweaking the alsamixer settings but no mic. This is with cold boot with droid4-kexecboot if that might make a difference, we may have some register uninitialized somewhere. Any ideas?
Ok, I was focusing on the speaker side.
alsamixer, tab to go to capture settings, set it to 37 37 Mic2 Mic1 should work, according to my notes, but not recently tested and not tested against real human.
I'll attempt to test it, but something in my userland shuts down system just after boot 60% of time, which is rather annoying.
Hmm. So I tried again, and setting Mic1 and back in the capture settings crashed the modem. Bang, disconnected from the USB.
Next try, and it worked this time.
_Before the call_, set mode to Normal and then Call. Then go to capture, and set 100 100 Mic2 Mic1. Then place a call,
AT+CFUN=1 OK ATD6;
No luck with microphone here :( Using ttyUSB4, AT+CFUN=1 works, but ATD command on it just hangs the USB interface and I have to reload phy-mapphone-mdm6600 to reset the modem.
Test call with real human worked (thanks to Rolf K.), I could hear him well but he reported call was very quiet. And that was with capture settings at 100%.
Maybe the volume also needs to be controlled at mdm6600 end. I'm seeing some AT+CLVL=n with n being between [0-7] calls on DLCI2 in my Android logcat logs.
If you had a register dump from android with mics working, preferably not in speaker mode, perhaps I could try to figure it out?
OK here are four diffs against starting the phone app for regular call, speaker call, and muted versions of them:
http://muru.com/linux/d4/cpcap/
Also, I'm connected over cdma right now, not 3g, but I doubt that makes a difference for the microphone.
Regards,
Tony
* Tony Lindgren tony@atomide.com [180401 15:38]:
- Pavel Machek pavel@ucw.cz [180401 13:20]:
On Sat 2018-03-31 16:43:14, Tony Lindgren wrote:
- Pavel Machek pavel@ucw.cz [180331 19:56]:
On Sat 2018-03-31 21:46:16, Pavel Machek wrote:
On Sat 2018-03-31 21:19:39, Pavel Machek wrote:
On Sat 2018-03-31 11:19:35, Tony Lindgren wrote: > Cool :) Microphone still does not work for me.. I tried tweaking > the alsamixer settings but no mic. This is with cold boot with > droid4-kexecboot if that might make a difference, we may have > some register uninitialized somewhere. Any ideas?
Ok, I was focusing on the speaker side.
alsamixer, tab to go to capture settings, set it to 37 37 Mic2 Mic1 should work, according to my notes, but not recently tested and not tested against real human.
I'll attempt to test it, but something in my userland shuts down system just after boot 60% of time, which is rather annoying.
Hmm. So I tried again, and setting Mic1 and back in the capture settings crashed the modem. Bang, disconnected from the USB.
Next try, and it worked this time.
_Before the call_, set mode to Normal and then Call. Then go to capture, and set 100 100 Mic2 Mic1. Then place a call,
AT+CFUN=1 OK ATD6;
No luck with microphone here :( Using ttyUSB4, AT+CFUN=1 works, but ATD command on it just hangs the USB interface and I have to reload phy-mapphone-mdm6600 to reset the modem.
Test call with real human worked (thanks to Rolf K.), I could hear him well but he reported call was very quiet. And that was with capture settings at 100%.
Maybe the volume also needs to be controlled at mdm6600 end. I'm seeing some AT+CLVL=n with n being between [0-7] calls on DLCI2 in my Android logcat logs.
If you had a register dump from android with mics working, preferably not in speaker mode, perhaps I could try to figure it out?
OK here are four diffs against starting the phone app for regular call, speaker call, and muted versions of them:
http://muru.com/linux/d4/cpcap/
Also, I'm connected over cdma right now, not 3g, but I doubt that makes a difference for the microphone.
Found it! Here's what I need to do over n_gsm:
ngsm 1 "AT+CFUN=1" ngsm 1 "AT+CFUN?" ngsm 2 "AT+EACC=3,0" # enable mic ngsm 2 "AT+CLVL=4" # set speaker volume ngsm 2 "AT+CMUT=0" # unmute mic ngsm 1 "ATD${number}" ngsm 1 "AT+CLCC" # list current calls ngsm 2 "AT+NREC=1" # enable noise cancellation ngsm 1 "AT+SCRN=0" # ??? not sure if this does anything
while [ 1 ]; do date ngsm 1 "AT+CLCC" sleep 10 done
So speaker phone call works just fine, I just tested with a human at the other end :)
Hmm let's hope all those also translate to some qmi calls.
Regards,
Tony
Hi!
If you had a register dump from android with mics working, preferably not in speaker mode, perhaps I could try to figure it out?
OK here are four diffs against starting the phone app for regular call, speaker call, and muted versions of them:
http://muru.com/linux/d4/cpcap/
Also, I'm connected over cdma right now, not 3g, but I doubt that makes a difference for the microphone.
Thanks!
When I apply register settings directly, I indeed get working "normal" call working. Good. Diff for illustration is below. Clearly needs some improvements.
And it shows that alsa mixers are expected to be simple and obvious on D4 -- they just do not work.
Current version of phone gui is at https://github.com/pavelmachek/unicsy_demo
- ofono interface should be usable - AT commands need a lot more work, and they'll probably never work nicely in this design. They are just a temporary hack.
(ouch, and it should be rewritten, in Vala or better Rust...) Pavel
commit 06acc26c318558ed6a50b5a22afffeb9abbe7553 Author: Pavel pavel@ucw.cz Date: Wed Apr 4 00:04:34 2018 +0200
Add support for "normal" (not handsfree) call.
diff --git a/sound/soc/codecs/cpcap.c b/sound/soc/codecs/cpcap.c index 7aaa4db..59c02b7 100644 --- a/sound/soc/codecs/cpcap.c +++ b/sound/soc/codecs/cpcap.c @@ -331,7 +331,7 @@ static const char * const cpcap_in_left_mux_texts[] = { };
static const char * const cpcap_mode_texts[] = { - "Normal", "Call" + "Normal", "Handsfree", "Call", };
@@ -464,25 +464,11 @@ static int cpcap_mode_get_enum(struct snd_kcontrol *kcontrol, return 0; }
-static int cpcap_mode_put_enum(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) +static int enable_call(struct cpcap_audio *cpcap, int on) { - struct snd_soc_codec *codec = snd_soc_dapm_kcontrol_codec(kcontrol); - struct cpcap_audio *cpcap = snd_soc_codec_get_drvdata(codec); - struct snd_soc_dapm_context *dapm = - snd_soc_dapm_kcontrol_dapm(kcontrol); - struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; - unsigned int muxval = ucontrol->value.enumerated.item[0]; - unsigned int mask = BIT(e->shift_l); int err; - - printk("Requested mode %d\n", muxval); - - mode = muxval; - - switch (muxval) { - case 1: - + unsigned long mask; + err = regmap_update_bits(cpcap->regmap, CPCAP_REG_VAUDIOC, 0xffff, 0x0025); // OK if (err) @@ -516,6 +502,56 @@ static int cpcap_mode_put_enum(struct snd_kcontrol *kcontrol, 0x0400, 0x0400); if (err) printk("error #1\n"); + + return 0; +out: + printk("Error!\n"); + return -EIO; +} + +static int cpcap_mode_put_enum(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_soc_dapm_kcontrol_codec(kcontrol); + struct cpcap_audio *cpcap = snd_soc_codec_get_drvdata(codec); + struct snd_soc_dapm_context *dapm = + snd_soc_dapm_kcontrol_dapm(kcontrol); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int muxval = ucontrol->value.enumerated.item[0]; + unsigned int mask = BIT(e->shift_l); + int err; + + printk("Requested mode %d\n", muxval); + + mode = muxval; + + switch (muxval) { + case 1: + enable_call(cpcap, 1); + break; + case 2: + enable_call(cpcap, 1); + + err = regmap_update_bits(cpcap->regmap, CPCAP_REG_TXI, + 0xffff, 0x0cc6); + if (err) + goto out; + + err = regmap_update_bits(cpcap->regmap, CPCAP_REG_TXMP, + 0xffff, 0x0673); + if (err) + goto out; + + + err = regmap_update_bits(cpcap->regmap, CPCAP_REG_RXOA, + 0xffff, 0x0001); + if (err) + goto out; + + err = regmap_update_bits(cpcap->regmap, CPCAP_REG_RXCOA, + 0xffff, 0x0601); + if (err) + goto out; default: break;
* Pavel Machek pavel@ucw.cz [180331 19:21]:
On Sat 2018-03-31 11:19:35, Tony Lindgren wrote:
- Pavel Machek pavel@ucw.cz [180331 14:56]:
Hi!
Hmm well I got audio call hacked to work as a proof of concept hack, see below. Maybe it can be used to verify some of the assumptions above.
Then.. To split the work a bit, can you guys maybe try to decode the cpcap register values and try to do a proper ASoC driver patch?
This is not proper patch yet, but it should be a step in that direction...
Cool :) Microphone still does not work for me.. I tried tweaking the alsamixer settings but no mic. This is with cold boot with droid4-kexecboot if that might make a difference, we may have some register uninitialized somewhere. Any ideas?
Ok, I was focusing on the speaker side.
alsamixer, tab to go to capture settings, set it to 37 37 Mic2 Mic1 should work, according to my notes, but not recently tested and not tested against real human.
In alsamixer after setting the second "Speaker" to "Voice" and "Mode" to "Call" and "Left" to "Mic 2" and "Right" to "Mic 1" and doing a test call the mic does not work, speaker works though.
After the call in alsamixer, setting the second "Speaker" to "HiFi", and "Mode" to "Normal" mode, I can record a wav file and play it back just fine. But this only works for me after reloading snd-soc-cpcap.ko after the voice call.
I'll attempt to test it, but something in my userland shuts down system just after boot 60% of time, which is rather annoying.
Hmm battery voltage too low? We have cpcap_battery_irq_thread() call orderly_poweroff() on lowbpl interrupt.
FYI, I got one of the remaining n_gsm issues tracked down yesterday, at least one more bug to go. Things only work with n_gsm debug enabled right now for some reason..
I don't know why I need n_gsm, but I have my fingers crossed. :-).
More features it seems :) But sounds like you're all set for now with USB access.
Regards,
Tony
On Wed, Mar 28, 2018 at 04:02:19PM +0200, Sebastian Reichel wrote:
In case of the last two variants, the bus clock is provided by CPCAP, so it needs to be enabled for any audio stream. I suppose the codec <-> codec as part of TDM is out of scope for the graph card and we need a Droid 4 specific card driver?
It's definitely pushing it towards being more useful to consider at least in the short term, basically nobody ever did these TDM muxes since usually you end up needing something more than just straight digital data muxing (mixing for example) so things usually get routed through a chip that can do more with the data.
Hi!
I can talk to the modem and start a call.
Doing an AT query is the easy part :)
Then something like this (untested!) is certainly needed. Probably more...
I intentionally left this part out. The CPCAP codec has two DAIs and not 3+. The code you just added is a hack from Motorola. Their driver is full of hacks and it's obvious its author(s) did not fully understand the ASoC APIs.
My understanding is, that we only need to replace the audio-graph-card driver to something more complex. The VOICE DAI needs to be configured differently based on the use case:
Does the voice part work for you? I configured all the mixers to voice, and then am using
sudo aplay -D plughw:CARD=Audio,DEV=1 /usr/share/sounds/alsa/Front_Center.wav -r 16000 X11 connection rejected because of wrong authentication. xcb_connection_has_error() returned true Playing WAVE '/usr/share/sounds/alsa/Front_Center.wav' : Signed 16 bit Little Endian, Rate 48000 Hz, Mono user@devuan:/my/tui/rweb$
to play the test sound. But I get white noise, not a test sound :-(. [This also shows problem with card naming.]
Any ideas? Pavel
Hi!
I can talk to the modem and start a call.
Doing an AT query is the easy part :)
Then something like this (untested!) is certainly needed. Probably more...
I intentionally left this part out. The CPCAP codec has two DAIs and not 3+. The code you just added is a hack from Motorola. Their driver is full of hacks and it's obvious its author(s) did not fully understand the ASoC APIs.
My understanding is, that we only need to replace the audio-graph-card driver to something more complex. The VOICE DAI needs to be configured differently based on the use case:
Does the voice part work for you? I configured all the mixers to voice, and then am using
I tried with 8kHz, 8-bit, too:
user@devuan:/my/tui/rweb$ arecord -D plughw:CARD=Audio,DEV=1 -t wav --max-file-time 30 mon.wav Recording WAVE 'mon.wav' : Unsigned 8 bit, Rate 8000 Hz, Mono ^CAborted by signal Interrupt... arecord: pcm_read:2103: read error: Interrupted system call user@devuan:/my/tui/rweb$ aplay -D plughw:CARD=Audio,DEV=1 -t wav mon.wav Playing WAVE 'mon.wav' : Unsigned 8 bit, Rate 8000 Hz, Mono ^CAborted by signal Interrupt...
Recording works okay, but playback produces ugly noise (not white this time). If I copy mon.wav to PC, it playes back in pretty acceptable quality.
So summary
HIFI DAI works VOICE DAI recording is ok VOICE DAI playbacks plays noise.
Best regards, Pavel
Hi!
I think I should have the required options enabled...
CONFIG_SND_OMAP_SOC=y CONFIG_SND_OMAP_SOC_DMIC=y CONFIG_SND_OMAP_SOC_MCBSP=y CONFIG_SND_OMAP_SOC_MCPDM=y CONFIG_SND_OMAP_SOC_HDMI_AUDIO=y
That's the SoC (OMAP) side.
# CONFIG_SND_OMAP_SOC_RX51 is not set # CONFIG_SND_OMAP_SOC_N9 is not set CONFIG_SND_OMAP_SOC_OMAP_TWL4030=y CONFIG_SND_OMAP_SOC_OMAP_ABE_TWL6040=y
That's not needed (but does not hurt). The Droid 4 has no TWL companion chip and uses CPCAP instead.
Umm. AFAICT CONFIG_SND_OMAP_SOC_MCBSP=y can not be selected without selecting one of the other options... right?
Seems like bug in Kconfig.
Pavel
* Sebastian Reichel sebastian.reichel@collabora.co.uk [180223 20:03]:
Hi,
This adds audio support to Motorola Droid 4. I dropped the regulator from the DT binding as requested by Mark.
Still works for me. Mark, are you happy now with the way the nodes are set up?
For the binding and driver changes:
Acked-by: Tony Lindgren tony@atomide.com
Then I'll be picking up the dts related patches after no more comments.
Regards,
Tony
* Tony Lindgren tony@atomide.com [180223 22:25]:
Then I'll be picking up the dts related patches after no more comments.
Applying patches 4/5 and 5/5 into omap-for-v4.17/dt thanks.
Tony
participants (9)
-
kbuild test robot
-
Lee Jones
-
Mark Brown
-
Merlijn Wajer
-
Michael Nazzareno Trimarchi
-
Pavel Machek
-
Rob Herring
-
Sebastian Reichel
-
Tony Lindgren