Alsa-devel
Threads by month
- ----- 2024 -----
- December
- November
- 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
February 2014
- 136 participants
- 300 discussions
25 Feb '14
This set adds support for Baytrail SST DSP with RT5640 based machine
driver. Design is quite similar and is actually inherited from Intel
Haswell SST DSP drivers that were posted recently. Reason for another
implementation is that there are more differences than similarities
between Haswell and Baytrail.
First difference comes from the SHIM registers that are 32-bit on
Haswell and 64-bit on Baytrail. Also memory map and message mailbox is
different. Second difference is message passing and message headers
between host and SST DSP. For instance the DSP notifies the host using
different SHIM registers and bits than on Haswell.
Biggest difference comes from the DSP firmware. It has different IPC
messages, stream setup and how DMA pointer is tracked. Currently
firmware has less features than Haswell DSP firmware and it doesn't
support compress offload and volume controls support.
The RT5640 based machine driver is for our customer reference boards
but at quick test it appears to work on Asus T100 too. Note currently
the SST is the I2S master on this machine driver and codec is set to
use I2C BCLK as it's input clock.
Jarkko Nikula (7):
ASoC: Intel: Add Baytrail SST ID and Baytrail specific register bits
ASoC: Intel: Add Intel Baytrail SST DSP support
ASoC: Intel: Add Intel Baytrail SST DSP IPC support
ASoC: Intel: Add Intel Baytrail SST PCM platform driver
ASoC: Intel: Add machine driver for Baytrail SST with RT5640 codec
ASoC: Intel: Add Baytrail SST and byt-rt5640 machine driver probing
ASoC: Intel: Add build support for Baytrail SST
sound/soc/intel/Kconfig | 14 +-
sound/soc/intel/Makefile | 5 +
sound/soc/intel/byt-rt5640.c | 194 +++++++++
sound/soc/intel/sst-acpi.c | 17 +
sound/soc/intel/sst-baytrail-dsp.c | 372 ++++++++++++++++
sound/soc/intel/sst-baytrail-ipc.c | 866 +++++++++++++++++++++++++++++++++++++
sound/soc/intel/sst-baytrail-ipc.h | 69 +++
sound/soc/intel/sst-baytrail-pcm.c | 422 ++++++++++++++++++
sound/soc/intel/sst-dsp-priv.h | 1 +
sound/soc/intel/sst-dsp.h | 11 +
10 files changed, 1970 insertions(+), 1 deletion(-)
create mode 100644 sound/soc/intel/byt-rt5640.c
create mode 100644 sound/soc/intel/sst-baytrail-dsp.c
create mode 100644 sound/soc/intel/sst-baytrail-ipc.c
create mode 100644 sound/soc/intel/sst-baytrail-ipc.h
create mode 100644 sound/soc/intel/sst-baytrail-pcm.c
--
1.8.5.3
3
10
25 Feb '14
The codec->control_data contains a pointer to the device's regmap struct. But
wm8994_bulk_write() expects a pointer to the parent wm8998 device.
The issue was introduced in commit d9a7666f ("ASoC: Remove ASoC-specific
WM8994 I/O code").
Signed-off-by: Lars-Peter Clausen <lars(a)metafoo.de>
---
sound/soc/codecs/wm8958-dsp2.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/sound/soc/codecs/wm8958-dsp2.c b/sound/soc/codecs/wm8958-dsp2.c
index 1974377..7ac2e51 100644
--- a/sound/soc/codecs/wm8958-dsp2.c
+++ b/sound/soc/codecs/wm8958-dsp2.c
@@ -153,7 +153,7 @@ static int wm8958_dsp2_fw(struct snd_soc_codec *codec, const char *name,
data32 &= 0xffffff;
- wm8994_bulk_write(codec->control_data,
+ wm8994_bulk_write(wm8994->wm8994,
data32 & 0xffffff,
block_len / 2,
(void *)(data + 8));
--
1.8.0
2
1
[alsa-devel] [PATCH 1/2] ASoC: wm8996: Replace codec->control_data with wm8996->regmap
by Lars-Peter Clausen 25 Feb '14
by Lars-Peter Clausen 25 Feb '14
25 Feb '14
With the ongoing component-ization of the ASoC framework and the continuing
migration to using regmap for IO the control_data field of the snd_soc_codec
struct will eventually be removed. Prepare the wm8996 driver for this by using
wm8996->regmap instead of accessing the CODEC's control_data field.
Signed-off-by: Lars-Peter Clausen <lars(a)metafoo.de>
---
sound/soc/codecs/wm8996.c | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/sound/soc/codecs/wm8996.c b/sound/soc/codecs/wm8996.c
index 0330165..e2d0dd7 100644
--- a/sound/soc/codecs/wm8996.c
+++ b/sound/soc/codecs/wm8996.c
@@ -1608,8 +1608,8 @@ static int wm8996_set_bias_level(struct snd_soc_codec *codec,
msleep(5);
}
- regcache_cache_only(codec->control_data, false);
- regcache_sync(codec->control_data);
+ regcache_cache_only(wm8996->regmap, false);
+ regcache_sync(wm8996->regmap);
}
/* Bypass the MICBIASes for lowest power */
@@ -1620,10 +1620,10 @@ static int wm8996_set_bias_level(struct snd_soc_codec *codec,
break;
case SND_SOC_BIAS_OFF:
- regcache_cache_only(codec->control_data, true);
+ regcache_cache_only(wm8996->regmap, true);
if (wm8996->pdata.ldo_ena >= 0) {
gpio_set_value_cansleep(wm8996->pdata.ldo_ena, 0);
- regcache_cache_only(codec->control_data, true);
+ regcache_cache_only(wm8996->regmap, true);
}
regulator_bulk_disable(ARRAY_SIZE(wm8996->supplies),
wm8996->supplies);
--
1.8.0
2
3
[alsa-devel] [PATCH] ASoC: wm8993: Remove unused pointer in wm8993_remove()
by Christian Engelmayer 25 Feb '14
by Christian Engelmayer 25 Feb '14
25 Feb '14
Commit 88b5bdfd (ASoC: wm8993: drop regulator_bulk_free of devm_ allocated
data) eliminated the last user of driver data pointer 'wm8993' in function
wm8993_remove() - Thus remove it. Detected by Coverity: CID 1186208.
Signed-off-by: Christian Engelmayer <cengelma(a)gmx.at>
---
Applies against branch for-next in tree
git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git
---
sound/soc/codecs/wm8993.c | 2 --
1 file changed, 2 deletions(-)
diff --git a/sound/soc/codecs/wm8993.c b/sound/soc/codecs/wm8993.c
index 2ee23a3..f7978b3 100644
--- a/sound/soc/codecs/wm8993.c
+++ b/sound/soc/codecs/wm8993.c
@@ -1559,8 +1559,6 @@ static int wm8993_probe(struct snd_soc_codec *codec)
static int wm8993_remove(struct snd_soc_codec *codec)
{
- struct wm8993_priv *wm8993 = snd_soc_codec_get_drvdata(codec);
-
wm8993_set_bias_level(codec, SND_SOC_BIAS_OFF);
return 0;
}
--
1.8.3.2
2
1
[alsa-devel] [PATCH] ASoC: da732x: Use da732x->regmap instead of codec->control_data
by Lars-Peter Clausen 25 Feb '14
by Lars-Peter Clausen 25 Feb '14
25 Feb '14
With the ongoing component-ization of the ASoC framework and the continuing
migration to using regmap for IO the control_data field of the snd_soc_codec
struct will eventually be removed. Prepare the da732x driver for this by using
da732x->regmap instead of accessing the CODEC's control_data field.
Signed-off-by: Lars-Peter Clausen <lars(a)metafoo.de>
---
sound/soc/codecs/da732x.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/sound/soc/codecs/da732x.c b/sound/soc/codecs/da732x.c
index f295b65..54db45d 100644
--- a/sound/soc/codecs/da732x.c
+++ b/sound/soc/codecs/da732x.c
@@ -1487,8 +1487,8 @@ static int da732x_set_bias_level(struct snd_soc_codec *codec,
da732x_hp_dc_offset_cancellation(codec);
- regcache_cache_only(codec->control_data, false);
- regcache_sync(codec->control_data);
+ regcache_cache_only(da732x->regmap, false);
+ regcache_sync(da732x->regmap);
} else {
snd_soc_update_bits(codec, DA732X_REG_BIAS_EN,
DA732X_BIAS_BOOST_MASK,
@@ -1499,7 +1499,7 @@ static int da732x_set_bias_level(struct snd_soc_codec *codec,
}
break;
case SND_SOC_BIAS_OFF:
- regcache_cache_only(codec->control_data, true);
+ regcache_cache_only(da732x->regmap, true);
da732x_set_charge_pump(codec, DA732X_DISABLE_CP);
snd_soc_update_bits(codec, DA732X_REG_BIAS_EN, DA732X_BIAS_EN,
DA732X_BIAS_DIS);
--
1.8.0
3
2
This patch adds support for the Cirrus Logic CS42888 Audio CODEC that
has four 24-bit A/D and eight 24-bit D/A converters.
[ CS42888 supports both I2C and SPI control ports. As initial patch,
this patch only adds the support for I2C. ]
Signed-off-by: Nicolin Chen <Guangyu.Chen(a)freescale.com>
---
.../devicetree/bindings/sound/cs42888.txt | 27 +
sound/soc/codecs/Kconfig | 5 +
sound/soc/codecs/Makefile | 2 +
sound/soc/codecs/cs42888.c | 552 +++++++++++++++++++++
sound/soc/codecs/cs42888.h | 209 ++++++++
5 files changed, 795 insertions(+)
create mode 100644 Documentation/devicetree/bindings/sound/cs42888.txt
create mode 100644 sound/soc/codecs/cs42888.c
create mode 100644 sound/soc/codecs/cs42888.h
diff --git a/Documentation/devicetree/bindings/sound/cs42888.txt b/Documentation/devicetree/bindings/sound/cs42888.txt
new file mode 100644
index 0000000..7736527
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/cs42888.txt
@@ -0,0 +1,27 @@
+CS42888 audio CODEC
+
+Required properties:
+
+ - compatible : "cirrus,cs42888"
+
+ - reg : the I2C address of the device for I2C
+
+ - clocks : phandle to the clock source for MCLK
+
+ - clock-names : must contain "mclk".
+
+ - VA-supply, VD-supply, VLS-supply, VLC-supply: power supplies for the device,
+ as covered in Documentation/devicetree/bindings/regulator/regulator.txt
+
+Example:
+
+codec: cs42888@48 {
+ compatible = "cirrus,cs42888";
+ reg = <0x48>;
+ clocks = <&codec_mclk 0>;
+ clock-names = "mclk";
+ VA-supply = <®_audio>;
+ VD-supply = <®_audio>;
+ VLS-supply = <®_audio>;
+ VLC-supply = <®_audio>;
+};
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index f2383eb..3211488 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -44,6 +44,7 @@ config SND_SOC_ALL_CODECS
select SND_SOC_CS42L73 if I2C
select SND_SOC_CS4270 if I2C
select SND_SOC_CS4271 if SND_SOC_I2C_AND_SPI
+ select SND_SOC_CS42888 if I2C
select SND_SOC_CX20442 if TTY
select SND_SOC_DA7210 if I2C
select SND_SOC_DA7213 if I2C
@@ -300,6 +301,10 @@ config SND_SOC_CS4271
tristate "Cirrus Logic CS4271 CODEC"
depends on SND_SOC_I2C_AND_SPI
+config SND_SOC_CS42888
+ tristate "Cirrus Logic CS42888 CODEC"
+ depends on I2C
+
config SND_SOC_CX20442
tristate
depends on TTY
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index 6af7a55..75fe757 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -30,6 +30,7 @@ snd-soc-cs42l52-objs := cs42l52.o
snd-soc-cs42l73-objs := cs42l73.o
snd-soc-cs4270-objs := cs4270.o
snd-soc-cs4271-objs := cs4271.o
+snd-soc-cs42888-objs := cs42888.o
snd-soc-cx20442-objs := cx20442.o
snd-soc-da7210-objs := da7210.o
snd-soc-da7213-objs := da7213.o
@@ -173,6 +174,7 @@ obj-$(CONFIG_SND_SOC_CS42L52) += snd-soc-cs42l52.o
obj-$(CONFIG_SND_SOC_CS42L73) += snd-soc-cs42l73.o
obj-$(CONFIG_SND_SOC_CS4270) += snd-soc-cs4270.o
obj-$(CONFIG_SND_SOC_CS4271) += snd-soc-cs4271.o
+obj-$(CONFIG_SND_SOC_CS42888) += snd-soc-cs42888.o
obj-$(CONFIG_SND_SOC_CX20442) += snd-soc-cx20442.o
obj-$(CONFIG_SND_SOC_DA7210) += snd-soc-da7210.o
obj-$(CONFIG_SND_SOC_DA7213) += snd-soc-da7213.o
diff --git a/sound/soc/codecs/cs42888.c b/sound/soc/codecs/cs42888.c
new file mode 100644
index 0000000..8561a25
--- /dev/null
+++ b/sound/soc/codecs/cs42888.c
@@ -0,0 +1,552 @@
+/*
+ * Cirrus Logic CS42888 Audio CODEC Digital Audio Interface (DAI) driver
+ *
+ * Copyright (C) 2014 Freescale Semiconductor, Inc.
+ *
+ * Author: Nicolin Chen <Guangyu.Chen(a)freescale.com>
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/pm_runtime.h>
+#include <linux/regulator/consumer.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/tlv.h>
+
+#include "cs42888.h"
+
+#define CS42888_NUM_SUPPLIES 4
+static const char *cs42888_supply_names[CS42888_NUM_SUPPLIES] = {
+ "VA",
+ "VD",
+ "VLS",
+ "VLC",
+};
+
+#define CS42888_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S20_3LE | \
+ SNDRV_PCM_FMTBIT_S24_LE | \
+ SNDRV_PCM_FMTBIT_S32_LE)
+
+/* codec private data */
+struct cs42888_priv {
+ struct regulator_bulk_data supplies[CS42888_NUM_SUPPLIES];
+ struct regmap *regmap;
+ struct clk *clk;
+
+ bool slave_mode;
+ unsigned long sysclk;
+};
+
+/* -127.5dB to 0dB with step of 0.5dB */
+static const DECLARE_TLV_DB_SCALE(dac_tlv, -12750, 50, 1);
+/* -64dB to 24dB with step of 0.5dB */
+static const DECLARE_TLV_DB_SCALE(adc_tlv, -6400, 50, 0);
+
+static const char *cs42888_adc_single[] = { "Differential", "Single-Ended" };
+static const char *cs42888_szc[] = { "Immediate Change", "Zero Cross",
+ "Soft Ramp", "Soft Ramp on Zero Cross" };
+
+static const struct soc_enum cs42888_enum[] = {
+ SOC_ENUM_SINGLE(CS42888_ADCCTL, 4, 2, cs42888_adc_single),
+ SOC_ENUM_SINGLE(CS42888_ADCCTL, 3, 2, cs42888_adc_single),
+ SOC_ENUM_SINGLE(CS42888_TXCTL, 5, 4, cs42888_szc),
+ SOC_ENUM_SINGLE(CS42888_TXCTL, 0, 4, cs42888_szc),
+};
+
+static const struct snd_kcontrol_new cs42888_snd_controls[] = {
+ SOC_DOUBLE_R_TLV("DAC1 Playback Volume", CS42888_VOLAOUT1,
+ CS42888_VOLAOUT2, 0, 0xff, 1, dac_tlv),
+ SOC_DOUBLE_R_TLV("DAC2 Playback Volume", CS42888_VOLAOUT3,
+ CS42888_VOLAOUT4, 0, 0xff, 1, dac_tlv),
+ SOC_DOUBLE_R_TLV("DAC3 Playback Volume", CS42888_VOLAOUT5,
+ CS42888_VOLAOUT6, 0, 0xff, 1, dac_tlv),
+ SOC_DOUBLE_R_TLV("DAC4 Playback Volume", CS42888_VOLAOUT7,
+ CS42888_VOLAOUT8, 0, 0xff, 1, dac_tlv),
+ SOC_DOUBLE_R_S_TLV("ADC1 Capture Volume", CS42888_VOLAIN1,
+ CS42888_VOLAIN2, 0, -0x80, 0x30, 7, 0, adc_tlv),
+ SOC_DOUBLE_R_S_TLV("ADC2 Capture Volume", CS42888_VOLAIN3,
+ CS42888_VOLAIN4, 0, -0x80, 0x30, 7, 0, adc_tlv),
+ SOC_DOUBLE("DAC1 Invert Switch", CS42888_DACINV, 0, 1, 1, 0),
+ SOC_DOUBLE("DAC2 Invert Switch", CS42888_DACINV, 2, 3, 1, 0),
+ SOC_DOUBLE("DAC3 Invert Switch", CS42888_DACINV, 4, 5, 1, 0),
+ SOC_DOUBLE("DAC4 Invert Switch", CS42888_DACINV, 6, 7, 1, 0),
+ SOC_DOUBLE("ADC1 Invert Switch", CS42888_ADCINV, 0, 1, 1, 0),
+ SOC_DOUBLE("ADC2 Invert Switch", CS42888_ADCINV, 2, 3, 1, 0),
+ SOC_SINGLE("ADC High-Pass Filter Switch", CS42888_ADCCTL, 7, 1, 1),
+ SOC_SINGLE("DAC De-emphasis Switch", CS42888_ADCCTL, 5, 1, 0),
+ SOC_ENUM("ADC1 Single Ended Mode Switch", cs42888_enum[0]),
+ SOC_ENUM("ADC2 Single Ended Mode Switch", cs42888_enum[1]),
+ SOC_SINGLE("DAC Single Volume Control Switch", CS42888_TXCTL, 7, 1, 0),
+ SOC_ENUM("DAC Soft Ramp & Zero Cross Control Switch", cs42888_enum[2]),
+ SOC_SINGLE("DAC Auto Mute Switch", CS42888_TXCTL, 4, 1, 0),
+ SOC_SINGLE("Mute ADC Serial Port Switch", CS42888_TXCTL, 3, 1, 0),
+ SOC_SINGLE("ADC Single Volume Control Switch", CS42888_TXCTL, 2, 1, 0),
+ SOC_ENUM("ADC Soft Ramp & Zero Cross Control Switch", cs42888_enum[3]),
+};
+
+static const struct snd_soc_dapm_widget cs42888_dapm_widgets[] = {
+ SND_SOC_DAPM_DAC("DAC1", "Playback", CS42888_PWRCTL, 1, 1),
+ SND_SOC_DAPM_DAC("DAC2", "Playback", CS42888_PWRCTL, 2, 1),
+ SND_SOC_DAPM_DAC("DAC3", "Playback", CS42888_PWRCTL, 3, 1),
+ SND_SOC_DAPM_DAC("DAC4", "Playback", CS42888_PWRCTL, 4, 1),
+
+ SND_SOC_DAPM_OUTPUT("AOUT1L"),
+ SND_SOC_DAPM_OUTPUT("AOUT1R"),
+ SND_SOC_DAPM_OUTPUT("AOUT2L"),
+ SND_SOC_DAPM_OUTPUT("AOUT2R"),
+ SND_SOC_DAPM_OUTPUT("AOUT3L"),
+ SND_SOC_DAPM_OUTPUT("AOUT3R"),
+ SND_SOC_DAPM_OUTPUT("AOUT4L"),
+ SND_SOC_DAPM_OUTPUT("AOUT4R"),
+
+ SND_SOC_DAPM_ADC("ADC1", "Capture", CS42888_PWRCTL, 5, 1),
+ SND_SOC_DAPM_ADC("ADC2", "Capture", CS42888_PWRCTL, 6, 1),
+
+ SND_SOC_DAPM_INPUT("AIN1L"),
+ SND_SOC_DAPM_INPUT("AIN1R"),
+ SND_SOC_DAPM_INPUT("AIN2L"),
+ SND_SOC_DAPM_INPUT("AIN2R"),
+
+ SND_SOC_DAPM_SUPPLY("PWR", CS42888_PWRCTL, 0, 1, NULL, 0),
+};
+
+static const struct snd_soc_dapm_route cs42888_dapm_routes[] = {
+ /* Playback */
+ { "AOUT1L", NULL, "DAC1" },
+ { "AOUT1R", NULL, "DAC1" },
+ { "DAC1", NULL, "PWR" },
+
+ { "AOUT2L", NULL, "DAC2" },
+ { "AOUT2R", NULL, "DAC2" },
+ { "DAC2", NULL, "PWR" },
+
+ { "AOUT3L", NULL, "DAC3" },
+ { "AOUT3R", NULL, "DAC3" },
+ { "DAC3", NULL, "PWR" },
+
+ { "AOUT4L", NULL, "DAC4" },
+ { "AOUT4R", NULL, "DAC4" },
+ { "DAC4", NULL, "PWR" },
+
+ /* Capture */
+ { "ADC1", NULL, "AIN1L" },
+ { "ADC1", NULL, "AIN1R" },
+ { "ADC1", NULL, "PWR" },
+
+ { "ADC2", NULL, "AIN2L" },
+ { "ADC2", NULL, "AIN2R" },
+ { "ADC2", NULL, "PWR" },
+};
+
+struct cs42888_ratios {
+ unsigned int ratio;
+ unsigned char speed;
+ unsigned char mclk;
+};
+
+static struct cs42888_ratios cs42888_ratios[] = {
+ { 64, CS42888_FM_QUAD, CS42888_FUNCMOD_MFREQ_256(4) },
+ { 96, CS42888_FM_QUAD, CS42888_FUNCMOD_MFREQ_384(4) },
+ { 128, CS42888_FM_QUAD, CS42888_FUNCMOD_MFREQ_512(4) },
+ { 192, CS42888_FM_QUAD, CS42888_FUNCMOD_MFREQ_768(4) },
+ { 256, CS42888_FM_SINGLE, CS42888_FUNCMOD_MFREQ_256(1) },
+ { 384, CS42888_FM_SINGLE, CS42888_FUNCMOD_MFREQ_384(1) },
+ { 512, CS42888_FM_SINGLE, CS42888_FUNCMOD_MFREQ_512(1) },
+ { 768, CS42888_FM_SINGLE, CS42888_FUNCMOD_MFREQ_768(1) },
+ { 1024, CS42888_FM_SINGLE, CS42888_FUNCMOD_MFREQ_1024(1) }
+};
+
+static int cs42888_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct cs42888_priv *cs42888 = snd_soc_codec_get_drvdata(codec);
+
+ cs42888->sysclk = freq;
+
+ return 0;
+}
+
+static int cs42888_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int format)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct cs42888_priv *cs42888 = snd_soc_codec_get_drvdata(codec);
+ u32 val;
+
+ /* Set DAI format */
+ switch (format & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_LEFT_J:
+ val = CS42888_INTF_DAC_DIF_LEFTJ | CS42888_INTF_ADC_DIF_LEFTJ;
+ break;
+ case SND_SOC_DAIFMT_I2S:
+ val = CS42888_INTF_DAC_DIF_I2S | CS42888_INTF_ADC_DIF_I2S;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ val = CS42888_INTF_DAC_DIF_RIGHTJ | CS42888_INTF_ADC_DIF_RIGHTJ;
+ break;
+ default:
+ dev_err(codec->dev, "unsupported dai format\n");
+ return -EINVAL;
+ }
+
+ regmap_update_bits(cs42888->regmap, CS42888_INTF,
+ CS42888_INTF_DAC_DIF_MASK |
+ CS42888_INTF_ADC_DIF_MASK, val);
+
+ /* Set master/slave audio interface */
+ switch (format & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ cs42888->slave_mode = true;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ cs42888->slave_mode = false;
+ break;
+ default:
+ dev_err(codec->dev, "unsupported master/slave mode\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int cs42888_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_codec *codec = rtd->codec;
+ struct cs42888_priv *cs42888 = snd_soc_codec_get_drvdata(codec);
+ bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+ u32 ratio = cs42888->sysclk / params_rate(params);
+ u32 i, fm, val, mask;
+
+ for (i = 0; i < ARRAY_SIZE(cs42888_ratios); i++) {
+ if (cs42888_ratios[i].ratio == ratio)
+ break;
+ }
+
+ if (i == ARRAY_SIZE(cs42888_ratios)) {
+ dev_err(codec->dev, "unsupported sysclk ratio\n");
+ return -EINVAL;
+ }
+
+ mask = CS42888_FUNCMOD_MFREQ_MASK;
+ val = cs42888_ratios[i].mclk;
+
+ fm = cs42888->slave_mode ? CS42888_FM_AUTO : cs42888_ratios[i].speed;
+
+ regmap_update_bits(cs42888->regmap, CS42888_FUNCMOD,
+ CS42888_FUNCMOD_xC_FM_MASK(tx) | mask,
+ CS42888_FUNCMOD_xC_FM(tx, fm) | val);
+
+ return 0;
+}
+
+static int cs42888_digital_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct cs42888_priv *cs42888 = snd_soc_codec_get_drvdata(codec);
+
+ regmap_update_bits(cs42888->regmap, CS42888_DACMUTE,
+ CS42888_DACMUTE_ALL, mute ? CS42888_DACMUTE_ALL : 0);
+
+ return 0;
+}
+
+static struct snd_soc_dai_ops cs42888_dai_ops = {
+ .set_fmt = cs42888_set_dai_fmt,
+ .set_sysclk = cs42888_set_dai_sysclk,
+ .hw_params = cs42888_hw_params,
+ .digital_mute = cs42888_digital_mute,
+};
+
+static struct snd_soc_dai_driver cs42888_dai = {
+ .name = "cs42888",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 8,
+ .rates = SNDRV_PCM_RATE_8000_192000,
+ .formats = CS42888_FORMATS,
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 4,
+ .rates = SNDRV_PCM_RATE_8000_192000,
+ .formats = CS42888_FORMATS,
+ },
+ .ops = &cs42888_dai_ops,
+};
+
+static struct reg_default cs42888_reg[] = {
+ { 0x01, 0x01 }, /* Chip I.D. and Revision Register */
+ { 0x02, 0x00 }, /* Power Control */
+ { 0x03, 0xF0 }, /* Functional Mode */
+ { 0x04, 0x46 }, /* Interface Formats */
+ { 0x05, 0x00 }, /* ADC Control & DAC De-Emphasis */
+ { 0x06, 0x10 }, /* Transition Control */
+ { 0x07, 0x00 }, /* DAC Channel Mute */
+ { 0x08, 0x00 }, /* Volume Control AOUT1 */
+ { 0x09, 0x00 }, /* Volume Control AOUT2 */
+ { 0x0a, 0x00 }, /* Volume Control AOUT3 */
+ { 0x0b, 0x00 }, /* Volume Control AOUT4 */
+ { 0x0c, 0x00 }, /* Volume Control AOUT5 */
+ { 0x0d, 0x00 }, /* Volume Control AOUT6 */
+ { 0x0e, 0x00 }, /* Volume Control AOUT7 */
+ { 0x0f, 0x00 }, /* Volume Control AOUT8 */
+ { 0x10, 0x00 }, /* DAC Channel Invert */
+ { 0x11, 0x00 }, /* Volume Control AIN1 */
+ { 0x12, 0x00 }, /* Volume Control AIN2 */
+ { 0x13, 0x00 }, /* Volume Control AIN3 */
+ { 0x14, 0x00 }, /* Volume Control AIN4 */
+ { 0x17, 0x00 }, /* ADC Channel Invert */
+ { 0x18, 0x00 }, /* Status Control */
+ { 0x1a, 0x00 }, /* Status Mask */
+ { 0x1b, 0x00 }, /* MUTEC Pin Control */
+};
+
+static bool cs42888_volatile_register(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case CS42888_STATUS:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool cs42888_writeable_register(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case CS42888_CHIPID:
+ case CS42888_STATUS:
+ return false;
+ default:
+ return true;
+ }
+}
+
+static const struct regmap_config cs42888_regmap = {
+ .reg_bits = 8,
+ .val_bits = 8,
+
+ .max_register = CS42888_LASTREG,
+ .reg_defaults = cs42888_reg,
+ .num_reg_defaults = ARRAY_SIZE(cs42888_reg),
+ .volatile_reg = cs42888_volatile_register,
+ .writeable_reg = cs42888_writeable_register,
+ .cache_type = REGCACHE_RBTREE,
+};
+
+static int cs42888_probe(struct snd_soc_codec *codec)
+{
+ struct cs42888_priv *cs42888 = snd_soc_codec_get_drvdata(codec);
+
+ /* Disable auto-mute */
+ regmap_update_bits(cs42888->regmap, CS42888_TXCTL,
+ CS42888_TXCTL_AMUTE | CS42888_TXCTL_DAC_SZC_MASK,
+ CS42888_TXCTL_DAC_SZC_SR);
+
+ /* Mute all DAC channels */
+ regmap_write(cs42888->regmap, CS42888_DACMUTE, CS42888_DACMUTE_ALL);
+
+ return 0;
+}
+
+static struct snd_soc_codec_driver cs42888_driver = {
+ .probe = cs42888_probe,
+ .idle_bias_off = true,
+
+ .controls = cs42888_snd_controls,
+ .num_controls = ARRAY_SIZE(cs42888_snd_controls),
+ .dapm_widgets = cs42888_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(cs42888_dapm_widgets),
+ .dapm_routes = cs42888_dapm_routes,
+ .num_dapm_routes = ARRAY_SIZE(cs42888_dapm_routes),
+};
+
+static int cs42888_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct cs42888_priv *cs42888;
+ int ret, val, i;
+
+ cs42888 = devm_kzalloc(&i2c->dev, sizeof(*cs42888), GFP_KERNEL);
+ if (cs42888 == NULL)
+ return -ENOMEM;
+
+ i2c_set_clientdata(i2c, cs42888);
+
+ cs42888->clk = devm_clk_get(&i2c->dev, "mclk");
+ if (IS_ERR(cs42888->clk))
+ dev_warn(&i2c->dev, "failed to get the clock: %ld\n",
+ PTR_ERR(cs42888->clk));
+ else
+ cs42888->sysclk = clk_get_rate(cs42888->clk);
+
+ for (i = 0; i < ARRAY_SIZE(cs42888->supplies); i++)
+ cs42888->supplies[i].supply = cs42888_supply_names[i];
+
+ ret = devm_regulator_bulk_get(&i2c->dev,
+ ARRAY_SIZE(cs42888->supplies), cs42888->supplies);
+ if (ret) {
+ dev_err(&i2c->dev, "failed to request supplies: %d\n", ret);
+ return ret;
+ }
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(cs42888->supplies),
+ cs42888->supplies);
+ if (ret) {
+ dev_err(&i2c->dev, "failed to enable supplies: %d\n", ret);
+ return ret;
+ }
+
+ /* Make sure hardware reset done */
+ msleep(5);
+
+ cs42888->regmap = devm_regmap_init_i2c(i2c, &cs42888_regmap);
+ if (IS_ERR(cs42888->regmap)) {
+ ret = PTR_ERR(cs42888->regmap);
+ dev_err(&i2c->dev, "failed to allocate regmap: %d\n", ret);
+ goto err_enable;
+ }
+
+ /*
+ * We haven't marked the chip revision as volatile due to
+ * sharing a register with the right input volume; explicitly
+ * bypass the cache to read it.
+ */
+ regcache_cache_bypass(cs42888->regmap, true);
+
+ /* Validate the chip ID */
+ regmap_read(cs42888->regmap, CS42888_CHIPID, &val);
+ if (val < 0) {
+ dev_err(&i2c->dev, "failed to get device ID: %x", val);
+ ret = -EINVAL;
+ goto err_enable;
+ }
+
+ /* The top four bits of the chip ID should be 0000 */
+ if ((val & CS42888_CHIPID_CHIP_ID_MASK) != 0x00) {
+ dev_err(&i2c->dev, "unmatched chip ID: %d\n",
+ val & CS42888_CHIPID_CHIP_ID_MASK);
+ ret = -EINVAL;
+ goto err_enable;
+ }
+
+ dev_info(&i2c->dev, "found device at %X, revision %X\n",
+ i2c->addr, val & CS42888_CHIPID_REV_ID_MASK);
+
+ regcache_cache_bypass(cs42888->regmap, false);
+
+ pm_runtime_enable(&i2c->dev);
+ pm_request_idle(&i2c->dev);
+
+ ret = snd_soc_register_codec(&i2c->dev, &cs42888_driver, &cs42888_dai, 1);
+ if (ret) {
+ dev_err(&i2c->dev, "failed to register codec:%d\n", ret);
+ goto err_enable;
+ }
+
+ regcache_cache_only(cs42888->regmap, true);
+
+err_enable:
+ regulator_bulk_disable(ARRAY_SIZE(cs42888->supplies), cs42888->supplies);
+
+ return ret;
+}
+
+static int cs42888_i2c_remove(struct i2c_client *i2c_client)
+{
+ snd_soc_unregister_codec(&i2c_client->dev);
+ return 0;
+}
+
+#ifdef CONFIG_PM_RUNTIME
+static int cs42888_runtime_resume(struct device *dev)
+{
+ struct cs42888_priv *cs42888 = dev_get_drvdata(dev);
+ int ret;
+
+ if (!IS_ERR(cs42888->clk))
+ clk_prepare_enable(cs42888->clk);
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(cs42888->supplies),
+ cs42888->supplies);
+ if (ret) {
+ dev_err(dev, "failed to enable supplies: %d\n", ret);
+ return ret;
+ }
+
+ /*
+ * In case the device was put to hard reset during sleep,
+ * we need to wait 500ns here before any I2C communication
+ */
+ mdelay(5);
+
+ regcache_cache_only(cs42888->regmap, false);
+
+ regcache_sync(cs42888->regmap);
+
+ return 0;
+}
+
+static int cs42888_runtime_suspend(struct device *dev)
+{
+ struct cs42888_priv *cs42888 = dev_get_drvdata(dev);
+
+ regcache_cache_only(cs42888->regmap, true);
+
+ regulator_bulk_disable(ARRAY_SIZE(cs42888->supplies),
+ cs42888->supplies);
+
+ if (!IS_ERR(cs42888->clk))
+ clk_disable_unprepare(cs42888->clk);
+
+ return 0;
+}
+#endif
+
+static const struct dev_pm_ops cs42888_pm = {
+ SET_RUNTIME_PM_OPS(cs42888_runtime_suspend, cs42888_runtime_resume, NULL)
+};
+
+static struct i2c_device_id cs42888_i2c_id[] = {
+ {"cs42888", 0},
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, cs42888_i2c_id);
+
+static const struct of_device_id cs42888_of_match[] = {
+ { .compatible = "cirrus,cs42888", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, cs42888_of_match);
+
+static struct i2c_driver cs42888_i2c_driver = {
+ .driver = {
+ .name = "cs42888",
+ .owner = THIS_MODULE,
+ .of_match_table = cs42888_of_match,
+ .pm = &cs42888_pm,
+ },
+ .probe = cs42888_i2c_probe,
+ .remove = cs42888_i2c_remove,
+ .id_table = cs42888_i2c_id,
+};
+
+module_i2c_driver(cs42888_i2c_driver);
+
+MODULE_DESCRIPTION("Cirrus Logic CS42888 ALSA SoC Codec Driver");
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/cs42888.h b/sound/soc/codecs/cs42888.h
new file mode 100644
index 0000000..6d2cdec
--- /dev/null
+++ b/sound/soc/codecs/cs42888.h
@@ -0,0 +1,209 @@
+/*
+ * cs42888.h - Cirrus Logic CS42888 Audio CODEC driver header file
+ *
+ * Copyright (C) 2014 Freescale Semiconductor, Inc.
+ *
+ * Author: Nicolin Chen <Guangyu.Chen(a)freescale.com>
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+
+#ifndef _CS42888_H
+#define _CS42888_H
+
+/* CS42888 register map */
+#define CS42888_CHIPID 0x01 /* Chip ID */
+#define CS42888_PWRCTL 0x02 /* Power Control */
+#define CS42888_FUNCMOD 0x03 /* Functional Mode */
+#define CS42888_INTF 0x04 /* Interface Formats */
+#define CS42888_ADCCTL 0x05 /* ADC Control */
+#define CS42888_TXCTL 0x06 /* Transition Control */
+#define CS42888_DACMUTE 0x07 /* DAC Mute Control */
+#define CS42888_VOLAOUT1 0x08 /* Volume Control AOUT1 */
+#define CS42888_VOLAOUT2 0x09 /* Volume Control AOUT2 */
+#define CS42888_VOLAOUT3 0x0A /* Volume Control AOUT3 */
+#define CS42888_VOLAOUT4 0x0B /* Volume Control AOUT4 */
+#define CS42888_VOLAOUT5 0x0C /* Volume Control AOUT5 */
+#define CS42888_VOLAOUT6 0x0D /* Volume Control AOUT6 */
+#define CS42888_VOLAOUT7 0x0E /* Volume Control AOUT7 */
+#define CS42888_VOLAOUT8 0x0F /* Volume Control AOUT8 */
+#define CS42888_DACINV 0x10 /* DAC Channel Invert */
+#define CS42888_VOLAIN1 0x11 /* Volume Control AIN1 */
+#define CS42888_VOLAIN2 0x12 /* Volume Control AIN2 */
+#define CS42888_VOLAIN3 0x13 /* Volume Control AIN3 */
+#define CS42888_VOLAIN4 0x14 /* Volume Control AIN4 */
+#define CS42888_ADCINV 0x17 /* ADC Channel Invert */
+#define CS42888_STATUSCTL 0x18 /* Status Control */
+#define CS42888_STATUS 0x19 /* Status */
+#define CS42888_STATUSM 0x1A /* Status Mask */
+#define CS42888_MUTEC 0x1B /* MUTEC Pin Control */
+
+#define CS42888_FIRSTREG CS42888_CHIPID
+#define CS42888_LASTREG CS42888_MUTEC
+#define CS42888_NUMREGS (CS42888_LASTREG - CS42888_FIRSTREG + 1)
+#define CS42888_I2C_INCR 0x80
+
+/* Chip I.D. and Revision Register (Address 01h) */
+#define CS42888_CHIPID_CHIP_ID_MASK 0xF0
+#define CS42888_CHIPID_REV_ID_MASK 0x0F
+
+/* Power Control (Address 02h) */
+#define CS42888_PWRCTL_PDN_ADC2_SHIFT 6
+#define CS42888_PWRCTL_PDN_ADC2_MASK (1 << CS42888_PWRCTL_PDN_ADC2_SHIFT)
+#define CS42888_PWRCTL_PDN_ADC2 (1 << CS42888_PWRCTL_PDN_ADC2_SHIFT)
+#define CS42888_PWRCTL_PDN_ADC1_SHIFT 5
+#define CS42888_PWRCTL_PDN_ADC1_MASK (1 << CS42888_PWRCTL_PDN_ADC1_SHIFT)
+#define CS42888_PWRCTL_PDN_ADC1 (1 << CS42888_PWRCTL_PDN_ADC1_SHIFT)
+#define CS42888_PWRCTL_PDN_DAC4_SHIFT 4
+#define CS42888_PWRCTL_PDN_DAC4_MASK (1 << CS42888_PWRCTL_PDN_DAC4_SHIFT)
+#define CS42888_PWRCTL_PDN_DAC4 (1 << CS42888_PWRCTL_PDN_DAC4_SHIFT)
+#define CS42888_PWRCTL_PDN_DAC3_SHIFT 3
+#define CS42888_PWRCTL_PDN_DAC3_MASK (1 << CS42888_PWRCTL_PDN_DAC3_SHIFT)
+#define CS42888_PWRCTL_PDN_DAC3 (1 << CS42888_PWRCTL_PDN_DAC3_SHIFT)
+#define CS42888_PWRCTL_PDN_DAC2_SHIFT 2
+#define CS42888_PWRCTL_PDN_DAC2_MASK (1 << CS42888_PWRCTL_PDN_DAC2_SHIFT)
+#define CS42888_PWRCTL_PDN_DAC2 (1 << CS42888_PWRCTL_PDN_DAC2_SHIFT)
+#define CS42888_PWRCTL_PDN_DAC1_SHIFT 1
+#define CS42888_PWRCTL_PDN_DAC1_MASK (1 << CS42888_PWRCTL_PDN_DAC1_SHIFT)
+#define CS42888_PWRCTL_PDN_DAC1 (1 << CS42888_PWRCTL_PDN_DAC1_SHIFT)
+#define CS42888_PWRCTL_PDN_SHIFT 0
+#define CS42888_PWRCTL_PDN_MASK (1 << CS42888_PWRCTL_PDN_SHIFT)
+#define CS42888_PWRCTL_PDN (1 << CS42888_PWRCTL_PDN_SHIFT)
+
+/* Functional Mode (Address 03h) */
+#define CS42888_FUNCMOD_DAC_FM_SHIFT 6
+#define CS42888_FUNCMOD_DAC_FM_WIDTH 2
+#define CS42888_FUNCMOD_DAC_FM_MASK (((1 << CS42888_FUNCMOD_DAC_FM_WIDTH) - 1) << CS42888_FUNCMOD_DAC_FM_SHIFT)
+#define CS42888_FUNCMOD_DAC_FM(v) ((v) << CS42888_FUNCMOD_DAC_FM_SHIFT)
+#define CS42888_FUNCMOD_ADC_FM_SHIFT 4
+#define CS42888_FUNCMOD_ADC_FM_WIDTH 2
+#define CS42888_FUNCMOD_ADC_FM_MASK (((1 << CS42888_FUNCMOD_ADC_FM_WIDTH) - 1) << CS42888_FUNCMOD_ADC_FM_SHIFT)
+#define CS42888_FUNCMOD_ADC_FM(v) ((v) << CS42888_FUNCMOD_ADC_FM_SHIFT)
+#define CS42888_FUNCMOD_xC_FM_MASK(x) ((x) ? CS42888_FUNCMOD_DAC_FM_MASK : CS42888_FUNCMOD_ADC_FM_MASK)
+#define CS42888_FUNCMOD_xC_FM(x, v) ((x) ? CS42888_FUNCMOD_DAC_FM(v) : CS42888_FUNCMOD_ADC_FM(v))
+#define CS42888_FUNCMOD_MFREQ_SHIFT 1
+#define CS42888_FUNCMOD_MFREQ_WIDTH 3
+#define CS42888_FUNCMOD_MFREQ_MASK (((1 << CS42888_FUNCMOD_MFREQ_WIDTH) - 1) << CS42888_FUNCMOD_MFREQ_SHIFT)
+#define CS42888_FUNCMOD_MFREQ_256(s) ((0 << CS42888_FUNCMOD_MFREQ_SHIFT) >> (s >> 1))
+#define CS42888_FUNCMOD_MFREQ_384(s) ((1 << CS42888_FUNCMOD_MFREQ_SHIFT) >> (s >> 1))
+#define CS42888_FUNCMOD_MFREQ_512(s) ((2 << CS42888_FUNCMOD_MFREQ_SHIFT) >> (s >> 1))
+#define CS42888_FUNCMOD_MFREQ_768(s) ((3 << CS42888_FUNCMOD_MFREQ_SHIFT) >> (s >> 1))
+#define CS42888_FUNCMOD_MFREQ_1024(s) ((4 << CS42888_FUNCMOD_MFREQ_SHIFT) >> (s >> 1))
+
+#define CS42888_FM_SINGLE 0
+#define CS42888_FM_DOUBLE 1
+#define CS42888_FM_QUAD 2
+#define CS42888_FM_AUTO 3
+
+/* Interface Formats (Address 04h) */
+#define CS42888_INTF_FREEZE_SHIFT 7
+#define CS42888_INTF_FREEZE_MASK (1 << CS42888_INTF_FREEZE_SHIFT)
+#define CS42888_INTF_FREEZE (1 << CS42888_INTF_FREEZE_SHIFT)
+#define CS42888_INTF_AUX_DIF_SHIFT 6
+#define CS42888_INTF_AUX_DIF_MASK (1 << CS42888_INTF_AUX_DIF_SHIFT)
+#define CS42888_INTF_AUX_DIF (1 << CS42888_INTF_AUX_DIF_SHIFT)
+#define CS42888_INTF_DAC_DIF_SHIFT 3
+#define CS42888_INTF_DAC_DIF_WIDTH 3
+#define CS42888_INTF_DAC_DIF_MASK (((1 << CS42888_INTF_DAC_DIF_WIDTH) - 1) << CS42888_INTF_DAC_DIF_SHIFT)
+#define CS42888_INTF_DAC_DIF_LEFTJ (0 << CS42888_INTF_DAC_DIF_SHIFT)
+#define CS42888_INTF_DAC_DIF_I2S (1 << CS42888_INTF_DAC_DIF_SHIFT)
+#define CS42888_INTF_DAC_DIF_RIGHTJ (2 << CS42888_INTF_DAC_DIF_SHIFT)
+#define CS42888_INTF_DAC_DIF_RIGHTJ_16 (3 << CS42888_INTF_DAC_DIF_SHIFT)
+#define CS42888_INTF_DAC_DIF_ONELINE_20 (4 << CS42888_INTF_DAC_DIF_SHIFT)
+#define CS42888_INTF_DAC_DIF_ONELINE_24 (6 << CS42888_INTF_DAC_DIF_SHIFT)
+#define CS42888_INTF_DAC_DIF_TDM (7 << CS42888_INTF_DAC_DIF_SHIFT)
+#define CS42888_INTF_ADC_DIF_SHIFT 0
+#define CS42888_INTF_ADC_DIF_WIDTH 3
+#define CS42888_INTF_ADC_DIF_MASK (((1 << CS42888_INTF_ADC_DIF_WIDTH) - 1) << CS42888_INTF_ADC_DIF_SHIFT)
+#define CS42888_INTF_ADC_DIF_LEFTJ (0 << CS42888_INTF_ADC_DIF_SHIFT)
+#define CS42888_INTF_ADC_DIF_I2S (1 << CS42888_INTF_ADC_DIF_SHIFT)
+#define CS42888_INTF_ADC_DIF_RIGHTJ (2 << CS42888_INTF_ADC_DIF_SHIFT)
+#define CS42888_INTF_ADC_DIF_RIGHTJ_16 (3 << CS42888_INTF_ADC_DIF_SHIFT)
+#define CS42888_INTF_ADC_DIF_ONELINE_20 (4 << CS42888_INTF_ADC_DIF_SHIFT)
+#define CS42888_INTF_ADC_DIF_ONELINE_24 (6 << CS42888_INTF_ADC_DIF_SHIFT)
+#define CS42888_INTF_ADC_DIF_TDM (7 << CS42888_INTF_ADC_DIF_SHIFT)
+
+/* ADC Control & DAC De-Emphasis (Address 05h) */
+#define CS42888_ADCCTL_ADC_HPF_FREEZE_SHIFT 7
+#define CS42888_ADCCTL_ADC_HPF_FREEZE_MASK (1 << CS42888_ADCCTL_ADC_HPF_FREEZE_SHIFT)
+#define CS42888_ADCCTL_ADC_HPF_FREEZE (1 << CS42888_ADCCTL_ADC_HPF_FREEZE_SHIFT)
+#define CS42888_ADCCTL_DAC_DEM_SHIFT 5
+#define CS42888_ADCCTL_DAC_DEM_MASK (1 << CS42888_ADCCTL_DAC_DEM_SHIFT)
+#define CS42888_ADCCTL_DAC_DEM (1 << CS42888_ADCCTL_DAC_DEM_SHIFT)
+#define CS42888_ADCCTL_ADC1_SINGLE_SHIFT 4
+#define CS42888_ADCCTL_ADC1_SINGLE_MASK (1 << CS42888_ADCCTL_ADC1_SINGLE_SHIFT)
+#define CS42888_ADCCTL_ADC1_SINGLE (1 << CS42888_ADCCTL_ADC1_SINGLE_SHIFT)
+#define CS42888_ADCCTL_ADC2_SINGLE_SHIFT 3
+#define CS42888_ADCCTL_ADC2_SINGLE_MASK (1 << CS42888_ADCCTL_ADC2_SINGLE_SHIFT)
+#define CS42888_ADCCTL_ADC2_SINGLE (1 << CS42888_ADCCTL_ADC2_SINGLE_SHIFT)
+
+/* Transition Control (Address 06h) */
+#define CS42888_TXCTL_DAC_SNGVOL_SHIFT 7
+#define CS42888_TXCTL_DAC_SNGVOL_MASK (1 << CS42888_TXCTL_DAC_SNGVOL_SHIFT)
+#define CS42888_TXCTL_DAC_SNGVOL (1 << CS42888_TXCTL_DAC_SNGVOL_SHIFT)
+#define CS42888_TXCTL_DAC_SZC_SHIFT 5
+#define CS42888_TXCTL_DAC_SZC_WIDTH 2
+#define CS42888_TXCTL_DAC_SZC_MASK (((1 << CS42888_TXCTL_DAC_SZC_WIDTH) - 1) << CS42888_TXCTL_DAC_SZC_SHIFT)
+#define CS42888_TXCTL_DAC_SZC_IC (0 << CS42888_TXCTL_DAC_SZC_SHIFT)
+#define CS42888_TXCTL_DAC_SZC_ZC (1 << CS42888_TXCTL_DAC_SZC_SHIFT)
+#define CS42888_TXCTL_DAC_SZC_SR (2 << CS42888_TXCTL_DAC_SZC_SHIFT)
+#define CS42888_TXCTL_DAC_SZC_SRZC (3 << CS42888_TXCTL_DAC_SZC_SHIFT)
+#define CS42888_TXCTL_AMUTE_SHIFT 4
+#define CS42888_TXCTL_AMUTE_MASK (1 << CS42888_TXCTL_AMUTE_SHIFT)
+#define CS42888_TXCTL_AMUTE (1 << CS42888_TXCTL_AMUTE_SHIFT)
+#define CS42888_TXCTL_MUTE_ADC_SP_SHIFT 3
+#define CS42888_TXCTL_MUTE_ADC_SP_MASK (1 << CS42888_TXCTL_MUTE_ADC_SP_SHIFT)
+#define CS42888_TXCTL_MUTE_ADC_SP (1 << CS42888_TXCTL_MUTE_ADC_SP_SHIFT)
+#define CS42888_TXCTL_ADC_SNGVOL_SHIFT 2
+#define CS42888_TXCTL_ADC_SNGVOL_MASK (1 << CS42888_TXCTL_ADC_SNGVOL_SHIFT)
+#define CS42888_TXCTL_ADC_SNGVOL (1 << CS42888_TXCTL_ADC_SNGVOL_SHIFT)
+#define CS42888_TXCTL_ADC_SZC_SHIFT 0
+#define CS42888_TXCTL_ADC_SZC_MASK (((1 << CS42888_TXCTL_ADC_SZC_WIDTH) - 1) << CS42888_TXCTL_ADC_SZC_SHIFT)
+#define CS42888_TXCTL_ADC_SZC_IC (0 << CS42888_TXCTL_ADC_SZC_SHIFT)
+#define CS42888_TXCTL_ADC_SZC_ZC (1 << CS42888_TXCTL_ADC_SZC_SHIFT)
+#define CS42888_TXCTL_ADC_SZC_SR (2 << CS42888_TXCTL_ADC_SZC_SHIFT)
+#define CS42888_TXCTL_ADC_SZC_SRZC (3 << CS42888_TXCTL_ADC_SZC_SHIFT)
+
+/* DAC Channel Mute (Address 07h) */
+#define CS42888_DACMUTE_AOUT(n) (0x1 << n)
+#define CS42888_DACMUTE_ALL 0xff
+
+/* Status Control (Address 18h)*/
+#define CS42888_STATUSCTL_INI_SHIFT 2
+#define CS42888_STATUSCTL_INI_WIDTH 2
+#define CS42888_STATUSCTL_INI_MASK (((1 << CS42888_STATUSCTL_INI_WIDTH) - 1) << CS42888_STATUSCTL_INI_SHIFT)
+#define CS42888_STATUSCTL_INT_ACTIVE_HIGH (0 << CS42888_STATUSCTL_INI_SHIFT)
+#define CS42888_STATUSCTL_INT_ACTIVE_LOW (1 << CS42888_STATUSCTL_INI_SHIFT)
+#define CS42888_STATUSCTL_INT_OPEN_DRAIN (2 << CS42888_STATUSCTL_INI_SHIFT)
+
+/* Status (Address 19h)*/
+#define CS42888_STATUS_DAC_CLK_ERR_SHIFT 4
+#define CS42888_STATUS_DAC_CLK_ERR_MASK (1 << CS42888_STATUS_DAC_CLK_ERR_SHIFT)
+#define CS42888_STATUS_ADC_CLK_ERR_SHIFT 3
+#define CS42888_STATUS_ADC_CLK_ERR_MASK (1 << CS42888_STATUS_ADC_CLK_ERR_SHIFT)
+#define CS42888_STATUS_ADC2_OVFL_SHIFT 1
+#define CS42888_STATUS_ADC2_OVFL_MASK (1 << CS42888_STATUS_ADC2_OVFL_SHIFT)
+#define CS42888_STATUS_ADC1_OVFL_SHIFT 0
+#define CS42888_STATUS_ADC1_OVFL_MASK (1 << CS42888_STATUS_ADC1_OVFL_SHIFT)
+
+/* Status Mask (Address 1Ah) */
+#define CS42888_STATUS_DAC_CLK_ERR_M_SHIFT 4
+#define CS42888_STATUS_DAC_CLK_ERR_M_MASK (1 << CS42888_STATUS_DAC_CLK_ERR_M_SHIFT)
+#define CS42888_STATUS_ADC_CLK_ERR_M_SHIFT 3
+#define CS42888_STATUS_ADC_CLK_ERR_M_MASK (1 << CS42888_STATUS_ADC_CLK_ERR_M_SHIFT)
+#define CS42888_STATUS_ADC2_OVFL_M_SHIFT 1
+#define CS42888_STATUS_ADC2_OVFL_M_MASK (1 << CS42888_STATUS_ADC2_OVFL_M_SHIFT)
+#define CS42888_STATUS_ADC1_OVFL_M_SHIFT 0
+#define CS42888_STATUS_ADC1_OVFL_M_MASK (1 << CS42888_STATUS_ADC1_OVFL_M_SHIFT)
+
+/* MUTEC Pin Control (Address 1Bh) */
+#define CS42888_MUTEC_MCPOLARITY_SHIFT 1
+#define CS42888_MUTEC_MCPOLARITY_MASK (1 << CS42888_MUTEC_MCPOLARITY_SHIFT)
+#define CS42888_MUTEC_MCPOLARITY_ACTIVE_LOW (0 << CS42888_MUTEC_MCPOLARITY_SHIFT)
+#define CS42888_MUTEC_MCPOLARITY_ACTIVE_HIGH (1 << CS42888_MUTEC_MCPOLARITY_SHIFT)
+#define CS42888_MUTEC_MUTEC_ACTIVE_SHIFT 0
+#define CS42888_MUTEC_MUTEC_ACTIVE_MASK (1 << CS42888_MUTEC_MUTEC_ACTIVE_SHIFT)
+#define CS42888_MUTEC_MUTEC_ACTIVE (1 << CS42888_MUTEC_MUTEC_ACTIVE_SHIFT)
+#endif /* _CS42888_H */
--
1.8.4
5
19
25 Feb '14
From: nikesh <Nikesh.Oswal(a)wolfsonmicro.com>
dai-link params for codec-codec links were fixed.
The fixed link between codec and another chip which
may be another codec, baseband, bluetooth codec etc
may require run time configuaration changes.
This change provides an optional callback to modify
these params.
Change-Id: Iad6ee3951bc4e8b8bc519c62642a2b4bcd949c18
Signed-off-by: nikesh <Nikesh.Oswal(a)wolfsonmicro.com>
---
include/sound/soc-dapm.h | 7 ++++---
include/sound/soc.h | 4 +++-
sound/soc/soc-core.c | 4 ++--
sound/soc/soc-dapm.c | 21 +++++++++++++++++----
4 files changed, 26 insertions(+), 10 deletions(-)
diff --git a/include/sound/soc-dapm.h b/include/sound/soc-dapm.h
index 68d92e3..e3497ed 100644
--- a/include/sound/soc-dapm.h
+++ b/include/sound/soc-dapm.h
@@ -414,9 +414,10 @@ int snd_soc_dapm_new_dai_widgets(struct snd_soc_dapm_context *dapm,
int snd_soc_dapm_link_dai_widgets(struct snd_soc_card *card);
void snd_soc_dapm_connect_dai_link_widgets(struct snd_soc_card *card);
int snd_soc_dapm_new_pcm(struct snd_soc_card *card,
- const struct snd_soc_pcm_stream *params,
+ struct snd_soc_pcm_stream *params,
struct snd_soc_dapm_widget *source,
- struct snd_soc_dapm_widget *sink);
+ struct snd_soc_dapm_widget *sink,
+ struct snd_soc_dai_link *dai_link);
/* dapm path setup */
int snd_soc_dapm_new_widgets(struct snd_soc_card *card);
@@ -561,7 +562,7 @@ struct snd_soc_dapm_widget {
void *priv; /* widget specific data */
struct regulator *regulator; /* attached regulator */
- const struct snd_soc_pcm_stream *params; /* params for dai links */
+ struct snd_soc_pcm_stream *params; /* params for dai links */
/* dapm control */
int reg; /* negative reg = no direct dapm */
diff --git a/include/sound/soc.h b/include/sound/soc.h
index 9a00147..401c911 100644
--- a/include/sound/soc.h
+++ b/include/sound/soc.h
@@ -875,7 +875,9 @@ struct snd_soc_dai_link {
const struct device_node *platform_of_node;
int be_id; /* optional ID for machine driver BE identification */
- const struct snd_soc_pcm_stream *params;
+ struct snd_soc_pcm_stream *params;
+ /* optional params re-writing for dai links */
+ int (*params_fixup)(struct snd_soc_dapm_widget *w, int event);
unsigned int dai_fmt; /* format to set on init */
diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c
index fe1df50..4f03e88 100644
--- a/sound/soc/soc-core.c
+++ b/sound/soc/soc-core.c
@@ -1469,7 +1469,7 @@ static int soc_probe_link_dais(struct snd_soc_card *card, int num, int order)
capture_w = cpu_dai->capture_widget;
if (play_w && capture_w) {
ret = snd_soc_dapm_new_pcm(card, dai_link->params,
- capture_w, play_w);
+ capture_w, play_w, dai_link);
if (ret != 0) {
dev_err(card->dev, "ASoC: Can't link %s to %s: %d\n",
play_w->name, capture_w->name, ret);
@@ -1481,7 +1481,7 @@ static int soc_probe_link_dais(struct snd_soc_card *card, int num, int order)
capture_w = codec_dai->capture_widget;
if (play_w && capture_w) {
ret = snd_soc_dapm_new_pcm(card, dai_link->params,
- capture_w, play_w);
+ capture_w, play_w, dai_link);
if (ret != 0) {
dev_err(card->dev, "ASoC: Can't link %s to %s: %d\n",
play_w->name, capture_w->name, ret);
diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c
index dc8ff13..dc1ef8b 100644
--- a/sound/soc/soc-dapm.c
+++ b/sound/soc/soc-dapm.c
@@ -3378,11 +3378,12 @@ static int snd_soc_dai_link_event(struct snd_soc_dapm_widget *w,
{
struct snd_soc_dapm_path *source_p, *sink_p;
struct snd_soc_dai *source, *sink;
- const struct snd_soc_pcm_stream *config = w->params;
+ struct snd_soc_pcm_stream *config = w->params;
+ struct snd_soc_dai_link *dai_link = w->priv;
struct snd_pcm_substream substream;
struct snd_pcm_hw_params *params = NULL;
u64 fmt;
- int ret;
+ int ret = 0;
if (WARN_ON(!config) ||
WARN_ON(list_empty(&w->sources) || list_empty(&w->sinks)))
@@ -3402,6 +3403,16 @@ static int snd_soc_dai_link_event(struct snd_soc_dapm_widget *w,
source = source_p->source->priv;
sink = sink_p->sink->priv;
+ if (dai_link && dai_link->params_fixup) {
+ ret = dai_link->params_fixup(w, event);
+ if (ret < 0) {
+ dev_err(w->dapm->dev,
+ "ASoC: params_fixup for dai link widget failed %d\n",
+ ret);
+ goto out;
+ }
+ }
+
/* Be a little careful as we don't want to overflow the mask array */
if (config->formats) {
fmt = ffs(config->formats) - 1;
@@ -3483,9 +3494,10 @@ out:
}
int snd_soc_dapm_new_pcm(struct snd_soc_card *card,
- const struct snd_soc_pcm_stream *params,
+ struct snd_soc_pcm_stream *params,
struct snd_soc_dapm_widget *source,
- struct snd_soc_dapm_widget *sink)
+ struct snd_soc_dapm_widget *sink,
+ struct snd_soc_dai_link *dai_link)
{
struct snd_soc_dapm_route routes[2];
struct snd_soc_dapm_widget template;
@@ -3517,6 +3529,7 @@ int snd_soc_dapm_new_pcm(struct snd_soc_card *card,
}
w->params = params;
+ w->priv = (void *)dai_link;
memset(&routes, 0, sizeof(routes));
--
1.7.9.5
2
1
24 Feb '14
This patch adds initial support for the Behringer BCD2000 USB DJ controller.
At the moment, only the MIDI part of the device is working, i.e. knobs,
buttons and LEDs.
I also plan to add support for the audio part, but I assume that this will
require more effort than the rather simple MIDI interface. Progress can be
tracked at https://github.com/anyc/snd-usb-bcd2000.
Changes since v6:
- applied more style improvements
Changes since v5:
- use kernel bitmap functions for devices_used
Changes since v4:
- devices_used as array to support arbitrary number of SNDRV_CARDS
- removed unused array "enable"
Changes since v3:
- applied style and snd_printk changes as suggested by Daniel Mack
Changes since v2:
- applied more changes from Daniel Mack and Clemens Ladisch
Changes since v1:
- fixed the various code style issues, thanks to Daniel Mack and
checkpatch.pl.
Signed-off-by: Mario Kicherer <dev(a)kicherer.org>
---
sound/usb/Kconfig | 13 ++
sound/usb/Makefile | 2 +-
sound/usb/bcd2000/Makefile | 3 +
sound/usb/bcd2000/bcd2000.c | 477 ++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 494 insertions(+), 1 deletion(-)
create mode 100644 sound/usb/bcd2000/Makefile
create mode 100644 sound/usb/bcd2000/bcd2000.c
diff --git a/sound/usb/Kconfig b/sound/usb/Kconfig
index de9408b..145653a 100644
--- a/sound/usb/Kconfig
+++ b/sound/usb/Kconfig
@@ -146,5 +146,18 @@ config SND_USB_HIFACE
To compile this driver as a module, choose M here: the module
will be called snd-usb-hiface.
+config SND_BCD2000
+ tristate "Behringer BCD2000 MIDI driver"
+ select SND_RAWMIDI
+ help
+ Say Y here to include MIDI support for the Behringer BCD2000 DJ
+ controller.
+
+ Audio support is still work-in-progress at
+ https://github.com/anyc/snd-usb-bcd2000
+
+ To compile this driver as a module, choose M here: the module
+ will be called snd-bcd2000.
+
endif # SND_USB
diff --git a/sound/usb/Makefile b/sound/usb/Makefile
index abe668f..2b92f0d 100644
--- a/sound/usb/Makefile
+++ b/sound/usb/Makefile
@@ -23,4 +23,4 @@ obj-$(CONFIG_SND_USB_UA101) += snd-usbmidi-lib.o
obj-$(CONFIG_SND_USB_USX2Y) += snd-usbmidi-lib.o
obj-$(CONFIG_SND_USB_US122L) += snd-usbmidi-lib.o
-obj-$(CONFIG_SND) += misc/ usx2y/ caiaq/ 6fire/ hiface/
+obj-$(CONFIG_SND) += misc/ usx2y/ caiaq/ 6fire/ hiface/ bcd2000/
diff --git a/sound/usb/bcd2000/Makefile b/sound/usb/bcd2000/Makefile
new file mode 100644
index 0000000..f09ccc0
--- /dev/null
+++ b/sound/usb/bcd2000/Makefile
@@ -0,0 +1,3 @@
+snd-bcd2000-y := bcd2000.o
+
+obj-$(CONFIG_SND_BCD2000) += snd-bcd2000.o
\ No newline at end of file
diff --git a/sound/usb/bcd2000/bcd2000.c b/sound/usb/bcd2000/bcd2000.c
new file mode 100644
index 0000000..5085c58
--- /dev/null
+++ b/sound/usb/bcd2000/bcd2000.c
@@ -0,0 +1,477 @@
+/*
+ * Behringer BCD2000 driver
+ *
+ * Copyright (C) 2014 Mario Kicherer (dev(a)kicherer.org)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This 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/kernel.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/bitmap.h>
+#include <linux/usb.h>
+#include <linux/usb/audio.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/rawmidi.h>
+
+#define PREFIX "snd-bcd2000: "
+#define BUFSIZE 64
+
+static struct usb_device_id id_table[] = {
+ { USB_DEVICE(0x1397, 0x00bd) },
+ { },
+};
+
+static unsigned char device_cmd_prefix[] = {0x03, 0x00};
+
+static unsigned char bcd2000_init_sequence[] = {
+ 0x07, 0x00, 0x00, 0x00, 0x78, 0x48, 0x1c, 0x81,
+ 0xc4, 0x00, 0x00, 0x00, 0x5e, 0x53, 0x4a, 0xf7,
+ 0x18, 0xfa, 0x11, 0xff, 0x6c, 0xf3, 0x90, 0xff,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x18, 0xfa, 0x11, 0xff, 0x14, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xf2, 0x34, 0x4a, 0xf7,
+ 0x18, 0xfa, 0x11, 0xff
+};
+
+struct bcd2000 {
+ struct usb_device *dev;
+ struct snd_card *card;
+ struct usb_interface *intf;
+ int card_index;
+
+ int midi_out_active;
+ struct snd_rawmidi *rmidi;
+ struct snd_rawmidi_substream *midi_receive_substream;
+ struct snd_rawmidi_substream *midi_out_substream;
+
+ unsigned char midi_in_buf[BUFSIZE];
+ unsigned char midi_out_buf[BUFSIZE];
+
+ struct urb *midi_out_urb;
+ struct urb *midi_in_urb;
+
+ struct usb_anchor anchor;
+};
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
+
+static DEFINE_MUTEX(devices_mutex);
+DECLARE_BITMAP(devices_used, SNDRV_CARDS);
+static struct usb_driver bcd2000_driver;
+
+#ifdef CONFIG_SND_DEBUG
+static void bcd2000_dump_buffer(const char *prefix, const char *buf, int len)
+{
+ print_hex_dump(KERN_DEBUG, prefix,
+ DUMP_PREFIX_NONE, 16, 1,
+ buf, len, false);
+}
+#else
+static void bcd2000_dump_buffer(const char *prefix, const char *buf, int len) {}
+#endif
+
+static int bcd2000_midi_input_open(struct snd_rawmidi_substream *substream)
+{
+ return 0;
+}
+
+static int bcd2000_midi_input_close(struct snd_rawmidi_substream *substream)
+{
+ return 0;
+}
+
+/* register midi substream */
+static void bcd2000_midi_input_trigger(struct snd_rawmidi_substream *substream,
+ int up)
+{
+ struct bcd2000 *bcd2k = substream->rmidi->private_data;
+ bcd2k->midi_receive_substream = up ? substream : NULL;
+}
+
+static void bcd2000_midi_handle_input(struct bcd2000 *bcd2k,
+ const unsigned char *buf, unsigned int buf_len)
+{
+ unsigned int payload_length, tocopy;
+ struct snd_rawmidi_substream *midi_receive_substream;
+
+ midi_receive_substream = ACCESS_ONCE(bcd2k->midi_receive_substream);
+ if (!midi_receive_substream)
+ return;
+
+ bcd2000_dump_buffer(PREFIX "received from device: ", buf, buf_len);
+
+ if (buf_len < 2)
+ return;
+
+ payload_length = buf[0];
+
+ /* ignore packets without payload */
+ if (payload_length == 0)
+ return;
+
+ tocopy = min(payload_length, buf_len-1);
+
+ bcd2000_dump_buffer(PREFIX "sending to userspace: ",
+ &buf[1], tocopy);
+
+ snd_rawmidi_receive(midi_receive_substream,
+ &buf[1], tocopy);
+}
+
+static void bcd2000_midi_send(struct bcd2000 *bcd2k)
+{
+ int len, ret;
+ struct snd_rawmidi_substream *midi_out_substream;
+
+ BUILD_BUG_ON(sizeof(device_cmd_prefix) >= BUFSIZE);
+
+ midi_out_substream = ACCESS_ONCE(bcd2k->midi_out_substream);
+ if (!midi_out_substream)
+ return;
+
+ /* copy command prefix bytes */
+ memcpy(bcd2k->midi_out_buf, device_cmd_prefix,
+ sizeof(device_cmd_prefix));
+
+ /*
+ * get MIDI packet and leave space for command prefix
+ * and payload length
+ */
+ len = snd_rawmidi_transmit(midi_out_substream,
+ bcd2k->midi_out_buf + 3, BUFSIZE - 3);
+
+ if (len < 0)
+ dev_err(&bcd2k->dev->dev, "%s: snd_rawmidi_transmit error %d\n",
+ __func__, len);
+
+ if (len <= 0)
+ return;
+
+ /* set payload length */
+ bcd2k->midi_out_buf[2] = len;
+ bcd2k->midi_out_urb->transfer_buffer_length = BUFSIZE;
+
+ bcd2000_dump_buffer(PREFIX "sending to device: ",
+ bcd2k->midi_out_buf, len+3);
+
+ /* send packet to the BCD2000 */
+ ret = usb_submit_urb(bcd2k->midi_out_urb, GFP_ATOMIC);
+ if (ret < 0)
+ dev_err(&bcd2k->dev->dev, PREFIX
+ "%s (%p): usb_submit_urb() failed, ret=%d, len=%d\n",
+ __func__, midi_out_substream, ret, len);
+ else
+ bcd2k->midi_out_active = 1;
+}
+
+static int bcd2000_midi_output_open(struct snd_rawmidi_substream *substream)
+{
+ return 0;
+}
+
+static int bcd2000_midi_output_close(struct snd_rawmidi_substream *substream)
+{
+ struct bcd2000 *bcd2k = substream->rmidi->private_data;
+
+ if (bcd2k->midi_out_active) {
+ usb_kill_urb(bcd2k->midi_out_urb);
+ bcd2k->midi_out_active = 0;
+ }
+
+ return 0;
+}
+
+/* register midi substream */
+static void bcd2000_midi_output_trigger(struct snd_rawmidi_substream *substream,
+ int up)
+{
+ struct bcd2000 *bcd2k = substream->rmidi->private_data;
+
+ if (up) {
+ bcd2k->midi_out_substream = substream;
+ /* check if there is data userspace wants to send */
+ if (!bcd2k->midi_out_active)
+ bcd2000_midi_send(bcd2k);
+ } else {
+ bcd2k->midi_out_substream = NULL;
+ }
+}
+
+static void bcd2000_output_complete(struct urb *urb)
+{
+ struct bcd2000 *bcd2k = urb->context;
+
+ bcd2k->midi_out_active = 0;
+
+ if (urb->status)
+ dev_warn(&urb->dev->dev,
+ PREFIX "output urb->status: %d\n", urb->status);
+
+ if (urb->status == -ESHUTDOWN)
+ return;
+
+ /* check if there is more data userspace wants to send */
+ bcd2000_midi_send(bcd2k);
+}
+
+static void bcd2000_input_complete(struct urb *urb)
+{
+ int ret;
+ struct bcd2000 *bcd2k = urb->context;
+
+ if (urb->status)
+ dev_warn(&urb->dev->dev,
+ PREFIX "input urb->status: %i\n", urb->status);
+
+ if (!bcd2k || urb->status == -ESHUTDOWN)
+ return;
+
+ if (urb->actual_length > 0)
+ bcd2000_midi_handle_input(bcd2k, urb->transfer_buffer,
+ urb->actual_length);
+
+ /* acknowledge received packet */
+ ret = usb_submit_urb(bcd2k->midi_in_urb, GFP_ATOMIC);
+ if (ret < 0)
+ dev_err(&bcd2k->dev->dev, PREFIX
+ "%s: usb_submit_urb() failed, ret=%d\n",
+ __func__, ret);
+}
+
+static struct snd_rawmidi_ops bcd2000_midi_output = {
+ .open = bcd2000_midi_output_open,
+ .close = bcd2000_midi_output_close,
+ .trigger = bcd2000_midi_output_trigger,
+};
+
+static struct snd_rawmidi_ops bcd2000_midi_input = {
+ .open = bcd2000_midi_input_open,
+ .close = bcd2000_midi_input_close,
+ .trigger = bcd2000_midi_input_trigger,
+};
+
+static void bcd2000_init_device(struct bcd2000 *bcd2k)
+{
+ int ret;
+
+ init_usb_anchor(&bcd2k->anchor);
+ usb_anchor_urb(bcd2k->midi_out_urb, &bcd2k->anchor);
+ usb_anchor_urb(bcd2k->midi_in_urb, &bcd2k->anchor);
+
+ /* copy init sequence into buffer */
+ memcpy(bcd2k->midi_out_buf, bcd2000_init_sequence, 52);
+ bcd2k->midi_out_urb->transfer_buffer_length = 52;
+
+ /* submit sequence */
+ ret = usb_submit_urb(bcd2k->midi_out_urb, GFP_KERNEL);
+ if (ret < 0)
+ dev_err(&bcd2k->dev->dev, PREFIX
+ "%s: usb_submit_urb() out failed, ret=%d: ",
+ __func__, ret);
+ else
+ bcd2k->midi_out_active = 1;
+
+ /* send empty packet to enable button and controller events */
+ ret = usb_submit_urb(bcd2k->midi_in_urb, GFP_KERNEL);
+ if (ret < 0)
+ dev_err(&bcd2k->dev->dev, PREFIX
+ "%s: usb_submit_urb() in failed, ret=%d: ",
+ __func__, ret);
+
+ /* ensure initialization is finished */
+ usb_wait_anchor_empty_timeout(&bcd2k->anchor, 1000);
+}
+
+static int bcd2000_init_midi(struct bcd2000 *bcd2k)
+{
+ int ret;
+ struct snd_rawmidi *rmidi;
+
+ ret = snd_rawmidi_new(bcd2k->card, bcd2k->card->shortname, 0,
+ 1, /* output */
+ 1, /* input */
+ &rmidi);
+
+ if (ret < 0)
+ return ret;
+
+ strlcpy(rmidi->name, bcd2k->card->shortname, sizeof(rmidi->name));
+
+ rmidi->info_flags = SNDRV_RAWMIDI_INFO_DUPLEX;
+ rmidi->private_data = bcd2k;
+
+ rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT;
+ snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT,
+ &bcd2000_midi_output);
+
+ rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT;
+ snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT,
+ &bcd2000_midi_input);
+
+ bcd2k->rmidi = rmidi;
+
+ bcd2k->midi_in_urb = usb_alloc_urb(0, GFP_KERNEL);
+ bcd2k->midi_out_urb = usb_alloc_urb(0, GFP_KERNEL);
+
+ if (!bcd2k->midi_in_urb || !bcd2k->midi_out_urb) {
+ dev_err(&bcd2k->dev->dev, PREFIX "usb_alloc_urb failed\n");
+ return -ENOMEM;
+ }
+
+ usb_fill_int_urb(bcd2k->midi_in_urb, bcd2k->dev,
+ usb_rcvintpipe(bcd2k->dev, 0x81),
+ bcd2k->midi_in_buf, BUFSIZE,
+ bcd2000_input_complete, bcd2k, 1);
+
+ usb_fill_int_urb(bcd2k->midi_out_urb, bcd2k->dev,
+ usb_sndintpipe(bcd2k->dev, 0x1),
+ bcd2k->midi_out_buf, BUFSIZE,
+ bcd2000_output_complete, bcd2k, 1);
+
+ bcd2000_init_device(bcd2k);
+
+ return 0;
+}
+
+static void bcd2000_free_usb_related_resources(struct bcd2000 *bcd2k,
+ struct usb_interface *interface)
+{
+ /* usb_kill_urb not necessary, urb is aborted automatically */
+
+ usb_free_urb(bcd2k->midi_out_urb);
+ usb_free_urb(bcd2k->midi_in_urb);
+
+ if (bcd2k->intf) {
+ usb_set_intfdata(bcd2k->intf, NULL);
+ bcd2k->intf = NULL;
+ }
+}
+
+static int bcd2000_probe(struct usb_interface *interface,
+ const struct usb_device_id *usb_id)
+{
+ struct snd_card *card;
+ struct bcd2000 *bcd2k;
+ unsigned int card_index;
+ char usb_path[32];
+ int err;
+
+ mutex_lock(&devices_mutex);
+
+ for (card_index = 0; card_index < SNDRV_CARDS; ++card_index)
+ if (!test_bit(card_index, devices_used))
+ break;
+
+ if (card_index >= SNDRV_CARDS) {
+ mutex_unlock(&devices_mutex);
+ return -ENOENT;
+ }
+
+ err = snd_card_create(index[card_index], id[card_index], THIS_MODULE,
+ sizeof(*bcd2k), &card);
+ if (err < 0) {
+ mutex_unlock(&devices_mutex);
+ return err;
+ }
+
+ bcd2k = card->private_data;
+ bcd2k->dev = interface_to_usbdev(interface);
+ bcd2k->card = card;
+ bcd2k->card_index = card_index;
+ bcd2k->intf = interface;
+
+ snd_card_set_dev(card, &interface->dev);
+
+ strncpy(card->driver, "snd-bcd2000", sizeof(card->driver));
+ strncpy(card->shortname, "BCD2000", sizeof(card->shortname));
+ usb_make_path(bcd2k->dev, usb_path, sizeof(usb_path));
+ snprintf(bcd2k->card->longname, sizeof(bcd2k->card->longname),
+ "Behringer BCD2000 at %s",
+ usb_path);
+
+ err = bcd2000_init_midi(bcd2k);
+ if (err < 0)
+ goto probe_error;
+
+ err = snd_card_register(card);
+ if (err < 0)
+ goto probe_error;
+
+ usb_set_intfdata(interface, bcd2k);
+ set_bit(card_index, devices_used);
+
+ mutex_unlock(&devices_mutex);
+ return 0;
+
+probe_error:
+ dev_info(&bcd2k->dev->dev, PREFIX "error during probing");
+ bcd2000_free_usb_related_resources(bcd2k, interface);
+ snd_card_free(card);
+ mutex_unlock(&devices_mutex);
+ return err;
+}
+
+static void bcd2000_disconnect(struct usb_interface *interface)
+{
+ struct bcd2000 *bcd2k = usb_get_intfdata(interface);
+
+ if (!bcd2k)
+ return;
+
+ mutex_lock(&devices_mutex);
+
+ /* make sure that userspace cannot create new requests */
+ snd_card_disconnect(bcd2k->card);
+
+ bcd2000_free_usb_related_resources(bcd2k, interface);
+
+ clear_bit(bcd2k->card_index, devices_used);
+
+ snd_card_free_when_closed(bcd2k->card);
+
+ mutex_unlock(&devices_mutex);
+}
+
+static struct usb_driver bcd2000_driver = {
+ .name = "snd-bcd2000",
+ .probe = bcd2000_probe,
+ .disconnect = bcd2000_disconnect,
+ .id_table = id_table,
+};
+
+static int __init bcd2000_init(void)
+{
+ int retval = 0;
+
+ retval = usb_register(&bcd2000_driver);
+ if (retval)
+ pr_info(PREFIX "usb_register failed. Error: %d", retval);
+ return retval;
+}
+
+static void __exit bcd2000_exit(void)
+{
+ usb_deregister(&bcd2000_driver);
+}
+
+module_init(bcd2000_init);
+module_exit(bcd2000_exit);
+
+MODULE_DEVICE_TABLE(usb, id_table);
+MODULE_AUTHOR("Mario Kicherer, dev(a)kicherer.org");
+MODULE_DESCRIPTION("Behringer BCD2000 driver");
+MODULE_LICENSE("GPL");
--
1.8.3.2
1
0
24 Feb '14
This patch adds initial support for the Behringer BCD2000 USB DJ controller.
At the moment, only the MIDI part of the device is working, i.e. knobs,
buttons and LEDs.
I also plan to add support for the audio part, but I assume that this will
require more effort than the rather simple MIDI interface. Progress can be
tracked at https://github.com/anyc/snd-usb-bcd2000.
Changes since v5:
- use kernel bitmap functions for devices_used
Changes since v4:
- devices_used as array to support arbitrary number of SNDRV_CARDS
- removed unused array "enable"
Changes since v3:
- applied style and snd_printk changes as suggested by Daniel Mack
Changes since v2:
- applied more changes from Daniel Mack and Clemens Ladisch
Changes since v1:
- fixed the various code style issues, thanks to Daniel Mack and
checkpatch.pl.
Signed-off-by: Mario Kicherer <dev(a)kicherer.org>
---
sound/usb/Kconfig | 13 ++
sound/usb/Makefile | 2 +-
sound/usb/bcd2000/Makefile | 3 +
sound/usb/bcd2000/bcd2000.c | 488 ++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 505 insertions(+), 1 deletion(-)
create mode 100644 sound/usb/bcd2000/Makefile
create mode 100644 sound/usb/bcd2000/bcd2000.c
diff --git a/sound/usb/Kconfig b/sound/usb/Kconfig
index de9408b..145653a 100644
--- a/sound/usb/Kconfig
+++ b/sound/usb/Kconfig
@@ -146,5 +146,18 @@ config SND_USB_HIFACE
To compile this driver as a module, choose M here: the module
will be called snd-usb-hiface.
+config SND_BCD2000
+ tristate "Behringer BCD2000 MIDI driver"
+ select SND_RAWMIDI
+ help
+ Say Y here to include MIDI support for the Behringer BCD2000 DJ
+ controller.
+
+ Audio support is still work-in-progress at
+ https://github.com/anyc/snd-usb-bcd2000
+
+ To compile this driver as a module, choose M here: the module
+ will be called snd-bcd2000.
+
endif # SND_USB
diff --git a/sound/usb/Makefile b/sound/usb/Makefile
index abe668f..2b92f0d 100644
--- a/sound/usb/Makefile
+++ b/sound/usb/Makefile
@@ -23,4 +23,4 @@ obj-$(CONFIG_SND_USB_UA101) += snd-usbmidi-lib.o
obj-$(CONFIG_SND_USB_USX2Y) += snd-usbmidi-lib.o
obj-$(CONFIG_SND_USB_US122L) += snd-usbmidi-lib.o
-obj-$(CONFIG_SND) += misc/ usx2y/ caiaq/ 6fire/ hiface/
+obj-$(CONFIG_SND) += misc/ usx2y/ caiaq/ 6fire/ hiface/ bcd2000/
diff --git a/sound/usb/bcd2000/Makefile b/sound/usb/bcd2000/Makefile
new file mode 100644
index 0000000..f09ccc0
--- /dev/null
+++ b/sound/usb/bcd2000/Makefile
@@ -0,0 +1,3 @@
+snd-bcd2000-y := bcd2000.o
+
+obj-$(CONFIG_SND_BCD2000) += snd-bcd2000.o
\ No newline at end of file
diff --git a/sound/usb/bcd2000/bcd2000.c b/sound/usb/bcd2000/bcd2000.c
new file mode 100644
index 0000000..82d5e9d
--- /dev/null
+++ b/sound/usb/bcd2000/bcd2000.c
@@ -0,0 +1,488 @@
+/*
+ * Behringer BCD2000 driver
+ *
+ * Copyright (C) 2014 Mario Kicherer (dev(a)kicherer.org)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This 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/kernel.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/bitmap.h>
+#include <linux/usb.h>
+#include <linux/usb/audio.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/rawmidi.h>
+
+#define DEVICE_NAME "Behringer BCD2000"
+#define DEVICE_SHORTNAME "bcd2000"
+
+#define PREFIX DEVICE_SHORTNAME ": "
+#define BUFSIZE 64
+
+static struct usb_device_id id_table[] = {
+ { USB_DEVICE(0x1397, 0x00bd) },
+ { },
+};
+
+static unsigned char device_cmd_prefix[] = {0x03, 0x00};
+
+static unsigned char bcd2000_init_sequence[] = {
+ 0x07, 0x00, 0x00, 0x00, 0x78, 0x48, 0x1c, 0x81,
+ 0xc4, 0x00, 0x00, 0x00, 0x5e, 0x53, 0x4a, 0xf7,
+ 0x18, 0xfa, 0x11, 0xff, 0x6c, 0xf3, 0x90, 0xff,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x18, 0xfa, 0x11, 0xff, 0x14, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xf2, 0x34, 0x4a, 0xf7,
+ 0x18, 0xfa, 0x11, 0xff
+};
+
+struct bcd2000 {
+ struct usb_device *dev;
+ struct snd_card *card;
+ struct usb_interface *intf;
+ int card_index;
+
+ int midi_out_active;
+ struct snd_rawmidi *rmidi;
+ struct snd_rawmidi_substream *midi_receive_substream;
+ struct snd_rawmidi_substream *midi_out_substream;
+
+ unsigned char midi_in_buf[BUFSIZE];
+ unsigned char midi_out_buf[BUFSIZE];
+
+ struct urb *midi_out_urb;
+ struct urb *midi_in_urb;
+
+ struct usb_anchor anchor;
+};
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
+
+static DEFINE_MUTEX(devices_mutex);
+DECLARE_BITMAP(devices_used, SNDRV_CARDS);
+static struct usb_driver bcd2000_driver;
+
+#ifdef CONFIG_SND_DEBUG
+static void bcd2000_dump_buffer(const char *prefix, const char *buf, int len)
+{
+ print_hex_dump(KERN_DEBUG, prefix,
+ DUMP_PREFIX_NONE, 16, 1,
+ buf, len, false);
+}
+#else
+static void bcd2000_dump_buffer(const char *prefix, const char *buf, int len) {}
+#endif
+
+static int bcd2000_midi_input_open(struct snd_rawmidi_substream *substream)
+{
+ return 0;
+}
+
+static int bcd2000_midi_input_close(struct snd_rawmidi_substream *substream)
+{
+ return 0;
+}
+
+/* register midi substream */
+static void bcd2000_midi_input_trigger(struct snd_rawmidi_substream *substream,
+ int up)
+{
+ struct bcd2000 *bcd2k = substream->rmidi->private_data;
+ bcd2k->midi_receive_substream = up ? substream : NULL;
+}
+
+static void bcd2000_midi_handle_input(struct bcd2000 *bcd2k,
+ const unsigned char *buf, unsigned int len)
+{
+ unsigned int length, tocopy;
+ struct snd_rawmidi_substream *midi_receive_substream;
+
+ midi_receive_substream = ACCESS_ONCE(bcd2k->midi_receive_substream);
+ if (!midi_receive_substream)
+ return;
+
+ bcd2000_dump_buffer(PREFIX "received from device: ", buf, len);
+
+ if (len < 2)
+ return;
+
+ /*
+ * Packet structure: mm nn oo (pp)
+ * mm: payload length
+ * nn: MIDI command or note
+ * oo: note or velocity
+ * pp: velocity
+ */
+
+ length = buf[0];
+
+ /* ignore packets without payload */
+ if (length == 0)
+ return;
+
+ tocopy = min(length, len-1);
+
+ bcd2000_dump_buffer(PREFIX "sending to userspace: ",
+ &buf[1], tocopy);
+
+ snd_rawmidi_receive(midi_receive_substream,
+ &buf[1], tocopy);
+}
+
+static void bcd2000_midi_send(struct bcd2000 *bcd2k,
+ struct snd_rawmidi_substream *substream)
+{
+ int len, ret;
+
+ BUILD_BUG_ON(sizeof(device_cmd_prefix) >= BUFSIZE);
+
+ /* copy the "set LED" command bytes */
+ memcpy(bcd2k->midi_out_buf, device_cmd_prefix,
+ sizeof(device_cmd_prefix));
+
+ /*
+ * get MIDI packet and leave space for command prefix
+ * and payload length
+ */
+ len = snd_rawmidi_transmit(substream,
+ bcd2k->midi_out_buf + 3, BUFSIZE - 3);
+
+ if (len < 0)
+ dev_err(&bcd2k->dev->dev, "%s: snd_rawmidi_transmit error %d\n",
+ __func__, len);
+
+ if (len <= 0)
+ return;
+
+ /* set payload length */
+ bcd2k->midi_out_buf[2] = len;
+ bcd2k->midi_out_urb->transfer_buffer_length = BUFSIZE;
+
+ bcd2000_dump_buffer(PREFIX "sending to device: ",
+ bcd2k->midi_out_buf, len+3);
+
+ /* send packet to the BCD2000 */
+ ret = usb_submit_urb(bcd2k->midi_out_urb, GFP_ATOMIC);
+ if (ret < 0)
+ dev_err(&bcd2k->dev->dev, PREFIX
+ "%s (%p): usb_submit_urb() failed, ret=%d, len=%d\n",
+ __func__, substream, ret, len);
+ else
+ bcd2k->midi_out_active = 1;
+}
+
+static int bcd2000_midi_output_open(struct snd_rawmidi_substream *substream)
+{
+ return 0;
+}
+
+static int bcd2000_midi_output_close(struct snd_rawmidi_substream *substream)
+{
+ struct bcd2000 *bcd2k = substream->rmidi->private_data;
+
+ if (bcd2k->midi_out_active) {
+ usb_kill_urb(bcd2k->midi_out_urb);
+ bcd2k->midi_out_active = 0;
+ }
+
+ return 0;
+}
+
+/* register midi substream */
+static void bcd2000_midi_output_trigger(struct snd_rawmidi_substream *substream,
+ int up)
+{
+ struct bcd2000 *bcd2k = substream->rmidi->private_data;
+
+ if (up) {
+ bcd2k->midi_out_substream = substream;
+ /* check if there is data userspace wants to send */
+ if (!bcd2k->midi_out_active)
+ bcd2000_midi_send(bcd2k, substream);
+ } else {
+ bcd2k->midi_out_substream = NULL;
+ }
+}
+
+static void bcd2000_output_complete(struct urb *urb)
+{
+ struct bcd2000 *bcd2k = urb->context;
+ struct snd_rawmidi_substream *midi_out_substream;
+
+ bcd2k->midi_out_active = 0;
+
+ if (urb->status != 0)
+ dev_err(&urb->dev->dev,
+ PREFIX "output urb->status: %d\n", urb->status);
+
+ midi_out_substream = ACCESS_ONCE(bcd2k->midi_out_substream);
+ if (!midi_out_substream)
+ return;
+
+ /* check if there is more data userspace wants to send */
+ bcd2000_midi_send(bcd2k, midi_out_substream);
+}
+
+static void bcd2000_input_complete(struct urb *urb)
+{
+ int ret;
+ struct device *dev = &urb->dev->dev;
+ struct bcd2000 *bcd2k = urb->context;
+
+ if (urb->status) {
+ dev_warn(dev, PREFIX "input urb->status: %i\n", urb->status);
+ return;
+ }
+
+ if (!bcd2k)
+ return;
+
+ if (urb->actual_length > 0)
+ bcd2000_midi_handle_input(bcd2k, urb->transfer_buffer,
+ urb->actual_length);
+
+ /* acknowledge received packet */
+ ret = usb_submit_urb(bcd2k->midi_in_urb, GFP_ATOMIC);
+ if (ret < 0)
+ dev_err(dev, "unable to submit urb. OOM!?\n");
+}
+
+static struct snd_rawmidi_ops bcd2000_midi_output = {
+ .open = bcd2000_midi_output_open,
+ .close = bcd2000_midi_output_close,
+ .trigger = bcd2000_midi_output_trigger,
+};
+
+static struct snd_rawmidi_ops bcd2000_midi_input = {
+ .open = bcd2000_midi_input_open,
+ .close = bcd2000_midi_input_close,
+ .trigger = bcd2000_midi_input_trigger,
+};
+
+static void bcd2000_init_device(struct bcd2000 *bcd2k)
+{
+ int ret;
+
+ init_usb_anchor(&bcd2k->anchor);
+ usb_anchor_urb(bcd2k->midi_out_urb, &bcd2k->anchor);
+ usb_anchor_urb(bcd2k->midi_in_urb, &bcd2k->anchor);
+
+ /* copy init sequence into buffer */
+ memcpy(bcd2k->midi_out_buf, bcd2000_init_sequence, 52);
+ bcd2k->midi_out_urb->transfer_buffer_length = 52;
+
+ /* submit sequence */
+ ret = usb_submit_urb(bcd2k->midi_out_urb, GFP_KERNEL);
+ if (ret < 0)
+ dev_err(&bcd2k->dev->dev, PREFIX
+ "%s: usb_submit_urb() out failed, ret=%d: ",
+ __func__, ret);
+ else
+ bcd2k->midi_out_active = 1;
+
+ /* send empty packet to enable button and controller events */
+ ret = usb_submit_urb(bcd2k->midi_in_urb, GFP_KERNEL);
+ if (ret < 0)
+ dev_err(&bcd2k->dev->dev, PREFIX
+ "%s: usb_submit_urb() in failed, ret=%d: ",
+ __func__, ret);
+
+ /* ensure initialization is finished */
+ usb_wait_anchor_empty_timeout(&bcd2k->anchor, 1000);
+}
+
+static int bcd2000_init_midi(struct bcd2000 *bcd2k)
+{
+ int ret;
+ struct snd_rawmidi *rmidi;
+
+ ret = snd_rawmidi_new(bcd2k->card, bcd2k->card->shortname, 0,
+ 1, /* output */
+ 1, /* input */
+ &rmidi);
+
+ if (ret < 0)
+ return ret;
+
+ strlcpy(rmidi->name, bcd2k->card->shortname, sizeof(rmidi->name));
+
+ rmidi->info_flags = SNDRV_RAWMIDI_INFO_DUPLEX;
+ rmidi->private_data = bcd2k;
+
+ rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT;
+ snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT,
+ &bcd2000_midi_output);
+
+ rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT;
+ snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT,
+ &bcd2000_midi_input);
+
+ bcd2k->rmidi = rmidi;
+
+ bcd2k->midi_in_urb = usb_alloc_urb(0, GFP_KERNEL);
+ bcd2k->midi_out_urb = usb_alloc_urb(0, GFP_KERNEL);
+
+ if (!bcd2k->midi_in_urb || !bcd2k->midi_out_urb) {
+ dev_err(&bcd2k->dev->dev, PREFIX "usb_alloc_urb failed\n");
+ return -ENOMEM;
+ }
+
+ usb_fill_int_urb(bcd2k->midi_in_urb, bcd2k->dev,
+ usb_rcvintpipe(bcd2k->dev, 0x81),
+ bcd2k->midi_in_buf, BUFSIZE,
+ bcd2000_input_complete, bcd2k, 1);
+
+ usb_fill_int_urb(bcd2k->midi_out_urb, bcd2k->dev,
+ usb_sndintpipe(bcd2k->dev, 0x1),
+ bcd2k->midi_out_buf, BUFSIZE,
+ bcd2000_output_complete, bcd2k, 1);
+
+ bcd2000_init_device(bcd2k);
+
+ return 0;
+}
+
+static void bcd2000_free_usb_related_resources(struct bcd2000 *bcd2k,
+ struct usb_interface *interface)
+{
+ /* usb_kill_urb not necessary, urb is aborted automatically */
+
+ usb_free_urb(bcd2k->midi_out_urb);
+ usb_free_urb(bcd2k->midi_in_urb);
+
+ if (bcd2k->intf) {
+ usb_set_intfdata(bcd2k->intf, NULL);
+ bcd2k->intf = NULL;
+ }
+}
+
+static int bcd2000_probe(struct usb_interface *interface,
+ const struct usb_device_id *usb_id)
+{
+ struct snd_card *card;
+ struct bcd2000 *bcd2k;
+ unsigned int card_index;
+ char usb_path[32];
+ int err;
+
+ mutex_lock(&devices_mutex);
+
+ for (card_index = 0; card_index < SNDRV_CARDS; ++card_index)
+ if (!test_bit(card_index, devices_used))
+ break;
+
+ if (card_index >= SNDRV_CARDS) {
+ mutex_unlock(&devices_mutex);
+ return -ENOENT;
+ }
+
+ err = snd_card_create(index[card_index], id[card_index], THIS_MODULE,
+ sizeof(*bcd2k), &card);
+ if (err < 0) {
+ mutex_unlock(&devices_mutex);
+ return err;
+ }
+
+ bcd2k = card->private_data;
+ bcd2k->dev = interface_to_usbdev(interface);
+ bcd2k->card = card;
+ bcd2k->card_index = card_index;
+ bcd2k->intf = interface;
+
+ snd_card_set_dev(card, &interface->dev);
+
+ strcpy(card->driver, DEVICE_NAME);
+ strcpy(card->shortname, DEVICE_SHORTNAME);
+ usb_make_path(bcd2k->dev, usb_path, sizeof(usb_path));
+ snprintf(bcd2k->card->longname, sizeof(bcd2k->card->longname),
+ DEVICE_NAME ", at %s",
+ usb_path);
+
+ dev_info(&bcd2k->dev->dev, PREFIX "%s", bcd2k->card->longname);
+
+ err = bcd2000_init_midi(bcd2k);
+ if (err < 0)
+ goto probe_error;
+
+ err = snd_card_register(card);
+ if (err < 0)
+ goto probe_error;
+
+ usb_set_intfdata(interface, bcd2k);
+ set_bit(card_index, devices_used);
+
+ mutex_unlock(&devices_mutex);
+ return 0;
+
+probe_error:
+ dev_info(&bcd2k->dev->dev, PREFIX "error during probing");
+ bcd2000_free_usb_related_resources(bcd2k, interface);
+ snd_card_free(card);
+ mutex_unlock(&devices_mutex);
+ return err;
+}
+
+static void bcd2000_disconnect(struct usb_interface *interface)
+{
+ struct bcd2000 *bcd2k = usb_get_intfdata(interface);
+
+ if (!bcd2k)
+ return;
+
+ mutex_lock(&devices_mutex);
+
+ /* make sure that userspace cannot create new requests */
+ snd_card_disconnect(bcd2k->card);
+
+ bcd2000_free_usb_related_resources(bcd2k, interface);
+
+ clear_bit(bcd2k->card_index, devices_used);
+
+ snd_card_free_when_closed(bcd2k->card);
+
+ mutex_unlock(&devices_mutex);
+}
+
+static struct usb_driver bcd2000_driver = {
+ .name = DEVICE_SHORTNAME,
+ .probe = bcd2000_probe,
+ .disconnect = bcd2000_disconnect,
+ .id_table = id_table,
+};
+
+static int __init bcd2000_init(void)
+{
+ int retval = 0;
+
+ retval = usb_register(&bcd2000_driver);
+ if (retval)
+ pr_info(PREFIX "usb_register failed. Error: %d", retval);
+ return retval;
+}
+
+static void __exit bcd2000_exit(void)
+{
+ usb_deregister(&bcd2000_driver);
+}
+
+module_init(bcd2000_init);
+module_exit(bcd2000_exit);
+
+MODULE_DEVICE_TABLE(usb, id_table);
+MODULE_AUTHOR("Mario Kicherer, dev(a)kicherer.org");
+MODULE_DESCRIPTION("Behringer BCD2000 driver");
+MODULE_LICENSE("GPL");
--
1.8.3.2
2
3
[alsa-devel] [PATCH 1/6] ASoC: Intel: Add support for Haswell/Broadwell DSP
by Liam Girdwood 24 Feb '14
by Liam Girdwood 24 Feb '14
24 Feb '14
Add support for low level differentiation functions for Haswell and Broadwell
SST DSPs. This includes suppoprt for DSP boot and reset, DSP firmware module
parsing and DSP memory block map initialisation.
Signed-off-by: Liam Girdwood <liam.r.girdwood(a)linux.intel.com>
---
sound/soc/intel/sst-dsp.c | 60 +++++
sound/soc/intel/sst-dsp.h | 10 +
sound/soc/intel/sst-haswell-dsp.c | 517 ++++++++++++++++++++++++++++++++++++++
3 files changed, 587 insertions(+)
create mode 100644 sound/soc/intel/sst-haswell-dsp.c
diff --git a/sound/soc/intel/sst-dsp.c b/sound/soc/intel/sst-dsp.c
index 6e22c12..0c129fd 100644
--- a/sound/soc/intel/sst-dsp.c
+++ b/sound/soc/intel/sst-dsp.c
@@ -27,6 +27,66 @@
#define CREATE_TRACE_POINTS
#include <trace/events/intel-sst.h>
+/* Internal generic low-level SST IO functions - can be overidden */
+void sst_shim32_write(void __iomem *addr, u32 offset, u32 value)
+{
+ writel(value, addr + offset);
+}
+EXPORT_SYMBOL_GPL(sst_shim32_write);
+
+u32 sst_shim32_read(void __iomem *addr, u32 offset)
+{
+ return readl(addr + offset);
+}
+EXPORT_SYMBOL_GPL(sst_shim32_read);
+
+void sst_shim32_write64(void __iomem *addr, u32 offset, u64 value)
+{
+ memcpy_toio(addr + offset, &value, sizeof(value));
+}
+EXPORT_SYMBOL_GPL(sst_shim32_write64);
+
+u64 sst_shim32_read64(void __iomem *addr, u32 offset)
+{
+ u64 val;
+
+ memcpy_fromio(&val, addr + offset, sizeof(val));
+ return val;
+}
+EXPORT_SYMBOL_GPL(sst_shim32_read64);
+
+static inline void _sst_memcpy_toio_32(volatile u32 __iomem *dest,
+ u32 *src, size_t bytes)
+{
+ int i, words = bytes >> 2;
+
+ for (i = 0; i < words; i++)
+ writel(src[i], dest + i);
+}
+
+static inline void _sst_memcpy_fromio_32(u32 *dest,
+ const volatile __iomem u32 *src, size_t bytes)
+{
+ int i, words = bytes >> 2;
+
+ for (i = 0; i < words; i++)
+ dest[i] = readl(src + i);
+}
+
+void sst_memcpy_toio_32(struct sst_dsp *sst,
+ void __iomem *dest, void *src, size_t bytes)
+{
+ _sst_memcpy_toio_32(dest, src, bytes);
+}
+EXPORT_SYMBOL_GPL(sst_memcpy_toio_32);
+
+void sst_memcpy_fromio_32(struct sst_dsp *sst, void *dest,
+ void __iomem *src, size_t bytes)
+{
+ _sst_memcpy_fromio_32(dest, src, bytes);
+}
+EXPORT_SYMBOL_GPL(sst_memcpy_fromio_32);
+
/* Public API */
void sst_dsp_shim_write(struct sst_dsp *sst, u32 offset, u32 value)
{
diff --git a/sound/soc/intel/sst-dsp.h b/sound/soc/intel/sst-dsp.h
index 3730fd3..608418c 100644
--- a/sound/soc/intel/sst-dsp.h
+++ b/sound/soc/intel/sst-dsp.h
@@ -189,6 +189,16 @@ u64 sst_dsp_shim_read64_unlocked(struct sst_dsp *sst, u32 offset);
int sst_dsp_shim_update_bits64_unlocked(struct sst_dsp *sst, u32 offset,
u64 mask, u64 value);
+/* Internal generic low-level SST IO functions - can be overidden */
+void sst_shim32_write(void __iomem *addr, u32 offset, u32 value);
+u32 sst_shim32_read(void __iomem *addr, u32 offset);
+void sst_shim32_write64(void __iomem *addr, u32 offset, u64 value);
+u64 sst_shim32_read64(void __iomem *addr, u32 offset);
+void sst_memcpy_toio_32(struct sst_dsp *sst,
+ void __iomem *dest, void *src, size_t bytes);
+void sst_memcpy_fromio_32(struct sst_dsp *sst,
+ void *dest, void __iomem *src, size_t bytes);
+
/* DSP reset & boot */
void sst_dsp_reset(struct sst_dsp *sst);
int sst_dsp_boot(struct sst_dsp *sst);
diff --git a/sound/soc/intel/sst-haswell-dsp.c b/sound/soc/intel/sst-haswell-dsp.c
new file mode 100644
index 0000000..12f7317
--- /dev/null
+++ b/sound/soc/intel/sst-haswell-dsp.c
@@ -0,0 +1,517 @@
+/*
+ * Intel Haswell SST DSP driver
+ *
+ * Copyright (C) 2013, Intel Corporation. All rights reserved.
+ *
+ * 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/delay.h>
+#include <linux/fs.h>
+#include <linux/slab.h>
+#include <linux/device.h>
+#include <linux/sched.h>
+#include <linux/export.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/dma-mapping.h>
+#include <linux/platform_device.h>
+#include <linux/pci.h>
+#include <linux/firmware.h>
+#include <linux/pm_runtime.h>
+
+#include <linux/acpi.h>
+#include <acpi/acpi_bus.h>
+
+#include "sst-dsp.h"
+#include "sst-dsp-priv.h"
+#include "sst-haswell-ipc.h"
+
+#include <trace/events/hswadsp.h>
+
+#define SST_HSW_FW_SIGNATURE_SIZE 4
+#define SST_HSW_FW_SIGN "$SST"
+#define SST_HSW_FW_LIB_SIGN "$LIB"
+
+#define SST_WPT_SHIM_OFFSET 0xFB000
+#define SST_LP_SHIM_OFFSET 0xE7000
+#define SST_WPT_IRAM_OFFSET 0xA0000
+#define SST_LP_IRAM_OFFSET 0x80000
+
+#define SST_SHIM_PM_REG 0x84
+
+#define SST_HSW_IRAM 1
+#define SST_HSW_DRAM 2
+#define SST_HSW_REGS 3
+
+struct dma_block_info {
+ __le32 type; /* IRAM/DRAM */
+ __le32 size; /* Bytes */
+ __le32 ram_offset; /* Offset in I/DRAM */
+ __le32 rsvd; /* Reserved field */
+} __attribute__((packed));
+
+struct fw_module_info {
+ __le32 persistent_size;
+ __le32 scratch_size;
+} __attribute__((packed));
+
+struct fw_header {
+ unsigned char signature[SST_HSW_FW_SIGNATURE_SIZE]; /* FW signature */
+ __le32 file_size; /* size of fw minus this header */
+ __le32 modules; /* # of modules */
+ __le32 file_format; /* version of header format */
+ __le32 reserved[4];
+} __attribute__((packed));
+
+struct fw_module_header {
+ unsigned char signature[SST_HSW_FW_SIGNATURE_SIZE]; /* module signature */
+ __le32 mod_size; /* size of module */
+ __le32 blocks; /* # of blocks */
+ __le16 padding;
+ __le16 type; /* codec type, pp lib */
+ __le32 entry_point;
+ struct fw_module_info info;
+} __attribute__((packed));
+
+static void hsw_free(struct sst_dsp *sst);
+
+static int hsw_parse_module(struct sst_dsp *dsp, struct sst_fw *fw,
+ struct fw_module_header *module)
+{
+ struct dma_block_info *block;
+ struct sst_module *mod;
+ struct sst_module_data block_data;
+ struct sst_module_template template;
+ int count;
+ void __iomem *ram;
+
+ /* TODO: allowed module types need to be configurable */
+ if (module->type != SST_HSW_MODULE_BASE_FW
+ && module->type != SST_HSW_MODULE_PCM_SYSTEM
+ && module->type != SST_HSW_MODULE_PCM
+ && module->type != SST_HSW_MODULE_PCM_REFERENCE
+ && module->type != SST_HSW_MODULE_PCM_CAPTURE
+ && module->type != SST_HSW_MODULE_LPAL)
+ return 0;
+
+ dev_dbg(dsp->dev, "new module sign 0x%s size 0x%x blocks 0x%x type 0x%x\n",
+ module->signature, module->mod_size,
+ module->blocks, module->type);
+ dev_dbg(dsp->dev, " entrypoint 0x%x\n", module->entry_point);
+ dev_dbg(dsp->dev, " persistent 0x%x scratch 0x%x\n",
+ module->info.persistent_size, module->info.scratch_size);
+
+ memset(&template, 0, sizeof(template));
+ template.id = module->type;
+ template.entry = module->entry_point;
+ template.p.size = module->info.persistent_size;
+ template.p.type = SST_MEM_DRAM;
+ template.p.data_type = SST_DATA_P;
+ template.s.size = module->info.scratch_size;
+ template.s.type = SST_MEM_DRAM;
+ template.s.data_type = SST_DATA_S;
+
+ mod = sst_module_new(fw, &template, NULL);
+ if (mod == NULL)
+ return -ENOMEM;
+
+ block = (void *)module + sizeof(*module);
+
+ for (count = 0; count < module->blocks; count++) {
+
+ if (block->size <= 0) {
+ dev_err(dsp->dev,
+ "error: block %d size invalid\n", count);
+ sst_module_free(mod);
+ return -EINVAL;
+ }
+
+ switch (block->type) {
+ case SST_HSW_IRAM:
+ ram = dsp->addr.lpe;
+ block_data.offset =
+ block->ram_offset + dsp->addr.iram_offset;
+ block_data.type = SST_MEM_IRAM;
+ break;
+ case SST_HSW_DRAM:
+ ram = dsp->addr.lpe;
+ block_data.offset = block->ram_offset;
+ block_data.type = SST_MEM_DRAM;
+ break;
+ default:
+ dev_err(dsp->dev, "error: bad type 0x%x for block 0x%x\n",
+ block->type, count);
+ sst_module_free(mod);
+ return -EINVAL;
+ }
+
+ block_data.size = block->size;
+ block_data.data_type = SST_DATA_M;
+ block_data.data = (void *)block + sizeof(*block);
+ block_data.data_offset = block_data.data - fw->dma_buf;
+
+ dev_dbg(dsp->dev, "copy firmware block %d type 0x%x "
+ "size 0x%x ==> ram %p offset 0x%x\n",
+ count, block->type, block->size, ram,
+ block->ram_offset);
+
+ sst_module_insert_fixed_block(mod, &block_data);
+
+ block = (void *)block + sizeof(*block) + block->size;
+ }
+ return 0;
+}
+
+static int hsw_parse_fw_image(struct sst_fw *sst_fw)
+{
+ struct fw_header *header;
+ struct sst_module *scratch;
+ struct fw_module_header *module;
+ struct sst_dsp *dsp = sst_fw->dsp;
+ struct sst_hsw *hsw = sst_fw->private;
+ int ret, count;
+
+ /* Read the header information from the data pointer */
+ header = (struct fw_header *)sst_fw->dma_buf;
+
+ /* verify FW */
+ if ((strncmp(header->signature, SST_HSW_FW_SIGN, 4) != 0) ||
+ (sst_fw->size != header->file_size + sizeof(*header))) {
+ dev_err(dsp->dev, "error: invalid fw sign/filesize mismatch\n");
+ return -EINVAL;
+ }
+
+ dev_dbg(dsp->dev, "header size=0x%x modules=0x%x fmt=0x%x size=%zu\n",
+ header->file_size, header->modules,
+ header->file_format, sizeof(*header));
+
+ /* parse each module */
+ module = (void *)sst_fw->dma_buf + sizeof(*header);
+ for (count = 0; count < header->modules; count++) {
+
+ /* module */
+ ret = hsw_parse_module(dsp, sst_fw, module);
+ if (ret < 0) {
+ dev_err(dsp->dev, "error: invalid module %d\n", count);
+ return ret;
+ }
+ module = (void *)module + sizeof(*module) + module->mod_size;
+ }
+
+ /* allocate persistent/scratch mem regions */
+ scratch = sst_mem_block_alloc_scratch(dsp);
+ if (scratch == NULL)
+ return -ENOMEM;
+
+ sst_hsw_set_scratch_module(hsw, scratch);
+
+ return 0;
+}
+
+static irqreturn_t hsw_irq(int irq, void *context)
+{
+ struct sst_dsp *sst = (struct sst_dsp *) context;
+ u32 isr;
+ int ret = IRQ_NONE;
+
+ spin_lock(&sst->spinlock);
+
+ /* Interrupt arrived, check src */
+ isr = sst_dsp_shim_read_unlocked(sst, SST_ISRX);
+ if (isr & SST_ISRX_DONE) {
+ trace_sst_irq_done(isr,
+ sst_dsp_shim_read_unlocked(sst, SST_IMRX));
+
+ /* Mask Done interrupt before return */
+ sst_dsp_shim_update_bits_unlocked(sst, SST_IMRX,
+ SST_IMRX_DONE, SST_IMRX_DONE);
+ ret = IRQ_WAKE_THREAD;
+ }
+
+ if (isr & SST_ISRX_BUSY) {
+ trace_sst_irq_busy(isr,
+ sst_dsp_shim_read_unlocked(sst, SST_IMRX));
+
+ /* Mask Busy interrupt before return */
+ sst_dsp_shim_update_bits_unlocked(sst, SST_IMRX,
+ SST_IMRX_BUSY, SST_IMRX_BUSY);
+ ret = IRQ_WAKE_THREAD;
+ }
+
+ spin_unlock(&sst->spinlock);
+ return ret;
+}
+
+static void hsw_boot(struct sst_dsp *sst)
+{
+ /* select SSP1 19.2MHz base clock, SSP clock 0, turn off Low Power Clock */
+ sst_dsp_shim_update_bits_unlocked(sst, SST_CSR,
+ SST_CSR_S1IOCS | SST_CSR_SBCS1 | SST_CSR_LPCS, 0x0);
+
+ /* stall DSP core, set clk to 192/96Mhz */
+ sst_dsp_shim_update_bits_unlocked(sst,
+ SST_CSR, SST_CSR_STALL | SST_CSR_DCS_MASK,
+ SST_CSR_STALL | SST_CSR_DCS(4));
+
+ /* Set 24MHz MCLK, prevent local clock gating, enable SSP0 clock */
+ sst_dsp_shim_update_bits_unlocked(sst, SST_CLKCTL,
+ SST_CLKCTL_MASK | SST_CLKCTL_DCPLCG | SST_CLKCTL_SCOE0,
+ SST_CLKCTL_MASK | SST_CLKCTL_DCPLCG | SST_CLKCTL_SCOE0);
+
+ /* disable DMA finish function for SSP0 & SSP1 */
+ sst_dsp_shim_update_bits_unlocked(sst, SST_CSR2, SST_CSR2_SDFD_SSP1,
+ SST_CSR2_SDFD_SSP1);
+
+ /* enable DMA engine 0,1 all channels to access host memory */
+ sst_dsp_shim_update_bits_unlocked(sst, SST_HDMC,
+ SST_HDMC_HDDA1(0xff) | SST_HDMC_HDDA0(0xff),
+ SST_HDMC_HDDA1(0xff) | SST_HDMC_HDDA0(0xff));
+
+ /* disable all clock gating */
+ writel(0x0, sst->addr.pci_cfg + SST_VDRTCTL2);
+
+ /* set DSP to RUN */
+ sst_dsp_shim_update_bits_unlocked(sst, SST_CSR, SST_CSR_STALL, 0x0);
+}
+
+static void hsw_reset(struct sst_dsp *sst)
+{
+ /* put DSP into reset and stall */
+ sst_dsp_shim_update_bits_unlocked(sst, SST_CSR,
+ SST_CSR_RST | SST_CSR_STALL, SST_CSR_RST | SST_CSR_STALL);
+
+ /* keep in reset for 10ms */
+ mdelay(10);
+
+ /* take DSP out of reset and keep stalled for FW loading */
+ sst_dsp_shim_update_bits_unlocked(sst, SST_CSR,
+ SST_CSR_RST | SST_CSR_STALL, SST_CSR_STALL);
+}
+
+struct sst_adsp_memregion {
+ u32 start;
+ u32 end;
+ int blocks;
+ enum sst_mem_type type;
+};
+
+/* lynx point ADSP mem regions */
+static const struct sst_adsp_memregion lp_region[] = {
+ {0x00000, 0x40000, 8, SST_MEM_DRAM}, /* D-SRAM0 - 8 * 32kB */
+ {0x40000, 0x80000, 8, SST_MEM_DRAM}, /* D-SRAM1 - 8 * 32kB */
+ {0x80000, 0xE0000, 12, SST_MEM_IRAM}, /* I-SRAM - 12 * 32kB */
+};
+
+/* wild cat point ADSP mem regions */
+static const struct sst_adsp_memregion wpt_region[] = {
+ {0x00000, 0x40000, 8, SST_MEM_DRAM}, /* D-SRAM0 - 8 * 32kB */
+ {0x40000, 0x80000, 8, SST_MEM_DRAM}, /* D-SRAM1 - 8 * 32kB */
+ {0x80000, 0xA0000, 4, SST_MEM_DRAM}, /* D-SRAM2 - 4 * 32kB */
+ {0xA0000, 0xF0000, 10, SST_MEM_IRAM}, /* I-SRAM - 10 * 32kB */
+};
+
+static int hsw_acpi_resource_map(struct sst_dsp *sst, struct sst_pdata *pdata)
+{
+ /* ADSP DRAM & IRAM */
+ sst->addr.lpe_base = pdata->lpe_base;
+ sst->addr.lpe = ioremap(pdata->lpe_base, pdata->lpe_size);
+ if (!sst->addr.lpe)
+ return -ENODEV;
+
+ /* ADSP PCI MMIO config space */
+ sst->addr.pci_cfg = ioremap(pdata->pcicfg_base, pdata->pcicfg_size);
+ if (!sst->addr.pci_cfg) {
+ iounmap(sst->addr.lpe);
+ return -ENODEV;
+ }
+
+ /* SST Shim */
+ sst->addr.shim = sst->addr.lpe + sst->addr.shim_offset;
+ return 0;
+}
+
+static u32 hsw_block_get_bit(struct sst_mem_block *block)
+{
+ u32 bit = 0, shift = 0;
+
+ switch (block->type) {
+ case SST_MEM_DRAM:
+ shift = 16;
+ break;
+ case SST_MEM_IRAM:
+ shift = 6;
+ break;
+ default:
+ return 0;
+ }
+
+ bit = 1 << (block->index + shift);
+
+ return bit;
+}
+
+/* enable 32kB memory block - locks held by caller */
+static int hsw_block_enable(struct sst_mem_block *block)
+{
+ struct sst_dsp *sst = block->dsp;
+ u32 bit, val;
+
+ if (block->users++ > 0)
+ return 0;
+
+ dev_dbg(block->dsp->dev, " enabled block %d:%d at offset 0x%x\n",
+ block->type, block->index, block->offset);
+
+ val = readl(sst->addr.pci_cfg + SST_VDRTCTL0);
+ bit = hsw_block_get_bit(block);
+ writel(val & ~bit, sst->addr.pci_cfg + SST_VDRTCTL0);
+
+ /* wait 18 DSP clock ticks */
+ udelay(10);
+
+ return 0;
+}
+
+/* disable 32kB memory block - locks held by caller */
+static int hsw_block_disable(struct sst_mem_block *block)
+{
+ struct sst_dsp *sst = block->dsp;
+ u32 bit, val;
+
+ if (--block->users > 0)
+ return 0;
+
+ dev_dbg(block->dsp->dev, " disabled block %d:%d at offset 0x%x\n",
+ block->type, block->index, block->offset);
+
+ val = readl(sst->addr.pci_cfg + SST_VDRTCTL0);
+ bit = hsw_block_get_bit(block);
+ writel(val | bit, sst->addr.pci_cfg + SST_VDRTCTL0);
+
+ return 0;
+}
+
+static struct sst_block_ops sst_hsw_ops = {
+ .enable = hsw_block_enable,
+ .disable = hsw_block_disable,
+};
+
+static int hsw_enable_shim(struct sst_dsp *sst)
+{
+ int tries = 10;
+ u32 reg;
+
+ /* enable shim */
+ reg = readl(sst->addr.pci_cfg + SST_SHIM_PM_REG);
+ writel(reg & ~0x3, sst->addr.pci_cfg + SST_SHIM_PM_REG);
+
+ /* check that ADSP shim is enabled */
+ while (tries--) {
+ reg = sst_dsp_shim_read_unlocked(sst, SST_CSR);
+ if (reg != 0xffffffff)
+ return 0;
+
+ msleep(1);
+ }
+
+ return -ENODEV;
+}
+
+static int hsw_init(struct sst_dsp *sst, struct sst_pdata *pdata)
+{
+ const struct sst_adsp_memregion *region;
+ struct device *dev;
+ int ret = -ENODEV, i, j, region_count;
+ u32 offset, size;
+
+ dev = sst->dev;
+
+ switch (sst->id) {
+ case SST_DEV_ID_LYNX_POINT:
+ region = lp_region;
+ region_count = ARRAY_SIZE(lp_region);
+ sst->addr.iram_offset = SST_LP_IRAM_OFFSET;
+ sst->addr.shim_offset = SST_LP_SHIM_OFFSET;
+ break;
+ case SST_DEV_ID_WILDCAT_POINT:
+ region = wpt_region;
+ region_count = ARRAY_SIZE(wpt_region);
+ sst->addr.iram_offset = SST_WPT_IRAM_OFFSET;
+ sst->addr.shim_offset = SST_WPT_SHIM_OFFSET;
+ break;
+ default:
+ dev_err(dev, "error: failed to get mem resources\n");
+ return ret;
+ }
+
+ ret = hsw_acpi_resource_map(sst, pdata);
+ if (ret < 0) {
+ dev_err(dev, "error: failed to map resources\n");
+ return ret;
+ }
+
+ /* enable the DSP SHIM */
+ ret = hsw_enable_shim(sst);
+ if (ret < 0) {
+ dev_err(dev, "error: failed to set DSP D0 and reset SHIM\n");
+ return ret;
+ }
+
+ ret = dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(32));
+ if (ret)
+ return ret;
+
+ /* Enable Interrupt from both sides */
+ sst_dsp_shim_update_bits_unlocked(sst, SST_IMRX, 0x3, 0x0);
+ sst_dsp_shim_update_bits_unlocked(sst, SST_IMRD,
+ (0x3 | 0x1 << 16 | 0x3 << 21), 0x0);
+
+ /* register DSP memory blocks - ideally we should get this from ACPI */
+ for (i = 0; i < region_count; i++) {
+ offset = region[i].start;
+ size = (region[i].end - region[i].start) / region[i].blocks;
+
+ /* register individual memory blocks */
+ for (j = 0; j < region[i].blocks; j++) {
+ sst_mem_block_register(sst, offset, size,
+ region[i].type, &sst_hsw_ops, j, sst);
+ offset += size;
+ }
+ }
+
+ /* set default power gating mask */
+ writel(0x0, sst->addr.pci_cfg + SST_VDRTCTL0);
+
+ return 0;
+}
+
+static void hsw_free(struct sst_dsp *sst)
+{
+ sst_mem_block_unregister_all(sst);
+ iounmap(sst->addr.lpe);
+ iounmap(sst->addr.pci_cfg);
+}
+
+struct sst_ops haswell_ops = {
+ .reset = hsw_reset,
+ .boot = hsw_boot,
+ .write = sst_shim32_write,
+ .read = sst_shim32_read,
+ .write64 = sst_shim32_write64,
+ .read64 = sst_shim32_read64,
+ .ram_read = sst_memcpy_fromio_32,
+ .ram_write = sst_memcpy_toio_32,
+ .irq_handler = hsw_irq,
+ .init = hsw_init,
+ .free = hsw_free,
+ .parse_fw = hsw_parse_fw_image,
+};
--
1.8.3.2
4
21