[alsa-devel] [PATCH 16/16] ASoC: Ux500: Add machine-driver

Ola Lilja ola.o.lilja at stericsson.com
Tue Mar 13 16:11:43 CET 2012


Add machine-driver for ST-Ericsson U8500 platform, including
support for the AB8500-codec.

Signed-off-by: Ola Lilja <ola.o.lilja at stericsson.com>
---
 sound/soc/ux500/Kconfig        |    8 +
 sound/soc/ux500/Makefile       |   13 +
 sound/soc/ux500/u8500.c        |  150 ++++++++
 sound/soc/ux500/ux500_ab8500.c |  828 ++++++++++++++++++++++++++++++++++++++++
 sound/soc/ux500/ux500_ab8500.h |   25 ++
 5 files changed, 1024 insertions(+), 0 deletions(-)
 create mode 100644 sound/soc/ux500/u8500.c
 create mode 100644 sound/soc/ux500/ux500_ab8500.c
 create mode 100644 sound/soc/ux500/ux500_ab8500.h

diff --git a/sound/soc/ux500/Kconfig b/sound/soc/ux500/Kconfig
index 6c0271c..f651564 100644
--- a/sound/soc/ux500/Kconfig
+++ b/sound/soc/ux500/Kconfig
@@ -23,6 +23,14 @@ config SND_SOC_UX500_PLATFORM
 	help
 		Say Y if you want to enable the Ux500 platform-driver.
 
+config SND_SOC_UX500_AB8500
+	bool "Codec/Machine - AB8500 (MSA)"
+	depends on SND_SOC_UX500_MSP_I2S && SND_SOC_UX500_PLATFORM && AB8500_CORE && AB8500_GPADC
+	select SND_SOC_AB8500
+	default n
+	help
+	   Select this to enable the Ux500 machine-driver and the AB8500 codec-driver.
+
 config SND_SOC_UX500_DEBUG
 	bool "Activate Ux500 platform debug-mode (pr_debug)"
 	depends on SND_SOC_UX500
diff --git a/sound/soc/ux500/Makefile b/sound/soc/ux500/Makefile
index 72e806e..7c1cce6 100644
--- a/sound/soc/ux500/Makefile
+++ b/sound/soc/ux500/Makefile
@@ -3,6 +3,9 @@
 ifdef CONFIG_SND_SOC_UX500_DEBUG
 CFLAGS_ux500_msp_dai.o := -DDEBUG
 CFLAGS_ux500_msp_i2s.o := -DDEBUG
+CFLAGS_ux500_pcm.o := -DDEBUG
+CFLAGS_u8500.o := -DDEBUG
+CFLAGS_ux500_ab8500.o := -DDEBUG
 endif
 
 ifdef CONFIG_SND_SOC_UX500_MSP_I2S
@@ -15,3 +18,13 @@ snd-soc-ux500-platform-dma-objs := ux500_pcm.o
 obj-$(CONFIG_SND_SOC_UX500_PLATFORM) += snd-soc-ux500-platform-dma.o
 endif
 
