Alsa-devel
Threads by month
- ----- 2024 -----
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2013 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2012 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2011 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2010 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2009 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2008 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2007 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
November 2013
- 132 participants
- 321 discussions
[alsa-devel] firewire-lib: an issue to generate packet with 'no data' in blocking mode
by Takashi Sakamoto 22 Nov '13
by Takashi Sakamoto 22 Nov '13
22 Nov '13
Clemens,
I have a question about generate packet with 'no data' in blocking mode.
I think there is out of specification in current firewire-lib.
If this is a qurk for some devices, I'll prepare patches to switch
generating mode because BeBoB cannot sound with current firewire-lib. If
this is a bug, then I want to discuss which is better for firewire-lib.
In my understanding of IEC 61883-6, there are two ways:
1. generate 'empty packet' defined in IEC 61883-1
- size of packet is 2 quadlets
- FDF = sfc
- packet includes just CIP headers
2. generate 'special non-empty packet' defined in IEC 61883-6
- size of packet is following to blocking mode
- FDF = 0xff ('NO-DATA' code)
- packet includes dummy data
But current implementation is a strange combination of them.
- size of packet is 2 (way 1)
- FDF = 0xff (way 2)
I attach two patches. 'empty.patch' is for 1 and 'special.patch' is for 2.
Regards
Takashi Sakamoto
3
4
This driver adds support for digital audio (I2S)
for the BCM2835 SoC that is used by the
Raspberry Pi. External audio codecs can be
connected to the Raspberry Pi via P5 header.
It relies on cyclic DMA engine support for BCM2835.
Signed-off-by: Florian Meier <florian.meier(a)koalo.de>
---
Minor style improvements compared to PATCHv3.
.../devicetree/bindings/sound/bcm2835-i2s.txt | 25 +
sound/soc/Kconfig | 1 +
sound/soc/Makefile | 1 +
sound/soc/bcm/Kconfig | 10 +
sound/soc/bcm/Makefile | 5 +
sound/soc/bcm/bcm2835-i2s.c | 892 ++++++++++++++++++++
6 files changed, 934 insertions(+)
create mode 100644 Documentation/devicetree/bindings/sound/bcm2835-i2s.txt
create mode 100644 sound/soc/bcm/Kconfig
create mode 100644 sound/soc/bcm/Makefile
create mode 100644 sound/soc/bcm/bcm2835-i2s.c
diff --git a/Documentation/devicetree/bindings/sound/bcm2835-i2s.txt b/Documentation/devicetree/bindings/sound/bcm2835-i2s.txt
new file mode 100644
index 0000000..65783de
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/bcm2835-i2s.txt
@@ -0,0 +1,25 @@
+* Broadcom BCM2835 SoC I2S/PCM module
+
+Required properties:
+- compatible: "brcm,bcm2835-i2s"
+- reg: A list of base address and size entries:
+ * The first entry should cover the PCM registers
+ * The second entry should cover the PCM clock registers
+- dmas: List of DMA controller phandle and DMA request line ordered pairs.
+- dma-names: Identifier string for each DMA request line in the dmas property.
+ These strings correspond 1:1 with the ordered pairs in dmas.
+
+ One of the DMA channels will be responsible for transmission (should be
+ named "tx") and one for reception (should be named "rx").
+
+Example:
+
+bcm2835_i2s: i2s@7e203000 {
+ compatible = "brcm,bcm2835-i2s";
+ reg = <0x7e203000 0x20>,
+ <0x7e101098 0x02>;
+
+ dmas = <&dma 2>,
+ <&dma 3>;
+ dma-names = "tx", "rx";
+};
diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig
index 5138b84..a5e3a70 100644
--- a/sound/soc/Kconfig
+++ b/sound/soc/Kconfig
@@ -33,6 +33,7 @@ config SND_SOC_GENERIC_DMAENGINE_PCM
# All the supported SoCs
source "sound/soc/atmel/Kconfig"
source "sound/soc/au1x/Kconfig"
+source "sound/soc/bcm/Kconfig"
source "sound/soc/blackfin/Kconfig"
source "sound/soc/cirrus/Kconfig"
source "sound/soc/davinci/Kconfig"
diff --git a/sound/soc/Makefile b/sound/soc/Makefile
index 8b9e701..b52d4aa 100644
--- a/sound/soc/Makefile
+++ b/sound/soc/Makefile
@@ -10,6 +10,7 @@ obj-$(CONFIG_SND_SOC) += codecs/
obj-$(CONFIG_SND_SOC) += generic/
obj-$(CONFIG_SND_SOC) += atmel/
obj-$(CONFIG_SND_SOC) += au1x/
+obj-$(CONFIG_SND_SOC) += bcm/
obj-$(CONFIG_SND_SOC) += blackfin/
obj-$(CONFIG_SND_SOC) += cirrus/
obj-$(CONFIG_SND_SOC) += davinci/
diff --git a/sound/soc/bcm/Kconfig b/sound/soc/bcm/Kconfig
new file mode 100644
index 0000000..3d82a29
--- /dev/null
+++ b/sound/soc/bcm/Kconfig
@@ -0,0 +1,10 @@
+config SND_BCM2835_SOC_I2S
+ tristate "SoC Audio support for the Broadcom BCM2835 I2S module"
+ depends on ARCH_BCM2835 || COMPILE_TEST
+ select SND_SOC_DMAENGINE_PCM
+ select SND_SOC_GENERIC_DMAENGINE_PCM
+ select REGMAP_MMIO
+ help
+ Say Y or M if you want to add support for codecs attached to
+ the BCM2835 I2S interface. You will also need
+ to select the audio interfaces to support below.
diff --git a/sound/soc/bcm/Makefile b/sound/soc/bcm/Makefile
new file mode 100644
index 0000000..bc816b7
--- /dev/null
+++ b/sound/soc/bcm/Makefile
@@ -0,0 +1,5 @@
+# BCM2835 Platform Support
+snd-soc-bcm2835-i2s-objs := bcm2835-i2s.o
+
+obj-$(CONFIG_SND_BCM2835_SOC_I2S) += snd-soc-bcm2835-i2s.o
+
diff --git a/sound/soc/bcm/bcm2835-i2s.c b/sound/soc/bcm/bcm2835-i2s.c
new file mode 100644
index 0000000..7b27c35
--- /dev/null
+++ b/sound/soc/bcm/bcm2835-i2s.c
@@ -0,0 +1,892 @@
+/*
+ * ALSA SoC I2S Audio Layer for Broadcom BCM2835 SoC
+ *
+ * Author: Florian Meier <florian.meier(a)koalo.de>
+ * Copyright 2013
+ *
+ * Based on
+ * Raspberry Pi PCM I2S ALSA Driver
+ * Copyright (c) by Phil Poole 2013
+ *
+ * ALSA SoC I2S (McBSP) Audio Layer for TI DAVINCI processor
+ * Vladimir Barinov, <vbarinov(a)embeddedalley.com>
+ * Copyright (C) 2007 MontaVista Software, Inc., <source(a)mvista.com>
+ *
+ * OMAP ALSA SoC DAI driver using McBSP port
+ * Copyright (C) 2008 Nokia Corporation
+ * Contact: Jarkko Nikula <jarkko.nikula(a)bitmer.com>
+ * Peter Ujfalusi <peter.ujfalusi(a)ti.com>
+ *
+ * Freescale SSI ALSA SoC Digital Audio Interface (DAI) driver
+ * Author: Timur Tabi <timur(a)freescale.com>
+ * Copyright 2007-2010 Freescale Semiconductor, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/clk.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+#include <sound/dmaengine_pcm.h>
+
+/* Clock registers */
+#define BCM2835_CLK_PCMCTL_REG 0x00
+#define BCM2835_CLK_PCMDIV_REG 0x04
+
+/* Clock register settings */
+#define BCM2835_CLK_PASSWD (0x5a000000)
+#define BCM2835_CLK_PASSWD_MASK (0xff000000)
+#define BCM2835_CLK_MASH(v) ((v) << 9)
+#define BCM2835_CLK_FLIP BIT(8)
+#define BCM2835_CLK_BUSY BIT(7)
+#define BCM2835_CLK_KILL BIT(5)
+#define BCM2835_CLK_ENAB BIT(4)
+#define BCM2835_CLK_SRC(v) (v)
+
+#define BCM2835_CLK_SHIFT (12)
+#define BCM2835_CLK_DIVI(v) ((v) << BCM2835_CLK_SHIFT)
+#define BCM2835_CLK_DIVF(v) (v)
+#define BCM2835_CLK_DIVF_MASK (0xFFF)
+
+enum {
+ BCM2835_CLK_MASH_0 = 0,
+ BCM2835_CLK_MASH_1,
+ BCM2835_CLK_MASH_2,
+ BCM2835_CLK_MASH_3,
+};
+
+enum {
+ BCM2835_CLK_SRC_GND = 0,
+ BCM2835_CLK_SRC_OSC,
+ BCM2835_CLK_SRC_DBG0,
+ BCM2835_CLK_SRC_DBG1,
+ BCM2835_CLK_SRC_PLLA,
+ BCM2835_CLK_SRC_PLLC,
+ BCM2835_CLK_SRC_PLLD,
+ BCM2835_CLK_SRC_HDMI,
+};
+
+/* Most clocks are not useable (freq = 0) */
+static const unsigned int bcm2835_clk_freq[BCM2835_CLK_SRC_HDMI+1] = {
+ [BCM2835_CLK_SRC_GND] = 0,
+ [BCM2835_CLK_SRC_OSC] = 19200000,
+ [BCM2835_CLK_SRC_DBG0] = 0,
+ [BCM2835_CLK_SRC_DBG1] = 0,
+ [BCM2835_CLK_SRC_PLLA] = 0,
+ [BCM2835_CLK_SRC_PLLC] = 0,
+ [BCM2835_CLK_SRC_PLLD] = 500000000,
+ [BCM2835_CLK_SRC_HDMI] = 0,
+};
+
+/* I2S registers */
+#define BCM2835_I2S_CS_A_REG 0x00
+#define BCM2835_I2S_FIFO_A_REG 0x04
+#define BCM2835_I2S_MODE_A_REG 0x08
+#define BCM2835_I2S_RXC_A_REG 0x0c
+#define BCM2835_I2S_TXC_A_REG 0x10
+#define BCM2835_I2S_DREQ_A_REG 0x14
+#define BCM2835_I2S_INTEN_A_REG 0x18
+#define BCM2835_I2S_INTSTC_A_REG 0x1c
+#define BCM2835_I2S_GRAY_REG 0x20
+
+/* I2S register settings */
+#define BCM2835_I2S_STBY BIT(25)
+#define BCM2835_I2S_SYNC BIT(24)
+#define BCM2835_I2S_RXSEX BIT(23)
+#define BCM2835_I2S_RXF BIT(22)
+#define BCM2835_I2S_TXE BIT(21)
+#define BCM2835_I2S_RXD BIT(20)
+#define BCM2835_I2S_TXD BIT(19)
+#define BCM2835_I2S_RXR BIT(18)
+#define BCM2835_I2S_TXW BIT(17)
+#define BCM2835_I2S_CS_RXERR BIT(16)
+#define BCM2835_I2S_CS_TXERR BIT(15)
+#define BCM2835_I2S_RXSYNC BIT(14)
+#define BCM2835_I2S_TXSYNC BIT(13)
+#define BCM2835_I2S_DMAEN BIT(9)
+#define BCM2835_I2S_RXTHR(v) ((v) << 7)
+#define BCM2835_I2S_TXTHR(v) ((v) << 5)
+#define BCM2835_I2S_RXCLR BIT(4)
+#define BCM2835_I2S_TXCLR BIT(3)
+#define BCM2835_I2S_TXON BIT(2)
+#define BCM2835_I2S_RXON BIT(1)
+#define BCM2835_I2S_EN (1)
+
+#define BCM2835_I2S_CLKDIS BIT(28)
+#define BCM2835_I2S_PDMN BIT(27)
+#define BCM2835_I2S_PDME BIT(26)
+#define BCM2835_I2S_FRXP BIT(25)
+#define BCM2835_I2S_FTXP BIT(24)
+#define BCM2835_I2S_CLKM BIT(23)
+#define BCM2835_I2S_CLKI BIT(22)
+#define BCM2835_I2S_FSM BIT(21)
+#define BCM2835_I2S_FSI BIT(20)
+#define BCM2835_I2S_FLEN(v) ((v) << 10)
+#define BCM2835_I2S_FSLEN(v) (v)
+
+#define BCM2835_I2S_CHWEX BIT(15)
+#define BCM2835_I2S_CHEN BIT(14)
+#define BCM2835_I2S_CHPOS(v) ((v) << 4)
+#define BCM2835_I2S_CHWID(v) (v)
+#define BCM2835_I2S_CH1(v) ((v) << 16)
+#define BCM2835_I2S_CH2(v) (v)
+
+#define BCM2835_I2S_TX_PANIC(v) ((v) << 24)
+#define BCM2835_I2S_RX_PANIC(v) ((v) << 16)
+#define BCM2835_I2S_TX(v) ((v) << 8)
+#define BCM2835_I2S_RX(v) (v)
+
+#define BCM2835_I2S_INT_RXERR BIT(3)
+#define BCM2835_I2S_INT_TXERR BIT(2)
+#define BCM2835_I2S_INT_RXR BIT(1)
+#define BCM2835_I2S_INT_TXW BIT(0)
+
+/* I2S DMA interface */
+/* FIXME: Needs IOMMU support */
+#define BCM2835_VCMMU_SHIFT (0x7E000000 - 0x20000000)
+
+/* General device struct */
+struct bcm2835_i2s_dev {
+ struct device *dev;
+ struct snd_dmaengine_dai_dma_data dma_data[2];
+ unsigned int fmt;
+ unsigned int bclk_ratio;
+
+ struct regmap *i2s_regmap;
+ struct regmap *clk_regmap;
+};
+
+static void bcm2835_i2s_start_clock(struct bcm2835_i2s_dev *dev)
+{
+ /* Start the clock if in master mode */
+ unsigned int master = dev->fmt & SND_SOC_DAIFMT_MASTER_MASK;
+
+ switch (master) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ case SND_SOC_DAIFMT_CBS_CFM:
+ regmap_update_bits(dev->clk_regmap, BCM2835_CLK_PCMCTL_REG,
+ BCM2835_CLK_PASSWD_MASK | BCM2835_CLK_ENAB,
+ BCM2835_CLK_PASSWD | BCM2835_CLK_ENAB);
+ break;
+ default:
+ break;
+ }
+}
+
+static void bcm2835_i2s_stop_clock(struct bcm2835_i2s_dev *dev)
+{
+ uint32_t clkreg;
+ int timeout = 1000;
+
+ /* Stop clock */
+ regmap_update_bits(dev->clk_regmap, BCM2835_CLK_PCMCTL_REG,
+ BCM2835_CLK_PASSWD_MASK | BCM2835_CLK_ENAB,
+ BCM2835_CLK_PASSWD);
+
+ /* Wait for the BUSY flag going down */
+ while (--timeout) {
+ regmap_read(dev->clk_regmap, BCM2835_CLK_PCMCTL_REG, &clkreg);
+ if (!(clkreg & BCM2835_CLK_BUSY))
+ break;
+ }
+
+ if (!timeout) {
+ /* KILL the clock */
+ dev_err(dev->dev, "I2S clock didn't stop. Kill the clock!\n");
+ regmap_update_bits(dev->clk_regmap, BCM2835_CLK_PCMCTL_REG,
+ BCM2835_CLK_KILL | BCM2835_CLK_PASSWD_MASK,
+ BCM2835_CLK_KILL | BCM2835_CLK_PASSWD);
+ }
+}
+
+static void bcm2835_i2s_clear_fifos(struct bcm2835_i2s_dev *dev,
+ bool tx, bool rx)
+{
+ int timeout = 1000;
+ uint32_t syncval;
+ uint32_t csreg;
+ uint32_t i2s_active_state;
+ uint32_t clkreg;
+ uint32_t clk_active_state;
+ uint32_t off;
+ uint32_t clr;
+
+ off = tx ? BCM2835_I2S_TXON : 0;
+ off |= rx ? BCM2835_I2S_RXON : 0;
+
+ clr = tx ? BCM2835_I2S_TXCLR : 0;
+ clr |= rx ? BCM2835_I2S_RXCLR : 0;
+
+ /* Backup the current state */
+ regmap_read(dev->i2s_regmap, BCM2835_I2S_CS_A_REG, &csreg);
+ i2s_active_state = csreg & (BCM2835_I2S_RXON | BCM2835_I2S_TXON);
+
+ regmap_read(dev->clk_regmap, BCM2835_CLK_PCMCTL_REG, &clkreg);
+ clk_active_state = clkreg & BCM2835_CLK_ENAB;
+
+ /* Start clock if not running */
+ if (!clk_active_state) {
+ regmap_update_bits(dev->clk_regmap, BCM2835_CLK_PCMCTL_REG,
+ BCM2835_CLK_PASSWD_MASK | BCM2835_CLK_ENAB,
+ BCM2835_CLK_PASSWD | BCM2835_CLK_ENAB);
+ }
+
+ /* Stop I2S module */
+ regmap_update_bits(dev->i2s_regmap, BCM2835_I2S_CS_A_REG, off, 0);
+
+ /*
+ * Clear the FIFOs
+ * Requires at least 2 PCM clock cycles to take effect
+ */
+ regmap_update_bits(dev->i2s_regmap, BCM2835_I2S_CS_A_REG, clr, clr);
+
+ /* Wait for 2 PCM clock cycles */
+
+ /*
+ * Toggle the SYNC flag. After 2 PCM clock cycles it can be read back
+ * FIXME: This does not seem to work for slave mode!
+ */
+ regmap_read(dev->i2s_regmap, BCM2835_I2S_CS_A_REG, &syncval);
+ syncval &= BCM2835_I2S_SYNC;
+
+ regmap_update_bits(dev->i2s_regmap, BCM2835_I2S_CS_A_REG,
+ BCM2835_I2S_SYNC, ~syncval);
+
+ /* Wait for the SYNC flag changing it's state */
+ while (--timeout) {
+ regmap_read(dev->i2s_regmap, BCM2835_I2S_CS_A_REG, &csreg);
+ if ((csreg & BCM2835_I2S_SYNC) != syncval)
+ break;
+ }
+
+ if (!timeout)
+ dev_err(dev->dev, "I2S SYNC error!\n");
+
+ /* Stop clock if it was not running before */
+ if (!clk_active_state)
+ bcm2835_i2s_stop_clock(dev);
+
+ /* Restore I2S state */
+ regmap_update_bits(dev->i2s_regmap, BCM2835_I2S_CS_A_REG,
+ BCM2835_I2S_RXON | BCM2835_I2S_TXON, i2s_active_state);
+}
+
+static int bcm2835_i2s_set_dai_fmt(struct snd_soc_dai *dai,
+ unsigned int fmt)
+{
+ struct bcm2835_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
+ dev->fmt = fmt;
+ return 0;
+}
+
+static int bcm2835_i2s_set_dai_bclk_ratio(struct snd_soc_dai *dai,
+ unsigned int ratio)
+{
+ struct bcm2835_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
+ dev->bclk_ratio = ratio;
+ return 0;
+}
+
+static int bcm2835_i2s_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct bcm2835_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
+
+ unsigned int sampling_rate = params_rate(params);
+ unsigned int data_length, data_delay, bclk_ratio;
+ unsigned int ch1pos, ch2pos, mode, format;
+ unsigned int mash = BCM2835_CLK_MASH_1;
+ unsigned int divi, divf, target_frequency;
+ int clk_src = -1;
+ unsigned int master = dev->fmt & SND_SOC_DAIFMT_MASTER_MASK;
+ bool bit_master = (master == SND_SOC_DAIFMT_CBS_CFS
+ || master == SND_SOC_DAIFMT_CBS_CFM);
+
+ bool frame_master = (master == SND_SOC_DAIFMT_CBS_CFS
+ || master == SND_SOC_DAIFMT_CBM_CFS);
+ uint32_t csreg;
+
+ /*
+ * If a stream is already enabled,
+ * the registers are already set properly.
+ */
+ regmap_read(dev->i2s_regmap, BCM2835_I2S_CS_A_REG, &csreg);
+
+ if (csreg & (BCM2835_I2S_TXON | BCM2835_I2S_RXON))
+ return 0;
+
+ /*
+ * Adjust the data length according to the format.
+ * We prefill the half frame length with an integer
+ * divider of 2400 as explained at the clock settings.
+ * Maybe it is overwritten there, if the Integer mode
+ * does not apply.
+ */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ data_length = 16;
+ bclk_ratio = 40;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ data_length = 32;
+ bclk_ratio = 80;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* If bclk_ratio already set, use that one. */
+ if (dev->bclk_ratio)
+ bclk_ratio = dev->bclk_ratio;
+
+ /*
+ * Clock Settings
+ *
+ * The target frequency of the bit clock is
+ * sampling rate * frame length
+ *
+ * Integer mode:
+ * Sampling rates that are multiples of 8000 kHz
+ * can be driven by the oscillator of 19.2 MHz
+ * with an integer divider as long as the frame length
+ * is an integer divider of 19200000/8000=2400 as set up above.
+ * This is no longer possible if the sampling rate
+ * is too high (e.g. 192 kHz), because the oscillator is too slow.
+ *
+ * MASH mode:
+ * For all other sampling rates, it is not possible to
+ * have an integer divider. Approximate the clock
+ * with the MASH module that induces a slight frequency
+ * variance. To minimize that it is best to have the fastest
+ * clock here. That is PLLD with 500 MHz.
+ */
+ target_frequency = sampling_rate * bclk_ratio;
+ clk_src = BCM2835_CLK_SRC_OSC;
+ mash = BCM2835_CLK_MASH_0;
+
+ if (bcm2835_clk_freq[clk_src] % target_frequency == 0
+ && bit_master && frame_master) {
+ divi = bcm2835_clk_freq[clk_src] / target_frequency;
+ divf = 0;
+ } else {
+ uint64_t dividend;
+
+ if (!dev->bclk_ratio) {
+ /*
+ * Overwrite bclk_ratio, because the
+ * above trick is not needed or can
+ * not be used.
+ */
+ bclk_ratio = 2 * data_length;
+ }
+
+ target_frequency = sampling_rate * bclk_ratio;
+
+ clk_src = BCM2835_CLK_SRC_PLLD;
+ mash = BCM2835_CLK_MASH_1;
+
+ dividend = bcm2835_clk_freq[clk_src];
+ dividend <<= BCM2835_CLK_SHIFT;
+ do_div(dividend, target_frequency);
+ divi = dividend >> BCM2835_CLK_SHIFT;
+ divf = dividend & BCM2835_CLK_DIVF_MASK;
+ }
+
+ /* Set clock divider */
+ regmap_write(dev->clk_regmap, BCM2835_CLK_PCMDIV_REG, BCM2835_CLK_PASSWD
+ | BCM2835_CLK_DIVI(divi)
+ | BCM2835_CLK_DIVF(divf));
+
+ /* Setup clock, but don't start it yet */
+ regmap_write(dev->clk_regmap, BCM2835_CLK_PCMCTL_REG, BCM2835_CLK_PASSWD
+ | BCM2835_CLK_MASH(mash)
+ | BCM2835_CLK_SRC(clk_src));
+
+ /* Setup the frame format */
+ format = BCM2835_I2S_CHEN;
+
+ if (data_length > 24)
+ format |= BCM2835_I2S_CHWEX;
+
+ format |= BCM2835_I2S_CHWID((data_length-8)&0xf);
+
+ switch (dev->fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ data_delay = 1;
+ break;
+ default:
+ /*
+ * TODO
+ * Others are possible but are not implemented at the moment.
+ */
+ dev_err(dev->dev, "%s:bad format\n", __func__);
+ return -EINVAL;
+ }
+
+ ch1pos = data_delay;
+ ch2pos = bclk_ratio / 2 + data_delay;
+
+ switch (params_channels(params)) {
+ case 2:
+ format = BCM2835_I2S_CH1(format) | BCM2835_I2S_CH2(format);
+ format |= BCM2835_I2S_CH1(BCM2835_I2S_CHPOS(ch1pos));
+ format |= BCM2835_I2S_CH2(BCM2835_I2S_CHPOS(ch2pos));
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /*
+ * Set format for both streams.
+ * We cannot set another frame length
+ * (and therefore word length) anyway,
+ * so the format will be the same.
+ */
+ regmap_write(dev->i2s_regmap, BCM2835_I2S_RXC_A_REG, format);
+ regmap_write(dev->i2s_regmap, BCM2835_I2S_TXC_A_REG, format);
+
+ /* Setup the I2S mode */
+ mode = 0;
+
+ if (data_length <= 16) {
+ /*
+ * Use frame packed mode (2 channels per 32 bit word)
+ * We cannot set another frame length in the second stream
+ * (and therefore word length) anyway,
+ * so the format will be the same.
+ */
+ mode |= BCM2835_I2S_FTXP | BCM2835_I2S_FRXP;
+ }
+
+ mode |= BCM2835_I2S_FLEN(bclk_ratio - 1);
+ mode |= BCM2835_I2S_FSLEN(bclk_ratio / 2);
+
+ /* Master or slave? */
+ switch (dev->fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ /* CPU is master */
+ break;
+ case SND_SOC_DAIFMT_CBM_CFS:
+ /*
+ * CODEC is bit clock master
+ * CPU is frame master
+ */
+ mode |= BCM2835_I2S_CLKM;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFM:
+ /*
+ * CODEC is frame master
+ * CPU is bit clock master
+ */
+ mode |= BCM2835_I2S_FSM;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ /* CODEC is master */
+ mode |= BCM2835_I2S_CLKM;
+ mode |= BCM2835_I2S_FSM;
+ break;
+ default:
+ dev_err(dev->dev, "%s:bad master\n", __func__);
+ return -EINVAL;
+ }
+
+ /*
+ * Invert clocks?
+ *
+ * The BCM approach seems to be inverted to the classical I2S approach.
+ */
+ switch (dev->fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ /* None. Therefore, both for BCM */
+ mode |= BCM2835_I2S_CLKI;
+ mode |= BCM2835_I2S_FSI;
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ /* Both. Therefore, none for BCM */
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ /*
+ * Invert only frame sync. Therefore,
+ * invert only bit clock for BCM
+ */
+ mode |= BCM2835_I2S_CLKI;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ /*
+ * Invert only bit clock. Therefore,
+ * invert only frame sync for BCM
+ */
+ mode |= BCM2835_I2S_FSI;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ regmap_write(dev->i2s_regmap, BCM2835_I2S_MODE_A_REG, mode);
+
+ /* Setup the DMA parameters */
+ regmap_update_bits(dev->i2s_regmap, BCM2835_I2S_CS_A_REG,
+ BCM2835_I2S_RXTHR(1)
+ | BCM2835_I2S_TXTHR(1)
+ | BCM2835_I2S_DMAEN, 0xffffffff);
+
+ regmap_update_bits(dev->i2s_regmap, BCM2835_I2S_DREQ_A_REG,
+ BCM2835_I2S_TX_PANIC(0x10)
+ | BCM2835_I2S_RX_PANIC(0x30)
+ | BCM2835_I2S_TX(0x30)
+ | BCM2835_I2S_RX(0x20), 0xffffffff);
+
+ /* Clear FIFOs */
+ bcm2835_i2s_clear_fifos(dev, true, true);
+
+ return 0;
+}
+
+static int bcm2835_i2s_prepare(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct bcm2835_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
+ uint32_t cs_reg;
+
+ bcm2835_i2s_start_clock(dev);
+
+ /*
+ * Clear both FIFOs if the one that should be started
+ * is not empty at the moment. This should only happen
+ * after overrun. Otherwise, hw_params would have cleared
+ * the FIFO.
+ */
+ regmap_read(dev->i2s_regmap, BCM2835_I2S_CS_A_REG, &cs_reg);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK
+ && !(cs_reg & BCM2835_I2S_TXE))
+ bcm2835_i2s_clear_fifos(dev, true, false);
+ else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE
+ && (cs_reg & BCM2835_I2S_RXD))
+ bcm2835_i2s_clear_fifos(dev, false, true);
+
+ return 0;
+}
+
+static void bcm2835_i2s_stop(struct bcm2835_i2s_dev *dev,
+ struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ uint32_t mask;
+
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ mask = BCM2835_I2S_RXON;
+ else
+ mask = BCM2835_I2S_TXON;
+
+ regmap_update_bits(dev->i2s_regmap,
+ BCM2835_I2S_CS_A_REG, mask, 0);
+
+ /* Stop also the clock when not SND_SOC_DAIFMT_CONT */
+ if (!dai->active && !(dev->fmt & SND_SOC_DAIFMT_CONT))
+ bcm2835_i2s_stop_clock(dev);
+}
+
+static int bcm2835_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ struct bcm2835_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
+ uint32_t mask;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ bcm2835_i2s_start_clock(dev);
+
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ mask = BCM2835_I2S_RXON;
+ else
+ mask = BCM2835_I2S_TXON;
+
+ regmap_update_bits(dev->i2s_regmap,
+ BCM2835_I2S_CS_A_REG, mask, mask);
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ bcm2835_i2s_stop(dev, substream, dai);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int bcm2835_i2s_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct bcm2835_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
+
+ if (dai->active)
+ return 0;
+
+ /* Should this still be running stop it */
+ bcm2835_i2s_stop_clock(dev);
+
+ /* Enable PCM block */
+ regmap_update_bits(dev->i2s_regmap, BCM2835_I2S_CS_A_REG,
+ BCM2835_I2S_EN, BCM2835_I2S_EN);
+
+ /*
+ * Disable STBY.
+ * Requires at least 4 PCM clock cycles to take effect.
+ */
+ regmap_update_bits(dev->i2s_regmap, BCM2835_I2S_CS_A_REG,
+ BCM2835_I2S_STBY, BCM2835_I2S_STBY);
+
+ return 0;
+}
+
+static void bcm2835_i2s_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct bcm2835_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
+
+ bcm2835_i2s_stop(dev, substream, dai);
+
+ /* If both streams are stopped, disable module and clock */
+ if (dai->active)
+ return;
+
+ /* Disable the module */
+ regmap_update_bits(dev->i2s_regmap, BCM2835_I2S_CS_A_REG,
+ BCM2835_I2S_EN, 0);
+
+ /*
+ * Stopping clock is necessary, because stop does
+ * not stop the clock when SND_SOC_DAIFMT_CONT
+ */
+ bcm2835_i2s_stop_clock(dev);
+}
+
+static const struct snd_soc_dai_ops bcm2835_i2s_dai_ops = {
+ .startup = bcm2835_i2s_startup,
+ .shutdown = bcm2835_i2s_shutdown,
+ .prepare = bcm2835_i2s_prepare,
+ .trigger = bcm2835_i2s_trigger,
+ .hw_params = bcm2835_i2s_hw_params,
+ .set_fmt = bcm2835_i2s_set_dai_fmt,
+ .set_bclk_ratio = bcm2835_i2s_set_dai_bclk_ratio
+};
+
+static int bcm2835_i2s_dai_probe(struct snd_soc_dai *dai)
+{
+ struct bcm2835_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
+
+ snd_soc_dai_init_dma_data(dai,
+ &dev->dma_data[SNDRV_PCM_STREAM_PLAYBACK],
+ &dev->dma_data[SNDRV_PCM_STREAM_CAPTURE]);
+
+ return 0;
+}
+
+static struct snd_soc_dai_driver bcm2835_i2s_dai = {
+ .name = "bcm2835-i2s",
+ .probe = bcm2835_i2s_dai_probe,
+ .playback = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_192000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE
+ | SNDRV_PCM_FMTBIT_S32_LE
+ },
+ .capture = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_192000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE
+ | SNDRV_PCM_FMTBIT_S32_LE
+ },
+ .ops = &bcm2835_i2s_dai_ops,
+ .symmetric_rates = 1
+};
+
+static bool bcm2835_i2s_volatile_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case BCM2835_I2S_CS_A_REG:
+ case BCM2835_I2S_FIFO_A_REG:
+ case BCM2835_I2S_INTSTC_A_REG:
+ case BCM2835_I2S_GRAY_REG:
+ return true;
+ default:
+ return false;
+ };
+}
+
+static bool bcm2835_i2s_precious_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case BCM2835_I2S_FIFO_A_REG:
+ return true;
+ default:
+ return false;
+ };
+}
+
+static bool bcm2835_clk_volatile_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case BCM2835_CLK_PCMCTL_REG:
+ return true;
+ default:
+ return false;
+ };
+}
+
+static const struct regmap_config bcm2835_regmap_config[] = {
+ {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = BCM2835_I2S_GRAY_REG,
+ .precious_reg = bcm2835_i2s_precious_reg,
+ .volatile_reg = bcm2835_i2s_volatile_reg,
+ .cache_type = REGCACHE_RBTREE,
+ },
+ {
+ .reg_bits = 32,
+ .reg_stride = 4,
+ .val_bits = 32,
+ .max_register = BCM2835_CLK_PCMDIV_REG,
+ .volatile_reg = bcm2835_clk_volatile_reg,
+ .cache_type = REGCACHE_RBTREE,
+ },
+};
+
+static const struct snd_soc_component_driver bcm2835_i2s_component = {
+ .name = "bcm2835-i2s-comp",
+};
+
+static int bcm2835_i2s_probe(struct platform_device *pdev)
+{
+ struct bcm2835_i2s_dev *dev;
+ int i;
+ int ret;
+ struct regmap *regmap[2];
+ struct resource *mem[2];
+
+ /* Request both ioareas */
+ for (i = 0; i <= 1; i++) {
+ void __iomem *base;
+
+ mem[i] = platform_get_resource(pdev, IORESOURCE_MEM, i);
+ base = devm_ioremap_resource(&pdev->dev, mem[i]);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ regmap[i] = devm_regmap_init_mmio(&pdev->dev, base,
+ &bcm2835_regmap_config[i]);
+ if (IS_ERR(regmap[i])) {
+ dev_err(&pdev->dev, "I2S probe: regmap init failed\n");
+ return PTR_ERR(regmap[i]);
+ }
+ }
+
+ dev = devm_kzalloc(&pdev->dev, sizeof(*dev),
+ GFP_KERNEL);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ dev->i2s_regmap = regmap[0];
+ dev->clk_regmap = regmap[1];
+
+ /* Set the DMA address */
+ dev->dma_data[SNDRV_PCM_STREAM_PLAYBACK].addr =
+ (dma_addr_t)mem[0]->start + BCM2835_I2S_FIFO_A_REG
+ + BCM2835_VCMMU_SHIFT;
+
+ dev->dma_data[SNDRV_PCM_STREAM_CAPTURE].addr =
+ (dma_addr_t)mem[0]->start + BCM2835_I2S_FIFO_A_REG
+ + BCM2835_VCMMU_SHIFT;
+
+ /* Set the bus width */
+ dev->dma_data[SNDRV_PCM_STREAM_PLAYBACK].addr_width =
+ DMA_SLAVE_BUSWIDTH_4_BYTES;
+ dev->dma_data[SNDRV_PCM_STREAM_CAPTURE].addr_width =
+ DMA_SLAVE_BUSWIDTH_4_BYTES;
+
+ /* Set burst */
+ dev->dma_data[SNDRV_PCM_STREAM_PLAYBACK].maxburst = 2;
+ dev->dma_data[SNDRV_PCM_STREAM_CAPTURE].maxburst = 2;
+
+ /* BCLK ratio - use default */
+ dev->bclk_ratio = 0;
+
+ /* Store the pdev */
+ dev->dev = &pdev->dev;
+ dev_set_drvdata(&pdev->dev, dev);
+
+ ret = snd_soc_register_component(&pdev->dev,
+ &bcm2835_i2s_component, &bcm2835_i2s_dai, 1);
+
+ if (ret) {
+ dev_err(&pdev->dev, "Could not register DAI: %d\n", ret);
+ ret = -ENOMEM;
+ return ret;
+ }
+
+ ret = snd_dmaengine_pcm_register(&pdev->dev, NULL, 0);
+ if (ret) {
+ dev_err(&pdev->dev, "Could not register PCM: %d\n", ret);
+ snd_soc_unregister_component(&pdev->dev);
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct of_device_id bcm2835_i2s_of_match[] = {
+ { .compatible = "brcm,bcm2835-i2s", },
+ {},
+};
+
+static int bcm2835_i2s_remove(struct platform_device *pdev)
+{
+ snd_dmaengine_pcm_unregister(&pdev->dev);
+ snd_soc_unregister_component(&pdev->dev);
+ return 0;
+}
+
+static struct platform_driver bcm2835_i2s_driver = {
+ .probe = bcm2835_i2s_probe,
+ .remove = bcm2835_i2s_remove,
+ .driver = {
+ .name = "bcm2835-i2s",
+ .owner = THIS_MODULE,
+ .of_match_table = bcm2835_i2s_of_match,
+ },
+};
+
+module_platform_driver(bcm2835_i2s_driver);
+
+MODULE_ALIAS("platform:bcm2835-i2s");
+MODULE_DESCRIPTION("BCM2835 I2S interface");
+MODULE_AUTHOR("Florian Meier <florian.meier(a)koalo.de>");
+MODULE_LICENSE("GPL v2");
--
1.7.9.5
2
2
22 Nov '13
On Tue, Nov 19, 2013 at 8:08 AM, Andreas Reis <andreas.reis(a)gmail.com> wrote:
> Correction: The other jack duplicated for the front panel is Mic In
> (no mention of the rear panel jack's shared Line In functionality
> here), not Rear Speaker Out.
>
> On Tue, Nov 19, 2013 at 5:04 PM, Andreas Reis <andreas.reis(a)gmail.com> wrote:
>> Posting here by Takashi's advice after my alsa-user post remained unanswered.
>>
>> There are a number of grave issues with the CA0132 codec on my
>> Gigabyte G1.Sniper M5 (rev. 1.0) (Intel Z87, Recon3Di onboard, latest
>> bios F8c). I've tried kernels (x64) from 3.9 to 3.13-preRC1 (as
>> currently in Torvald's git), both with pulse on Ubuntu 13.10 and
>> without on Arch Linux.
>>
Sorry I don't have good news for you. The CA0132 is very dependent on
the firmware that is loaded into the DSP that resides in the codec.
To get your system to work you'll have to obtain the firmware that was
designed for your motherboard, then change the Linux driver to be able
to talk to it, without a spec for how it works.
This is not impossible, but it would be rather time consuming.
-Dylan
2
1
[alsa-devel] [PATCH] ALSA: hda - Set current_headset_type to ALC_HEADSET_TYPE_ENUM (janitorial)
by David Henningsson 22 Nov '13
by David Henningsson 22 Nov '13
22 Nov '13
current_headset_type should be of the HEADSET_TYPE enum, not the
HEADSET_MODE enum. Since ALC_HEADSET_TYPE_UNKNOWN and ALC_HEADSET_MODE_UNKNOWN
are both 0, this patch is just janitorial.
Signed-off-by: David Henningsson <david.henningsson(a)canonical.com>
---
sound/pci/hda/patch_realtek.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c
index 6d8d9b3..5e42059 100644
--- a/sound/pci/hda/patch_realtek.c
+++ b/sound/pci/hda/patch_realtek.c
@@ -3414,7 +3414,7 @@ static void alc_update_headset_mode_hook(struct hda_codec *codec,
static void alc_update_headset_jack_cb(struct hda_codec *codec, struct hda_jack_tbl *jack)
{
struct alc_spec *spec = codec->spec;
- spec->current_headset_type = ALC_HEADSET_MODE_UNKNOWN;
+ spec->current_headset_type = ALC_HEADSET_TYPE_UNKNOWN;
snd_hda_gen_hp_automute(codec, jack);
}
--
1.7.9.5
2
1
22 Nov '13
From: Mark Brown <broonie(a)linaro.org>
Instead of using a fake register and events to manage input power use a
supply to do the job, saving code and preparing for regmap conversion of
the driver.
Signed-off-by: Mark Brown <broonie(a)linaro.org>
---
sound/soc/codecs/wm8991.c | 53 +++++++++++++++--------------------------------
sound/soc/codecs/wm8991.h | 9 --------
2 files changed, 17 insertions(+), 45 deletions(-)
diff --git a/sound/soc/codecs/wm8991.c b/sound/soc/codecs/wm8991.c
index 3a39df7a3829..5078fc8e10f7 100644
--- a/sound/soc/codecs/wm8991.c
+++ b/sound/soc/codecs/wm8991.c
@@ -374,30 +374,6 @@ static const struct snd_kcontrol_new wm8991_snd_controls[] = {
/*
* _DAPM_ Controls
*/
-static int inmixer_event(struct snd_soc_dapm_widget *w,
- struct snd_kcontrol *kcontrol, int event)
-{
- u16 reg, fakepower;
-
- reg = snd_soc_read(w->codec, WM8991_POWER_MANAGEMENT_2);
- fakepower = snd_soc_read(w->codec, WM8991_INTDRIVBITS);
-
- if (fakepower & ((1 << WM8991_INMIXL_PWR_BIT) |
- (1 << WM8991_AINLMUX_PWR_BIT)))
- reg |= WM8991_AINL_ENA;
- else
- reg &= ~WM8991_AINL_ENA;
-
- if (fakepower & ((1 << WM8991_INMIXR_PWR_BIT) |
- (1 << WM8991_AINRMUX_PWR_BIT)))
- reg |= WM8991_AINR_ENA;
- else
- reg &= ~WM8991_AINR_ENA;
-
- snd_soc_write(w->codec, WM8991_POWER_MANAGEMENT_2, reg);
- return 0;
-}
-
static int outmixer_event(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event)
{
@@ -655,6 +631,11 @@ static const struct snd_soc_dapm_widget wm8991_dapm_widgets[] = {
SND_SOC_DAPM_INPUT("RIN2"),
SND_SOC_DAPM_INPUT("Internal ADC Source"),
+ SND_SOC_DAPM_SUPPLY("INL", WM8991_POWER_MANAGEMENT_2,
+ WM8991_AINL_ENA_BIT, 0, NULL, 0),
+ SND_SOC_DAPM_SUPPLY("INR", WM8991_POWER_MANAGEMENT_2,
+ WM8991_AINR_ENA_BIT, 0, NULL, 0),
+
/* DACs */
SND_SOC_DAPM_ADC("Left ADC", "Left Capture", WM8991_POWER_MANAGEMENT_2,
WM8991_ADCL_ENA_BIT, 0),
@@ -676,26 +657,22 @@ static const struct snd_soc_dapm_widget wm8991_dapm_widgets[] = {
ARRAY_SIZE(wm8991_dapm_rin34_pga_controls)),
/* INMIXL */
- SND_SOC_DAPM_MIXER_E("INMIXL", WM8991_INTDRIVBITS, WM8991_INMIXL_PWR_BIT, 0,
+ SND_SOC_DAPM_MIXER("INMIXL", SND_SOC_NOPM, 0, 0,
&wm8991_dapm_inmixl_controls[0],
- ARRAY_SIZE(wm8991_dapm_inmixl_controls),
- inmixer_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+ ARRAY_SIZE(wm8991_dapm_inmixl_controls)),
/* AINLMUX */
- SND_SOC_DAPM_MUX_E("AINLMUX", WM8991_INTDRIVBITS, WM8991_AINLMUX_PWR_BIT, 0,
- &wm8991_dapm_ainlmux_controls, inmixer_event,
- SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+ SND_SOC_DAPM_MUX("AINLMUX", SND_SOC_NOPM, 0, 0,
+ &wm8991_dapm_ainlmux_controls),
/* INMIXR */
- SND_SOC_DAPM_MIXER_E("INMIXR", WM8991_INTDRIVBITS, WM8991_INMIXR_PWR_BIT, 0,
+ SND_SOC_DAPM_MIXER("INMIXR", SND_SOC_NOPM, 0, 0,
&wm8991_dapm_inmixr_controls[0],
- ARRAY_SIZE(wm8991_dapm_inmixr_controls),
- inmixer_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+ ARRAY_SIZE(wm8991_dapm_inmixr_controls)),
/* AINRMUX */
- SND_SOC_DAPM_MUX_E("AINRMUX", WM8991_INTDRIVBITS, WM8991_AINRMUX_PWR_BIT, 0,
- &wm8991_dapm_ainrmux_controls, inmixer_event,
- SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+ SND_SOC_DAPM_MUX("AINRMUX", SND_SOC_NOPM, 0, 0,
+ &wm8991_dapm_ainrmux_controls),
/* Output Side */
/* DACs */
@@ -797,6 +774,10 @@ static const struct snd_soc_dapm_route audio_map[] = {
{"Right ADC", NULL, "Internal ADC Source"},
/* Input Side */
+ {"INMIXL", NULL, "INL"},
+ {"AINLMUX", NULL, "INL"},
+ {"INMIXR", NULL, "INR"},
+ {"AINRMUX", NULL, "INR"},
/* LIN12 PGA */
{"LIN12 PGA", "LIN1 Switch", "LIN1"},
{"LIN12 PGA", "LIN2 Switch", "LIN2"},
diff --git a/sound/soc/codecs/wm8991.h b/sound/soc/codecs/wm8991.h
index 07707d8d7e20..08ed383303c0 100644
--- a/sound/soc/codecs/wm8991.h
+++ b/sound/soc/codecs/wm8991.h
@@ -76,7 +76,6 @@
#define WM8991_PLL1 0x3C
#define WM8991_PLL2 0x3D
#define WM8991_PLL3 0x3E
-#define WM8991_INTDRIVBITS 0x3F
#define WM8991_REGISTER_COUNT 60
#define WM8991_MAX_REGISTER 0x3F
@@ -807,14 +806,6 @@
*/
#define WM8991_PLLK2_MASK 0x00FF /* PLLK2 - [7:0] */
-/*
- * R63 (0x3F) - Internal Driver Bits
- */
-#define WM8991_INMIXL_PWR_BIT 0
-#define WM8991_AINLMUX_PWR_BIT 1
-#define WM8991_INMIXR_PWR_BIT 2
-#define WM8991_AINRMUX_PWR_BIT 3
-
#define WM8991_MCLK_DIV 0
#define WM8991_DACCLK_DIV 1
#define WM8991_ADCCLK_DIV 2
--
1.8.4.4
2
1
[alsa-devel] Audio Mini Conference Minutes - Edinburgh 21st October 2013
by Liam Girdwood 22 Nov '13
by Liam Girdwood 22 Nov '13
22 Nov '13
I've pasted below the minutes from this years ALSA developer mini
conference in Edinburgh. I'll also add it to the developer Wiki
alongside some slides from Eric and Tanu (once I know the correct answer
to the anti-spam captcha).
Fwiw, there are some interesting "unclaimed" tasks/projects below.
Please feel free to volunteer for anything below that may interest
you.
Btw, if you are replying to start a thread about a certain subject
below, please snip the other context and change the email to match the
new $SUBJECT. This should make things easier to track.
Thanks
Liam
Audio BOF - Monday October 21st 2013 - Edinburgh
================================================
Attendees :-
-----------------------------------------------------------------------
Mark Brown, Liam Girdwood, Pierre Bossart, Patrick Lai, Lars-Peter Clausen,
Eric Laurent, David Henningsson, Takashi Iwai, Colin Guthrie, Tanu Kaskinen,
Arun Raghavan, Paul Handrigan, Daniel Mack, Dylan Reid, Sven Neuman,
Dmimtris Papastamos, Charles Keepax, Nariman Poushin, João Paulo Rechi Vita,
Alexander Patrakov, Micheal Wu, Lennart Poettering
Topics
=======
Pulseaudio route management and UCM
-----------------------------------
There was interest for shared policy configuration file format across
different systems. No such sharing has been planned. Tanu's initial reaction
is that it's not feasible, but he is open to discussing it.
What to do if there are multiple paths from A to B? One will be chosen,
either arbitrarily or based on connection requirements or prioritization.
How to control effect offloading? Tanu's vague idea is that if some kind of
filtering is required/preferred, it's expressed as a requirement for a
connection, and the node implementation will then enable the details of
enabling the effect if such effect is available. Tanu doesn't know how well
that would work in practice - and probably unaware of many details that
would have to be taken into account.
UCM being used by some people but missing good documentation. Liam to write
documentation and use examples from Arun as "gold standard" example
configurations. More UCM configs to go online in alsa-lib repo.
UCM configs sometimes need updated for new kernel releases as Kcontrol strings
have changed. Suggestions made for additional UUID for each kcontrol to lessen UCM
config name changes (e.g. vendor-part-reg-shift-mask could be used as ASoC
kcontrol UUID). Naming changes also effect HDA kcontrols, but no HDA UCM configs
yet ? kcontrol UUID harder for DSP and HDA.
Arun reported UCM issue with combination of devices. e.g. Enable dev 1 & 2 then
disable dev 1 & 2. Maybe virtual device that combines devices ??
#include for UCM - should be easy to add. Simplifies config for family of similar
devices like Panda, PandaES etc
Device Names in UCM names. There are some standard names in UCM header but need
more. Liam to add more names.
Arbitration, UCM does not do arbitration but leaves that up to sound server.
Controls names need more standardisation. Kcontrol naming mainly based on HW
specified within data sheets. Difficult for userspace to cope with great degree
of naming variations and provide enough information for userspace to know
which volume is master etc.
Audio Delay and Timing granularity
----------------------------------
Some DMA drivers very bad at providing position information used to work out
buffer positions. Difficult for userspace to render audio and wake up correctly
with such HW unless we can provide mechanism for userspace to know the DMA position
accuracy. IOCTL suggested, info is in driver so easy to expose.
There already is the SNDRV_PCM_INFO_BATCH flag which is supposed to indicated that
the granularity is period-size-ish. It makes sense for applications
like pulse-audio to check whether this flag is set or not.
Hostless Audio
--------------
Hostless should now use kcontrol to join the graph links to enable or disable and
not hostless PCM device (old Android hack).
e.g. FM playback loopback to codec, Modem PCM handling.
i.e. CODEC <-> CODEC type. Need mixer to expose set config changes as link
config is static e.g. WB/NB configs.
Audio Offloading for Android
----------------------------
Avail in KK, supports > 1 compressed devices. No guarantee on no dropped samples
when changing compressed to PCM and vice versa (or enabling effects ????)
Not available yet when in use with video, no tech reason to stop this.
AV sync good. Some effects can be offloaded and some not.
Compressed API
--------------
Intel to upstream remaining fixes. AR - Liam, Vinod.
Stable updates with no users. 3.10 is stable but we can changes core API since
there are no users.
Android Audio Effects Framework Modifications
---------------------------------------------
Composite with HW and SW representations. Supports 3rd party GUI controls
but they must use the effect API and not bypass with private IOCTLs.
How do we do library, like tiny effects ? API exists in use by Factory,
can we extend to alsa ?
Nobody using compressed and effects outside of Android atm.
24 Bit HiRes Audio for Android
------------------------------
How do we handle 24 bit audio at fast rates e.g. 192kHz. Being pushed by market.
Flac suggested as source media and not PCM.
We could use offload, on roadmap for Hires audio on android PCM,
maybe float instead of 24 bit. Need for 192kHz, Probably not use effects.
Effect Support in ALSA
----------------------
Several proposals for APIs.
Control device type, do we need new control or use existing CTL ? Per card or
per DSP / CODEC. ? Not to use control API for everything , could confuse some
userspace. Get/set could be any control type, not just binary ? Binary blobs
used to hide workings of effects.
Topology Query and edit - query the graph from HDA/DAPM -
used to establish connections and end points. per card based IOCTL. Could be new
alsamixer to allow users to control volumes.
Media controller API. How do we match to ALSA. Need non LGPL library for android.
Missing certain features, someone needs to write code. MC API is Video oriented,
library is just wrappers around IOCTLs, 1 man week of simple work to implement
non LGPL version.
Removal/creation - kernel can provide any type of information to user space key values.
Document new keys, generic structure. Channel mapping possible using tuples.
Need to support dynamic entity information. Handles static pipeline well,
and can also expose dynamic with some complexity (with more entities).
Abstract away sink+sources in userspace library, maybe too complex.
Trivial to use UUIDs for dynamic entities. Start with prototype and see what happens.
Action on Intel/Vinod ?
Topology 2 NODES and EFFECT IOCTLS could be replaced with media controller as MediaC
provides good match.
Sending/Receiving Binary data to drivers
----------------------------------------
Use cases that go above 256/512 ? byte limit for binary controls.
Extend for larger ? Do something about it. Action for ????? Tiwai/Mark ????
MMAP for some parameters. Visualizer, peak meters. ? Multiple of 4k pages.
Can be used for DMA buffer positions on IPC IRQ based DSPs.
Avoid copying large amounts of data. trade off for DSP. Usable for DEBUG too.
Prototype and see what happens - Who ??
Expose API for sending large chunks of data e.g. 2MB. Nice for generic code to send
this data (no special tooling) Needs new API for large data chunks. What about write().
Patrick to prototype some code.
DSP resource management
-----------------------
Do we need pre-trigger to work out MIPS/MHz + buffer resources for DSP
resources. Tables used atm for most DSPs.
Something for policy or UCM to specify ? Could change for different FWs.
Peak and average loading gives difficulty.
Might not get feedback, latency in the pipe and need to agree on units of
measurements. Complex problem. Estimate worst case.
OMAP4 example uses DAPM, expose reource usage numbers, expose capability limits.
Liam to publish clock resource management logic for Haswell DSP. Can be used as
starting point for investigations into feasibility etc.
HDMI - Hotplug
--------------
Can cause changes to audio path. Do we need to tell userspace card has changed ?
Specific for ELD or generic problem ?
Suggestion to remove and then insert card not usable
Signal for monitor re-pluging, ELD not available right away.
How to sync ELD information, ELD time out signal ? DSPs can change capabilities,
ELDS can be garbage. PA maybe ask to quickly for ELD info, expose new jack for HDA
when ELD is known. Generic event to tell userspace that card has changed.
HDA/MIMI specific solution.
Generic API to talk between audio and DRM devices. Bespoke atm. Code lives in
the wrong place atm. Need to sync. Code is already upstream.
David to prototype a solution based on deferring the probe until ELD is ready.
Mic/LED hotkey
--------------
Few different scenarios - from slides. Multiple cards and multiple mics.
Which Mic will LED represent ? card or system or other.
We'll not use the LED class because it does not buy us anything: LED
class is writable for root only, and PA does not run as root.
Nobody seemed to have a firm convincing argument in any direction. As such,
keeping the current behaviour (reflect mute state of internal card) will be
kept for now.
Alexander suggested looking at what NetworkManager does with the wifi-off
indicator in the presence of multiple wireless devices with
(potentially) their own wifi-off switches.
Performance - Use case startup
------------------------------
Discussing optimisation from DPCM - users QC, TI and Intel. DSP can route to
multiple endpoints from a single PCM.
Startup call back -> PCM -> FE -> BE. Then hw_params(), followed by prepare()
and trigger(). Cold start latency 113ms on QDSP since some slow ops on QDSP
are serialised. Solution to shift some prepare() work into DAPM.
DAPM ops are tun in parallel and a workq.
Compressed offload
------------------
Video/Audio offload - pass audio timestamp. Metadata for QDSP stored at front of
compressed data by QC. Transport stream already supported by compressed API.
Timestamps may differ in DSP implementations. AV sync topic requested by V4Linux
for long time. Only discussed for PCM not offload, need TS for offload and support
of V4Linux guys.
Compressed offload transcoding. - How do we specify this transform. New API for
userspace to spec output of decoder. All sorts of routing combinations are possible
making implementation very difficult. Query the the BE connected to the FE, we may
be able to call existing PCM API's on the BE PCM. Patrick to prototype some
code.
Firmware based Kcontrols and Graphs
-----------------------------------
Patches have been developed to attach Kcontrol and Graph meta data to DSP FW.
This allows single DSP driver to be used with multiple FWs without modification.
Patch V1 reviewed last year before TI layoff and we should be ready for V2 before
end of year.
Consists of kernel patch and userspace tool to generate the control and graph
data. Current userspace tool is needing work to improve config generation.
There is no config file format at present. Paul and some Wolfson guys have
volunteered to assist in this effort. Code currently works on OMAP4 platforms.
Kernel code is here (top 5 patches) :-
https://git.kernel.org/cgit/linux/kernel/git/lrg/asoc.git/log/?h=topic/firm…
Userspace tool is here :-
git@gitorious.org:omap-audio/asoc-fw.git
Clean up ALSA code
------------------
Move plugins to their projects - move ffmpeg plugin to ffmpeg project, etc.
Drivers, requests to move to device probe model, away from alsa model.
Need to register device files at end of driver creation.
Input susbsys uses possible method that could be copied. sysfs files need sync
to avoid races for creation. Agreement.
Process, How do we work
-----------------------
Bug reports. Who looks at bugs. No takers.
New mailing lists ? no, just use filters to hide emails that are not
of interest.
Misc
----
Softvol, add SND_CTL for no softvol in ctl open. Use alsa conf file or ctl.
Arun encouraged to revive the email thread on the topic.
Possible bug where specially crafted javascript can set PA volume to 100%.
Defined pulseaudio behviour (Colin), related to flat volumes.
New ASoC drivers must now use generic DMA engine PCM driver.
Non atomic trigger requirments from DSP drivers. DSPs mostly sleep when performing
IPC and need a trigger call that can sleep. Patrick will investigate.
Propagate errors from BEs to FEs. Patrick to investigate. This will also include
reporting underruns too.
System restart for DSP FW crashes. This requires reloading all FW and trying to
recover all active streams. Patrick to investigate.
Lennart asked about the support of audio "mute" via systemd session management,
like input and drm drivers already do. Takashi took a look at input evdev code,
and actually it's revoking instead of muting. The advantage of revoking over
muting is that it's simpler and doesn't need the capability check, as far as he
read through discussion threads. But Takashi is not sure whether revoking
behavior is good for PA at all.
4
6
[alsa-devel] [PATCH] ALSA: hda - Provide missing pin configs for VAIO with ALC260
by Takashi Iwai 22 Nov '13
by Takashi Iwai 22 Nov '13
22 Nov '13
Some models (or maybe depending on BIOS version) of Sony VAIO with
ALC260 give no proper pin configurations as default, resulting in the
non-working speaker, etc. Just provide the whole pin configurations
via a fixup.
Reported-by: Matthew Markus <mmarkus(a)hearit.co>
Cc: <stable(a)vger.kernel.org>
Signed-off-by: Takashi Iwai <tiwai(a)suse.de>
---
sound/pci/hda/patch_realtek.c | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c
index bb653dd0f813..6d8d9b3520d0 100644
--- a/sound/pci/hda/patch_realtek.c
+++ b/sound/pci/hda/patch_realtek.c
@@ -1512,6 +1512,7 @@ enum {
ALC260_FIXUP_KN1,
ALC260_FIXUP_FSC_S7020,
ALC260_FIXUP_FSC_S7020_JWSE,
+ ALC260_FIXUP_VAIO_PINS,
};
static void alc260_gpio1_automute(struct hda_codec *codec)
@@ -1652,6 +1653,24 @@ static const struct hda_fixup alc260_fixups[] = {
.chained = true,
.chain_id = ALC260_FIXUP_FSC_S7020,
},
+ [ALC260_FIXUP_VAIO_PINS] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = (const struct hda_pintbl[]) {
+ /* Pin configs are missing completely on some VAIOs */
+ { 0x0f, 0x01211020 },
+ { 0x10, 0x0001003f },
+ { 0x11, 0x411111f0 },
+ { 0x12, 0x01a15930 },
+ { 0x13, 0x411111f0 },
+ { 0x14, 0x411111f0 },
+ { 0x15, 0x411111f0 },
+ { 0x16, 0x411111f0 },
+ { 0x17, 0x411111f0 },
+ { 0x18, 0x411111f0 },
+ { 0x19, 0x411111f0 },
+ { }
+ }
+ },
};
static const struct snd_pci_quirk alc260_fixup_tbl[] = {
@@ -1660,6 +1679,7 @@ static const struct snd_pci_quirk alc260_fixup_tbl[] = {
SND_PCI_QUIRK(0x1025, 0x008f, "Acer", ALC260_FIXUP_GPIO1),
SND_PCI_QUIRK(0x103c, 0x280a, "HP dc5750", ALC260_FIXUP_HP_DC5750),
SND_PCI_QUIRK(0x103c, 0x30ba, "HP Presario B1900", ALC260_FIXUP_HP_B1900),
+ SND_PCI_QUIRK(0x104d, 0x81bb, "Sony VAIO", ALC260_FIXUP_VAIO_PINS),
SND_PCI_QUIRK(0x104d, 0x81e2, "Sony VAIO TX", ALC260_FIXUP_HP_PIN_0F),
SND_PCI_QUIRK(0x10cf, 0x1326, "FSC LifeBook S7020", ALC260_FIXUP_FSC_S7020),
SND_PCI_QUIRK(0x1509, 0x4540, "Favorit 100XS", ALC260_FIXUP_GPIO1),
--
1.8.4.3
1
0
Hello Takashi,
what is acceptable noise level for mic boost?
My laptop has three levels of mic boost (+36dB). Level 3 is useless
since noise level will be about 0db. With level 1 i get -12dB noise, so
there is almost 12dB room for data... but it is not enough for normal
speech, which is about 20dB.
Are there any ALSA guide for HW designers, which will say some thing
like acceptable levels are 90-20dB?
There are some Skype and Microsoft prescriptions for certification.
For example M$ prescribe 18dB or more for speech to noise ration for
build in mics. It sound like good number to start.
In my case, i need to remove mic boost completely to guarantee minimum
18dB SpNR. Suddenly i didn't found correct way to do it.
I will be thankful for some tip.
--
Regards,
Oleksij
2
6
[alsa-devel] [PATCH v2 4/4]: ALSA: oxygen: The new Xonar DG mixer implementation
by Roman Volkov 21 Nov '13
by Roman Volkov 21 Nov '13
21 Nov '13
The mixer now at the upper level of hierarchy and actually controls the Xonar DG driver. There are also other system structures like model description structure needed for upper-level oxygen driver. Headphone volume control is implemented in the mixer instead of selecting the HP impedance (I have my own Xonar DG card and that's okay). Independent volume and mute control of each capture source.
Signed-off-by: Roman I. Volkov <v1ron(a)mail.ru>
---
diff -uprN linux-3.12/sound/pci/oxygen/Makefile linux-3.12-my/sound/pci/oxygen/Makefile
--- linux-3.12/sound/pci/oxygen/Makefile 2013-11-04 03:41:51.000000000 +0400
+++ linux-3.12-my/sound/pci/oxygen/Makefile 2013-11-20 19:04:54.000000000 +0400
@@ -1,5 +1,5 @@
snd-oxygen-lib-objs := oxygen_io.o oxygen_lib.o oxygen_mixer.o oxygen_pcm.o
-snd-oxygen-objs := oxygen.o xonar_dg.o
+snd-oxygen-objs := oxygen.o xonar_dg_mixer.o xonar_dg.o
snd-virtuoso-objs := virtuoso.o xonar_lib.o \
xonar_pcm179x.o xonar_cs43xx.o xonar_wm87x6.o xonar_hdmi.o
diff -uprN linux-3.12/sound/pci/oxygen/xonar_dg_mixer.c linux-3.12-my/sound/pci/oxygen/xonar_dg_mixer.c
--- linux-3.12/sound/pci/oxygen/xonar_dg_mixer.c 1970-01-01 03:00:00.000000000 +0300
+++ linux-3.12-my/sound/pci/oxygen/xonar_dg_mixer.c 2013-11-21 14:05:55.000000000 +0400
@@ -0,0 +1,483 @@
+/*
+ * Mixer controls for the Xonar DG/DGX
+ *
+ * Copyright (c) Clemens Ladisch <clemens(a)ladisch.de>
+ * Copyright (c) Roman Volkov <v1ron(a)mail.ru>
+ *
+ * This driver is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License, version 2.
+ *
+ * This driver is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this driver; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * It is not good that we do not have software volume control
+ * as the Windows driver does. CS4361 cannot attenuate the volume.
+ * When we are switching from headphones to the speakers,
+ * which are controlled by the simple DAC CS4361, our headphones
+ * in the rear jack can stun us.
+ */
+
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <sound/control.h>
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/pcm.h>
+#include <sound/tlv.h>
+#include "oxygen.h"
+#include "xonar_dg.h"
+#include "cs4245.h"
+
+/* CS4245 Headphone Channels A&B Volume Control */
+static int xonar_dg_dac_stereo_volume_info(struct snd_kcontrol *ctl,
+ struct snd_ctl_elem_info *info)
+{
+ info->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ info->count = 2;
+ info->value.integer.min = 0;
+ info->value.integer.max = 255;
+ return 0;
+}
+
+static int xonar_dg_dac_stereo_volume_get(struct snd_kcontrol *ctl,
+ struct snd_ctl_elem_value *val)
+{
+ struct oxygen *chip = ctl->private_data;
+ struct dg *data = chip->model_data;
+ unsigned int tmp;
+
+ mutex_lock(&chip->mutex);
+ tmp = (~data->cs4245_shadow[CS4245_DAC_A_CTRL]) & 255;
+ val->value.integer.value[0] = tmp;
+ tmp = (~data->cs4245_shadow[CS4245_DAC_B_CTRL]) & 255;
+ val->value.integer.value[1] = tmp;
+ mutex_unlock(&chip->mutex);
+ return 0;
+}
+
+static int xonar_dg_dac_stereo_volume_put(struct snd_kcontrol *ctl,
+ struct snd_ctl_elem_value *val)
+{
+ struct oxygen *chip = ctl->private_data;
+ struct dg *data = chip->model_data;
+
+ if ((val->value.integer.value[0] > 255) ||
+ (val->value.integer.value[0] < 0) ||
+ (val->value.integer.value[1] > 255) ||
+ (val->value.integer.value[1] < 0)) {
+ return -EINVAL;
+ }
+ mutex_lock(&chip->mutex);
+ data->cs4245_shadow[CS4245_DAC_A_CTRL] = ~val->value.integer.value[0];
+ data->cs4245_shadow[CS4245_DAC_B_CTRL] = ~val->value.integer.value[1];
+ cs4245_write_spi(chip, CS4245_DAC_A_CTRL);
+ cs4245_write_spi(chip, CS4245_DAC_B_CTRL);
+ mutex_unlock(&chip->mutex);
+ return 0;
+}
+
+/* CS4245 DAC Mute */
+static int xonar_dg_dac_mute_get(struct snd_kcontrol *ctl,
+ struct snd_ctl_elem_value *val)
+{
+ struct oxygen *chip = ctl->private_data;
+ struct dg *data = chip->model_data;
+
+ mutex_lock(&chip->mutex);
+ val->value.integer.value[0] = ((data->cs4245_shadow[CS4245_DAC_CTRL_1]
+ & CS4245_MUTE_DAC) == 0);
+ mutex_unlock(&chip->mutex);
+ return 0;
+}
+
+static int xonar_dg_dac_mute_put(struct snd_kcontrol *ctl,
+ struct snd_ctl_elem_value *val)
+{
+ struct oxygen *chip = ctl->private_data;
+ struct dg *data = chip->model_data;
+
+ if (val->value.integer.value[0] > 1)
+ return -EINVAL;
+ mutex_lock(&chip->mutex);
+ data->cs4245_shadow[CS4245_DAC_CTRL_1] &= ~CS4245_MUTE_DAC;
+ data->cs4245_shadow[CS4245_DAC_CTRL_1] |=
+ (~val->value.integer.value[0] << 2) & CS4245_MUTE_DAC;
+ cs4245_write_spi(chip, CS4245_DAC_CTRL_1);
+ mutex_unlock(&chip->mutex);
+ return 0;
+}
+
+/* Xonar DG Output Select */
+static int xonar_dg_pcm_route_apply(struct oxygen *chip)
+{
+ struct dg *data = chip->model_data;
+
+ data->cs4245_shadow[CS4245_SIGNAL_SEL] &=
+ ~CS4245_A_OUT_SEL_MASK;
+ if (data->pcm_output == PLAYBACK_DST_HP) {
+ /* Mute FP (aux output) amplifier, switch rear jack to CS4245 */
+ oxygen_set_bits8(chip, OXYGEN_GPIO_DATA, GPIO_HP_REAR);
+ } else if (data->pcm_output == PLAYBACK_DST_HP_FP) {
+ /*
+ * Unmute FP amplifier, switch rear jack to CS4361,
+ * I2S ports 2,3,4 should be inactive
+ */
+ oxygen_clear_bits8(chip, OXYGEN_GPIO_DATA, GPIO_HP_REAR);
+ data->cs4245_shadow[CS4245_SIGNAL_SEL] |= CS4245_A_OUT_SEL_DAC;
+ } else {
+ /*
+ * 2.0, 4.0, 5.1: switch to CS4361, mute FP amp.,
+ * and change playback routing / DMA channels
+ */
+ oxygen_clear_bits8(chip, OXYGEN_GPIO_DATA, GPIO_HP_REAR);
+ }
+ cs4245_write_spi(chip, CS4245_SIGNAL_SEL);
+ return 0;
+}
+
+static int xonar_dg_pcm_route_info(struct snd_kcontrol *ctl,
+ struct snd_ctl_elem_info *info)
+{
+ static const char *const names[3] = {
+ "Stereo Headphones",
+ "Stereo Headphones FP",
+ "Multichannel",
+ };
+
+ return snd_ctl_enum_info(info, 1, 3, names);
+}
+
+static int xonar_dg_pcm_route_get(struct snd_kcontrol *ctl,
+ struct snd_ctl_elem_value *val)
+{
+ struct oxygen *chip = ctl->private_data;
+ struct dg *data = chip->model_data;
+
+ mutex_lock(&chip->mutex);
+ val->value.integer.value[0] = data->pcm_output;
+ mutex_unlock(&chip->mutex);
+
+ return 0;
+}
+
+static int xonar_dg_pcm_route_put(struct snd_kcontrol *ctl,
+ struct snd_ctl_elem_value *val)
+{
+ struct oxygen *chip = ctl->private_data;
+ struct dg *data = chip->model_data;
+ int ret;
+
+ mutex_lock(&chip->mutex);
+ data->pcm_output = val->value.integer.value[0];
+ ret = xonar_dg_pcm_route_apply(chip);
+ oxygen_update_dac_routing(chip);
+ mutex_unlock(&chip->mutex);
+
+ return ret;
+}
+
+/* CS4245 PGA Source */
+static int xonar_dg_pga_mute_reg_toggle(struct oxygen *chip, bool mute);
+static int xonar_dg_pga_volume_reg_write(struct oxygen *chip, char left,
+ char right);
+
+static int xonar_dg_pga_source_apply(struct oxygen *chip)
+{
+ struct dg *data = chip->model_data;
+
+ data->cs4245_shadow[CS4245_ANALOG_IN] &= ~CS4245_SEL_MASK;
+ if (data->pga_source == CAPTURE_SRC_FP_MIC)
+ data->cs4245_shadow[CS4245_ANALOG_IN] |= CS4245_SEL_INPUT_2;
+ else if (data->pga_source == CAPTURE_SRC_LINE)
+ data->cs4245_shadow[CS4245_ANALOG_IN] |= CS4245_SEL_INPUT_4;
+ else if (data->pga_source != CAPTURE_SRC_MIC)
+ data->cs4245_shadow[CS4245_ANALOG_IN] |= CS4245_SEL_INPUT_1;
+ oxygen_set_bits16(chip, OXYGEN_GPIO_DATA, GPIO_INPUT_ROUTE);
+ return cs4245_write_spi(chip, CS4245_ANALOG_IN);
+}
+
+static int xonar_dg_pga_source_info(struct snd_kcontrol *ctl,
+ struct snd_ctl_elem_info *info)
+{
+ static const char *const ps_names[4] = {
+ "Microphone",
+ "Microphone FP",
+ "Line In",
+ "Aux"
+ };
+
+ return snd_ctl_enum_info(info, 1, 4, ps_names);
+}
+
+static int xonar_dg_pga_source_get(struct snd_kcontrol *ctl,
+ struct snd_ctl_elem_value *val)
+{
+ struct oxygen *chip = ctl->private_data;
+ struct dg *data = chip->model_data;
+
+ mutex_lock(&chip->mutex);
+ val->value.integer.value[0] = data->pga_source;
+ mutex_unlock(&chip->mutex);
+
+ return 0;
+}
+
+/* Switch the source and change volume/mute */
+static int xonar_dg_pga_source_put(struct snd_kcontrol *ctl,
+ struct snd_ctl_elem_value *val)
+{
+ struct oxygen *chip = ctl->private_data;
+ struct dg *data = chip->model_data;
+ long route = val->value.integer.value[0];
+ int ret;
+
+ if ((route > CAPTURE_SRC_AUX) || (route < 0))
+ return -EINVAL;
+
+ mutex_lock(&chip->mutex);
+ data->pga_source = route;
+ ret = xonar_dg_pga_source_apply(chip);
+ if (ret < 0)
+ goto err;
+ ret = xonar_dg_pga_mute_reg_toggle(chip, data->cap_mute[route]);
+ if (ret < 0)
+ goto err;
+ ret = xonar_dg_pga_volume_reg_write(chip,
+ data->cap_vol[route][0],
+ data->cap_vol[route][1]);
+ if (ret < 0)
+ goto err;
+err:
+ mutex_unlock(&chip->mutex);
+
+ return ret;
+}
+
+/* Common routines to access capture volume register - Mic, Line, Aux */
+static int xonar_dg_pga_volume_reg_write(struct oxygen *chip, char left,
+ char right)
+{
+ struct dg *data = chip->model_data;
+ int ret;
+
+ data->cs4245_shadow[CS4245_PGA_A_CTRL] = left;
+ data->cs4245_shadow[CS4245_PGA_B_CTRL] = right;
+ ret = cs4245_write_spi(chip, CS4245_PGA_A_CTRL);
+ if (ret < 0)
+ return ret;
+ return cs4245_write_spi(chip, CS4245_PGA_B_CTRL);
+}
+
+static int xonar_dg_pga_volume_info(struct snd_kcontrol *ctl,
+ struct snd_ctl_elem_info *info)
+{
+ info->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ info->count = 2;
+ info->value.integer.min = -24;
+ info->value.integer.max = 24;
+ return 0;
+}
+
+static int xonar_dg_pga_volume_get(struct snd_kcontrol *ctl,
+ struct snd_ctl_elem_value *val)
+{
+ struct oxygen *chip = ctl->private_data;
+ struct dg *data = chip->model_data;
+
+ mutex_lock(&chip->mutex);
+ val->value.integer.value[0] = data->cap_vol[ctl->private_value][0];
+ val->value.integer.value[1] = data->cap_vol[ctl->private_value][1];
+ mutex_unlock(&chip->mutex);
+
+ return 0;
+}
+
+static int xonar_dg_pga_volume_put(struct snd_kcontrol *ctl,
+ struct snd_ctl_elem_value *val)
+{
+ struct oxygen *chip = ctl->private_data;
+ struct dg *data = chip->model_data;
+ long left = val->value.integer.value[0];
+ long right = val->value.integer.value[1];
+ int ret = 0;
+
+ if ((left > 24) || (left < -24) || (right > 24) || (right < -24))
+ return -EINVAL;
+
+ mutex_lock(&chip->mutex);
+ data->cap_vol[ctl->private_value][0] = left;
+ data->cap_vol[ctl->private_value][1] = right;
+ ret = xonar_dg_pga_volume_reg_write(chip,
+ data->cap_vol[data->pga_source][0],
+ data->cap_vol[data->pga_source][1]);
+ mutex_unlock(&chip->mutex);
+
+ return ret;
+}
+
+/* Common routines to access capture mute register - Mic, Line, Aux */
+static int xonar_dg_pga_mute_reg_toggle(struct oxygen *chip, bool mute)
+{
+ struct dg *data = chip->model_data;
+
+ data->cs4245_shadow[CS4245_ADC_CTRL] &= ~CS4245_MUTE_ADC;
+ data->cs4245_shadow[CS4245_ADC_CTRL] |= ((mute == 0) << 2) &
+ CS4245_MUTE_ADC;
+ return cs4245_write_spi(chip, CS4245_ADC_CTRL);
+}
+
+static int xonar_dg_pga_mute_get(struct snd_kcontrol *ctl,
+ struct snd_ctl_elem_value *val)
+{
+ struct oxygen *chip = ctl->private_data;
+ struct dg *data = chip->model_data;
+
+ mutex_lock(&chip->mutex);
+ val->value.integer.value[0] = (long)data->cap_mute[ctl->private_value];
+ mutex_unlock(&chip->mutex);
+
+ return 0;
+}
+
+static int xonar_dg_pga_mute_put(struct snd_kcontrol *ctl,
+ struct snd_ctl_elem_value *val)
+{
+ struct oxygen *chip = ctl->private_data;
+ struct dg *data = chip->model_data;
+ long tmp = val->value.integer.value[0];
+ int ret = 0;
+
+ if ((tmp > 1) || (tmp < 0))
+ return -EINVAL;
+
+ mutex_lock(&chip->mutex);
+ data->cap_mute[ctl->private_value] = tmp != 0;
+ ret = xonar_dg_pga_mute_reg_toggle(chip,
+ data->cap_mute[data->pga_source]);
+ mutex_unlock(&chip->mutex);
+
+ return ret;
+}
+
+#define CAPTURE_VOLUME(xname, private) { \
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+ .name = xname, \
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | \
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ, \
+ .info = xonar_dg_pga_volume_info, \
+ .get = xonar_dg_pga_volume_get, \
+ .put = xonar_dg_pga_volume_put, \
+ .tlv = { .p = xonar_dg_pga_db_scale, }, \
+ .private_value = private, \
+ }
+#define CAPTURE_SWITCH(xname, private) { \
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+ .name = xname, \
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \
+ .info = snd_ctl_boolean_mono_info, \
+ .get = xonar_dg_pga_mute_get, \
+ .put = xonar_dg_pga_mute_put, \
+ .private_value = private, \
+ }
+
+static const DECLARE_TLV_DB_MINMAX(xonar_dg_hp_db_scale, -12550, 0);
+static const DECLARE_TLV_DB_MINMAX(xonar_dg_pga_db_scale, -1200, 1200);
+static const struct snd_kcontrol_new xonar_dg_controls[] = {
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Headphone Playback Volume",
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ,
+ .info = xonar_dg_dac_stereo_volume_info,
+ .get = xonar_dg_dac_stereo_volume_get,
+ .put = xonar_dg_dac_stereo_volume_put,
+ .tlv = { .p = xonar_dg_hp_db_scale, },
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Headphone Playback Switch",
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .info = snd_ctl_boolean_mono_info,
+ .get = xonar_dg_dac_mute_get,
+ .put = xonar_dg_dac_mute_put,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Output Playback Enum",
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .info = xonar_dg_pcm_route_info,
+ .get = xonar_dg_pcm_route_get,
+ .put = xonar_dg_pcm_route_put,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Input Capture Enum",
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .info = xonar_dg_pga_source_info,
+ .get = xonar_dg_pga_source_get,
+ .put = xonar_dg_pga_source_put,
+ },
+ CAPTURE_VOLUME("Mic Capture Volume", CAPTURE_SRC_MIC),
+ CAPTURE_SWITCH("Mic Capture Switch", CAPTURE_SRC_MIC),
+ CAPTURE_VOLUME("FP Mic Capture Volume", CAPTURE_SRC_FP_MIC),
+ CAPTURE_SWITCH("FP Mic Capture Switch", CAPTURE_SRC_FP_MIC),
+ CAPTURE_VOLUME("Line Capture Volume", CAPTURE_SRC_LINE),
+ CAPTURE_SWITCH("Line Capture Switch", CAPTURE_SRC_LINE),
+ CAPTURE_VOLUME("Aux Capture Volume", CAPTURE_SRC_AUX),
+ CAPTURE_SWITCH("Aux Capture Switch", CAPTURE_SRC_AUX),
+};
+
+int xonar_dg_mixer_init(struct oxygen *chip)
+{
+ unsigned int i;
+ int ret;
+ for (i = 0; i < ARRAY_SIZE(xonar_dg_controls); i++) {
+ ret = snd_ctl_add(chip->card,
+ snd_ctl_new1(&xonar_dg_controls[i], chip));
+ if (ret < 0)
+ return ret;
+ }
+ return 0;
+}
+
+/* Disable built-in volume control, we have only headphone volume */
+static int xonar_control_filter(struct snd_kcontrol_new *template)
+{
+ if (!strncmp(template->name, "Master Playback ", 16))
+ return 1;
+ return 0;
+}
+
+struct oxygen_model model_xonar_dg = {
+ .longname = "C-Media Oxygen HD Audio",
+ .chip = "CMI8786",
+ .init = dg_init,
+ .control_filter = xonar_control_filter,
+ .mixer_init = xonar_dg_mixer_init,
+ .cleanup = dg_cleanup,
+ .suspend = dg_suspend,
+ .resume = dg_resume,
+ .set_dac_params = set_cs4245_dac_params,
+ .set_adc_params = set_cs4245_adc_params,
+ .adjust_dac_routing = adjust_dg_dac_routing,
+ .dump_registers = dump_cs4245_registers,
+ .model_data_size = sizeof(struct dg),
+ .device_config = PLAYBACK_0_TO_I2S |
+ PLAYBACK_1_TO_SPDIF |
+ CAPTURE_0_FROM_I2S_1 |
+ CAPTURE_1_FROM_SPDIF,
+ .dac_channels_pcm = 6,
+ .dac_channels_mixer = 0,
+ .function_flags = OXYGEN_FUNCTION_SPI,
+ .dac_mclks = OXYGEN_MCLKS(256, 128, 128),
+ .adc_mclks = OXYGEN_MCLKS(256, 128, 128),
+ .dac_i2s_format = OXYGEN_I2S_FORMAT_LJUST,
+ .adc_i2s_format = OXYGEN_I2S_FORMAT_LJUST,
+};
--
Kind regards,
Roman Volkov,
v1ron(a)mail.ru
1
0
[alsa-devel] [PATCH v2 3/4]: ALSA: oxygen: Move the Xonar DG mixer code away from this file and actual driver changes
by Roman Volkov 21 Nov '13
by Roman Volkov 21 Nov '13
21 Nov '13
Move the mixer controls and other system structures to a separate file. Also there are actual driver changes: Multichannel routing, CS4245 initialization, CS4245 SPI reading and writing, DAC/ADC settings, suspend/resume logic.
Signed-off-by: Roman I. Volkov <v1ron(a)mail.ru>
---
I can confirm, the driver also works with OXYGEN_FUNCTION_ENABLE_SPI_4_5 removed.
diff -uprN linux-3.12/sound/pci/oxygen/oxygen_mixer.c linux-3.12-my/sound/pci/oxygen/oxygen_mixer.c
--- linux-3.12/sound/pci/oxygen/oxygen_mixer.c 2013-11-04 03:41:51.000000000 +0400
+++ linux-3.12-my/sound/pci/oxygen/oxygen_mixer.c 2013-11-20 19:13:09.000000000 +0400
@@ -190,6 +190,7 @@ void oxygen_update_dac_routing(struct ox
if (chip->model.update_center_lfe_mix)
chip->model.update_center_lfe_mix(chip, chip->dac_routing > 2);
}
+EXPORT_SYMBOL(oxygen_update_dac_routing);
static int upmix_put(struct snd_kcontrol *ctl, struct snd_ctl_elem_value *value)
{
diff -uprN linux-3.12/sound/pci/oxygen/cs4245.h linux-3.12-my/sound/pci/oxygen/cs4245.h
--- linux-3.12/sound/pci/oxygen/cs4245.h 2013-11-04 03:41:51.000000000 +0400
+++ linux-3.12-my/sound/pci/oxygen/cs4245.h 2013-11-20 12:08:07.000000000 +0400
@@ -103,5 +103,6 @@
#define CS4245_ADC_UNDRFL 0x01
-#define CS4245_SPI_ADDRESS (0x9e << 16)
-#define CS4245_SPI_WRITE (0 << 16)
+#define CS4245_SPI_ADDRESS 0x9e
+#define CS4245_SPI_WRITE 0
+#define CS4245_SPI_READ 1
diff -uprN linux-3.12/sound/pci/oxygen/xonar_dg.h linux-3.12-my/sound/pci/oxygen/xonar_dg.h
--- linux-3.12/sound/pci/oxygen/xonar_dg.h 2013-11-04 03:41:51.000000000 +0400
+++ linux-3.12-my/sound/pci/oxygen/xonar_dg.h 2013-11-21 13:41:44.000000000 +0400
@@ -3,6 +3,51 @@
#include "oxygen.h"
+#define GPIO_MAGIC 0x0008
+#define GPIO_HP_DETECT 0x0010
+#define GPIO_INPUT_ROUTE 0x0060
+#define GPIO_HP_REAR 0x0080
+#define GPIO_OUTPUT_ENABLE 0x0100
+
+#define CAPTURE_SRC_MIC 0
+#define CAPTURE_SRC_FP_MIC 1
+#define CAPTURE_SRC_LINE 2
+#define CAPTURE_SRC_AUX 3
+
+#define PLAYBACK_DST_HP 0
+#define PLAYBACK_DST_HP_FP 1
+#define PLAYBACK_DST_MULTICH 2
+
extern struct oxygen_model model_xonar_dg;
+/* Xonar DG Specific Data */
+struct dg {
+ unsigned char cs4245_shadow[16];
+ /* Output select: headphone/speakers */
+ unsigned char pcm_output;
+ /* Input select: Mic, FP Mic, Line, Aux */
+ unsigned char pga_source;
+ /* Independent capture volume */
+ char cap_vol[4][2];
+ bool cap_mute[4];
+};
+
+/* Xonar DG control routines */
+int cs4245_write_spi(struct oxygen *chip, u8 reg);
+int cs4245_read_spi(struct oxygen *chip, u8 reg);
+int cs4245_read_shadow(struct oxygen *chip);
+int cs4245_write_shadow(struct oxygen *chip);
+void dg_init(struct oxygen *chip);
+void set_cs4245_dac_params(struct oxygen *chip,
+ struct snd_pcm_hw_params *params);
+void set_cs4245_adc_params(struct oxygen *chip,
+ struct snd_pcm_hw_params *params);
+unsigned int adjust_dg_dac_routing(struct oxygen *chip,
+ unsigned int play_routing);
+void dump_cs4245_registers(struct oxygen *chip,
+ struct snd_info_buffer *buffer);
+void dg_suspend(struct oxygen *chip);
+void dg_resume(struct oxygen *chip);
+void dg_cleanup(struct oxygen *chip);
+
#endif
diff -uprN linux-3.12/sound/pci/oxygen/xonar_dg.c linux-3.12-my/sound/pci/oxygen/xonar_dg.c
--- linux-3.12/sound/pci/oxygen/xonar_dg.c 2013-11-04 03:41:51.000000000 +0400
+++ linux-3.12-my/sound/pci/oxygen/xonar_dg.c 2013-11-21 16:35:02.000000000 +0400
@@ -1,8 +1,8 @@
/*
- * card driver for the Xonar DG/DGX
+ * Card driver for the Xonar DG/DGX
*
* Copyright (c) Clemens Ladisch <clemens(a)ladisch.de>
- *
+ * Copyright (c) Roman Volkov <v1ron(a)mail.ru>
*
* This driver is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License, version 2.
@@ -20,29 +20,37 @@
* Xonar DG/DGX
* ------------
*
+ * CS4245/CS4361 both will mute all outputs if any clock ratio of
+ * any I²S channel is invalid.
+ *
* CMI8788:
*
* SPI 0 -> CS4245
*
+ * Playback:
* I²S 1 -> CS4245
* I²S 2 -> CS4361 (center/LFE)
* I²S 3 -> CS4361 (surround)
* I²S 4 -> CS4361 (front)
+ * Capture:
+ * I²S ADC 1 <- CS4245
*
* GPIO 3 <- ?
* GPIO 4 <- headphone detect
- * GPIO 5 -> route input jack to line-in (0) or mic-in (1)
- * GPIO 6 -> route input jack to line-in (0) or mic-in (1)
- * GPIO 7 -> enable rear headphone amp
- * GPIO 8 -> enable output to speakers
+ * GPIO 5 -> ADC analog circuit power supply control for left channel (?)
+ * GPIO 6 -> ADC analog circuit power supply control for right channel (?)
+ * GPIO 7 -> switches green rear output jack between CS4245 / CS4361 first
+ * channel (mechanical relay)
+ * GPIO 8 -> DAC analog circuit power supply control (?)
*
* CS4245:
*
- * input 1 <- aux
- * input 2 <- front mic
- * input 4 <- line/mic
+ * input 0 <- Microphone
+ * input 1 <- Aux
+ * input 2 <- Front Panel Mic
+ * input 4 <- Line In
* DAC out -> headphones
- * aux out -> front panel headphones
+ * AUX out -> front panel headphones
*/
#include <linux/pci.h>
@@ -56,553 +64,214 @@
#include "xonar_dg.h"
#include "cs4245.h"
-#define GPIO_MAGIC 0x0008
-#define GPIO_HP_DETECT 0x0010
-#define GPIO_INPUT_ROUTE 0x0060
-#define GPIO_HP_REAR 0x0080
-#define GPIO_OUTPUT_ENABLE 0x0100
-
-struct dg {
- unsigned int output_sel;
- s8 input_vol[4][2];
- unsigned int input_sel;
- u8 hp_vol_att;
- u8 cs4245_regs[0x11];
-};
-
-static void cs4245_write(struct oxygen *chip, unsigned int reg, u8 value)
-{
- struct dg *data = chip->model_data;
-
- oxygen_write_spi(chip, OXYGEN_SPI_TRIGGER |
- OXYGEN_SPI_DATA_LENGTH_3 |
- OXYGEN_SPI_CLOCK_1280 |
- (0 << OXYGEN_SPI_CODEC_SHIFT) |
- OXYGEN_SPI_CEN_LATCH_CLOCK_HI,
- CS4245_SPI_ADDRESS |
- CS4245_SPI_WRITE |
- (reg << 8) | value);
- data->cs4245_regs[reg] = value;
-}
-
-static void cs4245_write_cached(struct oxygen *chip, unsigned int reg, u8 value)
-{
- struct dg *data = chip->model_data;
-
- if (value != data->cs4245_regs[reg])
- cs4245_write(chip, reg, value);
-}
-
-static void cs4245_registers_init(struct oxygen *chip)
-{
- struct dg *data = chip->model_data;
-
- cs4245_write(chip, CS4245_POWER_CTRL, CS4245_PDN);
- cs4245_write(chip, CS4245_DAC_CTRL_1,
- data->cs4245_regs[CS4245_DAC_CTRL_1]);
- cs4245_write(chip, CS4245_ADC_CTRL,
- data->cs4245_regs[CS4245_ADC_CTRL]);
- cs4245_write(chip, CS4245_SIGNAL_SEL,
- data->cs4245_regs[CS4245_SIGNAL_SEL]);
- cs4245_write(chip, CS4245_PGA_B_CTRL,
- data->cs4245_regs[CS4245_PGA_B_CTRL]);
- cs4245_write(chip, CS4245_PGA_A_CTRL,
- data->cs4245_regs[CS4245_PGA_A_CTRL]);
- cs4245_write(chip, CS4245_ANALOG_IN,
- data->cs4245_regs[CS4245_ANALOG_IN]);
- cs4245_write(chip, CS4245_DAC_A_CTRL,
- data->cs4245_regs[CS4245_DAC_A_CTRL]);
- cs4245_write(chip, CS4245_DAC_B_CTRL,
- data->cs4245_regs[CS4245_DAC_B_CTRL]);
- cs4245_write(chip, CS4245_DAC_CTRL_2,
- CS4245_DAC_SOFT | CS4245_DAC_ZERO | CS4245_INVERT_DAC);
- cs4245_write(chip, CS4245_INT_MASK, 0);
- cs4245_write(chip, CS4245_POWER_CTRL, 0);
-}
-
-static void cs4245_init(struct oxygen *chip)
+int cs4245_write_spi(struct oxygen *chip, u8 reg)
{
struct dg *data = chip->model_data;
- data->cs4245_regs[CS4245_DAC_CTRL_1] =
- CS4245_DAC_FM_SINGLE | CS4245_DAC_DIF_LJUST;
- data->cs4245_regs[CS4245_ADC_CTRL] =
- CS4245_ADC_FM_SINGLE | CS4245_ADC_DIF_LJUST;
- data->cs4245_regs[CS4245_SIGNAL_SEL] =
- CS4245_A_OUT_SEL_HIZ | CS4245_ASYNCH;
- data->cs4245_regs[CS4245_PGA_B_CTRL] = 0;
- data->cs4245_regs[CS4245_PGA_A_CTRL] = 0;
- data->cs4245_regs[CS4245_ANALOG_IN] =
- CS4245_PGA_SOFT | CS4245_PGA_ZERO | CS4245_SEL_INPUT_4;
- data->cs4245_regs[CS4245_DAC_A_CTRL] = 0;
- data->cs4245_regs[CS4245_DAC_B_CTRL] = 0;
- cs4245_registers_init(chip);
- snd_component_add(chip->card, "CS4245");
-}
-
-static void dg_output_enable(struct oxygen *chip)
-{
- msleep(2500);
- oxygen_set_bits16(chip, OXYGEN_GPIO_DATA, GPIO_OUTPUT_ENABLE);
+ return oxygen_write_spi(chip, OXYGEN_SPI_TRIGGER |
+ OXYGEN_SPI_DATA_LENGTH_3 | OXYGEN_SPI_CLOCK_1280 |
+ (0 << OXYGEN_SPI_CODEC_SHIFT) | OXYGEN_SPI_CEN_LATCH_CLOCK_HI,
+ (((u32)CS4245_SPI_ADDRESS) << 16) | (((u32)reg) << 8) |
+ ((u32)data->cs4245_shadow[reg]));
}
-static void dg_init(struct oxygen *chip)
+int cs4245_read_spi(struct oxygen *chip, u8 reg)
{
struct dg *data = chip->model_data;
+ int ret;
- data->output_sel = 0;
- data->input_sel = 3;
- data->hp_vol_att = 2 * 16;
+ ret = oxygen_write_spi(chip, OXYGEN_SPI_TRIGGER |
+ OXYGEN_SPI_DATA_LENGTH_2 |
+ OXYGEN_SPI_CEN_LATCH_CLOCK_HI |
+ OXYGEN_SPI_CLOCK_1280 | (0 << OXYGEN_SPI_CODEC_SHIFT),
+ (CS4245_SPI_ADDRESS << 8) | reg);
+ if (ret < 0)
+ return ret;
- cs4245_init(chip);
-
- oxygen_clear_bits16(chip, OXYGEN_GPIO_CONTROL,
- GPIO_MAGIC | GPIO_HP_DETECT);
- oxygen_set_bits16(chip, OXYGEN_GPIO_CONTROL,
- GPIO_INPUT_ROUTE | GPIO_HP_REAR | GPIO_OUTPUT_ENABLE);
- oxygen_clear_bits16(chip, OXYGEN_GPIO_DATA,
- GPIO_INPUT_ROUTE | GPIO_HP_REAR);
- dg_output_enable(chip);
-}
-
-static void dg_cleanup(struct oxygen *chip)
-{
- oxygen_clear_bits16(chip, OXYGEN_GPIO_DATA, GPIO_OUTPUT_ENABLE);
-}
-
-static void dg_suspend(struct oxygen *chip)
-{
- dg_cleanup(chip);
-}
-
-static void dg_resume(struct oxygen *chip)
-{
- cs4245_registers_init(chip);
- dg_output_enable(chip);
-}
-
-static void set_cs4245_dac_params(struct oxygen *chip,
- struct snd_pcm_hw_params *params)
-{
- struct dg *data = chip->model_data;
- u8 value;
-
- value = data->cs4245_regs[CS4245_DAC_CTRL_1] & ~CS4245_DAC_FM_MASK;
- if (params_rate(params) <= 50000)
- value |= CS4245_DAC_FM_SINGLE;
- else if (params_rate(params) <= 100000)
- value |= CS4245_DAC_FM_DOUBLE;
- else
- value |= CS4245_DAC_FM_QUAD;
- cs4245_write_cached(chip, CS4245_DAC_CTRL_1, value);
-}
-
-static void set_cs4245_adc_params(struct oxygen *chip,
- struct snd_pcm_hw_params *params)
-{
- struct dg *data = chip->model_data;
- u8 value;
-
- value = data->cs4245_regs[CS4245_ADC_CTRL] & ~CS4245_ADC_FM_MASK;
- if (params_rate(params) <= 50000)
- value |= CS4245_ADC_FM_SINGLE;
- else if (params_rate(params) <= 100000)
- value |= CS4245_ADC_FM_DOUBLE;
- else
- value |= CS4245_ADC_FM_QUAD;
- cs4245_write_cached(chip, CS4245_ADC_CTRL, value);
-}
-
-static inline unsigned int shift_bits(unsigned int value,
- unsigned int shift_from,
- unsigned int shift_to,
- unsigned int mask)
-{
- if (shift_from < shift_to)
- return (value << (shift_to - shift_from)) & mask;
- else
- return (value >> (shift_from - shift_to)) & mask;
-}
-
-static unsigned int adjust_dg_dac_routing(struct oxygen *chip,
- unsigned int play_routing)
-{
- return (play_routing & OXYGEN_PLAY_DAC0_SOURCE_MASK) |
- shift_bits(play_routing,
- OXYGEN_PLAY_DAC2_SOURCE_SHIFT,
- OXYGEN_PLAY_DAC1_SOURCE_SHIFT,
- OXYGEN_PLAY_DAC1_SOURCE_MASK) |
- shift_bits(play_routing,
- OXYGEN_PLAY_DAC1_SOURCE_SHIFT,
- OXYGEN_PLAY_DAC2_SOURCE_SHIFT,
- OXYGEN_PLAY_DAC2_SOURCE_MASK) |
- shift_bits(play_routing,
- OXYGEN_PLAY_DAC0_SOURCE_SHIFT,
- OXYGEN_PLAY_DAC3_SOURCE_SHIFT,
- OXYGEN_PLAY_DAC3_SOURCE_MASK);
-}
-
-static int output_switch_info(struct snd_kcontrol *ctl,
- struct snd_ctl_elem_info *info)
-{
- static const char *const names[3] = {
- "Speakers", "Headphones", "FP Headphones"
- };
-
- return snd_ctl_enum_info(info, 1, 3, names);
-}
+ ret = oxygen_write_spi(chip, OXYGEN_SPI_TRIGGER |
+ OXYGEN_SPI_DATA_LENGTH_2 |
+ OXYGEN_SPI_CEN_LATCH_CLOCK_HI |
+ OXYGEN_SPI_CLOCK_1280 | (0 << OXYGEN_SPI_CODEC_SHIFT),
+ (CS4245_SPI_ADDRESS | CS4245_SPI_READ) << 8);
+ if (ret < 0)
+ return ret;
-static int output_switch_get(struct snd_kcontrol *ctl,
- struct snd_ctl_elem_value *value)
-{
- struct oxygen *chip = ctl->private_data;
- struct dg *data = chip->model_data;
+ data->cs4245_shadow[reg] = oxygen_read8(chip, OXYGEN_SPI_DATA1);
- mutex_lock(&chip->mutex);
- value->value.enumerated.item[0] = data->output_sel;
- mutex_unlock(&chip->mutex);
return 0;
}
-static int output_switch_put(struct snd_kcontrol *ctl,
- struct snd_ctl_elem_value *value)
+int cs4245_read_shadow(struct oxygen *chip)
{
- struct oxygen *chip = ctl->private_data;
- struct dg *data = chip->model_data;
- u8 reg;
- int changed;
-
- if (value->value.enumerated.item[0] > 2)
- return -EINVAL;
+ unsigned char address;
+ int ret;
- mutex_lock(&chip->mutex);
- changed = value->value.enumerated.item[0] != data->output_sel;
- if (changed) {
- data->output_sel = value->value.enumerated.item[0];
-
- reg = data->cs4245_regs[CS4245_SIGNAL_SEL] &
- ~CS4245_A_OUT_SEL_MASK;
- reg |= data->output_sel == 2 ?
- CS4245_A_OUT_SEL_DAC : CS4245_A_OUT_SEL_HIZ;
- cs4245_write_cached(chip, CS4245_SIGNAL_SEL, reg);
-
- cs4245_write_cached(chip, CS4245_DAC_A_CTRL,
- data->output_sel ? data->hp_vol_att : 0);
- cs4245_write_cached(chip, CS4245_DAC_B_CTRL,
- data->output_sel ? data->hp_vol_att : 0);
-
- oxygen_write16_masked(chip, OXYGEN_GPIO_DATA,
- data->output_sel == 1 ? GPIO_HP_REAR : 0,
- GPIO_HP_REAR);
+ for (address = 0; address <= 0x10; address++) {
+ ret = cs4245_read_spi(chip, address);
+ if (ret < 0)
+ return ret;
}
- mutex_unlock(&chip->mutex);
- return changed;
-}
-
-static int hp_volume_offset_info(struct snd_kcontrol *ctl,
- struct snd_ctl_elem_info *info)
-{
- static const char *const names[3] = {
- "< 64 ohms", "64-150 ohms", "150-300 ohms"
- };
-
- return snd_ctl_enum_info(info, 1, 3, names);
-}
-
-static int hp_volume_offset_get(struct snd_kcontrol *ctl,
- struct snd_ctl_elem_value *value)
-{
- struct oxygen *chip = ctl->private_data;
- struct dg *data = chip->model_data;
-
- mutex_lock(&chip->mutex);
- if (data->hp_vol_att > 2 * 7)
- value->value.enumerated.item[0] = 0;
- else if (data->hp_vol_att > 0)
- value->value.enumerated.item[0] = 1;
- else
- value->value.enumerated.item[0] = 2;
- mutex_unlock(&chip->mutex);
return 0;
}
-static int hp_volume_offset_put(struct snd_kcontrol *ctl,
- struct snd_ctl_elem_value *value)
+int cs4245_write_shadow(struct oxygen *chip)
{
- static const s8 atts[3] = { 2 * 16, 2 * 7, 0 };
- struct oxygen *chip = ctl->private_data;
- struct dg *data = chip->model_data;
- s8 att;
- int changed;
+ unsigned char address;
+ int ret;
- if (value->value.enumerated.item[0] > 2)
- return -EINVAL;
- att = atts[value->value.enumerated.item[0]];
- mutex_lock(&chip->mutex);
- changed = att != data->hp_vol_att;
- if (changed) {
- data->hp_vol_att = att;
- if (data->output_sel) {
- cs4245_write_cached(chip, CS4245_DAC_A_CTRL, att);
- cs4245_write_cached(chip, CS4245_DAC_B_CTRL, att);
- }
+ for (address = 0; address <= 0x10; address++) {
+ ret = cs4245_write_spi(chip, address);
+ if (ret < 0)
+ return ret;
}
- mutex_unlock(&chip->mutex);
- return changed;
-}
-
-static int input_vol_info(struct snd_kcontrol *ctl,
- struct snd_ctl_elem_info *info)
-{
- info->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
- info->count = 2;
- info->value.integer.min = 2 * -12;
- info->value.integer.max = 2 * 12;
- return 0;
-}
-
-static int input_vol_get(struct snd_kcontrol *ctl,
- struct snd_ctl_elem_value *value)
-{
- struct oxygen *chip = ctl->private_data;
- struct dg *data = chip->model_data;
- unsigned int idx = ctl->private_value;
-
- mutex_lock(&chip->mutex);
- value->value.integer.value[0] = data->input_vol[idx][0];
- value->value.integer.value[1] = data->input_vol[idx][1];
- mutex_unlock(&chip->mutex);
return 0;
}
-static int input_vol_put(struct snd_kcontrol *ctl,
- struct snd_ctl_elem_value *value)
+static void cs4245_init(struct oxygen *chip)
{
- struct oxygen *chip = ctl->private_data;
struct dg *data = chip->model_data;
- unsigned int idx = ctl->private_value;
- int changed = 0;
- if (value->value.integer.value[0] < 2 * -12 ||
- value->value.integer.value[0] > 2 * 12 ||
- value->value.integer.value[1] < 2 * -12 ||
- value->value.integer.value[1] > 2 * 12)
- return -EINVAL;
- mutex_lock(&chip->mutex);
- changed = data->input_vol[idx][0] != value->value.integer.value[0] ||
- data->input_vol[idx][1] != value->value.integer.value[1];
- if (changed) {
- data->input_vol[idx][0] = value->value.integer.value[0];
- data->input_vol[idx][1] = value->value.integer.value[1];
- if (idx == data->input_sel) {
- cs4245_write_cached(chip, CS4245_PGA_A_CTRL,
- data->input_vol[idx][0]);
- cs4245_write_cached(chip, CS4245_PGA_B_CTRL,
- data->input_vol[idx][1]);
- }
- }
- mutex_unlock(&chip->mutex);
- return changed;
-}
+ /* save the initial state */
+ cs4245_read_shadow(chip);
-static DECLARE_TLV_DB_SCALE(cs4245_pga_db_scale, -1200, 50, 0);
+ /*
+ * Power up the CODEC internals, enable soft ramp & zero cross, work
+ * in async. mode,
+ * Enable AUX output from DAC. DAC output in the Windows driver is
+ * inverted.
+ */
+ data->cs4245_shadow[CS4245_POWER_CTRL] = 0;
+ data->cs4245_shadow[CS4245_SIGNAL_SEL] = CS4245_A_OUT_SEL_DAC |
+ CS4245_ASYNCH;
+ data->cs4245_shadow[CS4245_DAC_CTRL_1] = 0;
+ data->cs4245_shadow[CS4245_DAC_CTRL_2] = CS4245_DAC_SOFT |
+ CS4245_DAC_ZERO | CS4245_INVERT_DAC;
+ data->cs4245_shadow[CS4245_ADC_CTRL] = 0;
+ data->cs4245_shadow[CS4245_ANALOG_IN] = CS4245_PGA_SOFT |
+ CS4245_PGA_ZERO;
-static int input_sel_info(struct snd_kcontrol *ctl,
- struct snd_ctl_elem_info *info)
-{
- static const char *const names[4] = {
- "Mic", "Aux", "Front Mic", "Line"
- };
+ cs4245_write_shadow(chip);
- return snd_ctl_enum_info(info, 1, 4, names);
+ snd_component_add(chip->card, "CS4245");
}
-static int input_sel_get(struct snd_kcontrol *ctl,
- struct snd_ctl_elem_value *value)
+void dg_init(struct oxygen *chip)
{
- struct oxygen *chip = ctl->private_data;
- struct dg *data = chip->model_data;
+ /*
+ * The pin XGPIO1 as XGPIO1, gpios 8,7,6,5,1,0 as outputs.
+ */
- mutex_lock(&chip->mutex);
- value->value.enumerated.item[0] = data->input_sel;
- mutex_unlock(&chip->mutex);
- return 0;
+ cs4245_init(chip);
+ oxygen_write16(chip, OXYGEN_GPIO_CONTROL, GPIO_OUTPUT_ENABLE |
+ GPIO_HP_REAR | GPIO_INPUT_ROUTE);
+ oxygen_write16(chip, OXYGEN_GPIO_DATA, GPIO_HP_REAR | GPIO_INPUT_ROUTE);
+ /* Anti-pop delay */
+ msleep(2500);
+ oxygen_write16(chip, OXYGEN_GPIO_DATA, GPIO_OUTPUT_ENABLE |
+ GPIO_HP_REAR | GPIO_INPUT_ROUTE);
}
-static int input_sel_put(struct snd_kcontrol *ctl,
- struct snd_ctl_elem_value *value)
+void set_cs4245_dac_params(struct oxygen *chip,
+ struct snd_pcm_hw_params *params)
{
- static const u8 sel_values[4] = {
- CS4245_SEL_MIC,
- CS4245_SEL_INPUT_1,
- CS4245_SEL_INPUT_2,
- CS4245_SEL_INPUT_4
- };
- struct oxygen *chip = ctl->private_data;
struct dg *data = chip->model_data;
- int changed;
-
- if (value->value.enumerated.item[0] > 3)
- return -EINVAL;
+ unsigned char dac_ctrl;
+ unsigned char mclk_freq;
- mutex_lock(&chip->mutex);
- changed = value->value.enumerated.item[0] != data->input_sel;
- if (changed) {
- data->input_sel = value->value.enumerated.item[0];
-
- cs4245_write(chip, CS4245_ANALOG_IN,
- (data->cs4245_regs[CS4245_ANALOG_IN] &
- ~CS4245_SEL_MASK) |
- sel_values[data->input_sel]);
-
- cs4245_write_cached(chip, CS4245_PGA_A_CTRL,
- data->input_vol[data->input_sel][0]);
- cs4245_write_cached(chip, CS4245_PGA_B_CTRL,
- data->input_vol[data->input_sel][1]);
-
- oxygen_write16_masked(chip, OXYGEN_GPIO_DATA,
- data->input_sel ? 0 : GPIO_INPUT_ROUTE,
- GPIO_INPUT_ROUTE);
+ dac_ctrl = data->cs4245_shadow[CS4245_DAC_CTRL_1] & ~CS4245_DAC_FM_MASK;
+ mclk_freq = data->cs4245_shadow[CS4245_MCLK_FREQ] & ~CS4245_MCLK1_MASK;
+ if (params_rate(params) <= 50000) {
+ dac_ctrl |= CS4245_DAC_FM_SINGLE;
+ mclk_freq |= CS4245_MCLK_1 << CS4245_MCLK1_SHIFT;
+ } else if (params_rate(params) <= 100000) {
+ dac_ctrl |= CS4245_DAC_FM_DOUBLE;
+ mclk_freq |= CS4245_MCLK_1 << CS4245_MCLK1_SHIFT;
+ } else {
+ dac_ctrl |= CS4245_DAC_FM_QUAD;
+ mclk_freq |= CS4245_MCLK_2 << CS4245_MCLK1_SHIFT;
}
- mutex_unlock(&chip->mutex);
- return changed;
+ data->cs4245_shadow[CS4245_DAC_CTRL_1] = dac_ctrl;
+ data->cs4245_shadow[CS4245_MCLK_FREQ] = mclk_freq;
+ cs4245_write_spi(chip, CS4245_DAC_CTRL_1);
+ cs4245_write_spi(chip, CS4245_MCLK_FREQ);
}
-static int hpf_info(struct snd_kcontrol *ctl, struct snd_ctl_elem_info *info)
+void set_cs4245_adc_params(struct oxygen *chip,
+ struct snd_pcm_hw_params *params)
{
- static const char *const names[2] = { "Active", "Frozen" };
+ struct dg *data = chip->model_data;
+ unsigned char adc_ctrl;
+ unsigned char mclk_freq;
- return snd_ctl_enum_info(info, 1, 2, names);
+ adc_ctrl = data->cs4245_shadow[CS4245_ADC_CTRL] & ~CS4245_ADC_FM_MASK;
+ mclk_freq = data->cs4245_shadow[CS4245_MCLK_FREQ] & ~CS4245_MCLK2_MASK;
+ if (params_rate(params) <= 50000) {
+ adc_ctrl |= CS4245_ADC_FM_SINGLE;
+ mclk_freq |= CS4245_MCLK_1 << CS4245_MCLK2_SHIFT;
+ } else if (params_rate(params) <= 100000) {
+ adc_ctrl |= CS4245_ADC_FM_DOUBLE;
+ mclk_freq |= CS4245_MCLK_1 << CS4245_MCLK2_SHIFT;
+ } else {
+ adc_ctrl |= CS4245_ADC_FM_QUAD;
+ mclk_freq |= CS4245_MCLK_2 << CS4245_MCLK2_SHIFT;
+ }
+ data->cs4245_shadow[CS4245_ADC_CTRL] = adc_ctrl;
+ data->cs4245_shadow[CS4245_MCLK_FREQ] = mclk_freq;
+ cs4245_write_spi(chip, CS4245_ADC_CTRL);
+ cs4245_write_spi(chip, CS4245_MCLK_FREQ);
}
-static int hpf_get(struct snd_kcontrol *ctl, struct snd_ctl_elem_value *value)
+unsigned int adjust_dg_dac_routing(struct oxygen *chip,
+ unsigned int play_routing)
{
- struct oxygen *chip = ctl->private_data;
struct dg *data = chip->model_data;
+ unsigned int routing = 0;
- value->value.enumerated.item[0] =
- !!(data->cs4245_regs[CS4245_ADC_CTRL] & CS4245_HPF_FREEZE);
- return 0;
+ switch (data->pcm_output) {
+ case PLAYBACK_DST_HP:
+ case PLAYBACK_DST_HP_FP:
+ oxygen_write8_masked(chip, OXYGEN_PLAY_ROUTING,
+ OXYGEN_PLAY_MUTE23 | OXYGEN_PLAY_MUTE45 |
+ OXYGEN_PLAY_MUTE67, OXYGEN_PLAY_MUTE_MASK);
+ break;
+ case PLAYBACK_DST_MULTICH:
+ routing = (0 << OXYGEN_PLAY_DAC0_SOURCE_SHIFT) |
+ (2 << OXYGEN_PLAY_DAC1_SOURCE_SHIFT) |
+ (1 << OXYGEN_PLAY_DAC2_SOURCE_SHIFT) |
+ (0 << OXYGEN_PLAY_DAC3_SOURCE_SHIFT);
+ oxygen_write8_masked(chip, OXYGEN_PLAY_ROUTING,
+ OXYGEN_PLAY_MUTE01, OXYGEN_PLAY_MUTE_MASK);
+ break;
+ }
+ return routing;
}
-static int hpf_put(struct snd_kcontrol *ctl, struct snd_ctl_elem_value *value)
+void dump_cs4245_registers(struct oxygen *chip,
+ struct snd_info_buffer *buffer)
{
- struct oxygen *chip = ctl->private_data;
struct dg *data = chip->model_data;
- u8 reg;
- int changed;
+ unsigned int reg;
- mutex_lock(&chip->mutex);
- reg = data->cs4245_regs[CS4245_ADC_CTRL] & ~CS4245_HPF_FREEZE;
- if (value->value.enumerated.item[0])
- reg |= CS4245_HPF_FREEZE;
- changed = reg != data->cs4245_regs[CS4245_ADC_CTRL];
- if (changed)
- cs4245_write(chip, CS4245_ADC_CTRL, reg);
- mutex_unlock(&chip->mutex);
- return changed;
-}
-
-#define INPUT_VOLUME(xname, index) { \
- .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
- .name = xname, \
- .info = input_vol_info, \
- .get = input_vol_get, \
- .put = input_vol_put, \
- .tlv = { .p = cs4245_pga_db_scale }, \
- .private_value = index, \
-}
-static const struct snd_kcontrol_new dg_controls[] = {
- {
- .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
- .name = "Analog Output Playback Enum",
- .info = output_switch_info,
- .get = output_switch_get,
- .put = output_switch_put,
- },
- {
- .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
- .name = "Headphones Impedance Playback Enum",
- .info = hp_volume_offset_info,
- .get = hp_volume_offset_get,
- .put = hp_volume_offset_put,
- },
- INPUT_VOLUME("Mic Capture Volume", 0),
- INPUT_VOLUME("Aux Capture Volume", 1),
- INPUT_VOLUME("Front Mic Capture Volume", 2),
- INPUT_VOLUME("Line Capture Volume", 3),
- {
- .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
- .name = "Capture Source",
- .info = input_sel_info,
- .get = input_sel_get,
- .put = input_sel_put,
- },
- {
- .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
- .name = "ADC High-pass Filter Capture Enum",
- .info = hpf_info,
- .get = hpf_get,
- .put = hpf_put,
- },
-};
+ snd_iprintf(buffer, "\nCS4245:");
+ cs4245_read_spi(chip, CS4245_INT_STATUS);
+ for (reg = 0; reg < ARRAY_SIZE(data->cs4245_shadow); ++reg)
+ snd_iprintf(buffer, " %02x", data->cs4245_shadow[reg]);
+ snd_iprintf(buffer, "\n");
+}
-static int dg_control_filter(struct snd_kcontrol_new *template)
+void dg_suspend(struct oxygen *chip)
{
- if (!strncmp(template->name, "Master Playback ", 16))
- return 1;
- return 0;
+ oxygen_clear_bits16(chip, OXYGEN_GPIO_DATA, GPIO_OUTPUT_ENABLE);
}
-static int dg_mixer_init(struct oxygen *chip)
+void dg_resume(struct oxygen *chip)
{
- unsigned int i;
- int err;
-
- for (i = 0; i < ARRAY_SIZE(dg_controls); ++i) {
- err = snd_ctl_add(chip->card,
- snd_ctl_new1(&dg_controls[i], chip));
- if (err < 0)
- return err;
- }
- return 0;
+ cs4245_write_shadow(chip);
+ msleep(2500);
+ oxygen_set_bits16(chip, OXYGEN_GPIO_DATA, GPIO_OUTPUT_ENABLE);
}
-static void dump_cs4245_registers(struct oxygen *chip,
- struct snd_info_buffer *buffer)
+void dg_cleanup(struct oxygen *chip)
{
- struct dg *data = chip->model_data;
- unsigned int i;
-
- snd_iprintf(buffer, "\nCS4245:");
- for (i = 1; i <= 0x10; ++i)
- snd_iprintf(buffer, " %02x", data->cs4245_regs[i]);
- snd_iprintf(buffer, "\n");
+ dg_suspend(chip);
}
-
-struct oxygen_model model_xonar_dg = {
- .longname = "C-Media Oxygen HD Audio",
- .chip = "CMI8786",
- .init = dg_init,
- .control_filter = dg_control_filter,
- .mixer_init = dg_mixer_init,
- .cleanup = dg_cleanup,
- .suspend = dg_suspend,
- .resume = dg_resume,
- .set_dac_params = set_cs4245_dac_params,
- .set_adc_params = set_cs4245_adc_params,
- .adjust_dac_routing = adjust_dg_dac_routing,
- .dump_registers = dump_cs4245_registers,
- .model_data_size = sizeof(struct dg),
- .device_config = PLAYBACK_0_TO_I2S |
- PLAYBACK_1_TO_SPDIF |
- CAPTURE_0_FROM_I2S_2 |
- CAPTURE_1_FROM_SPDIF,
- .dac_channels_pcm = 6,
- .dac_channels_mixer = 0,
- .function_flags = OXYGEN_FUNCTION_SPI,
- .dac_mclks = OXYGEN_MCLKS(256, 128, 128),
- .adc_mclks = OXYGEN_MCLKS(256, 128, 128),
- .dac_i2s_format = OXYGEN_I2S_FORMAT_LJUST,
- .adc_i2s_format = OXYGEN_I2S_FORMAT_LJUST,
-};
--
Kind regards,
Roman Volkov,
v1ron(a)mail.ru
1
0