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
May 2013
- 113 participants
- 233 discussions
[alsa-devel] How to specify data format if playback/capture were different ?
by Kuninori Morimoto 25 May '13
by Kuninori Morimoto 25 May '13
25 May '13
Hi Mark, and ALSA ML
Now, I'm creating ak4554 AD/DA.
My question is about data format.
If my understand is correct,
we can specify cpu/codec deivce data format by using
snd_soc_dai_set_fmt(dai, SND_SOC_DAIFMT_xxx);
But, ak4554 case, data formats are
playback : SND_SOC_DAIFMT_RIGHT_J
capture : SND_SOC_DAIFMT_LEFT_J
and, it can't exchange this data format.
CPU driver should set these information for ak4554 when playback/capture.
Then, how to specify these data format on ALSA SoC ?
Best regards
---
Kuninori Morimoto
3
8
Hi Mark,
this series contains more various code cleanup patches that I put
together while working on other stuff this drivers.
The first patch is actually a resend, as Linus Walleij agreed to drop
that code in the original thread (https://lkml.org/lkml/2013/5/17/487).
The others are just a bunch of sparse fixes and generic code cleanup and
dead code removal.
Thanks,
Fabio
Fabio Baltieri (6):
ASoC: ux500: Drop pinctrl sleep support
ASoC: ab8500-codec: Move codec ops on a separate structure
ASoC: ux500: Drop dangling struct i2s_controller
ASoC: ux500: Drop unused code from msp headers
ASoC: ux500: Add missing mop500_ab8500.h include
ASoC: ux500: Drop redundant msp id enumerations
sound/soc/codecs/ab8500-codec.c | 19 ++++-------
sound/soc/ux500/mop500_ab8500.c | 1 +
sound/soc/ux500/ux500_msp_dai.h | 2 --
sound/soc/ux500/ux500_msp_i2s.c | 75 ++---------------------------------------
sound/soc/ux500/ux500_msp_i2s.h | 59 ++------------------------------
5 files changed, 12 insertions(+), 144 deletions(-)
--
1.8.2
3
9
In my audio application when I sometimes need to switch to a
different sample format and/or rate, I usually leave the audio device
open and merely reconfigure the device to the new parameters. This
works with all the regular pcm devices, but it fails when I use the
"hdmi" device. It seemed to just silently fail and no audio output is
heard. As far as I know, no error condition is triggered (but I'll
admit, my knowledge of the alsa api is rather limited).
My question, Is this a bug or is it supposed to work this way and if
so are there ways to detect whether a devices needs to be reopened or
not.
Thanks,
Sander
2
2
25 May '13
Apart from pure matching, the bindings also support setting the the
reset gpio line.
Signed-off-by: Daniel Mack <zonque(a)gmail.com>
---
Lars,
thanks for the quick review. Here's v2 of that patch.
.../devicetree/bindings/sound/adi,adau1701.txt | 23 ++++++++++++++
sound/soc/codecs/adau1701.c | 35 +++++++++++++++++++++-
2 files changed, 57 insertions(+), 1 deletion(-)
create mode 100644 Documentation/devicetree/bindings/sound/adi,adau1701.txt
diff --git a/Documentation/devicetree/bindings/sound/adi,adau1701.txt b/Documentation/devicetree/bindings/sound/adi,adau1701.txt
new file mode 100644
index 0000000..3afeda7
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/adi,adau1701.txt
@@ -0,0 +1,23 @@
+Analog Devices ADAU1701
+
+Required properties:
+
+ - compatible: Should contain "adi,adau1701"
+ - reg: The i2c address. Value depends on the state of ADDR0
+ and ADDR1, as wired in hardware.
+
+Optional properties:
+
+ - reset-gpio: A GPIO spec to define which pin is connected to the
+ chip's !RESET pin. If specified, the driver will
+ assert a hardware reset at probe time.
+
+Examples:
+
+ i2c_bus {
+ adau1701@34 {
+ compatible = "adi,adau1701";
+ reg = <0x34>;
+ reset-gpio = <&gpio 23 0>;
+ };
+ };
diff --git a/sound/soc/codecs/adau1701.c b/sound/soc/codecs/adau1701.c
index 95e1677..3fc1763 100644
--- a/sound/soc/codecs/adau1701.c
+++ b/sound/soc/codecs/adau1701.c
@@ -13,6 +13,9 @@
#include <linux/i2c.h>
#include <linux/delay.h>
#include <linux/slab.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/of_device.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
@@ -452,6 +455,14 @@ static struct snd_soc_dai_driver adau1701_dai = {
.symmetric_rates = 1,
};
+#ifdef CONFIG_OF
+static const struct of_device_id adau1701_dt_ids[] = {
+ { .compatible = "adi,adau1701", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, adau1701_dt_ids);
+#endif
+
static int adau1701_probe(struct snd_soc_codec *codec)
{
int ret;
@@ -494,12 +505,33 @@ static int adau1701_i2c_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct adau1701 *adau1701;
+ struct device *dev = &client->dev;
+ int gpio_nreset = -EINVAL;
int ret;
- adau1701 = devm_kzalloc(&client->dev, sizeof(*adau1701), GFP_KERNEL);
+ adau1701 = devm_kzalloc(dev, sizeof(*adau1701), GFP_KERNEL);
if (!adau1701)
return -ENOMEM;
+ if (dev->of_node) {
+ gpio_nreset = of_get_named_gpio(dev->of_node, "reset-gpio", 0);
+ if (gpio_nreset < 0 && gpio_nreset != -ENOENT)
+ return gpio_nreset;
+ }
+
+ if (gpio_is_valid(gpio_nreset)) {
+ ret = devm_gpio_request_one(dev, gpio_nreset, GPIOF_OUT_INIT_LOW,
+ "ADAU1701 Reset");
+ if (ret < 0)
+ return ret;
+
+ /* minimum reset time is 20ns */
+ udelay(1);
+ gpio_set_value(gpio_nreset, 1);
+ /* power-up time may be as long as 85ms */
+ mdelay(85);
+ }
+
i2c_set_clientdata(client, adau1701);
ret = snd_soc_register_codec(&client->dev, &adau1701_codec_drv,
&adau1701_dai, 1);
@@ -522,6 +554,7 @@ static struct i2c_driver adau1701_i2c_driver = {
.driver = {
.name = "adau1701",
.owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(adau1701_dt_ids),
},
.probe = adau1701_i2c_probe,
.remove = adau1701_i2c_remove,
--
1.8.1.4
3
2
This patch adds a ASoC CODEC driver for the SSM2516. The SSM2516 is a stereo
Class-D audio amplifier with an I2S interface for audio in and a built-in
dynamic range control processor.
Signed-off-by: Lars-Peter Clausen <lars(a)metafoo.de>
---
Changes since v1:
* Power off the codec in the I2C probe function
* Allow a sysclk of 0 which won't but any constraints on the supported
sample rates. This can be used by systems which are able to change the
sysclk on demand.
* Remove {} around multi-line single statements
---
.../devicetree/bindings/sound/ssm2518.txt | 20 +
include/linux/platform_data/ssm2518.h | 22 +
sound/soc/codecs/Kconfig | 4 +
sound/soc/codecs/Makefile | 2 +
sound/soc/codecs/ssm2518.c | 856 +++++++++++++++++++++
sound/soc/codecs/ssm2518.h | 20 +
6 files changed, 924 insertions(+)
create mode 100644 Documentation/devicetree/bindings/sound/ssm2518.txt
create mode 100644 include/linux/platform_data/ssm2518.h
create mode 100644 sound/soc/codecs/ssm2518.c
create mode 100644 sound/soc/codecs/ssm2518.h
diff --git a/Documentation/devicetree/bindings/sound/ssm2518.txt b/Documentation/devicetree/bindings/sound/ssm2518.txt
new file mode 100644
index 0000000..59381a7
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/ssm2518.txt
@@ -0,0 +1,20 @@
+SSM2518 audio amplifier
+
+This device supports I2C only.
+
+Required properties:
+ - compatible : Must be "adi,ssm2518"
+ - reg : the I2C address of the device. This will either be 0x34 (ADDR pin low)
+ or 0x35 (ADDR pin high)
+
+Optional properties:
+ - gpios : GPIO connected to the nSD pin. If the property is not present it is
+ assumed that the nSD pin is hardwired to always on.
+
+Example:
+
+ ssm2518: ssm2518@34 {
+ compatible = "adi,ssm2518";
+ reg = <0x34>;
+ gpios = <&gpio 5 0>;
+ };
diff --git a/include/linux/platform_data/ssm2518.h b/include/linux/platform_data/ssm2518.h
new file mode 100644
index 0000000..9a8e3ea
--- /dev/null
+++ b/include/linux/platform_data/ssm2518.h
@@ -0,0 +1,22 @@
+/*
+ * SSM2518 amplifier audio driver
+ *
+ * Copyright 2013 Analog Devices Inc.
+ * Author: Lars-Peter Clausen <lars(a)metafoo.de>
+ *
+ * Licensed under the GPL-2.
+ */
+
+#ifndef __LINUX_PLATFORM_DATA_SSM2518_H__
+#define __LINUX_PLATFORM_DATA_SSM2518_H__
+
+/**
+ * struct ssm2518_platform_data - Platform data for the ssm2518 driver
+ * @enable_gpio: GPIO connected to the nSD pin. Set to -1 if the nSD pin is
+ * hardwired.
+ */
+struct ssm2518_platform_data {
+ int enable_gpio;
+};
+
+#endif
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 2f45f00..d76609a 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -60,6 +60,7 @@ config SND_SOC_ALL_CODECS
select SND_SOC_SI476X if MFD_SI476X_CORE
select SND_SOC_SN95031 if INTEL_SCU_IPC
select SND_SOC_SPDIF
+ select SND_SOC_SSM2518 if I2C
select SND_SOC_SSM2602 if SND_SOC_I2C_AND_SPI
select SND_SOC_STA32X if I2C
select SND_SOC_STA529 if I2C
@@ -313,6 +314,9 @@ config SND_SOC_SN95031
config SND_SOC_SPDIF
tristate
+config SND_SOC_SSM2518
+ tristate
+
config SND_SOC_SSM2602
tristate
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index dae0aa6..7204c24 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -52,6 +52,7 @@ snd-soc-si476x-objs := si476x.o
snd-soc-sn95031-objs := sn95031.o
snd-soc-spdif-tx-objs := spdif_transmitter.o
snd-soc-spdif-rx-objs := spdif_receiver.o
+snd-soc-ssm2518-objs := ssm2518.o
snd-soc-ssm2602-objs := ssm2602.o
snd-soc-sta32x-objs := sta32x.o
snd-soc-sta529-objs := sta529.o
@@ -176,6 +177,7 @@ obj-$(CONFIG_SND_SOC_SIGMADSP) += snd-soc-sigmadsp.o
obj-$(CONFIG_SND_SOC_SI476X) += snd-soc-si476x.o
obj-$(CONFIG_SND_SOC_SN95031) +=snd-soc-sn95031.o
obj-$(CONFIG_SND_SOC_SPDIF) += snd-soc-spdif-rx.o snd-soc-spdif-tx.o
+obj-$(CONFIG_SND_SOC_SSM2518) += snd-soc-ssm2518.o
obj-$(CONFIG_SND_SOC_SSM2602) += snd-soc-ssm2602.o
obj-$(CONFIG_SND_SOC_STA32X) += snd-soc-sta32x.o
obj-$(CONFIG_SND_SOC_STA529) += snd-soc-sta529.o
diff --git a/sound/soc/codecs/ssm2518.c b/sound/soc/codecs/ssm2518.c
new file mode 100644
index 0000000..3139a1b
--- /dev/null
+++ b/sound/soc/codecs/ssm2518.c
@@ -0,0 +1,856 @@
+/*
+ * SSM2518 amplifier audio driver
+ *
+ * Copyright 2013 Analog Devices Inc.
+ * Author: Lars-Peter Clausen <lars(a)metafoo.de>
+ *
+ * Licensed under the GPL-2.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/i2c.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/gpio.h>
+#include <linux/of_gpio.h>
+#include <linux/platform_data/ssm2518.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+#include "ssm2518.h"
+
+#define SSM2518_REG_POWER1 0x00
+#define SSM2518_REG_CLOCK 0x01
+#define SSM2518_REG_SAI_CTRL1 0x02
+#define SSM2518_REG_SAI_CTRL2 0x03
+#define SSM2518_REG_CHAN_MAP 0x04
+#define SSM2518_REG_LEFT_VOL 0x05
+#define SSM2518_REG_RIGHT_VOL 0x06
+#define SSM2518_REG_MUTE_CTRL 0x07
+#define SSM2518_REG_FAULT_CTRL 0x08
+#define SSM2518_REG_POWER2 0x09
+#define SSM2518_REG_DRC_1 0x0a
+#define SSM2518_REG_DRC_2 0x0b
+#define SSM2518_REG_DRC_3 0x0c
+#define SSM2518_REG_DRC_4 0x0d
+#define SSM2518_REG_DRC_5 0x0e
+#define SSM2518_REG_DRC_6 0x0f
+#define SSM2518_REG_DRC_7 0x10
+#define SSM2518_REG_DRC_8 0x11
+#define SSM2518_REG_DRC_9 0x12
+
+#define SSM2518_POWER1_RESET BIT(7)
+#define SSM2518_POWER1_NO_BCLK BIT(5)
+#define SSM2518_POWER1_MCS_MASK (0xf << 1)
+#define SSM2518_POWER1_MCS_64FS (0x0 << 1)
+#define SSM2518_POWER1_MCS_128FS (0x1 << 1)
+#define SSM2518_POWER1_MCS_256FS (0x2 << 1)
+#define SSM2518_POWER1_MCS_384FS (0x3 << 1)
+#define SSM2518_POWER1_MCS_512FS (0x4 << 1)
+#define SSM2518_POWER1_MCS_768FS (0x5 << 1)
+#define SSM2518_POWER1_MCS_100FS (0x6 << 1)
+#define SSM2518_POWER1_MCS_200FS (0x7 << 1)
+#define SSM2518_POWER1_MCS_400FS (0x8 << 1)
+#define SSM2518_POWER1_SPWDN BIT(0)
+
+#define SSM2518_CLOCK_ASR BIT(0)
+
+#define SSM2518_SAI_CTRL1_FMT_MASK (0x3 << 5)
+#define SSM2518_SAI_CTRL1_FMT_I2S (0x0 << 5)
+#define SSM2518_SAI_CTRL1_FMT_LJ (0x1 << 5)
+#define SSM2518_SAI_CTRL1_FMT_RJ_24BIT (0x2 << 5)
+#define SSM2518_SAI_CTRL1_FMT_RJ_16BIT (0x3 << 5)
+
+#define SSM2518_SAI_CTRL1_SAI_MASK (0x7 << 2)
+#define SSM2518_SAI_CTRL1_SAI_I2S (0x0 << 2)
+#define SSM2518_SAI_CTRL1_SAI_TDM_2 (0x1 << 2)
+#define SSM2518_SAI_CTRL1_SAI_TDM_4 (0x2 << 2)
+#define SSM2518_SAI_CTRL1_SAI_TDM_8 (0x3 << 2)
+#define SSM2518_SAI_CTRL1_SAI_TDM_16 (0x4 << 2)
+#define SSM2518_SAI_CTRL1_SAI_MONO (0x5 << 2)
+
+#define SSM2518_SAI_CTRL1_FS_MASK (0x3)
+#define SSM2518_SAI_CTRL1_FS_8000_12000 (0x0)
+#define SSM2518_SAI_CTRL1_FS_16000_24000 (0x1)
+#define SSM2518_SAI_CTRL1_FS_32000_48000 (0x2)
+#define SSM2518_SAI_CTRL1_FS_64000_96000 (0x3)
+
+#define SSM2518_SAI_CTRL2_BCLK_INTERAL BIT(7)
+#define SSM2518_SAI_CTRL2_LRCLK_PULSE BIT(6)
+#define SSM2518_SAI_CTRL2_LRCLK_INVERT BIT(5)
+#define SSM2518_SAI_CTRL2_MSB BIT(4)
+#define SSM2518_SAI_CTRL2_SLOT_WIDTH_MASK (0x3 << 2)
+#define SSM2518_SAI_CTRL2_SLOT_WIDTH_32 (0x0 << 2)
+#define SSM2518_SAI_CTRL2_SLOT_WIDTH_24 (0x1 << 2)
+#define SSM2518_SAI_CTRL2_SLOT_WIDTH_16 (0x2 << 2)
+#define SSM2518_SAI_CTRL2_BCLK_INVERT BIT(1)
+
+#define SSM2518_CHAN_MAP_RIGHT_SLOT_OFFSET 4
+#define SSM2518_CHAN_MAP_RIGHT_SLOT_MASK 0xf0
+#define SSM2518_CHAN_MAP_LEFT_SLOT_OFFSET 0
+#define SSM2518_CHAN_MAP_LEFT_SLOT_MASK 0x0f
+
+#define SSM2518_MUTE_CTRL_ANA_GAIN BIT(5)
+#define SSM2518_MUTE_CTRL_MUTE_MASTER BIT(0)
+
+#define SSM2518_POWER2_APWDN BIT(0)
+
+#define SSM2518_DAC_MUTE BIT(6)
+#define SSM2518_DAC_FS_MASK 0x07
+#define SSM2518_DAC_FS_8000 0x00
+#define SSM2518_DAC_FS_16000 0x01
+#define SSM2518_DAC_FS_32000 0x02
+#define SSM2518_DAC_FS_64000 0x03
+#define SSM2518_DAC_FS_128000 0x04
+
+struct ssm2518 {
+ struct regmap *regmap;
+ bool right_j;
+
+ unsigned int sysclk;
+ const struct snd_pcm_hw_constraint_list *constraints;
+
+ int enable_gpio;
+};
+
+static const struct reg_default ssm2518_reg_defaults[] = {
+ { 0x00, 0x05 },
+ { 0x01, 0x00 },
+ { 0x02, 0x02 },
+ { 0x03, 0x00 },
+ { 0x04, 0x10 },
+ { 0x05, 0x40 },
+ { 0x06, 0x40 },
+ { 0x07, 0x81 },
+ { 0x08, 0x0c },
+ { 0x09, 0x99 },
+ { 0x0a, 0x7c },
+ { 0x0b, 0x5b },
+ { 0x0c, 0x57 },
+ { 0x0d, 0x89 },
+ { 0x0e, 0x8c },
+ { 0x0f, 0x77 },
+ { 0x10, 0x26 },
+ { 0x11, 0x1c },
+ { 0x12, 0x97 },
+};
+
+static const DECLARE_TLV_DB_MINMAX_MUTE(ssm2518_vol_tlv, -7125, 2400);
+static const DECLARE_TLV_DB_SCALE(ssm2518_compressor_tlv, -3400, 200, 0);
+static const DECLARE_TLV_DB_SCALE(ssm2518_expander_tlv, -8100, 300, 0);
+static const DECLARE_TLV_DB_SCALE(ssm2518_noise_gate_tlv, -9600, 300, 0);
+static const DECLARE_TLV_DB_SCALE(ssm2518_post_drc_tlv, -2400, 300, 0);
+
+static const DECLARE_TLV_DB_RANGE(ssm2518_limiter_tlv,
+ 0, 7, TLV_DB_SCALE_ITEM(-2200, 200, 0),
+ 7, 15, TLV_DB_SCALE_ITEM(-800, 100, 0),
+);
+
+static const char * const ssm2518_drc_peak_detector_attack_time_text[] = {
+ "0 ms", "0.1 ms", "0.19 ms", "0.37 ms", "0.75 ms", "1.5 ms", "3 ms",
+ "6 ms", "12 ms", "24 ms", "48 ms", "96 ms", "192 ms", "384 ms",
+ "768 ms", "1536 ms",
+};
+
+static const char * const ssm2518_drc_peak_detector_release_time_text[] = {
+ "0 ms", "1.5 ms", "3 ms", "6 ms", "12 ms", "24 ms", "48 ms", "96 ms",
+ "192 ms", "384 ms", "768 ms", "1536 ms", "3072 ms", "6144 ms",
+ "12288 ms", "24576 ms"
+};
+
+static const char * const ssm2518_drc_hold_time_text[] = {
+ "0 ms", "0.67 ms", "1.33 ms", "2.67 ms", "5.33 ms", "10.66 ms",
+ "21.32 ms", "42.64 ms", "85.28 ms", "170.56 ms", "341.12 ms",
+ "682.24 ms", "1364 ms",
+};
+
+static const SOC_ENUM_SINGLE_DECL(ssm2518_drc_peak_detector_attack_time_enum,
+ SSM2518_REG_DRC_2, 4, ssm2518_drc_peak_detector_attack_time_text);
+static const SOC_ENUM_SINGLE_DECL(ssm2518_drc_peak_detector_release_time_enum,
+ SSM2518_REG_DRC_2, 0, ssm2518_drc_peak_detector_release_time_text);
+static const SOC_ENUM_SINGLE_DECL(ssm2518_drc_attack_time_enum,
+ SSM2518_REG_DRC_6, 4, ssm2518_drc_peak_detector_attack_time_text);
+static const SOC_ENUM_SINGLE_DECL(ssm2518_drc_decay_time_enum,
+ SSM2518_REG_DRC_6, 0, ssm2518_drc_peak_detector_release_time_text);
+static const SOC_ENUM_SINGLE_DECL(ssm2518_drc_hold_time_enum,
+ SSM2518_REG_DRC_7, 4, ssm2518_drc_hold_time_text);
+static const SOC_ENUM_SINGLE_DECL(ssm2518_drc_noise_gate_hold_time_enum,
+ SSM2518_REG_DRC_7, 0, ssm2518_drc_hold_time_text);
+static const SOC_ENUM_SINGLE_DECL(ssm2518_drc_rms_averaging_time_enum,
+ SSM2518_REG_DRC_9, 0, ssm2518_drc_peak_detector_release_time_text);
+
+static const struct snd_kcontrol_new ssm2518_snd_controls[] = {
+ SOC_SINGLE("Playback De-emphasis Switch", SSM2518_REG_MUTE_CTRL,
+ 4, 1, 0),
+ SOC_DOUBLE_R_TLV("Master Playback Volume", SSM2518_REG_LEFT_VOL,
+ SSM2518_REG_RIGHT_VOL, 0, 0xff, 1, ssm2518_vol_tlv),
+ SOC_DOUBLE("Master Playback Switch", SSM2518_REG_MUTE_CTRL, 2, 1, 1, 1),
+
+ SOC_SINGLE("Amp Low Power Mode Switch", SSM2518_REG_POWER2, 4, 1, 0),
+ SOC_SINGLE("DAC Low Power Mode Switch", SSM2518_REG_POWER2, 3, 1, 0),
+
+ SOC_SINGLE("DRC Limiter Switch", SSM2518_REG_DRC_1, 5, 1, 0),
+ SOC_SINGLE("DRC Compressor Switch", SSM2518_REG_DRC_1, 4, 1, 0),
+ SOC_SINGLE("DRC Expander Switch", SSM2518_REG_DRC_1, 3, 1, 0),
+ SOC_SINGLE("DRC Noise Gate Switch", SSM2518_REG_DRC_1, 2, 1, 0),
+ SOC_DOUBLE("DRC Switch", SSM2518_REG_DRC_1, 0, 1, 1, 0),
+
+ SOC_SINGLE_TLV("DRC Limiter Threshold Volume",
+ SSM2518_REG_DRC_3, 4, 15, 1, ssm2518_limiter_tlv),
+ SOC_SINGLE_TLV("DRC Compressor Lower Threshold Volume",
+ SSM2518_REG_DRC_3, 0, 15, 1, ssm2518_compressor_tlv),
+ SOC_SINGLE_TLV("DRC Expander Upper Threshold Volume", SSM2518_REG_DRC_4,
+ 4, 15, 1, ssm2518_expander_tlv),
+ SOC_SINGLE_TLV("DRC Noise Gate Threshold Volume",
+ SSM2518_REG_DRC_4, 0, 15, 1, ssm2518_noise_gate_tlv),
+ SOC_SINGLE_TLV("DRC Upper Output Threshold Volume",
+ SSM2518_REG_DRC_5, 4, 15, 1, ssm2518_limiter_tlv),
+ SOC_SINGLE_TLV("DRC Lower Output Threshold Volume",
+ SSM2518_REG_DRC_5, 0, 15, 1, ssm2518_noise_gate_tlv),
+ SOC_SINGLE_TLV("DRC Post Volume", SSM2518_REG_DRC_8,
+ 2, 15, 1, ssm2518_post_drc_tlv),
+
+ SOC_ENUM("DRC Peak Detector Attack Time",
+ ssm2518_drc_peak_detector_attack_time_enum),
+ SOC_ENUM("DRC Peak Detector Release Time",
+ ssm2518_drc_peak_detector_release_time_enum),
+ SOC_ENUM("DRC Attack Time", ssm2518_drc_attack_time_enum),
+ SOC_ENUM("DRC Decay Time", ssm2518_drc_decay_time_enum),
+ SOC_ENUM("DRC Hold Time", ssm2518_drc_hold_time_enum),
+ SOC_ENUM("DRC Noise Gate Hold Time",
+ ssm2518_drc_noise_gate_hold_time_enum),
+ SOC_ENUM("DRC RMS Averaging Time", ssm2518_drc_rms_averaging_time_enum),
+};
+
+static const struct snd_soc_dapm_widget ssm2518_dapm_widgets[] = {
+ SND_SOC_DAPM_DAC("DACL", "HiFi Playback", SSM2518_REG_POWER2, 1, 1),
+ SND_SOC_DAPM_DAC("DACR", "HiFi Playback", SSM2518_REG_POWER2, 2, 1),
+
+ SND_SOC_DAPM_OUTPUT("OUTL"),
+ SND_SOC_DAPM_OUTPUT("OUTR"),
+};
+
+static const struct snd_soc_dapm_route ssm2518_routes[] = {
+ { "OUTL", NULL, "DACL" },
+ { "OUTR", NULL, "DACR" },
+};
+
+struct ssm2518_mcs_lut {
+ unsigned int rate;
+ const unsigned int *sysclks;
+};
+
+static const unsigned int ssm2518_sysclks_2048000[] = {
+ 2048000, 4096000, 8192000, 12288000, 16384000, 24576000,
+ 3200000, 6400000, 12800000, 0
+};
+
+static const unsigned int ssm2518_sysclks_2822000[] = {
+ 2822000, 5644800, 11289600, 16934400, 22579200, 33868800,
+ 4410000, 8820000, 17640000, 0
+};
+
+static const unsigned int ssm2518_sysclks_3072000[] = {
+ 3072000, 6144000, 12288000, 16384000, 24576000, 38864000,
+ 4800000, 9600000, 19200000, 0
+};
+
+static const struct ssm2518_mcs_lut ssm2518_mcs_lut[] = {
+ { 8000, ssm2518_sysclks_2048000, },
+ { 11025, ssm2518_sysclks_2822000, },
+ { 12000, ssm2518_sysclks_3072000, },
+ { 16000, ssm2518_sysclks_2048000, },
+ { 24000, ssm2518_sysclks_3072000, },
+ { 22050, ssm2518_sysclks_2822000, },
+ { 32000, ssm2518_sysclks_2048000, },
+ { 44100, ssm2518_sysclks_2822000, },
+ { 48000, ssm2518_sysclks_3072000, },
+ { 96000, ssm2518_sysclks_3072000, },
+};
+
+static const unsigned int ssm2518_rates_2048000[] = {
+ 8000, 16000, 32000,
+};
+
+static const struct snd_pcm_hw_constraint_list ssm2518_constraints_2048000 = {
+ .list = ssm2518_rates_2048000,
+ .count = ARRAY_SIZE(ssm2518_rates_2048000),
+};
+
+static const unsigned int ssm2518_rates_2822000[] = {
+ 11025, 22050, 44100,
+};
+
+static const struct snd_pcm_hw_constraint_list ssm2518_constraints_2822000 = {
+ .list = ssm2518_rates_2822000,
+ .count = ARRAY_SIZE(ssm2518_rates_2822000),
+};
+
+static const unsigned int ssm2518_rates_3072000[] = {
+ 12000, 24000, 48000, 96000,
+};
+
+static const struct snd_pcm_hw_constraint_list ssm2518_constraints_3072000 = {
+ .list = ssm2518_rates_3072000,
+ .count = ARRAY_SIZE(ssm2518_rates_3072000),
+};
+
+static const unsigned int ssm2518_rates_12288000[] = {
+ 8000, 12000, 16000, 24000, 32000, 48000, 96000,
+};
+
+static const struct snd_pcm_hw_constraint_list ssm2518_constraints_12288000 = {
+ .list = ssm2518_rates_12288000,
+ .count = ARRAY_SIZE(ssm2518_rates_12288000),
+};
+
+static unsigned int ssm2518_lookup_mcs(struct ssm2518 *ssm2518,
+ unsigned int rate)
+{
+ const unsigned int *sysclks = NULL;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(ssm2518_mcs_lut); i++) {
+ if (ssm2518_mcs_lut[i].rate == rate) {
+ sysclks = ssm2518_mcs_lut[i].sysclks;
+ break;
+ }
+ }
+
+ if (!sysclks)
+ return -EINVAL;
+
+ for (i = 0; sysclks[i]; i++) {
+ if (sysclks[i] == ssm2518->sysclk)
+ return i;
+ }
+
+ return -EINVAL;
+}
+
+static int ssm2518_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct ssm2518 *ssm2518 = snd_soc_codec_get_drvdata(codec);
+ unsigned int rate = params_rate(params);
+ unsigned int ctrl1, ctrl1_mask;
+ int mcs;
+ int ret;
+
+ mcs = ssm2518_lookup_mcs(ssm2518, rate);
+ if (mcs < 0)
+ return mcs;
+
+ ctrl1_mask = SSM2518_SAI_CTRL1_FS_MASK;
+
+ if (rate >= 8000 && rate <= 12000)
+ ctrl1 = SSM2518_SAI_CTRL1_FS_8000_12000;
+ else if (rate >= 16000 && rate <= 24000)
+ ctrl1 = SSM2518_SAI_CTRL1_FS_16000_24000;
+ else if (rate >= 32000 && rate <= 48000)
+ ctrl1 = SSM2518_SAI_CTRL1_FS_32000_48000;
+ else if (rate >= 64000 && rate <= 96000)
+ ctrl1 = SSM2518_SAI_CTRL1_FS_64000_96000;
+ else
+ return -EINVAL;
+
+ if (ssm2518->right_j) {
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ ctrl1 |= SSM2518_SAI_CTRL1_FMT_RJ_16BIT;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ ctrl1 |= SSM2518_SAI_CTRL1_FMT_RJ_24BIT;
+ break;
+ default:
+ return -EINVAL;
+ }
+ ctrl1_mask |= SSM2518_SAI_CTRL1_FMT_MASK;
+ }
+
+ /* Disable auto samplerate detection */
+ ret = regmap_update_bits(ssm2518->regmap, SSM2518_REG_CLOCK,
+ SSM2518_CLOCK_ASR, SSM2518_CLOCK_ASR);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_update_bits(ssm2518->regmap, SSM2518_REG_SAI_CTRL1,
+ ctrl1_mask, ctrl1);
+ if (ret < 0)
+ return ret;
+
+ return regmap_update_bits(ssm2518->regmap, SSM2518_REG_POWER1,
+ SSM2518_POWER1_MCS_MASK, mcs << 1);
+}
+
+static int ssm2518_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct ssm2518 *ssm2518 = snd_soc_codec_get_drvdata(dai->codec);
+ unsigned int val;
+
+ if (mute)
+ val = SSM2518_MUTE_CTRL_MUTE_MASTER;
+ else
+ val = 0;
+
+ return regmap_update_bits(ssm2518->regmap, SSM2518_REG_MUTE_CTRL,
+ SSM2518_MUTE_CTRL_MUTE_MASTER, val);
+}
+
+static int ssm2518_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+ struct ssm2518 *ssm2518 = snd_soc_codec_get_drvdata(dai->codec);
+ unsigned int ctrl1 = 0, ctrl2 = 0;
+ bool invert_fclk;
+ int ret;
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ invert_fclk = false;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ ctrl2 |= SSM2518_SAI_CTRL2_BCLK_INVERT;
+ invert_fclk = false;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ invert_fclk = true;
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ ctrl2 |= SSM2518_SAI_CTRL2_BCLK_INVERT;
+ invert_fclk = true;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ssm2518->right_j = false;
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ ctrl1 |= SSM2518_SAI_CTRL1_FMT_I2S;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ ctrl1 |= SSM2518_SAI_CTRL1_FMT_LJ;
+ invert_fclk = !invert_fclk;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ ctrl1 |= SSM2518_SAI_CTRL1_FMT_RJ_24BIT;
+ ssm2518->right_j = true;
+ invert_fclk = !invert_fclk;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ ctrl2 |= SSM2518_SAI_CTRL2_LRCLK_PULSE;
+ ctrl1 |= SSM2518_SAI_CTRL1_FMT_I2S;
+ invert_fclk = false;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ ctrl2 |= SSM2518_SAI_CTRL2_LRCLK_PULSE;
+ ctrl1 |= SSM2518_SAI_CTRL1_FMT_LJ;
+ invert_fclk = false;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (invert_fclk)
+ ctrl2 |= SSM2518_SAI_CTRL2_LRCLK_INVERT;
+
+ ret = regmap_write(ssm2518->regmap, SSM2518_REG_SAI_CTRL1, ctrl1);
+ if (ret)
+ return ret;
+
+ return regmap_write(ssm2518->regmap, SSM2518_REG_SAI_CTRL2, ctrl2);
+}
+
+static int ssm2518_set_power(struct ssm2518 *ssm2518, bool enable)
+{
+ int ret = 0;
+
+ if (!enable) {
+ ret = regmap_update_bits(ssm2518->regmap, SSM2518_REG_POWER1,
+ SSM2518_POWER1_SPWDN, SSM2518_POWER1_SPWDN);
+ regcache_mark_dirty(ssm2518->regmap);
+ }
+
+ if (gpio_is_valid(ssm2518->enable_gpio))
+ gpio_set_value(ssm2518->enable_gpio, enable);
+
+ regcache_cache_only(ssm2518->regmap, !enable);
+
+ if (enable) {
+ ret = regmap_update_bits(ssm2518->regmap, SSM2518_REG_POWER1,
+ SSM2518_POWER1_SPWDN | SSM2518_POWER1_RESET, 0x00);
+ regcache_sync(ssm2518->regmap);
+ }
+
+ return ret;
+}
+
+static int ssm2518_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ struct ssm2518 *ssm2518 = snd_soc_codec_get_drvdata(codec);
+ int ret = 0;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ break;
+ case SND_SOC_BIAS_PREPARE:
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ if (codec->dapm.bias_level == SND_SOC_BIAS_OFF)
+ ret = ssm2518_set_power(ssm2518, true);
+ break;
+ case SND_SOC_BIAS_OFF:
+ ret = ssm2518_set_power(ssm2518, false);
+ break;
+ }
+
+ if (ret)
+ return ret;
+
+ codec->dapm.bias_level = level;
+
+ return 0;
+}
+
+static int ssm2518_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask,
+ unsigned int rx_mask, int slots, int width)
+{
+ struct ssm2518 *ssm2518 = snd_soc_codec_get_drvdata(dai->codec);
+ unsigned int ctrl1, ctrl2;
+ int left_slot, right_slot;
+ int ret;
+
+ if (slots == 0)
+ return regmap_update_bits(ssm2518->regmap,
+ SSM2518_REG_SAI_CTRL1, SSM2518_SAI_CTRL1_SAI_MASK,
+ SSM2518_SAI_CTRL1_SAI_I2S);
+
+ if (tx_mask == 0 || tx_mask != 0)
+ return -EINVAL;
+
+ if (slots == 1) {
+ if (tx_mask != 1)
+ return -EINVAL;
+ left_slot = 0;
+ right_slot = 0;
+ } else {
+ /* We assume the left channel < right channel */
+ left_slot = ffs(tx_mask);
+ tx_mask &= ~(1 << tx_mask);
+ if (tx_mask == 0) {
+ right_slot = left_slot;
+ } else {
+ right_slot = ffs(tx_mask);
+ tx_mask &= ~(1 << tx_mask);
+ }
+ }
+
+ if (tx_mask != 0 || left_slot >= slots || right_slot >= slots)
+ return -EINVAL;
+
+ switch (width) {
+ case 16:
+ ctrl2 = SSM2518_SAI_CTRL2_SLOT_WIDTH_16;
+ break;
+ case 24:
+ ctrl2 = SSM2518_SAI_CTRL2_SLOT_WIDTH_24;
+ break;
+ case 32:
+ ctrl2 = SSM2518_SAI_CTRL2_SLOT_WIDTH_32;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (slots) {
+ case 1:
+ ctrl1 = SSM2518_SAI_CTRL1_SAI_MONO;
+ break;
+ case 2:
+ ctrl1 = SSM2518_SAI_CTRL1_SAI_TDM_2;
+ break;
+ case 4:
+ ctrl1 = SSM2518_SAI_CTRL1_SAI_TDM_4;
+ break;
+ case 8:
+ ctrl1 = SSM2518_SAI_CTRL1_SAI_TDM_8;
+ break;
+ case 16:
+ ctrl1 = SSM2518_SAI_CTRL1_SAI_TDM_16;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = regmap_write(ssm2518->regmap, SSM2518_REG_CHAN_MAP,
+ (left_slot << SSM2518_CHAN_MAP_LEFT_SLOT_OFFSET) |
+ (right_slot << SSM2518_CHAN_MAP_RIGHT_SLOT_OFFSET));
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(ssm2518->regmap, SSM2518_REG_SAI_CTRL1,
+ SSM2518_SAI_CTRL1_SAI_MASK, ctrl1);
+ if (ret)
+ return ret;
+
+ return regmap_update_bits(ssm2518->regmap, SSM2518_REG_SAI_CTRL2,
+ SSM2518_SAI_CTRL2_SLOT_WIDTH_MASK, ctrl2);
+}
+
+static int ssm2518_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct ssm2518 *ssm2518 = snd_soc_codec_get_drvdata(dai->codec);
+
+ if (ssm2518->constraints)
+ snd_pcm_hw_constraint_list(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE, ssm2518->constraints);
+
+ return 0;
+}
+
+#define SSM2518_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE | \
+ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32)
+
+static const struct snd_soc_dai_ops ssm2518_dai_ops = {
+ .startup = ssm2518_startup,
+ .hw_params = ssm2518_hw_params,
+ .digital_mute = ssm2518_mute,
+ .set_fmt = ssm2518_set_dai_fmt,
+ .set_tdm_slot = ssm2518_set_tdm_slot,
+};
+
+static struct snd_soc_dai_driver ssm2518_dai = {
+ .name = "ssm2518-hifi",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_96000,
+ .formats = SSM2518_FORMATS,
+ },
+ .ops = &ssm2518_dai_ops,
+};
+
+static int ssm2518_probe(struct snd_soc_codec *codec)
+{
+ struct ssm2518 *ssm2518 = snd_soc_codec_get_drvdata(codec);
+ int ret;
+
+ codec->control_data = ssm2518->regmap;
+ ret = snd_soc_codec_set_cache_io(codec, 0, 0, SND_SOC_REGMAP);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+ return ret;
+ }
+
+ return ssm2518_set_bias_level(codec, SND_SOC_BIAS_OFF);
+}
+
+static int ssm2518_remove(struct snd_soc_codec *codec)
+{
+ ssm2518_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ return 0;
+}
+
+static int ssm2518_set_sysclk(struct snd_soc_codec *codec, int clk_id,
+ int source, unsigned int freq, int dir)
+{
+ struct ssm2518 *ssm2518 = snd_soc_codec_get_drvdata(codec);
+ unsigned int val;
+
+ if (clk_id != SSM2518_SYSCLK)
+ return -EINVAL;
+
+ switch (source) {
+ case SSM2518_SYSCLK_SRC_MCLK:
+ val = 0;
+ break;
+ case SSM2518_SYSCLK_SRC_BCLK:
+ /* In this case the bitclock is used as the system clock, and
+ * the bitclock signal needs to be connected to the MCLK pin and
+ * the BCLK pin is left unconnected */
+ val = SSM2518_POWER1_NO_BCLK;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (freq) {
+ case 0:
+ ssm2518->constraints = NULL;
+ break;
+ case 2048000:
+ case 4096000:
+ case 8192000:
+ case 3200000:
+ case 6400000:
+ case 12800000:
+ ssm2518->constraints = &ssm2518_constraints_2048000;
+ break;
+ case 2822000:
+ case 5644800:
+ case 11289600:
+ case 16934400:
+ case 22579200:
+ case 33868800:
+ case 4410000:
+ case 8820000:
+ case 17640000:
+ ssm2518->constraints = &ssm2518_constraints_2822000;
+ break;
+ case 3072000:
+ case 6144000:
+ case 38864000:
+ case 4800000:
+ case 9600000:
+ case 19200000:
+ ssm2518->constraints = &ssm2518_constraints_3072000;
+ break;
+ case 12288000:
+ case 16384000:
+ case 24576000:
+ ssm2518->constraints = &ssm2518_constraints_12288000;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ssm2518->sysclk = freq;
+
+ return regmap_update_bits(ssm2518->regmap, SSM2518_REG_POWER1,
+ SSM2518_POWER1_NO_BCLK, val);
+}
+
+static struct snd_soc_codec_driver ssm2518_codec_driver = {
+ .probe = ssm2518_probe,
+ .remove = ssm2518_remove,
+ .set_bias_level = ssm2518_set_bias_level,
+ .set_sysclk = ssm2518_set_sysclk,
+ .idle_bias_off = true,
+
+ .controls = ssm2518_snd_controls,
+ .num_controls = ARRAY_SIZE(ssm2518_snd_controls),
+ .dapm_widgets = ssm2518_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(ssm2518_dapm_widgets),
+ .dapm_routes = ssm2518_routes,
+ .num_dapm_routes = ARRAY_SIZE(ssm2518_routes),
+};
+
+static bool ssm2518_register_volatile(struct device *dev, unsigned int reg)
+{
+ return false;
+}
+
+static const struct regmap_config ssm2518_regmap_config = {
+ .val_bits = 8,
+ .reg_bits = 8,
+
+ .max_register = SSM2518_REG_DRC_9,
+ .volatile_reg = ssm2518_register_volatile,
+
+ .cache_type = REGCACHE_RBTREE,
+ .reg_defaults = ssm2518_reg_defaults,
+ .num_reg_defaults = ARRAY_SIZE(ssm2518_reg_defaults),
+};
+
+static int ssm2518_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct ssm2518_platform_data *pdata = i2c->dev.platform_data;
+ struct ssm2518 *ssm2518;
+ int ret;
+
+ ssm2518 = devm_kzalloc(&i2c->dev, sizeof(*ssm2518), GFP_KERNEL);
+ if (ssm2518 == NULL)
+ return -ENOMEM;
+
+ if (pdata) {
+ ssm2518->enable_gpio = pdata->enable_gpio;
+ } else if (i2c->dev.of_node) {
+ ssm2518->enable_gpio = of_get_gpio(i2c->dev.of_node, 0);
+ if (ssm2518->enable_gpio < 0 && ssm2518->enable_gpio != -ENOENT)
+ return ssm2518->enable_gpio;
+ } else {
+ ssm2518->enable_gpio = -1;
+ }
+
+ if (gpio_is_valid(ssm2518->enable_gpio)) {
+ ret = devm_gpio_request_one(&i2c->dev, ssm2518->enable_gpio,
+ GPIOF_OUT_INIT_HIGH, "SSM2518 nSD");
+ if (ret)
+ return ret;
+ }
+
+ i2c_set_clientdata(i2c, ssm2518);
+
+ ssm2518->regmap = devm_regmap_init_i2c(i2c, &ssm2518_regmap_config);
+ if (IS_ERR(ssm2518->regmap))
+ return PTR_ERR(ssm2518->regmap);
+
+ /*
+ * The reset bit is obviously volatile, but we need to be able to cache
+ * the other bits in the register, so we can't just mark the whole
+ * register as volatile. Since this is the only place where we'll ever
+ * touch the reset bit just bypass the cache for this operation.
+ */
+ regcache_cache_bypass(ssm2518->regmap, true);
+ ret = regmap_write(ssm2518->regmap, SSM2518_REG_POWER1,
+ SSM2518_POWER1_RESET);
+ regcache_cache_bypass(ssm2518->regmap, false);
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(ssm2518->regmap, SSM2518_REG_POWER2,
+ SSM2518_POWER2_APWDN, 0x00);
+ if (ret)
+ return ret;
+
+ ret = ssm2518_set_power(ssm2518, false);
+ if (ret)
+ return ret;
+
+ return snd_soc_register_codec(&i2c->dev, &ssm2518_codec_driver,
+ &ssm2518_dai, 1);
+}
+
+static int ssm2518_i2c_remove(struct i2c_client *client)
+{
+ snd_soc_unregister_codec(&client->dev);
+ return 0;
+}
+
+static const struct i2c_device_id ssm2518_i2c_ids[] = {
+ { "ssm2518", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, ssm2518_i2c_ids);
+
+static struct i2c_driver ssm2518_driver = {
+ .driver = {
+ .name = "ssm2518",
+ .owner = THIS_MODULE,
+ },
+ .probe = ssm2518_i2c_probe,
+ .remove = ssm2518_i2c_remove,
+ .id_table = ssm2518_i2c_ids,
+};
+module_i2c_driver(ssm2518_driver);
+
+MODULE_DESCRIPTION("ASoC SSM2518 driver");
+MODULE_AUTHOR("Lars-Peter Clausen <lars(a)metafoo.de>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/ssm2518.h b/sound/soc/codecs/ssm2518.h
new file mode 100644
index 0000000..62511d8
--- /dev/null
+++ b/sound/soc/codecs/ssm2518.h
@@ -0,0 +1,20 @@
+/*
+ * SSM2518 amplifier audio driver
+ *
+ * Copyright 2013 Analog Devices Inc.
+ * Author: Lars-Peter Clausen <lars(a)metafoo.de>
+ *
+ * Licensed under the GPL-2.
+ */
+
+#ifndef __SND_SOC_CODECS_SSM2518_H__
+#define __SND_SOC_CODECS_SSM2518_H__
+
+#define SSM2518_SYSCLK 0
+
+enum ssm2518_sysclk_src {
+ SSM2518_SYSCLK_SRC_MCLK = 0,
+ SSM2518_SYSCLK_SRC_BCLK = 1,
+};
+
+#endif
--
1.8.0
2
1
25 May '13
Signed-off-by: Charles Keepax <ckeepax(a)opensource.wolfsonmicro.com>
---
sound/soc/codecs/wm5110.c | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
diff --git a/sound/soc/codecs/wm5110.c b/sound/soc/codecs/wm5110.c
index c00480b..ba38f06 100644
--- a/sound/soc/codecs/wm5110.c
+++ b/sound/soc/codecs/wm5110.c
@@ -190,7 +190,7 @@ ARIZONA_MIXER_CONTROLS("DSP2R", ARIZONA_DSP2RMIX_INPUT_1_SOURCE),
ARIZONA_MIXER_CONTROLS("DSP3L", ARIZONA_DSP3LMIX_INPUT_1_SOURCE),
ARIZONA_MIXER_CONTROLS("DSP3R", ARIZONA_DSP3RMIX_INPUT_1_SOURCE),
ARIZONA_MIXER_CONTROLS("DSP4L", ARIZONA_DSP4LMIX_INPUT_1_SOURCE),
-ARIZONA_MIXER_CONTROLS("DSP5R", ARIZONA_DSP4RMIX_INPUT_1_SOURCE),
+ARIZONA_MIXER_CONTROLS("DSP4R", ARIZONA_DSP4RMIX_INPUT_1_SOURCE),
ARIZONA_MIXER_CONTROLS("Mic", ARIZONA_MICMIX_INPUT_1_SOURCE),
ARIZONA_MIXER_CONTROLS("Noise", ARIZONA_NOISEMIX_INPUT_1_SOURCE),
--
1.7.2.5
2
1
Hi,
On the OLPC XO-1.5 (VIA VX855 SoC with HDA) we sometimes see a problem
where audio playback fails. Easy to reproduce by playing a short wav
file in a loop, it will fail after a minute or two.
The playback app gets confused here: snd_pcm_wait() returns success,
but snd_pcm_writei() returns EAGAIN.
Digging into the HDA code, I can see that normally, when the stream
gets started, we get interrupts with SD_STS as 0x24. In the failure
case, as soon as we start the stream, we get a flood of interrupts
with SD_STS 0x8 - which indicates FIFO error.
Disabling interrupt generation upon FIFO error does not help much - it
avoids the flood of interrupts, but doesn't change the fact that audio
playing apps hang or loop infinitely.
I have tried the various position_fix options with no luck.
Are there any things I could check that would explain why a FIFO error
might be occurring? After a quick read of the HDA spec I don't really
understand what this FIFO actually is and what it does, how does it
relate to the BDL, CORB and RIRB?
Failing that... any suggestions for how to improve the hda_intel
driver to handle FIFO error gracefully? Right now there is no code to
handle that.
Thanks
Daniel
3
3
This driver adds support for digital audio (I2S)
for the BCM2708 SoC that is used by the
Raspberry Pi. External audio codecs can be
connected to the Raspberry Pi via P5 header.
It relies on cyclic DMA engine support for BCM2708
that is not included in this patch.
The currently supported audio codecs are
TLV320AIC23 and WM8731.
Signed-off-by: Florian Meier <florian.meier(a)koalo.de>
---
A Raspberry Pi kernel featuring this driver can be found at
https://github.com/koalo/linux rpi-3.8.y-asocdev
You can find more information about how to use it at
http://blog.koalo.de/2013/05/i2s-support-for-raspberry-pi.html
sound/soc/Kconfig | 1 +
sound/soc/Makefile | 1 +
sound/soc/bcm2708/Kconfig | 29 ++
sound/soc/bcm2708/Makefile | 12 +
sound/soc/bcm2708/bcm2708-i2s.c | 964 +++++++++++++++++++++++++++++++++++++++
sound/soc/bcm2708/bcm2708-pcm.c | 303 ++++++++++++
sound/soc/bcm2708/bcm2708-pcm.h | 40 ++
sound/soc/bcm2708/rpi-mbed.c | 113 +++++
sound/soc/bcm2708/rpi-proto.c | 131 ++++++
9 files changed, 1594 insertions(+)
create mode 100644 sound/soc/bcm2708/Kconfig
create mode 100644 sound/soc/bcm2708/Makefile
create mode 100644 sound/soc/bcm2708/bcm2708-i2s.c
create mode 100644 sound/soc/bcm2708/bcm2708-pcm.c
create mode 100644 sound/soc/bcm2708/bcm2708-pcm.h
create mode 100644 sound/soc/bcm2708/rpi-mbed.c
create mode 100644 sound/soc/bcm2708/rpi-proto.c
diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig
index 9e675c7..d3852f8 100644
--- a/sound/soc/Kconfig
+++ b/sound/soc/Kconfig
@@ -36,6 +36,7 @@ config SND_SOC_GENERIC_DMAENGINE_PCM
# All the supported SoCs
source "sound/soc/atmel/Kconfig"
source "sound/soc/au1x/Kconfig"
+source "sound/soc/bcm2708/Kconfig"
source "sound/soc/blackfin/Kconfig"
source "sound/soc/cirrus/Kconfig"
source "sound/soc/davinci/Kconfig"
diff --git a/sound/soc/Makefile b/sound/soc/Makefile
index 197b6ae..eef1a7b 100644
--- a/sound/soc/Makefile
+++ b/sound/soc/Makefile
@@ -14,6 +14,7 @@ obj-$(CONFIG_SND_SOC) += codecs/
obj-$(CONFIG_SND_SOC) += generic/
obj-$(CONFIG_SND_SOC) += atmel/
obj-$(CONFIG_SND_SOC) += au1x/
+obj-$(CONFIG_SND_SOC) += bcm2708/
obj-$(CONFIG_SND_SOC) += blackfin/
obj-$(CONFIG_SND_SOC) += cirrus/
obj-$(CONFIG_SND_SOC) += davinci/
diff --git a/sound/soc/bcm2708/Kconfig b/sound/soc/bcm2708/Kconfig
new file mode 100644
index 0000000..f47c2c0
--- /dev/null
+++ b/sound/soc/bcm2708/Kconfig
@@ -0,0 +1,29 @@
+config SND_BCM2708_SOC_I2S
+ tristate
+
+config SND_BCM2708_SOC
+ tristate "SoC Audio support for the Broadcom BCM2708 I2S module"
+ depends on MACH_BCM2708
+ select SND_SOC_DMAENGINE_PCM
+ select DMA_BCM2708
+ select SND_SOC_GENERIC_DMAENGINE_PCM
+ help
+ Say Y or M if you want to add support for codecs attached to
+ the BCM2708 I2S interface. You will also need
+ to select the audio interfaces to support below.
+
+config SND_BCM2708_SOC_RPI_CODEC_MBED
+ tristate "Support for AudioCODEC for mbed (TLV320AIC32B)"
+ depends on SND_BCM2708_SOC
+ select SND_BCM2708_SOC_I2S
+ select SND_SOC_TLV320AIC3X
+ help
+ Say Y if you want to add support for AudioCODEC for mbed (TLV320AIC32B)
+
+config SND_BCM2708_SOC_RPI_CODEC_PROTO
+ tristate "Support for Audio Codec Board - PROTO (WM8731)"
+ depends on SND_BCM2708_SOC
+ select SND_BCM2708_SOC_I2S
+ select SND_SOC_WM8731
+ help
+ Say Y if you want to add support for Audio Codec Board - PROTO (WM8731)
diff --git a/sound/soc/bcm2708/Makefile b/sound/soc/bcm2708/Makefile
new file mode 100644
index 0000000..284d76d
--- /dev/null
+++ b/sound/soc/bcm2708/Makefile
@@ -0,0 +1,12 @@
+# BCM2708 Platform Support
+snd-soc-bcm2708-objs := bcm2708-pcm.o
+snd-soc-bcm2708-i2s-objs := bcm2708-i2s.o
+snd-soc-rpi-mbed-objs := rpi-mbed.o
+snd-soc-rpi-proto-objs := rpi-proto.o
+
+obj-$(CONFIG_SND_BCM2708_SOC) += snd-soc-bcm2708.o
+obj-$(CONFIG_SND_BCM2708_SOC_I2S) += snd-soc-bcm2708-i2s.o
+
+# BCM2708 Machine Support
+obj-$(CONFIG_SND_BCM2708_SOC_RPI_CODEC_MBED) += snd-soc-rpi-mbed.o
+obj-$(CONFIG_SND_BCM2708_SOC_RPI_CODEC_PROTO) += snd-soc-rpi-proto.o
diff --git a/sound/soc/bcm2708/bcm2708-i2s.c b/sound/soc/bcm2708/bcm2708-i2s.c
new file mode 100644
index 0000000..a8e995f
--- /dev/null
+++ b/sound/soc/bcm2708/bcm2708-i2s.c
@@ -0,0 +1,964 @@
+/*
+ * ALSA SoC I2S Audio Layer for Broadcom BCM2708 SoC
+ *
+ * Author: Florian Meier, <florian.meier(a)koalo.de>
+ * Copyright 2013
+ *
+ * based on
+ * Raspberry Pi PCM I2S ALSA Driver
+ * Copyright (c) by Phil Poole 2013
+ *
+ * ALSA SoC I2S (McBSP) Audio Layer for TI DAVINCI processor
+ * Vladimir Barinov, <vbarinov(a)embeddedalley.com>
+ * Copyright (C) 2007 MontaVista Software, Inc., <source(a)mvista.com>
+ *
+ * OMAP ALSA SoC DAI driver using McBSP port
+ * Copyright (C) 2008 Nokia Corporation
+ * Contact: Jarkko Nikula <jarkko.nikula(a)bitmer.com>
+ * Peter Ujfalusi <peter.ujfalusi(a)ti.com>
+ *
+ * Freescale SSI ALSA SoC Digital Audio Interface (DAI) driver
+ * Author: Timur Tabi <timur(a)freescale.com>
+ * Copyright 2007-2010 Freescale Semiconductor, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/clk.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+#include <sound/soc.h>
+
+#include "bcm2708-pcm.h"
+
+/* Clock registers */
+#define BCM2708_CLK_PCMCTL_REG 0x00
+#define BCM2708_CLK_PCMDIV_REG 0x04
+
+/* Clock register settings */
+#define BCM2708_CLK_PASSWD (0x5a000000)
+#define BCM2708_CLK_MASH(v) ((v) << 9)
+#define BCM2708_CLK_FLIP (1 << 8)
+#define BCM2708_CLK_BUSY (1 << 7)
+#define BCM2708_CLK_KILL (1 << 5)
+#define BCM2708_CLK_ENAB (1 << 4)
+#define BCM2708_CLK_SRC(v) (v)
+
+#define BCM2708_CLK_DIVI(v) ((v) << 12)
+#define BCM2708_CLK_DIVF(v) (v)
+
+enum {
+ BCM2708_CLK_MASH_0 = 0,
+ BCM2708_CLK_MASH_1,
+ BCM2708_CLK_MASH_2,
+ BCM2708_CLK_MASH_3,
+};
+
+enum {
+ BCM2708_CLK_SRC_GND = 0,
+ BCM2708_CLK_SRC_OSC,
+ BCM2708_CLK_SRC_DBG0,
+ BCM2708_CLK_SRC_DBG1,
+ BCM2708_CLK_SRC_PLLA,
+ BCM2708_CLK_SRC_PLLC,
+ BCM2708_CLK_SRC_PLLD,
+ BCM2708_CLK_SRC_HDMI,
+};
+
+/* Most clocks are not useable (freq = 0) */
+static const unsigned int bcm2708_clk_freq[BCM2708_CLK_SRC_HDMI+1] = {
+ [BCM2708_CLK_SRC_GND] = 0,
+ [BCM2708_CLK_SRC_OSC] = 19200000,
+ [BCM2708_CLK_SRC_DBG0] = 0,
+ [BCM2708_CLK_SRC_DBG1] = 0,
+ [BCM2708_CLK_SRC_PLLA] = 0,
+ [BCM2708_CLK_SRC_PLLC] = 0,
+ [BCM2708_CLK_SRC_PLLD] = 500000000,
+ [BCM2708_CLK_SRC_HDMI] = 0,
+};
+
+
+/* I2S registers */
+#define BCM2708_I2S_CS_A_REG 0x00
+#define BCM2708_I2S_FIFO_A_REG 0x04
+#define BCM2708_I2S_MODE_A_REG 0x08
+#define BCM2708_I2S_RXC_A_REG 0x0c
+#define BCM2708_I2S_TXC_A_REG 0x10
+#define BCM2708_I2S_DREQ_A_REG 0x14
+#define BCM2708_I2S_INTEN_A_REG 0x18
+#define BCM2708_I2S_INTSTC_A_REG 0x1c
+#define BCM2708_I2S_GRAY_REG 0x20
+
+/* I2S register settings */
+#define BCM2708_I2S_STBY (1 << 25)
+#define BCM2708_I2S_SYNC (1 << 24)
+#define BCM2708_I2S_RXSEX (1 << 23)
+#define BCM2708_I2S_RXF (1 << 22)
+#define BCM2708_I2S_TXE (1 << 21)
+#define BCM2708_I2S_RXD (1 << 20)
+#define BCM2708_I2S_TXD (1 << 19)
+#define BCM2708_I2S_RXR (1 << 18)
+#define BCM2708_I2S_TXW (1 << 17)
+#define BCM2708_I2S_CS_RXERR (1 << 16)
+#define BCM2708_I2S_CS_TXERR (1 << 15)
+#define BCM2708_I2S_RXSYNC (1 << 14)
+#define BCM2708_I2S_TXSYNC (1 << 13)
+#define BCM2708_I2S_DMAEN (1 << 9)
+#define BCM2708_I2S_RXTHR(v) ((v) << 7)
+#define BCM2708_I2S_TXTHR(v) ((v) << 5)
+#define BCM2708_I2S_RXCLR (1 << 4)
+#define BCM2708_I2S_TXCLR (1 << 3)
+#define BCM2708_I2S_TXON (1 << 2)
+#define BCM2708_I2S_RXON (1 << 1)
+#define BCM2708_I2S_EN (1)
+
+#define BCM2708_I2S_CLKDIS (1 << 28)
+#define BCM2708_I2S_PDMN (1 << 27)
+#define BCM2708_I2S_PDME (1 << 26)
+#define BCM2708_I2S_FRXP (1 << 25)
+#define BCM2708_I2S_FTXP (1 << 24)
+#define BCM2708_I2S_CLKM (1 << 23)
+#define BCM2708_I2S_CLKI (1 << 22)
+#define BCM2708_I2S_FSM (1 << 21)
+#define BCM2708_I2S_FSI (1 << 20)
+#define BCM2708_I2S_FLEN(v) ((v) << 10)
+#define BCM2708_I2S_FSLEN(v) (v)
+
+#define BCM2708_I2S_CHWEX (1 << 15)
+#define BCM2708_I2S_CHEN (1 << 14)
+#define BCM2708_I2S_CHPOS(v) ((v) << 4)
+#define BCM2708_I2S_CHWID(v) (v)
+#define BCM2708_I2S_CH1(v) ((v) << 16)
+#define BCM2708_I2S_CH2(v) (v)
+
+#define BCM2708_I2S_TX_PANIC(v) ((v) << 24)
+#define BCM2708_I2S_RX_PANIC(v) ((v) << 16)
+#define BCM2708_I2S_TX(v) ((v) << 8)
+#define BCM2708_I2S_RX(v) (v)
+
+#define BCM2708_I2S_INT_RXERR (1 << 3)
+#define BCM2708_I2S_INT_TXERR (1 << 2)
+#define BCM2708_I2S_INT_RXR (1 << 1)
+#define BCM2708_I2S_INT_TXW (1 << 0)
+
+/* I2S DMA interface */
+#define BCM2708_I2S_FIFO_PHYSICAL_ADDR 0x7E203004
+#define BCM2708_DMA_DREQ_PCM_TX 2
+#define BCM2708_DMA_DREQ_PCM_RX 3
+
+/* General device struct */
+struct bcm2708_i2s_dev {
+ struct device *dev;
+ struct bcm2708_pcm_dma_data dma_params[2];
+ void __iomem *i2s_base;
+ void __iomem *clk_base;
+ unsigned int fmt;
+ struct snd_pcm_substream *first_stream;
+ struct snd_pcm_substream *second_stream;
+};
+
+static inline void bcm2708_i2s_write_reg(struct bcm2708_i2s_dev *dev,
+ int reg, u32 val)
+{
+ dev_dbg(dev->dev, "I2S write to register %p = %x\n",
+ dev->clk_base + reg, val);
+ __raw_writel(val, dev->i2s_base + reg);
+}
+
+static inline u32 bcm2708_i2s_read_reg(struct bcm2708_i2s_dev *dev, int reg)
+{
+ return __raw_readl(dev->i2s_base + reg);
+}
+
+static inline void bcm2708_i2s_set_bits(struct bcm2708_i2s_dev *dev,
+ int reg, u32 val)
+{
+ u32 oldval = __raw_readl(dev->i2s_base + reg);
+ bcm2708_i2s_write_reg(dev, reg, oldval | val);
+}
+
+static inline void bcm2708_i2s_clear_bits(struct bcm2708_i2s_dev *dev,
+ int reg, u32 val)
+{
+ u32 oldval = __raw_readl(dev->i2s_base + reg);
+ bcm2708_i2s_write_reg(dev, reg, oldval & ~val);
+}
+
+static inline void bcm2708_clk_write_reg(struct bcm2708_i2s_dev *dev,
+ int reg, u32 val)
+{
+ dev_dbg(dev->dev, "PCM clk write to register %p = %x\n",
+ dev->clk_base + reg, val);
+ __raw_writel(val, dev->clk_base + reg);
+}
+
+static inline u32 bcm2708_clk_read_reg(struct bcm2708_i2s_dev *dev, int reg)
+{
+ return __raw_readl(dev->clk_base + reg);
+}
+
+static void bcm2708_i2s_start_clock(struct bcm2708_i2s_dev *dev)
+{
+ /*
+ * Start the clock if in master mode.
+ */
+ unsigned int master = dev->fmt & SND_SOC_DAIFMT_MASTER_MASK;
+ if (master == SND_SOC_DAIFMT_CBS_CFS
+ || master == SND_SOC_DAIFMT_CBS_CFM) {
+ unsigned int clkreg = bcm2708_clk_read_reg(dev,
+ BCM2708_CLK_PCMCTL_REG);
+ bcm2708_clk_write_reg(dev, BCM2708_CLK_PCMCTL_REG,
+ BCM2708_CLK_PASSWD | clkreg | BCM2708_CLK_ENAB);
+ }
+}
+
+static void bcm2708_i2s_stop_clock(struct bcm2708_i2s_dev *dev)
+{
+ int timeout = 1000;
+
+ /* stop clock */
+ unsigned int clkreg = bcm2708_clk_read_reg(dev, BCM2708_CLK_PCMCTL_REG);
+ bcm2708_clk_write_reg(dev, BCM2708_CLK_PCMCTL_REG, ~(BCM2708_CLK_ENAB)
+ & (BCM2708_CLK_PASSWD | clkreg));
+
+ /* wait for the BUSY flag going down */
+ while ((bcm2708_clk_read_reg(dev, BCM2708_CLK_PCMCTL_REG)
+ & BCM2708_CLK_BUSY)
+ && timeout > 0) {
+ timeout--;
+ }
+
+ if (timeout <= 0) {
+ /* KILL the clock */
+ dev_err(dev->dev, "I2S clock didn't stop. Kill the clock!\n");
+ bcm2708_clk_write_reg(dev, BCM2708_CLK_PCMCTL_REG,
+ BCM2708_CLK_KILL | BCM2708_CLK_PASSWD
+ | clkreg);
+ }
+}
+
+static void bcm2708_i2s_clear_fifos(struct bcm2708_i2s_dev *dev)
+{
+ int timeout = 1000;
+ unsigned int syncval;
+
+ /* Backup the current state */
+ unsigned int i2s_active_state = bcm2708_i2s_read_reg(dev,
+ BCM2708_I2S_CS_A_REG)
+ & (BCM2708_I2S_RXON | BCM2708_I2S_TXON);
+
+ unsigned int clk_active_state = bcm2708_clk_read_reg(dev,
+ BCM2708_CLK_PCMCTL_REG) & BCM2708_CLK_ENAB;
+
+ /* Start clock if not running */
+ if (!clk_active_state) {
+ unsigned int clkreg = bcm2708_clk_read_reg(dev,
+ BCM2708_CLK_PCMCTL_REG);
+ bcm2708_clk_write_reg(dev, BCM2708_CLK_PCMCTL_REG,
+ BCM2708_CLK_PASSWD | clkreg | BCM2708_CLK_ENAB);
+ }
+
+ /* Stop I2S module */
+ bcm2708_i2s_clear_bits(dev, BCM2708_I2S_CS_A_REG, BCM2708_I2S_RXON
+ | BCM2708_I2S_TXON);
+
+ /*
+ * Clear the FIFOs
+ * Requires at least 2 PCM clock cycles to take effect
+ */
+ bcm2708_i2s_set_bits(dev, BCM2708_I2S_CS_A_REG, BCM2708_I2S_RXCLR
+ | BCM2708_I2S_TXCLR);
+
+ /* Wait for 2 PCM clock cycles */
+
+ /*
+ * Toggle the SYNC flag - after 2 PCM clock cycles it can be read back
+ * FIXME: This does not seem to work for slave mode!
+ */
+ syncval = bcm2708_i2s_read_reg(dev, BCM2708_I2S_CS_A_REG)
+ & BCM2708_I2S_SYNC;
+ if (syncval)
+ bcm2708_i2s_clear_bits(dev, BCM2708_I2S_CS_A_REG,
+ BCM2708_I2S_SYNC);
+ else
+ bcm2708_i2s_set_bits(dev, BCM2708_I2S_CS_A_REG,
+ BCM2708_I2S_SYNC);
+
+ /* Wait for the SYNC flag changing it's state */
+ while (((bcm2708_i2s_read_reg(dev, BCM2708_I2S_CS_A_REG)
+ & BCM2708_I2S_SYNC) == syncval)
+ && timeout > 0) {
+ timeout--;
+ }
+
+ if (timeout <= 0)
+ dev_err(dev->dev, "I2S SYNC error!\n");
+
+ /* Stop clock if it was not running before */
+ if (!clk_active_state)
+ bcm2708_i2s_stop_clock(dev);
+
+ /* Restore I2S state */
+ bcm2708_i2s_set_bits(dev, BCM2708_I2S_CS_A_REG, i2s_active_state);
+}
+
+static void bcm2708_i2s_start(struct bcm2708_i2s_dev *dev,
+ struct snd_pcm_substream *substream)
+{
+ bcm2708_i2s_start_clock(dev);
+
+ /*
+ * Enable the stream.
+ */
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
+ bcm2708_i2s_set_bits(dev, BCM2708_I2S_CS_A_REG,
+ BCM2708_I2S_RXON);
+ } else {
+ bcm2708_i2s_set_bits(dev, BCM2708_I2S_CS_A_REG,
+ BCM2708_I2S_TXON);
+ }
+}
+
+static void bcm2708_i2s_stop(struct bcm2708_i2s_dev *dev,
+ struct snd_pcm_substream *substream)
+{
+ unsigned int still_running;
+
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
+ bcm2708_i2s_clear_bits(dev, BCM2708_I2S_CS_A_REG,
+ BCM2708_I2S_RXON);
+ } else {
+ bcm2708_i2s_clear_bits(dev, BCM2708_I2S_CS_A_REG,
+ BCM2708_I2S_TXON);
+ }
+
+ still_running = bcm2708_i2s_read_reg(dev, BCM2708_I2S_CS_A_REG)
+ & (BCM2708_I2S_TXON | BCM2708_I2S_RXON);
+
+ /* Stop also the clock when not SND_SOC_DAIFMT_CONT */
+ if (!still_running && !(dev->fmt & SND_SOC_DAIFMT_CONT))
+ bcm2708_i2s_stop_clock(dev);
+
+}
+
+static int bcm2708_i2s_set_dai_fmt(struct snd_soc_dai *dai,
+ unsigned int fmt)
+{
+ struct bcm2708_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
+ dev->fmt = fmt;
+ return 0;
+}
+
+static bool bcm2708_i2s_check_other_stream(struct bcm2708_i2s_dev *dev,
+ struct snd_pcm_substream *substream,
+ struct snd_pcm_substream *other_stream,
+ struct snd_pcm_hw_params *params)
+{
+ if (other_stream->runtime->format &&
+ (other_stream->runtime->format != params_format(params))) {
+ dev_err(dev->dev,
+ "Sample formats of streams are different. %i (%s) != %i (%s) Initialization failed!\n",
+ other_stream->runtime->format,
+ (other_stream->stream ==
+ SNDRV_PCM_STREAM_PLAYBACK ?
+ "playback" : "capture"),
+ params_format(params),
+ (substream->stream ==
+ SNDRV_PCM_STREAM_PLAYBACK ?
+ "playback" : "capture"));
+ return false;
+ }
+
+ if (other_stream->runtime->rate &&
+ (other_stream->runtime->rate != params_rate(params))) {
+ dev_err(dev->dev,
+ "Sampling rates of streams are different. %i (%s) != %i (%s) Initialization failed!\n",
+ other_stream->runtime->rate,
+ (other_stream->stream ==
+ SNDRV_PCM_STREAM_PLAYBACK ?
+ "playback" : "capture"),
+ params_rate(params),
+ (substream->stream ==
+ SNDRV_PCM_STREAM_PLAYBACK ?
+ "playback" : "capture"));
+ return false;
+ }
+
+ return true;
+}
+
+static int bcm2708_i2s_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct bcm2708_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
+
+ unsigned int sampling_rate = params_rate(params);
+ unsigned int data_length, data_delay, half_frame;
+ unsigned int ch1pos, ch2pos, mode, format;
+ unsigned int mash = BCM2708_CLK_MASH_1;
+ unsigned int divi, divf, target_frequency;
+ int clk_src = -1;
+ unsigned int master = dev->fmt & SND_SOC_DAIFMT_MASTER_MASK;
+ unsigned int bit_master = (master == SND_SOC_DAIFMT_CBS_CFS
+ || master == SND_SOC_DAIFMT_CBS_CFM);
+
+ unsigned int frame_master = (master == SND_SOC_DAIFMT_CBS_CFS
+ || master == SND_SOC_DAIFMT_CBM_CFS);
+
+ /* Ensure, that both streams have the same settings */
+ struct snd_pcm_substream *other_stream = dev->first_stream;
+ if (other_stream == substream)
+ other_stream = dev->second_stream;
+
+ if (other_stream != NULL
+ && !bcm2708_i2s_check_other_stream(dev,
+ substream, other_stream, params))
+ return -EINVAL;
+
+ /*
+ * If a stream is already enabled,
+ * the registers are already set properly.
+ */
+ if (bcm2708_i2s_read_reg(dev, BCM2708_I2S_CS_A_REG)
+ & (BCM2708_I2S_TXON | BCM2708_I2S_RXON))
+ return 0;
+
+ /*
+ * Adjust the data length according to the format.
+ * We prefill the half frame length with an integer
+ * divider of 2400 as explained at the clock settings.
+ * Maybe it is overwritten there, if the Integer mode
+ * does not apply.
+ */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ data_length = 16;
+ half_frame = 20;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ data_length = 32;
+ half_frame = 40;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /*
+ * Clock Settings
+ *
+ * The target frequency of the bit clock is
+ * sampling rate * frame length
+ *
+ * Integer mode:
+ * Sampling rates that are multiples of 8000 kHz
+ * can be driven by the oscillator of 19.2 MHz
+ * with an integer divider as long as the frame length
+ * is an integer divider of 19200000/8000=2400 as set up above.
+ * This is no longer possible if the sampling rate
+ * is too high (e.g. 192 kHz), because the oscillator is too slow.
+ *
+ * MASH mode:
+ * For all other sampling rates, it is not possible to
+ * have an integer divider. Approximate the clock
+ * with the MASH module that induces a slight frequency
+ * variance. To minimize that it is best to have the fastest
+ * clock here. That is PLLD with 500 MHz.
+ */
+ target_frequency = sampling_rate*half_frame*2;
+ clk_src = BCM2708_CLK_SRC_OSC;
+ mash = BCM2708_CLK_MASH_0;
+
+ if (bcm2708_clk_freq[clk_src] % target_frequency == 0
+ && bit_master && frame_master) {
+ divi = bcm2708_clk_freq[clk_src]/target_frequency;
+ divf = 0;
+ } else {
+ uint64_t dividend;
+
+ /*
+ * Overwrite half frame length, because the
+ * above trick is not needed.
+ * This is fixed, because a bit clock of 64*fs
+ * seems to be what most codecs want.
+ * Is it necessary to have this dynamic?
+ */
+ half_frame = 32;
+ target_frequency = sampling_rate*half_frame*2;
+
+ clk_src = BCM2708_CLK_SRC_PLLD;
+ mash = BCM2708_CLK_MASH_1;
+
+ dividend = bcm2708_clk_freq[clk_src];
+ dividend *= 1024;
+ do_div(dividend, target_frequency);
+ divi = dividend / 1024;
+ divf = dividend % 1024;
+ }
+
+ /* Set clock divider */
+ bcm2708_clk_write_reg(dev, BCM2708_CLK_PCMDIV_REG, BCM2708_CLK_PASSWD
+ | BCM2708_CLK_DIVI(divi)
+ | BCM2708_CLK_DIVF(divf));
+
+ /* Setup clock, but don't start it yet */
+ bcm2708_clk_write_reg(dev, BCM2708_CLK_PCMCTL_REG, BCM2708_CLK_PASSWD
+ | BCM2708_CLK_MASH(mash)
+ | BCM2708_CLK_SRC(clk_src));
+
+
+ /* Setup the frame format */
+ format = BCM2708_I2S_CHEN;
+
+ if (data_length > 24)
+ format |= BCM2708_I2S_CHWEX;
+
+ format |= BCM2708_I2S_CHWID((data_length-8)&0xf);
+
+ switch (dev->fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ data_delay = 1;
+ break;
+ default:
+ /* TODO
+ * Others are possible but are not implemented at the moment.
+ */
+ dev_err(dev->dev, "%s:bad format\n", __func__);
+ return -EINVAL;
+ }
+
+ ch1pos = data_delay;
+ ch2pos = half_frame+data_delay;
+
+ switch (params_channels(params)) {
+ case 2:
+ format = BCM2708_I2S_CH1(format) | BCM2708_I2S_CH2(format);
+ format |= BCM2708_I2S_CH1(BCM2708_I2S_CHPOS(ch1pos));
+ format |= BCM2708_I2S_CH2(BCM2708_I2S_CHPOS(ch2pos));
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /*
+ * Set format for both streams.
+ * We cannot set another frame length
+ * (and therefore word length) anyway,
+ * so the format will be the same.
+ */
+ bcm2708_i2s_write_reg(dev, BCM2708_I2S_RXC_A_REG, format);
+ bcm2708_i2s_write_reg(dev, BCM2708_I2S_TXC_A_REG, format);
+
+
+ /* Setup the I2S mode */
+ mode = 0;
+
+ if (data_length <= 16) {
+ /*
+ * Use frame packed mode (2 channels per 32 bit word)
+ * We cannot set another frame length in the second stream
+ * (and therefore word length) anyway,
+ * so the format will be the same.
+ */
+ mode |= BCM2708_I2S_FTXP | BCM2708_I2S_FRXP;
+ }
+
+ mode |= BCM2708_I2S_FLEN(half_frame*2-1);
+ mode |= BCM2708_I2S_FSLEN(half_frame);
+
+ /* master or slave? */
+ switch (dev->fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ /* cpu is master */
+ break;
+ case SND_SOC_DAIFMT_CBM_CFS:
+ /*
+ * codec is bit clock master
+ * cpu is frame master
+ */
+ mode |= BCM2708_I2S_CLKM;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFM:
+ /*
+ * codec is frame master
+ * cpu is bit clock master
+ */
+ mode |= BCM2708_I2S_FSM;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ /* codec is master */
+ mode |= BCM2708_I2S_CLKM;
+ mode |= BCM2708_I2S_FSM;
+ break;
+ default:
+ dev_err(dev->dev, "%s:bad master\n", __func__);
+ return -EINVAL;
+ }
+
+ /*
+ * Invert clocks?
+ *
+ * The BCM approach seems to be inverted to the classical I2S approach.
+ */
+ switch (dev->fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ /* none - therefore, both for BCM */
+ mode |= BCM2708_I2S_CLKI;
+ mode |= BCM2708_I2S_FSI;
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ /* both - therefore, none for BCM*/
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ /*
+ * invert only frame sync - therefore,
+ * invert only bit clock for BCM
+ */
+ mode |= BCM2708_I2S_CLKI;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ /*
+ * invert only bit clock - therefore,
+ * invert only frame sync for BCM
+ */
+ mode |= BCM2708_I2S_FSI;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ bcm2708_i2s_write_reg(dev, BCM2708_I2S_MODE_A_REG, mode);
+
+
+ /* Setup the DMA parameters */
+ bcm2708_i2s_set_bits(dev, BCM2708_I2S_CS_A_REG, BCM2708_I2S_RXTHR(1)
+ | BCM2708_I2S_TXTHR(1)
+ | BCM2708_I2S_DMAEN);
+
+ bcm2708_i2s_write_reg(dev, BCM2708_I2S_DREQ_A_REG,
+ BCM2708_I2S_TX_PANIC(0x10)
+ | BCM2708_I2S_RX_PANIC(0x30)
+ | BCM2708_I2S_TX(0x30)
+ | BCM2708_I2S_RX(0x20));
+
+ /* Clear FIFOs */
+ bcm2708_i2s_clear_fifos(dev);
+
+ return 0;
+}
+
+
+static int bcm2708_i2s_prepare(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct bcm2708_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
+ unsigned int cs_reg;
+
+ bcm2708_i2s_start_clock(dev);
+
+ /*
+ * Clear both FIFOs if the one that should be started
+ * is not empty at the moment. This should only happen
+ * after overrun. Otherwise, hw_params would have cleared
+ * the FIFO.
+ */
+ cs_reg = bcm2708_i2s_read_reg(dev, BCM2708_I2S_CS_A_REG);
+
+ switch (substream->stream) {
+ case SNDRV_PCM_STREAM_PLAYBACK:
+ if (cs_reg & BCM2708_I2S_TXE)
+ break;
+ case SNDRV_PCM_STREAM_CAPTURE:
+ if (!(cs_reg & BCM2708_I2S_RXD))
+ break;
+
+ bcm2708_i2s_clear_fifos(dev);
+ }
+
+ return 0;
+}
+
+static int bcm2708_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ struct bcm2708_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ bcm2708_i2s_start(dev, substream);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ bcm2708_i2s_stop(dev, substream);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int bcm2708_i2s_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct bcm2708_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
+
+ if (!dev->first_stream) {
+ dev->first_stream = substream;
+
+ /* should this still be running stop it */
+ bcm2708_i2s_stop_clock(dev);
+
+ /* enable PCM block */
+ bcm2708_i2s_set_bits(dev, BCM2708_I2S_CS_A_REG, BCM2708_I2S_EN);
+
+ /*
+ * Disable STBY
+ * Requires at least 4 PCM clock cycles to take effect
+ */
+ bcm2708_i2s_set_bits(dev, BCM2708_I2S_CS_A_REG,
+ BCM2708_I2S_STBY);
+ } else {
+ struct snd_pcm_runtime *first_runtime =
+ dev->first_stream->runtime;
+
+ /*
+ * This is the second stream open, so we need to impose
+ * sample size and sampling rate constraints.
+ * This is because frame length and clock cannot be specified
+ * seperately.
+ *
+ * Note that this can cause a race condition if the
+ * second stream is opened before the first stream is
+ * fully initialized. We provide some protection by
+ * checking to make sure the first stream is
+ * initialized, but it's not perfect. ALSA sometimes
+ * re-initializes the driver with a different sample
+ * rate or size. If the second stream is opened
+ * before the first stream has received its final
+ * parameters, then the second stream may be
+ * constrained to the wrong sample rate or size.
+ *
+ * We will continue in case of failure and recheck the
+ * constraint in hw_params.
+ */
+ if (!first_runtime->format) {
+ dev_err(substream->pcm->card->dev,
+ "Set format in %s stream first! "
+ "Initialization may fail.\n",
+ substream->stream ==
+ SNDRV_PCM_STREAM_PLAYBACK
+ ? "capture" : "playback");
+ } else {
+ snd_pcm_hw_constraint_minmax(substream->runtime,
+ SNDRV_PCM_HW_PARAM_FORMAT,
+ first_runtime->format,
+ first_runtime->format);
+ }
+
+ if (!first_runtime->rate) {
+ dev_err(substream->pcm->card->dev,
+ "Set sampling rate in %s stream first! "
+ "Initialization may fail!\n",
+ substream->stream ==
+ SNDRV_PCM_STREAM_PLAYBACK
+ ? "capture" : "playback");
+ } else {
+ snd_pcm_hw_constraint_minmax(substream->runtime,
+ SNDRV_PCM_HW_PARAM_RATE,
+ first_runtime->rate,
+ first_runtime->rate);
+ }
+
+ dev->second_stream = substream;
+ }
+
+ snd_soc_dai_set_dma_data(dai, substream,
+ &dev->dma_params[substream->stream]);
+
+ return 0;
+}
+
+static void bcm2708_i2s_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct bcm2708_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
+
+ bcm2708_i2s_stop(dev, substream);
+
+ if (dev->first_stream == substream)
+ dev->first_stream = dev->second_stream;
+
+ dev->second_stream = NULL;
+
+ /* If both streams are stopped, disable module and clock */
+ if (!dev->first_stream) {
+ /* Disable the module */
+ bcm2708_i2s_clear_bits(dev, BCM2708_I2S_CS_A_REG,
+ BCM2708_I2S_EN);
+
+ /*
+ * Stopping clock is necessary, because stop does
+ * not stop the clock when SND_SOC_DAIFMT_CONT
+ */
+ bcm2708_i2s_stop_clock(dev);
+ }
+}
+
+static const struct snd_soc_dai_ops bcm2708_i2s_dai_ops = {
+ .startup = bcm2708_i2s_startup,
+ .shutdown = bcm2708_i2s_shutdown,
+ .prepare = bcm2708_i2s_prepare,
+ .trigger = bcm2708_i2s_trigger,
+ .hw_params = bcm2708_i2s_hw_params,
+ .set_fmt = bcm2708_i2s_set_dai_fmt,
+};
+
+static struct snd_soc_dai_driver bcm2708_i2s_dai = {
+ .playback = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_192000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE
+ | SNDRV_PCM_FMTBIT_S32_LE
+ },
+ .capture = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_192000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE
+ | SNDRV_PCM_FMTBIT_S32_LE
+ },
+ .ops = &bcm2708_i2s_dai_ops,
+ .symmetric_rates = 1
+};
+
+static void bcm2708_i2s_setup_gpio(void)
+{
+ /*
+ * This is the common way to handle the GPIO pins for
+ * the Raspberry Pi.
+ * TODO Better way would be to handle
+ * this in the device tree!
+ */
+#define INP_GPIO(g) *(gpio+((g)/10)) &= ~(7<<(((g)%10)*3))
+#define SET_GPIO_ALT(g,a) *(gpio+(((g)/10))) |= (((a)<=3?(a)+4:(a)==4?3:2)<<(((g)%10)*3))
+
+ unsigned int *gpio;
+ int pin;
+ gpio = ioremap(GPIO_BASE, SZ_16K);
+
+ /* SPI is on GPIO 7..11 */
+ for (pin = 28; pin <= 31; pin++) {
+ INP_GPIO(pin); /* set mode to GPIO input first */
+ SET_GPIO_ALT(pin, 2); /* set mode to ALT 0 */
+ }
+#undef INP_GPIO
+#undef SET_GPIO_ALT
+}
+
+static int bcm2708_i2s_probe(struct platform_device *pdev)
+{
+ struct bcm2708_i2s_dev *dev;
+ int i;
+ void __iomem *base[2];
+
+ /* request both ioareas */
+ for (i = 0; i <= 1; i++) {
+ struct resource *mem, *ioarea;
+ mem = platform_get_resource(pdev, IORESOURCE_MEM, i);
+ if (!mem) {
+ dev_err(&pdev->dev, "I2S probe: Memory resource could not be found.\n");
+ return -ENODEV;
+ }
+
+ ioarea = devm_request_mem_region(&pdev->dev, mem->start,
+ resource_size(mem),
+ pdev->name);
+ if (!ioarea) {
+ dev_err(&pdev->dev, "I2S probe: Memory region already claimed.\n");
+ return -EBUSY;
+ }
+
+ base[i] = devm_ioremap(&pdev->dev, mem->start,
+ resource_size(mem));
+ if (!base[i]) {
+ dev_err(&pdev->dev, "I2S probe: ioremap failed.\n");
+ return -ENOMEM;
+ }
+ }
+
+ dev = devm_kzalloc(&pdev->dev, sizeof(struct bcm2708_i2s_dev),
+ GFP_KERNEL);
+ if (!dev) {
+ dev_err(&pdev->dev, "I2S probe: kzalloc failed.\n");
+ return -ENOMEM;
+ }
+
+ dev->i2s_base = base[0];
+ dev->clk_base = base[1];
+
+ bcm2708_i2s_setup_gpio();
+
+ /* Set the appropriate DMA parameters */
+ dev->dma_params[SNDRV_PCM_STREAM_PLAYBACK].port_addr =
+ (dma_addr_t)BCM2708_I2S_FIFO_PHYSICAL_ADDR;
+ dev->dma_params[SNDRV_PCM_STREAM_CAPTURE].port_addr =
+ (dma_addr_t)BCM2708_I2S_FIFO_PHYSICAL_ADDR;
+
+ dev->dma_params[SNDRV_PCM_STREAM_PLAYBACK].dma_req =
+ BCM2708_DMA_DREQ_PCM_TX;
+ dev->dma_params[SNDRV_PCM_STREAM_CAPTURE].dma_req =
+ BCM2708_DMA_DREQ_PCM_RX;
+
+ /* Store the pdev */
+ dev->dev = &pdev->dev;
+ dev_set_drvdata(&pdev->dev, dev);
+
+ return snd_soc_register_dai(&pdev->dev, &bcm2708_i2s_dai);
+}
+
+static int bcm2708_i2s_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_dai(&pdev->dev);
+ return 0;
+}
+
+static struct platform_driver bcm2708_i2s_driver = {
+ .probe = bcm2708_i2s_probe,
+ .remove = bcm2708_i2s_remove,
+ .driver = {
+ .name = "bcm2708-i2s",
+ .owner = THIS_MODULE,
+ },
+};
+
+module_platform_driver(bcm2708_i2s_driver);
+
+MODULE_AUTHOR("Florian Meier");
+MODULE_DESCRIPTION("BCM2708 I2S Interface");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/bcm2708/bcm2708-pcm.c b/sound/soc/bcm2708/bcm2708-pcm.c
new file mode 100644
index 0000000..4b3e688
--- /dev/null
+++ b/sound/soc/bcm2708/bcm2708-pcm.c
@@ -0,0 +1,303 @@
+/*
+ * ALSA PCM interface for Broadcom BCM2708 SoC
+ *
+ * Author: Florian Meier, <florian.meier(a)koalo.de>
+ * Copyright 2013
+ *
+ * based on
+ * ALSA PCM interface for the OMAP SoC
+ * Copyright (C) 2008 Nokia Corporation
+ * Contact: Jarkko Nikula <jarkko.nikula(a)bitmer.com>
+ * Peter Ujfalusi <peter.ujfalusi(a)ti.com>
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * TODO This should be transferred to the generic DMA engine driver
+ * as soon as the official BCM2708 kernel migrates to 3.10.
+ * Otherwise, it would be difficult to test.
+ */
+
+#include <linux/dma-mapping.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/bcm2708-dmaengine.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/dmaengine_pcm.h>
+#include <sound/soc.h>
+
+#include "bcm2708-pcm.h"
+
+static const struct snd_pcm_hardware bcm2708_pcm_hardware = {
+ .info = SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_PAUSE |
+ SNDRV_PCM_INFO_RESUME |
+ SNDRV_PCM_INFO_JOINT_DUPLEX |
+ SNDRV_PCM_INFO_NO_PERIOD_WAKEUP,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S32_LE,
+ .period_bytes_min = 32,
+ .period_bytes_max = 64 * 1024,
+ .periods_min = 2,
+ .periods_max = 255,
+ .buffer_bytes_max = 128 * 1024,
+};
+
+static int bcm2708_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct bcm2708_pcm_dma_data *dma_data;
+ struct bcm2708_dma_slave_config config;
+ struct dma_chan *chan;
+ int err = 0;
+
+ /* get DMA data (e.g. FIFO address and DREQ) */
+ dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
+
+ /*
+ * There seems to be no use for bufferless
+ * transfer with this SoC.
+ */
+ if (!dma_data)
+ return 0;
+
+ snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+ runtime->dma_bytes = params_buffer_bytes(params);
+
+ chan = snd_dmaengine_pcm_get_chan(substream);
+ if (!chan)
+ return -EINVAL;
+
+ /* fills in addr_width and direction */
+ err = snd_hwparams_to_dma_slave_config(substream, params, &config.cfg);
+ if (err)
+ return err;
+
+ /* Override the *_dma addr_width if requested by the DAI driver */
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ config.cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+ else
+ config.cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+
+ config.cfg.src_addr = dma_data->port_addr;
+ config.cfg.dst_addr = dma_data->port_addr;
+ config.cfg.src_maxburst = dma_data->packet_size;
+ config.cfg.dst_maxburst = dma_data->packet_size;
+ config.sync_dreq = dma_data->dma_req;
+
+ return dmaengine_slave_config(chan, &config.cfg);
+}
+
+static int bcm2708_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ snd_pcm_set_runtime_buffer(substream, NULL);
+ return 0;
+}
+
+static int bcm2708_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct bcm2708_pcm_dma_data *dma_data;
+ int ret = 0;
+
+ dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ break;
+
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ if (ret == 0)
+ ret = snd_dmaengine_pcm_trigger(substream, cmd);
+
+ return ret;
+}
+
+static snd_pcm_uframes_t bcm2708_pcm_pointer(
+ struct snd_pcm_substream *substream)
+{
+ return snd_dmaengine_pcm_pointer(substream);
+}
+
+static int bcm2708_pcm_open(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct bcm2708_pcm_dma_data *dma_data;
+ int ret;
+
+ snd_soc_set_runtime_hwparams(substream, &bcm2708_pcm_hardware);
+
+ /* Ensure that buffer size is a multiple of period size */
+ ret = snd_pcm_hw_constraint_integer(runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS);
+ if (ret < 0)
+ return ret;
+
+ dma_data = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
+ return snd_dmaengine_pcm_open(substream, NULL, NULL);
+}
+
+static int bcm2708_pcm_close(struct snd_pcm_substream *substream)
+{
+ snd_dmaengine_pcm_close(substream);
+ return 0;
+}
+
+static int bcm2708_pcm_mmap(struct snd_pcm_substream *substream,
+ struct vm_area_struct *vma)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ return dma_mmap_writecombine(substream->pcm->card->dev, vma,
+ runtime->dma_area,
+ runtime->dma_addr,
+ runtime->dma_bytes);
+}
+
+static struct snd_pcm_ops bcm2708_pcm_ops = {
+ .open = bcm2708_pcm_open,
+ .close = bcm2708_pcm_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = bcm2708_pcm_hw_params,
+ .hw_free = bcm2708_pcm_hw_free,
+ .trigger = bcm2708_pcm_trigger,
+ .pointer = bcm2708_pcm_pointer,
+ .mmap = bcm2708_pcm_mmap,
+};
+
+static u64 bcm2708_pcm_dmamask = DMA_BIT_MASK(64);
+
+static int bcm2708_pcm_preallocate_dma_buffer(struct snd_pcm *pcm,
+ int stream)
+{
+ struct snd_pcm_substream *substream = pcm->streams[stream].substream;
+ struct snd_dma_buffer *buf = &substream->dma_buffer;
+ size_t size = bcm2708_pcm_hardware.buffer_bytes_max;
+
+ buf->dev.type = SNDRV_DMA_TYPE_DEV;
+ buf->dev.dev = pcm->card->dev;
+ buf->private_data = NULL;
+ buf->area = dma_alloc_writecombine(pcm->card->dev, size,
+ &buf->addr, GFP_KERNEL);
+ if (!buf->area)
+ return -ENOMEM;
+
+ buf->bytes = size;
+ return 0;
+}
+
+static void bcm2708_pcm_free_dma_buffers(struct snd_pcm *pcm)
+{
+ struct snd_pcm_substream *substream;
+ struct snd_dma_buffer *buf;
+ int stream;
+
+ for (stream = 0; stream < 2; stream++) {
+ substream = pcm->streams[stream].substream;
+ if (!substream)
+ continue;
+
+ buf = &substream->dma_buffer;
+ if (!buf->area)
+ continue;
+
+ dma_free_writecombine(pcm->card->dev, buf->bytes,
+ buf->area, buf->addr);
+ buf->area = NULL;
+ }
+}
+
+static int bcm2708_pcm_new(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_card *card = rtd->card->snd_card;
+ struct snd_pcm *pcm = rtd->pcm;
+ int ret = 0;
+
+ if (!card->dev->dma_mask)
+ card->dev->dma_mask = &bcm2708_pcm_dmamask;
+ if (!card->dev->coherent_dma_mask)
+ card->dev->coherent_dma_mask = DMA_BIT_MASK(64);
+
+ if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) {
+ ret = bcm2708_pcm_preallocate_dma_buffer(pcm,
+ SNDRV_PCM_STREAM_PLAYBACK);
+ if (ret)
+ goto out;
+ }
+
+ if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) {
+ ret = bcm2708_pcm_preallocate_dma_buffer(pcm,
+ SNDRV_PCM_STREAM_CAPTURE);
+ if (ret)
+ goto out;
+ }
+
+out:
+ /* free preallocated buffers in case of error */
+ if (ret)
+ bcm2708_pcm_free_dma_buffers(pcm);
+
+ return ret;
+}
+
+static struct snd_soc_platform_driver bcm2708_soc_platform = {
+ .ops = &bcm2708_pcm_ops,
+ .pcm_new = bcm2708_pcm_new,
+ .pcm_free = bcm2708_pcm_free_dma_buffers,
+};
+
+static int bcm2708_pcm_probe(struct platform_device *pdev)
+{
+ return snd_soc_register_platform(&pdev->dev,
+ &bcm2708_soc_platform);
+}
+
+static int bcm2708_pcm_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_platform(&pdev->dev);
+ return 0;
+}
+
+static struct platform_driver bcm2708_pcm_driver = {
+ .driver = {
+ .name = "bcm2708-pcm-audio",
+ .owner = THIS_MODULE,
+ },
+
+ .probe = bcm2708_pcm_probe,
+ .remove = bcm2708_pcm_remove,
+};
+
+module_platform_driver(bcm2708_pcm_driver);
+
+MODULE_AUTHOR("Florian Meier");
+MODULE_DESCRIPTION("BCM2708 PCM DMA module");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:bcm2708-pcm-audio");
diff --git a/sound/soc/bcm2708/bcm2708-pcm.h b/sound/soc/bcm2708/bcm2708-pcm.h
new file mode 100644
index 0000000..e6a6acb
--- /dev/null
+++ b/sound/soc/bcm2708/bcm2708-pcm.h
@@ -0,0 +1,40 @@
+/*
+ * ALSA PCM interface for Broadcom BCM2708 SoC
+ *
+ * Author: Florian Meier, <florian.meier(a)koalo.de>
+ * Copyright 2013
+ *
+ * based on
+ * ALSA PCM interface for the OMAP SoC
+ * Copyright (C) 2008 Nokia Corporation
+ * Contact: Jarkko Nikula <jarkko.nikula(a)bitmer.com>
+ * Peter Ujfalusi <peter.ujfalusi(a)ti.com>
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef __BCM2708_PCM_H__
+#define __BCM2708_PCM_H__
+
+struct snd_pcm_substream;
+
+struct bcm2708_pcm_dma_data {
+ char *name; /* stream identifier */
+ int dma_req; /* DMA request line */
+ unsigned long port_addr; /* transmit/receive register */
+ int packet_size;
+};
+
+#endif
diff --git a/sound/soc/bcm2708/rpi-mbed.c b/sound/soc/bcm2708/rpi-mbed.c
new file mode 100644
index 0000000..cb8d25f
--- /dev/null
+++ b/sound/soc/bcm2708/rpi-mbed.c
@@ -0,0 +1,113 @@
+/*
+ * ASoC driver for mbed AudioCODEC (with a TLV320AIC23B)
+ * connected to a Raspberry Pi
+ *
+ * Author: Florian Meier, <florian.meier(a)koalo.de>
+ * Copyright 2013
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+
+#include "../codecs/tlv320aic23.h"
+
+static int snd_rpi_mbed_init(struct snd_soc_pcm_runtime *rtd)
+{
+ return 0;
+}
+
+static int snd_rpi_mbed_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ int sysclk;
+
+ sysclk = 12000000; /* this is fixed on this board */
+
+ /* set tlv320aic23 sysclk */
+ snd_soc_dai_set_sysclk(codec_dai, 0, sysclk, 0);
+
+ return 0;
+}
+
+/* machine stream operations */
+static struct snd_soc_ops snd_rpi_mbed_ops = {
+ .hw_params = snd_rpi_mbed_hw_params,
+};
+
+static struct snd_soc_dai_link snd_rpi_mbed_dai[] = {
+{
+ .name = "TLV320AIC23",
+ .stream_name = "TLV320AIC23 HiFi",
+ .cpu_dai_name = "bcm2708-i2s.0",
+ .codec_dai_name = "tlv320aic23-hifi",
+ .platform_name = "bcm2708-pcm-audio.0",
+ .codec_name = "tlv320aic23-codec.1-001b",
+ .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS,
+ .ops = &snd_rpi_mbed_ops,
+ .init = snd_rpi_mbed_init,
+},
+};
+
+/* audio machine driver */
+static struct snd_soc_card snd_rpi_mbed = {
+ .name = "snd_rpi_mbed",
+ .dai_link = snd_rpi_mbed_dai,
+ .num_links = ARRAY_SIZE(snd_rpi_mbed_dai),
+};
+
+static int snd_rpi_mbed_probe(struct platform_device *pdev)
+{
+ int ret = 0;
+
+ snd_rpi_mbed.dev = &pdev->dev;
+ ret = snd_soc_register_card(&snd_rpi_mbed);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "snd_soc_register_card() failed: %d\n", ret);
+ }
+
+ return ret;
+}
+
+
+static int snd_rpi_mbed_remove(struct platform_device *pdev)
+{
+ return snd_soc_unregister_card(&snd_rpi_mbed);
+}
+
+static struct platform_driver snd_rpi_mbed_driver = {
+ .driver = {
+ .name = "snd-rpi-mbed",
+ .owner = THIS_MODULE,
+ },
+ .probe = snd_rpi_mbed_probe,
+ .remove = snd_rpi_mbed_remove,
+};
+
+module_platform_driver(snd_rpi_mbed_driver);
+
+MODULE_AUTHOR("Florian Meier");
+MODULE_DESCRIPTION("ASoC Driver for Raspberry Pi connected to mbed AudioCODEC");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/bcm2708/rpi-proto.c b/sound/soc/bcm2708/rpi-proto.c
new file mode 100644
index 0000000..25be85a
--- /dev/null
+++ b/sound/soc/bcm2708/rpi-proto.c
@@ -0,0 +1,131 @@
+/*
+ * ASoC driver for PROTO AudioCODEC (with a WM8731)
+ * connected to a Raspberry Pi
+ *
+ * Author: Florian Meier, <florian.meier(a)koalo.de>
+ * Copyright 2013
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/jack.h>
+
+#include "../codecs/wm8731.h"
+
+static const unsigned int wm8731_rates_12288000[] = {
+ 8000, 32000, 48000, 96000,
+};
+
+static struct snd_pcm_hw_constraint_list wm8731_constraints_12288000 = {
+ .list = wm8731_rates_12288000,
+ .count = ARRAY_SIZE(wm8731_rates_12288000),
+};
+
+static int snd_rpi_proto_startup(struct snd_pcm_substream *substream)
+{
+ /* Setup constraints, because there is a 12.288 MHz XTAL on the board */
+ snd_pcm_hw_constraint_list(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_RATE,
+ &wm8731_constraints_12288000);
+ return 0;
+}
+
+static int snd_rpi_proto_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_dai *codec_dai = rtd->codec_dai;
+ int sysclk = 12288000; /* This is fixed on this board */
+
+ /* Set proto sysclk */
+ int ret = snd_soc_dai_set_sysclk(codec_dai, WM8731_SYSCLK_XTAL,
+ sysclk, SND_SOC_CLOCK_IN);
+ if (ret < 0) {
+ dev_err(substream->pcm->dev,
+ "Failed to set WM8731 SYSCLK: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+/* machine stream operations */
+static struct snd_soc_ops snd_rpi_proto_ops = {
+ .startup = snd_rpi_proto_startup,
+ .hw_params = snd_rpi_proto_hw_params,
+};
+
+static struct snd_soc_dai_link snd_rpi_proto_dai[] = {
+{
+ .name = "WM8731",
+ .stream_name = "WM8731 HiFi",
+ .cpu_dai_name = "bcm2708-i2s.0",
+ .codec_dai_name = "wm8731-hifi",
+ .platform_name = "bcm2708-pcm-audio.0",
+ .codec_name = "wm8731.1-001a",
+ .dai_fmt = SND_SOC_DAIFMT_I2S
+ | SND_SOC_DAIFMT_NB_NF
+ | SND_SOC_DAIFMT_CBM_CFM,
+ .ops = &snd_rpi_proto_ops,
+},
+};
+
+/* audio machine driver */
+static struct snd_soc_card snd_rpi_proto = {
+ .name = "snd_rpi_proto",
+ .dai_link = snd_rpi_proto_dai,
+ .num_links = ARRAY_SIZE(snd_rpi_proto_dai),
+};
+
+static int snd_rpi_proto_probe(struct platform_device *pdev)
+{
+ int ret = 0;
+
+ snd_rpi_proto.dev = &pdev->dev;
+ ret = snd_soc_register_card(&snd_rpi_proto);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "snd_soc_register_card() failed: %d\n", ret);
+ }
+
+ return ret;
+}
+
+
+static int snd_rpi_proto_remove(struct platform_device *pdev)
+{
+ return snd_soc_unregister_card(&snd_rpi_proto);
+}
+
+static struct platform_driver snd_rpi_proto_driver = {
+ .driver = {
+ .name = "snd-rpi-proto",
+ .owner = THIS_MODULE,
+ },
+ .probe = snd_rpi_proto_probe,
+ .remove = snd_rpi_proto_remove,
+};
+
+module_platform_driver(snd_rpi_proto_driver);
+
+MODULE_AUTHOR("Florian Meier");
+MODULE_DESCRIPTION("ASoC Driver for Raspberry Pi connected to PROTO board (WM8731)");
+MODULE_LICENSE("GPL");
--
1.7.9.5
4
9
24 May '13
Add --with-max-cards option to specify the max number of cards in
configure script, when the support for more than 32 cards is
required.
Signed-off-by: Takashi Iwai <tiwai(a)suse.de>
---
configure.in | 14 ++++++++++++++
src/confmisc.c | 2 +-
src/control/cards.c | 6 +++---
src/control/control_hw.c | 2 +-
src/hwdep/hwdep_hw.c | 2 +-
5 files changed, 20 insertions(+), 6 deletions(-)
diff --git a/configure.in b/configure.in
index 35b8e84..3d394fc 100644
--- a/configure.in
+++ b/configure.in
@@ -632,6 +632,20 @@ for t in $CTL_PLUGIN_LIST; do
fi
done
+dnl Max number of cards
+AC_MSG_CHECKING(for max number of cards)
+AC_ARG_WITH(max-cards,
+ AS_HELP_STRING([--with-max-cards], [Specify the max number of cards (default = 32)]),
+ [ max_cards="$withval" ], [ max_cards="32" ])
+AC_MSG_RESULT([$max_cards])
+
+if test "$max_cards" -lt 1; then
+ AC_ERROR([Invalid max cards $max_cards])
+elif test "$max_cards" -gt 256; then
+ AC_ERROR([Invalid max cards $max_cards])
+fi
+AC_DEFINE_UNQUOTED(SND_MAX_CARDS, $max_cards, [Max number of cards])
+
dnl Make a symlink for inclusion of alsa/xxx.h
if test ! -L "$srcdir"/include/alsa ; then
echo "Making a symlink include/alsa"
diff --git a/src/confmisc.c b/src/confmisc.c
index 80b0027..af686be 100644
--- a/src/confmisc.c
+++ b/src/confmisc.c
@@ -668,7 +668,7 @@ int snd_determine_driver(int card, char **driver)
char *res = NULL;
int err;
- assert(card >= 0 && card <= 32);
+ assert(card >= 0 && card <= SND_MAX_CARDS);
err = open_ctl(card, &ctl);
if (err < 0) {
SNDERR("could not open control for card %i", card);
diff --git a/src/control/cards.c b/src/control/cards.c
index b528e33..5d7376c 100644
--- a/src/control/cards.c
+++ b/src/control/cards.c
@@ -103,7 +103,7 @@ int snd_card_next(int *rcard)
return -EINVAL;
card = *rcard;
card = card < 0 ? 0 : card + 1;
- for (; card < 32; card++) {
+ for (; card < SND_MAX_CARDS; card++) {
if (snd_card_load(card)) {
*rcard = card;
return 0;
@@ -134,7 +134,7 @@ int snd_card_get_index(const char *string)
(isdigit(*string) && isdigit(*(string + 1)) && *(string + 2) == 0)) {
if (sscanf(string, "%i", &card) != 1)
return -EINVAL;
- if (card < 0 || card > 31)
+ if (card < 0 || card >= SND_MAX_CARDS)
return -EINVAL;
err = snd_card_load1(card);
if (err >= 0)
@@ -143,7 +143,7 @@ int snd_card_get_index(const char *string)
}
if (string[0] == '/') /* device name */
return snd_card_load2(string);
- for (card = 0; card < 32; card++) {
+ for (card = 0; card < SND_MAX_CARDS; card++) {
#ifdef SUPPORT_ALOAD
if (! snd_card_load(card))
continue;
diff --git a/src/control/control_hw.c b/src/control/control_hw.c
index 90c4ba7..148097f 100644
--- a/src/control/control_hw.c
+++ b/src/control/control_hw.c
@@ -382,7 +382,7 @@ int snd_ctl_hw_open(snd_ctl_t **handle, const char *name, int card, int mode)
*handle = NULL;
- if (CHECK_SANITY(card < 0 || card >= 32)) {
+ if (CHECK_SANITY(card < 0 || card >= SND_MAX_CARDS)) {
SNDMSG("Invalid card index %d", card);
return -EINVAL;
}
diff --git a/src/hwdep/hwdep_hw.c b/src/hwdep/hwdep_hw.c
index e4fcdc3..4314e32 100644
--- a/src/hwdep/hwdep_hw.c
+++ b/src/hwdep/hwdep_hw.c
@@ -112,7 +112,7 @@ int snd_hwdep_hw_open(snd_hwdep_t **handle, const char *name, int card, int devi
*handle = NULL;
- if (card < 0 || card >= 32)
+ if (card < 0 || card >= SND_MAX_CARDS)
return -EINVAL;
sprintf(filename, SNDRV_FILE_HWDEP, card, device);
fd = snd_open_device(filename, mode);
--
1.8.2.3
1
0
[alsa-devel] [PATCH 1/2] ALSA: Fix the default suffix string with high card number
by Takashi Iwai 24 May '13
by Takashi Iwai 24 May '13
24 May '13
ALSA core tries to add a suffix as "_1" automatically when the given
id string conflicts. The current code assumes implicitly that the max
card number is 16 so that the single hex "_X" suffix can be put.
However, with the dynamic device management, the card can be at most
32, so it can put even a non-hex character there. Also, when the max
card number is increased in future, this would result in worse.
This patch rewrites the code to add the suffix string in a simpler
(thus cleaner) way. It can support up to three digits, so it should
suffice for most requirements.
Signed-off-by: Takashi Iwai <tiwai(a)suse.de>
---
sound/core/init.c | 30 +++++++++++++-----------------
1 file changed, 13 insertions(+), 17 deletions(-)
diff --git a/sound/core/init.c b/sound/core/init.c
index 6ef0640..ed4a481 100644
--- a/sound/core/init.c
+++ b/sound/core/init.c
@@ -549,7 +549,6 @@ static void snd_card_set_id_no_lock(struct snd_card *card, const char *src,
const char *nid)
{
int len, loops;
- bool with_suffix;
bool is_default = false;
char *id;
@@ -565,26 +564,23 @@ static void snd_card_set_id_no_lock(struct snd_card *card, const char *src,
is_default = true;
}
- with_suffix = false;
+ len = strlen(id);
for (loops = 0; loops < SNDRV_CARDS; loops++) {
+ char *spos;
+ char sfxstr[5]; /* "_012" */
+ int sfxlen;
+
if (card_id_ok(card, id))
return; /* OK */
- len = strlen(id);
- if (!with_suffix) {
- /* add the "_X" suffix */
- char *spos = id + len;
- if (len > sizeof(card->id) - 3)
- spos = id + sizeof(card->id) - 3;
- strcpy(spos, "_1");
- with_suffix = true;
- } else {
- /* modify the existing suffix */
- if (id[len - 1] != '9')
- id[len - 1]++;
- else
- id[len - 1] = 'A';
- }
+ /* Add _XYZ suffix */
+ sprintf(sfxstr, "_%X", loops + 1);
+ sfxlen = strlen(sfxstr);
+ if (len + sfxlen >= sizeof(card->id))
+ spos = id + sizeof(card->id) - sfxlen - 1;
+ else
+ spos = id + len;
+ strcpy(spos, sfxstr);
}
/* fallback to the default id */
if (!is_default) {
--
1.8.2.3
1
1