+ifdef CONFIG_SND_SOC_UX500_AB8500
+snd-soc-ux500-machine-objs += ux500_ab8500.o
+endif
+
+obj-y += snd-soc-ux500-machine.o
+
+ifdef CONFIG_SND_SOC_UX500_PLATFORM
+snd-soc-u8500-objs := u8500.o
+obj-$(CONFIG_SND_SOC_UX500_PLATFORM) += snd-soc-u8500.o
+endif
diff --git a/sound/soc/ux500/u8500.c b/sound/soc/ux500/u8500.c
new file mode 100644
index 0000000..5092698
--- /dev/null
+++ b/sound/soc/ux500/u8500.c
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2012
+ *
+ * Author: Ola Lilja (ola.o.lilja at stericsson.com)
+ *         for ST-Ericsson.
+ *
+ * License terms:
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as published
+ * by the Free Software Foundation.
+ */
+
+#include <asm/mach-types.h>
+
+#include <linux/io.h>
+#include <linux/spi/spi.h>
+
+#include <sound/soc.h>
+#include <sound/initval.h>
+
+#include "ux500_pcm.h"
+#include "ux500_msp_dai.h"
+
+#ifdef CONFIG_SND_SOC_UX500_AB8500
+#include <ux500_ab8500.h>
+#endif
+
+static struct platform_device *pdev;
+
+/* Create dummy devices for platform drivers */
+
+static struct platform_device ux500_pcm = {
+		.name = "ux500-pcm",
+		.id = 0,
+		.dev = {
+			.platform_data = NULL,
+		},
+};
+
+/* Define the whole U8500 soundcard, linking platform to the codec-drivers  */
+struct snd_soc_dai_link u8500_dai_links[] = {
+	#ifdef CONFIG_SND_SOC_UX500_AB8500
+	{
+	.name = "ab8500_0",
+	.stream_name = "ab8500_0",
+	.cpu_dai_name = "ux500-msp-i2s.1",
+	.codec_dai_name = "ab8500-codec-dai.0",
+	.platform_name = "ux500-pcm.0",
+	.codec_name = "ab8500-codec.0",
+	.init = ux500_ab8500_machine_init,
+	.ops = ux500_ab8500_ops,
+	},
+	{
+	.name = "ab8500_1",
+	.stream_name = "ab8500_1",
+	.cpu_dai_name = "ux500-msp-i2s.3",
+	.codec_dai_name = "ab8500-codec-dai.1",
+	.platform_name = "ux500-pcm.0",
+	.codec_name = "ab8500-codec.0",
+	.init = NULL,
+	.ops = ux500_ab8500_ops,
+	},
+	#endif
+};
+
+static struct snd_soc_card u8500_card = {
+	.name = "U8500-card",
+	.probe = NULL,
+	.dai_link = u8500_dai_links,
+	.num_links = ARRAY_SIZE(u8500_dai_links),
+};
+
+static int __init u8500_soc_init(void)
+{
+	int ret;
+
+	pr_debug("%s: Enter.\n", __func__);
+
+	pdev = platform_device_alloc("soc-audio", -1);
+	if (!pdev)
+		return -ENOMEM;
+	dev_dbg(&pdev->dev, "%s: Platform device 'soc-audio' allocated.\n", __func__);
+
+	dev_dbg(&pdev->dev, "%s: Card %s: Set platform drvdata.\n",
+		__func__,
+		u8500_card.name);
+	platform_set_drvdata(pdev, &u8500_card);
+
+	dev_dbg(&pdev->dev, "%s: Card %s: Add platform device.\n",
+		__func__,
+		u8500_card.name);
+	ret = platform_device_add(pdev);
+	if (ret) {
+		dev_err(&pdev->dev, "%s: Error: Failed to add platform device (%s).\n",
+			__func__,
+			u8500_card.name);
+		platform_device_put(pdev);
+	}
+
+	u8500_card.dev = &pdev->dev;
+
+	#ifdef CONFIG_SND_SOC_UX500_AB8500
+	dev_dbg(&pdev->dev, "%s: Calling init-function for AB8500 machine driver.\n",
+		__func__);
+	ret = ux500_ab8500_soc_machine_drv_init();
+	if (ret)
+		dev_err(&pdev->dev, "%s: ux500_ab8500_soc_machine_drv_init failed (%d).\n",
+			__func__, ret);
+	#endif
+
+	dev_dbg(&pdev->dev, "%s: Register device to generate a probe for Ux500-pcm platform.\n",
+		__func__);
+	platform_device_register(&ux500_pcm);
+
+	dev_dbg(&pdev->dev, "%s: Card %s: num_links = %d\n",
+		__func__,
+		u8500_card.name,
+		u8500_card.num_links);
+	dev_dbg(&pdev->dev, "%s: Card %s: DAI-link 0: name = %s\n",
+		__func__,
+		u8500_card.name,
+		u8500_card.dai_link[0].name);
+	dev_dbg(&pdev->dev, "%s: Card %s: DAI-link 0: stream_name = %s\n",
+		__func__,
+		u8500_card.name,
+		u8500_card.dai_link[0].stream_name);
+
+	return ret;
+}
+
+static void __exit u8500_soc_exit(void)
+{
+	pr_debug("%s: Enter.\n", __func__);
+
+	#ifdef CONFIG_SND_SOC_UX500_AB8500
+	pr_debug("%s: Calling exit-function for AB8500 machine driver.\n",
+		__func__);
+	ux500_ab8500_soc_machine_drv_cleanup();
+	#endif
+
+	pr_debug("%s: Unregister platform device (%s).\n",
+		__func__,
+		u8500_card.name);
+	platform_device_unregister(pdev);
+}
+
+module_init(u8500_soc_init);
+module_exit(u8500_soc_exit);
+
diff --git a/sound/soc/ux500/ux500_ab8500.c b/sound/soc/ux500/ux500_ab8500.c
new file mode 100644
index 0000000..faa05bc
--- /dev/null
+++ b/sound/soc/ux500/ux500_ab8500.c
@@ -0,0 +1,828 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2012
+ *
+ * Author: Ola Lilja <ola.o.lilja at stericsson.com>,
+ *         Kristoffer Karlsson <kristoffer.karlsson at stericsson.com>
+ *         for ST-Ericsson.
+ *
+ * License terms:
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as published
+ * by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/io.h>
+#include <linux/regulator/consumer.h>
+
+#include <mach/hardware.h>
+
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/pcm.h>
+#include <sound/jack.h>
+#include <sound/pcm_params.h>
+
+#include "ux500_pcm.h"
+#include "ux500_msp_dai.h"
+#include "../codecs/ab8500_audio.h"
+
+#define TX_SLOT_MONO	0x0008
+#define TX_SLOT_STEREO	0x000a
+#define RX_SLOT_MONO	0x0001
+#define RX_SLOT_STEREO	0x0003
+#define TX_SLOT_8CH	0x00FF
+#define RX_SLOT_8CH	0x00FF
+
+#define DEF_TX_SLOTS	TX_SLOT_STEREO
+#define DEF_RX_SLOTS	RX_SLOT_MONO
+
+#define DRIVERMODE_NORMAL	0
+#define DRIVERMODE_CODEC_ONLY	1
+
+/* Power-control */
+static DEFINE_MUTEX(power_lock);
+static int ab8500_power_count;
+
+/* Clocks */
+/* audioclk -> intclk -> sysclk/ulpclk */
+static int master_clock_sel;
+static struct clk *clk_ptr_audioclk;
+static struct clk *clk_ptr_intclk;
+static struct clk *clk_ptr_sysclk;
+static struct clk *clk_ptr_ulpclk;
+static struct clk *clk_ptr_gpio1;
+
+static const char * const enum_mclk[] = {
+	"SYSCLK",
+	"ULPCLK"
+};
+static SOC_ENUM_SINGLE_EXT_DECL(soc_enum_mclk, enum_mclk);
+
+/* ANC States */
+static const char * const enum_anc_state[] = {
+	"Unconfigured",
+	"Apply FIR and IIR",
+	"FIR and IIR are configured",
+	"Apply FIR",
+	"FIR is configured",
+	"Apply IIR",
+	"IIR is configured"
+};
+static SOC_ENUM_SINGLE_EXT_DECL(soc_enum_ancstate, enum_anc_state);
+enum anc_state {
+	ANC_UNCONFIGURED = 0,
+	ANC_APPLY_FIR_IIR = 1,
+	ANC_FIR_IIR_CONFIGURED = 2,
+	ANC_APPLY_FIR = 3,
+	ANC_FIR_CONFIGURED = 4,
+	ANC_APPLY_IIR = 5,
+	ANC_IIR_CONFIGURED = 6
+};
+static enum anc_state anc_status = ANC_UNCONFIGURED;
+
+/* Regulators */
+enum regulator_idx {
+	REGULATOR_AUDIO,
+	REGULATOR_DMIC,
+	REGULATOR_AMIC1,
+	REGULATOR_AMIC2
+};
+static struct regulator_bulk_data reg_info[4] = {
+	{	.consumer = NULL, .supply = "V-AUD"	},
+	{	.consumer = NULL, .supply = "V-DMIC"	},
+	{	.consumer = NULL, .supply = "V-AMIC1"	},
+	{	.consumer = NULL, .supply = "V-AMIC2"	}
+};
+static bool reg_enabled[4] =  {
+	false,
+	false,
+	false,
+	false
+};
+static int reg_claim[4];
+enum amic_idx { AMIC_1A, AMIC_1B, AMIC_2 };
+struct amic_conf {
+	enum regulator_idx reg_id;
+	bool enabled;
+	char *name;
+};
+static struct amic_conf amic_info[3] = {
+	{ REGULATOR_AMIC1, false, "amic1a" },
+	{ REGULATOR_AMIC1, false, "amic1b" },
+	{ REGULATOR_AMIC2, false, "amic2" }
+};
+
+/* Slot configuration */
+static unsigned int tx_slots = DEF_TX_SLOTS;
+static unsigned int rx_slots = DEF_RX_SLOTS;
+
+/* Regulators */
+
+static int enable_regulator(struct device *dev, enum regulator_idx idx)
+{
+	int ret;
+
+	if (reg_info[idx].consumer == NULL) {
+		dev_err(dev, "%s: Failure to enable regulator '%s'\n",
+			__func__, reg_info[idx].supply);
+		return -EIO;
+	}
+
+	if (reg_enabled[idx])
+		return 0;
+
+	ret = regulator_enable(reg_info[idx].consumer);
+	if (ret != 0) {
+		dev_err(dev, "%s: Failure to enable regulator '%s' (ret = %d)\n",
+			__func__, reg_info[idx].supply, ret);
+		return -EIO;
+	};
+
+	reg_enabled[idx] = true;
+	dev_dbg(dev, "%s: Enabled regulator '%s', status: %d, %d, %d, %d\n",
+		__func__,
+		reg_info[idx].supply,
+		(int)reg_enabled[0],
+		(int)reg_enabled[1],
+		(int)reg_enabled[2],
+		(int)reg_enabled[3]);
+	return 0;
+}
+
+static void disable_regulator(struct device *dev, enum regulator_idx idx)
+{
+	if (reg_info[idx].consumer == NULL) {
+		dev_err(dev, "%s: Failure to disable regulator '%s'\n",
+			__func__, reg_info[idx].supply);
+		return;
+	}
+
+	if (!reg_enabled[idx])
+		return;
+
+	regulator_disable(reg_info[idx].consumer);
+
+	reg_enabled[idx] = false;
+	dev_dbg(dev, "%s: Disabled regulator '%s', status: %d, %d, %d, %d\n",
+		__func__,
+		reg_info[idx].supply,
+		(int)reg_enabled[0],
+		(int)reg_enabled[1],
+		(int)reg_enabled[2],
+		(int)reg_enabled[3]);
+}
+
+static int create_regulators(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_codec *codec = rtd->codec;
+	struct device *dev = rtd->card->dev;
+	int i, status = 0;
+
+	dev_dbg(dev, "%s: Enter.\n", __func__);
+
+	for (i = 0; i < ARRAY_SIZE(reg_info); ++i)
+		reg_info[i].consumer = NULL;
+
+	for (i = 0; i < ARRAY_SIZE(reg_info); ++i) {
+		reg_info[i].consumer = regulator_get(codec->dev, reg_info[i].supply);
+		if (IS_ERR(reg_info[i].consumer)) {
+			status = PTR_ERR(reg_info[i].consumer);
+			dev_err(dev, "%s: ERROR: Failed to get regulator '%s' "
+				"(ret = %d)!\n", __func__, reg_info[i].supply,
+				status);
+			reg_info[i].consumer = NULL;
+			goto err_get;
+		}
+	}
+
+	return 0;
+
+err_get:
+
+	for (i = 0; i < ARRAY_SIZE(reg_info); ++i) {
+		if (reg_info[i].consumer) {
+			regulator_put(reg_info[i].consumer);
+			reg_info[i].consumer = NULL;
+		}
+	}
+
+	return status;
+}
+
+static int claim_amic_regulator(struct device *dev, enum amic_idx amic_id)
+{
+	enum regulator_idx reg_id = amic_info[amic_id].reg_id;
+	int ret = 0;
+
+	reg_claim[reg_id]++;
+	if (reg_claim[reg_id] > 1)
+		goto cleanup;
+
+	ret = enable_regulator(dev, reg_id);
+	if (ret < 0) {
+		dev_err(dev, "%s: Failed to claim %s for %s (ret = %d)!",
+			__func__, reg_info[reg_id].supply,
+			amic_info[amic_id].name, ret);
+		reg_claim[reg_id]--;
+	}
+
+cleanup:
+	amic_info[amic_id].enabled = (ret == 0);
+
+	return ret;
+}
+
+static void release_amic_regulator(struct device *dev, enum amic_idx amic_id)
+{
+	enum regulator_idx reg_id = amic_info[amic_id].reg_id;
+
+	reg_claim[reg_id]--;
+	if (reg_claim[reg_id] <= 0) {
+		disable_regulator(dev, reg_id);
+		reg_claim[reg_id] = 0;
+	}
+
+	amic_info[amic_id].enabled = false;
+}
+
+/* Power/clock control */
+
+static int ux500_ab8500_power_control_inc(struct snd_soc_codec *codec)
+{
+	int ret = 0;
+	struct device *dev = codec->card->dev;
+
+	dev_dbg(dev, "%s: Enter.\n", __func__);
+
+	mutex_lock(&power_lock);
+
+	ab8500_power_count++;
+	dev_dbg(dev, "%s: ab8500_power_count changed from %d to %d",
+		__func__, ab8500_power_count-1, ab8500_power_count);
+
+	if (ab8500_power_count == 1) {
+		/* Turn on audio-regulator */
+		ret = enable_regulator(dev, REGULATOR_AUDIO);
+		if (ret < 0)
+			goto reg_err;
+
+		ret = clk_set_parent(clk_ptr_intclk,
+				(master_clock_sel == 0) ? clk_ptr_sysclk : clk_ptr_ulpclk);
+		if (ret != 0) {
+			dev_err(dev, "%s: ERROR: Setting master-clock to %s failed (ret = %d)!",
+				__func__, (master_clock_sel == 0) ? "SYSCLK" : "ULPCLK", ret);
+			ret = -EIO;
+			goto clk_err;
+		}
+		dev_dbg(dev, "%s: Enabling master-clock (%s).",
+			__func__, (master_clock_sel == 0) ? "SYSCLK" : "ULPCLK");
+
+		/* Enable audio-clock */
+		ret = clk_enable(clk_ptr_audioclk);
+		if (ret != 0) {
+			dev_err(dev, "%s: ERROR: clk_enable failed (ret = %d)!",
+				__func__, ret);
+			ret = -EIO;
+			goto clk_err;
+		}
+
+		/* Power on audio-parts of AB8500 */
+		ab8500_audio_power_control(codec, true);
+		if (ret < 0)
+			goto pwr_err;
+	}
+
+	goto out;
+
+pwr_err:
+	clk_disable(clk_ptr_audioclk);
+
+clk_err:
+	disable_regulator(dev, REGULATOR_AUDIO);
+
+reg_err:
+	ab8500_power_count = 0;
+
+out:
+	mutex_unlock(&power_lock);
+
+	dev_dbg(dev, "%s: Exit.\n", __func__);
+
+	return ret;
+}
+
+static void ux500_ab8500_power_control_dec(struct snd_soc_codec *codec)
+{
+	struct device *dev = codec->card->dev;
+
+	dev_dbg(dev, "%s: Enter.\n", __func__);
+
+	mutex_lock(&power_lock);
+
+	ab8500_power_count--;
+
+	dev_dbg(dev, "%s: ab8500_power_count changed from %d to %d",
+		__func__,
+		ab8500_power_count+1,
+		ab8500_power_count);
+
+	if (ab8500_power_count == 0) {
+		/* Power off audio-parts of AB8500 */
+		ab8500_audio_power_control(codec, false);
+
+		/* Disable audio-clock */
+		dev_dbg(dev, "%s: Disabling master-clock (%s).",
+			__func__, (master_clock_sel == 0) ? "SYSCLK" : "ULPCLK");
+		clk_disable(clk_ptr_audioclk);
+
+		/* Turn off audio-regulator */
+		disable_regulator(dev, REGULATOR_AUDIO);
+	}
+
+	mutex_unlock(&power_lock);
+
+	dev_dbg(dev, "%s: Exit.\n", __func__);
+}
+
+/* Controls - Non-DAPM Non-ASoC */
+
+static int mclk_input_control_get(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	ucontrol->value.enumerated.item[0] = master_clock_sel;
+
+	return 0;
+}
+
+static int mclk_input_control_put(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	unsigned int val;
+
+	val = (ucontrol->value.enumerated.item[0] != 0);
+	if (master_clock_sel == val)
+		return 0;
+
+	master_clock_sel = val;
+
+	return 1;
+}
+
+static const struct snd_kcontrol_new mclk_input_control = \
+	SOC_ENUM_EXT("Master Clock Select", soc_enum_mclk,
+		mclk_input_control_get, mclk_input_control_put);
+
+static int anc_status_control_get(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+	mutex_lock(&codec->mutex);
+	ucontrol->value.integer.value[0] = anc_status;
+	mutex_unlock(&codec->mutex);
+
+	return 0;
+}
+
+static int anc_status_control_put(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct device *dev = codec->card->dev;
+	bool apply_fir, apply_iir;
+	int req, ret;
+
+	dev_dbg(dev, "%s: Enter.\n", __func__);
+
+	req = ucontrol->value.integer.value[0];
+	if (req != ANC_APPLY_FIR_IIR && req != ANC_APPLY_FIR &&
+		req != ANC_APPLY_IIR) {
+		dev_err(dev, "%s: ERROR: Unsupported status to set '%s'!\n",
+			__func__, enum_anc_state[req]);
+		return -EINVAL;
+	}
+	apply_fir = req == ANC_APPLY_FIR || req == ANC_APPLY_FIR_IIR;
+	apply_iir = req == ANC_APPLY_IIR || req == ANC_APPLY_FIR_IIR;
+
+	ret = ux500_ab8500_power_control_inc(codec);
+	if (ret < 0) {
+		dev_err(dev, "%s: ERROR: Failed to enable power (ret = %d)!\n",
+			__func__, ret);
+		goto cleanup;
+	}
+
+	mutex_lock(&codec->mutex);
+
+	ab8500_audio_anc_configure(codec, apply_fir, apply_iir);
+
+	if (apply_fir) {
+		if (anc_status == ANC_IIR_CONFIGURED)
+			anc_status = ANC_FIR_IIR_CONFIGURED;
+		else if (anc_status != ANC_FIR_IIR_CONFIGURED)
+			anc_status =  ANC_FIR_CONFIGURED;
+	}
+	if (apply_iir) {
+		if (anc_status == ANC_FIR_CONFIGURED)
+			anc_status = ANC_FIR_IIR_CONFIGURED;
+		else if (anc_status != ANC_FIR_IIR_CONFIGURED)
+			anc_status =  ANC_IIR_CONFIGURED;
+	}
+
+	mutex_unlock(&codec->mutex);
+
+	ux500_ab8500_power_control_dec(codec);
+
+cleanup:
+	if (ret < 0)
+		dev_err(dev, "%s: Unable to configure ANC! (ret = %d)\n",
+			__func__, ret);
+
+	dev_dbg(dev, "%s: Exit.\n", __func__);
+
+	return (ret < 0) ? ret : 1;
+}
+
+static const struct snd_kcontrol_new anc_status_control = \
+	SOC_ENUM_EXT("ANC Status", soc_enum_ancstate,
+		anc_status_control_get, anc_status_control_put);
+
+/* DAPM-events */
+
+static int dapm_audioreg_event(struct snd_soc_dapm_widget *w,
+			struct snd_kcontrol *k, int event)
+{
+	if (SND_SOC_DAPM_EVENT_ON(event))
+		ux500_ab8500_power_control_inc(w->codec);
+	else
+		ux500_ab8500_power_control_dec(w->codec);
+
+	return 0;
+}
+
+static int dapm_amicreg_event(struct device *dev, enum amic_idx amic_id, int event)
+{
+	int ret = 0;
+
+	if (SND_SOC_DAPM_EVENT_ON(event))
+		ret = claim_amic_regulator(dev, amic_id);
+	else if (amic_info[amic_id].enabled)
+		release_amic_regulator(dev, amic_id);
+
+	return ret;
+}
+
+static int dapm_amic1areg_event(struct snd_soc_dapm_widget *w,
+			struct snd_kcontrol *k, int event)
+{
+	return dapm_amicreg_event(w->dapm->dev, AMIC_1A, event);
+}
+
+static int dapm_amic1breg_event(struct snd_soc_dapm_widget *w,
+			struct snd_kcontrol *k, int event)
+{
+	return dapm_amicreg_event(w->dapm->dev, AMIC_1B, event);
+}
+
+static int dapm_amic2reg_event(struct snd_soc_dapm_widget *w,
+			struct snd_kcontrol *k, int event)
+{
+	return dapm_amicreg_event(w->dapm->dev, AMIC_2, event);
+}
+
+static int dapm_dmicreg_event(struct snd_soc_dapm_widget *w,
+			struct snd_kcontrol *k, int event)
+{
+	struct device *dev = w->dapm->dev;
+	int ret = 0;
+
+	if (SND_SOC_DAPM_EVENT_ON(event))
+		ret = enable_regulator(dev, REGULATOR_DMIC);
+	else
+		disable_regulator(dev, REGULATOR_DMIC);
+
+	return ret;
+}
+
+/* DAPM-widgets */
+
+static const struct snd_soc_dapm_widget ux500_ab8500_dapm_widgets[] = {
+	SND_SOC_DAPM_SUPPLY("AUDIO Regulator",
+			SND_SOC_NOPM, 0, 0, dapm_audioreg_event,
+			SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+	SND_SOC_DAPM_SUPPLY("AMIC1A Regulator",
+			SND_SOC_NOPM, 0, 0, dapm_amic1areg_event,
+			SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+	SND_SOC_DAPM_SUPPLY("AMIC1B Regulator",
+			SND_SOC_NOPM, 0, 0, dapm_amic1breg_event,
+			SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+	SND_SOC_DAPM_SUPPLY("AMIC2 Regulator",
+			SND_SOC_NOPM, 0, 0, dapm_amic2reg_event,
+			SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+	SND_SOC_DAPM_SUPPLY("DMIC Regulator",
+			SND_SOC_NOPM, 0, 0, dapm_dmicreg_event,
+			SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+};
+
+/* DAPM-routes */
+
+static const struct snd_soc_dapm_route ux500_ab8500_dapm_intercon[] = {
+
+	/* Power AB8500 audio-block when AD/DA is active */
+	{"DAC", NULL, "AUDIO Regulator"},
+	{"ADC", NULL, "AUDIO Regulator"},
+
+	/* Power configured regulator when an analog mic is enabled */
+	{"MIC1A Input", NULL, "AMIC1A Regulator"},
+	{"MIC1B Input", NULL, "AMIC1B Regulator"},
+	{"MIC2 Input", NULL, "AMIC2 Regulator"},
+
+	/* Power DMIC-regulator when any digital mic is enabled */
+	{"DMic 1", NULL, "DMIC Regulator"},
+	{"DMic 2", NULL, "DMIC Regulator"},
+	{"DMic 3", NULL, "DMIC Regulator"},
+	{"DMic 4", NULL, "DMIC Regulator"},
+	{"DMic 5", NULL, "DMIC Regulator"},
+	{"DMic 6", NULL, "DMIC Regulator"},
+};
+
+static int add_widgets(struct snd_soc_codec *codec)
+{
+	struct device *dev = codec->card->dev;
+	int ret;
+
+	ret = snd_soc_dapm_new_controls(&codec->dapm,
+			ux500_ab8500_dapm_widgets,
+			ARRAY_SIZE(ux500_ab8500_dapm_widgets));
+	if (ret < 0) {
+		dev_err(dev, "%s: Failed to create DAPM controls (%d).\n",
+			__func__, ret);
+		return ret;
+	}
+
+	ret = snd_soc_dapm_add_routes(&codec->dapm,
+				ux500_ab8500_dapm_intercon,
+				ARRAY_SIZE(ux500_ab8500_dapm_intercon));
+	if (ret < 0) {
+		dev_err(dev, "%s: Failed to add DAPM routes (%d).\n",
+			__func__, ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+/* ASoC */
+
+int ux500_ab8500_startup(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct device *dev = rtd->card->dev;
+	int ret = 0;
+
+	dev_dbg(dev, "%s: Enter\n", __func__);
+
+	if (IS_ERR(clk_ptr_sysclk)) {
+		dev_err(dev, "%s: ERROR: Clocks needed for streaming not available!",
+			__func__);
+		return -EAGAIN;
+	}
+
+	/* Enable gpio.1-clock (needed by DSP in burst mode) */
+	ret = clk_enable(clk_ptr_gpio1);
+	if (ret) {
+		dev_err(dev, "%s: ERROR: clk_enable(gpio.1) failed (ret = %d)!",
+			__func__, ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+void ux500_ab8500_shutdown(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct device *dev = rtd->card->dev;
+
+	dev_dbg(dev, "%s: Enter\n", __func__);
+
+	/* Reset slots configuration to default(s) */
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		tx_slots = DEF_TX_SLOTS;
+	else
+		rx_slots = DEF_RX_SLOTS;
+
+	clk_disable(clk_ptr_gpio1);
+}
+
+int ux500_ab8500_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;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	struct device *dev = rtd->card->dev;
+	unsigned int fmt, fmt_if1;
+	int channels, ret = 0, slots, slot_width, driver_mode;
+	bool is_playback;
+
+	dev_dbg(dev, "%s: Enter\n", __func__);
+
+	dev_dbg(dev, "%s: substream->pcm->name = %s\n"
+		"substream->pcm->id = %s.\n"
+		"substream->name = %s.\n"
+		"substream->number = %d.\n",
+		__func__,
+		substream->pcm->name,
+		substream->pcm->id,
+		substream->name,
+		substream->number);
+
+	channels = params_channels(params);
+
+	/* Setup codec depending on driver-mode */
+	driver_mode = (channels == 8) ?
+		DRIVERMODE_CODEC_ONLY : DRIVERMODE_NORMAL;
+	dev_dbg(dev, "%s: Driver-mode: %s.\n", __func__,
+		(driver_mode == DRIVERMODE_NORMAL) ? "NORMAL" : "CODEC_ONLY");
+
+	ab8500_audio_set_bit_delay(codec_dai, 1);
+
+	if (driver_mode == DRIVERMODE_NORMAL) {
+		ab8500_audio_set_word_length(codec_dai, 16);
+		fmt = SND_SOC_DAIFMT_DSP_A |
+			SND_SOC_DAIFMT_CBM_CFM |
+			SND_SOC_DAIFMT_NB_NF |
+			SND_SOC_DAIFMT_CONT;
+	} else {
+		ab8500_audio_set_word_length(codec_dai, 20);
+		fmt = SND_SOC_DAIFMT_DSP_A |
+			SND_SOC_DAIFMT_CBM_CFM |
+			SND_SOC_DAIFMT_NB_NF |
+			SND_SOC_DAIFMT_GATED;
+	}
+
+	ret = snd_soc_dai_set_fmt(codec_dai, fmt);
+	if (ret < 0) {
+		dev_err(dev, "%s: ERROR: snd_soc_dai_set_fmt failed "
+			"for codec_dai (ret = %d)!\n", __func__, ret);
+		return ret;
+	}
+
+	ret = snd_soc_dai_set_fmt(cpu_dai, fmt);
+	if (ret < 0) {
+		dev_err(dev, "%s: ERROR: snd_soc_dai_set_fmt failed "
+			"for cpu_dai (ret = %d)!\n", __func__, ret);
+		return ret;
+	}
+
+	/* Setup TDM-slots */
+
+	is_playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK);
+	switch (channels) {
+	case 1:
+		slots = 16;
+		slot_width = 16;
+		tx_slots = (is_playback) ? TX_SLOT_MONO : 0;
+		rx_slots = (is_playback) ? 0 : RX_SLOT_MONO;
+		break;
+	case 2:
+		slots = 16;
+		slot_width = 16;
+		tx_slots = (is_playback) ? TX_SLOT_STEREO : 0;
+		rx_slots = (is_playback) ? 0 : RX_SLOT_STEREO;
+		break;
+	case 8:
+		slots = 16;
+		slot_width = 16;
+		tx_slots = (is_playback) ? TX_SLOT_8CH : 0;
+		rx_slots = (is_playback) ? 0 : RX_SLOT_8CH;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	dev_dbg(dev, "%s: CPU-DAI TDM: TX=0x%04X RX=0x%04x\n", __func__,
+		tx_slots, rx_slots);
+	ret = snd_soc_dai_set_tdm_slot(cpu_dai, tx_slots, rx_slots, slots, slot_width);
+	if (ret)
+		return ret;
+
+	dev_dbg(dev, "%s: CODEC-DAI TDM: TX=0x%04X RX=0x%04x\n", __func__,
+		tx_slots, rx_slots);
+	ret = snd_soc_dai_set_tdm_slot(codec_dai, tx_slots, rx_slots, slots, slot_width);
+	if (ret)
+		return ret;
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		dev_dbg(dev, "%s: Setup IF1 for FM-radio.\n", __func__);
+		fmt_if1 = SND_SOC_DAIFMT_CBM_CFM | SND_SOC_DAIFMT_I2S;
+		ret = ab8500_audio_setup_if1(codec_dai->codec, fmt_if1, 16, 1);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+struct snd_soc_ops ux500_ab8500_ops[] = {
+	{
+	.hw_params = ux500_ab8500_hw_params,
+	.startup = ux500_ab8500_startup,
+	.shutdown = ux500_ab8500_shutdown,
+	}
+};
+
+int ux500_ab8500_machine_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_codec *codec = rtd->codec;
+	struct device *dev = rtd->card->dev;
+	int ret;
+
+	dev_dbg(dev, "%s Enter.\n", __func__);
+
+	/* Add controls */
+	snd_ctl_add(codec->card->snd_card, snd_ctl_new1(&mclk_input_control, codec));
+	snd_ctl_add(codec->card->snd_card, snd_ctl_new1(&anc_status_control, codec));
+
+	ret = create_regulators(rtd);
+	if (ret < 0) {
+		dev_err(dev, "%s: ERROR: Failed to instantiate regulators (ret = %d)!\n",
+			__func__, ret);
+		return ret;
+	}
+
+	/* Get references to clock-nodes */
+	clk_ptr_sysclk = NULL;
+	clk_ptr_ulpclk = NULL;
+	clk_ptr_intclk = NULL;
+	clk_ptr_audioclk = NULL;
+	clk_ptr_gpio1 = NULL;
+	clk_ptr_sysclk = clk_get(codec->dev, "sysclk");
+	if (IS_ERR(clk_ptr_sysclk))
+		dev_warn(dev, "WARNING: clk_get failed for 'sysclk'!\n");
+	clk_ptr_ulpclk = clk_get(codec->dev, "ulpclk");
+	if (IS_ERR(clk_ptr_sysclk))
+		dev_warn(dev, "WARNING: clk_get failed for 'ulpclk'!\n");
+	clk_ptr_intclk = clk_get(codec->dev, "intclk");
+	if (IS_ERR(clk_ptr_audioclk))
+		dev_warn(dev, "WARNING: clk_get failed for 'intclk'!\n");
+	clk_ptr_audioclk = clk_get(codec->dev, "audioclk");
+	if (IS_ERR(clk_ptr_audioclk))
+		dev_warn(dev, "WARNING: clk_get failed for 'audioclk'!\n");
+	clk_ptr_gpio1 = clk_get_sys("gpio.1", NULL);
+	if (IS_ERR(clk_ptr_gpio1))
+		dev_warn(dev, "WARNING: clk_get failed for 'gpio1'!\n");
+
+	/* Set intclk default parent to ulpclk */
+	ret = clk_set_parent(clk_ptr_intclk, clk_ptr_ulpclk);
+	if (ret)
+		dev_warn(dev, "%s: WARNING: Setting intclk parent to ulpclk failed (ret = %d)!",
+			__func__,
+			ret);
+
+	master_clock_sel = 1;
+
+	ab8500_power_count = 0;
+
+	reg_claim[REGULATOR_AMIC1] = 0;
+	reg_claim[REGULATOR_AMIC2] = 0;
+
+	/* Add DAPM-widgets */
+	ret = add_widgets(codec);
+	if (ret < 0) {
+		dev_err(dev, "%s: Failed add widgets (%d).\n", __func__, ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+int ux500_ab8500_soc_machine_drv_init(void)
+{
+	pr_debug("%s: Enter.\n", __func__);
+
+	return 0;
+}
+
+void ux500_ab8500_soc_machine_drv_cleanup(void)
+{
+	pr_debug("%s: Enter.\n", __func__);
+
+	regulator_bulk_free(ARRAY_SIZE(reg_info), reg_info);
+
+	if (clk_ptr_sysclk != NULL)
+		clk_put(clk_ptr_sysclk);
+	if (clk_ptr_ulpclk != NULL)
+		clk_put(clk_ptr_ulpclk);
+	if (clk_ptr_intclk != NULL)
+		clk_put(clk_ptr_intclk);
+	if (clk_ptr_audioclk != NULL)
+		clk_put(clk_ptr_audioclk);
+	if (clk_ptr_gpio1 != NULL)
+		clk_put(clk_ptr_gpio1);
+}
+
diff --git a/sound/soc/ux500/ux500_ab8500.h b/sound/soc/ux500/ux500_ab8500.h
new file mode 100644
index 0000000..4fffb8a
--- /dev/null
+++ b/sound/soc/ux500/ux500_ab8500.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2012
+ *
+ * Author: Ola Lilja <ola.o.lilja at stericsson.com>
+ *         for ST-Ericsson.
+ *
+ * License terms:
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as published
+ * by the Free Software Foundation.
+ */
+
+#ifndef UX500_AB8500_H
+#define UX500_AB8500_H
+
+extern struct snd_soc_ops ux500_ab8500_ops[];
+
+int ux500_ab8500_machine_init(struct snd_soc_pcm_runtime *runtime);
+
+int ux500_ab8500_soc_machine_drv_init(void);
+
+void ux500_ab8500_soc_machine_drv_cleanup(void);
+
+#endif
-- 
1.7.8.3



More information about the Alsa-devel mailing list