diff mbox

[3/3] ASoC: rt5677: Support DSP function

Message ID 1404718621-15516-3-git-send-email-oder_chiou@realtek.com (mailing list archive)
State New, archived
Headers show

Commit Message

Oder Chiou July 7, 2014, 7:37 a.m. UTC
The ALC5677 has a programmable DSP, and there is a SPI that is dadicated for DSP
firmware loading. Therefore, the patch includes a SPI driver for writing the DSP
firmware.

Signed-off-by: Oder Chiou <oder_chiou@realtek.com>
---
 sound/soc/codecs/Makefile     |   2 +-
 sound/soc/codecs/rt5677-spi.c | 151 ++++++++++++++++++++++++++++
 sound/soc/codecs/rt5677-spi.h |  20 ++++
 sound/soc/codecs/rt5677.c     | 227 ++++++++++++++++++++++++++++++++++++++++++
 sound/soc/codecs/rt5677.h     |   6 ++
 5 files changed, 405 insertions(+), 1 deletion(-)
 create mode 100644 sound/soc/codecs/rt5677-spi.c
 create mode 100644 sound/soc/codecs/rt5677-spi.h

Comments

Mark Brown Aug. 16, 2014, 3:55 p.m. UTC | #1
On Mon, Jul 07, 2014 at 03:37:01PM +0800, Oder Chiou wrote:

> +int rt5677_spi_write(u8 *txbuf, size_t len)
> +{
> +	static DEFINE_MUTEX(lock);
> +	int status;
> +
> +	mutex_lock(&lock);
> +
> +	status = spi_write(g_spi, txbuf, len);
> +
> +	mutex_unlock(&lock);

Since this lock is local to the function it doesn't do anything, the SPI
framework has its own locking which makes it reentrant.

> +int rt5677_spi_burst_write(u32 addr, const struct firmware *fw)
> +{
> +	u8 spi_cmd = 0x05;
> +	u8 *write_buf;
> +	unsigned int i, end, offset = 0;
> +
> +	write_buf = kmalloc(8 * 30 + 6, GFP_KERNEL);

Magic numbers!  You also need to be aware that memory allocated by
kmalloc() may not be contigous and while things using the SPI core DMA
handling ought to be able to cope not everything will be.

> +static int rt5677_spi_probe(struct spi_device *spi)
> +{
> +	g_spi = spi;
> +	return 0;
> +}

> +static int rt5677_spi_remove(struct spi_device *spi)
> +{
> +	return 0;
> +}

Delete empty functions.

> +static int __init rt5677_spi_init(void)
> +{
> +	return spi_register_driver(&rt5677_spi_driver);
> +}
> +module_init(rt5677_spi_init);

module_spi_driver()

> +static int rt5677_dsp_put(struct snd_kcontrol *kcontrol,
> +		struct snd_ctl_elem_value *ucontrol)
> +{
> +	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
> +	struct rt5677_priv *rt5677 = snd_soc_codec_get_drvdata(codec);
> +
> +	if (ucontrol->value.integer.value[0] != 0 && rt5677->dsp_en == false) {
> +		rt5677->dsp_en = true;
> +
> +		regmap_update_bits(rt5677->regmap, RT5677_PWR_ANLG1,
> +			RT5677_LDO1_SEL_MASK, 0x0);
> +		regmap_update_bits(rt5677->regmap, RT5677_PWR_ANLG2,
> +			RT5677_PWR_LDO1, RT5677_PWR_LDO1);
> +		regmap_write(rt5677->regmap, RT5677_GLB_CLK2, 0x0080);
> +		regmap_write(rt5677->regmap, RT5677_PWR_DSP2, 0x07ff);
> +		regmap_write(rt5677->regmap, RT5677_PWR_DSP1, 0x07ff);

This doesn't seem joined up with the rest of the CODEC power management
which suggests there's going to be problems - for example LDO1_SEL_MASK
is also being managed by the existing set_bias_level().  What other
devices do is manage the power for the DSP using a DAPM widget, enabling
it when there's audio flowing through it.

> +	mutex_init(&rt5677->dsp_cmd_lock);
> +
> +	request_firmware_nowait(THIS_MODULE, FW_ACTION_HOTPLUG,
> +		RT5677_FIRMWARE1, codec->dev, GFP_KERNEL, codec,
> +		rt5677_fw1_loaded);
> +
> +	request_firmware_nowait(THIS_MODULE, FW_ACTION_HOTPLUG,
> +		RT5677_FIRMWARE2, codec->dev, GFP_KERNEL, codec,
> +		rt5677_fw2_loaded);

It would be nicer to be able to only request firmware when needed rather
than loading it once a boot time, that way we use a little less memory
and we'll always have the latest firmware rather than what happened to
be there when the system is booting.
diff mbox

Patch

diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index be3377b..3534706 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -72,7 +72,7 @@  snd-soc-rt5631-objs := rt5631.o
 snd-soc-rt5640-objs := rt5640.o
 snd-soc-rt5645-objs := rt5645.o
 snd-soc-rt5651-objs := rt5651.o
-snd-soc-rt5677-objs := rt5677.o
+snd-soc-rt5677-objs := rt5677.o rt5677-spi.o
 snd-soc-sgtl5000-objs := sgtl5000.o
 snd-soc-alc5623-objs := alc5623.o
 snd-soc-alc5632-objs := alc5632.o
diff --git a/sound/soc/codecs/rt5677-spi.c b/sound/soc/codecs/rt5677-spi.c
new file mode 100644
index 0000000..6b98bf0
--- /dev/null
+++ b/sound/soc/codecs/rt5677-spi.c
@@ -0,0 +1,151 @@ 
+/*
+ * rt5677-spi.c  --  RT5677 ALSA SoC audio codec driver
+ *
+ * Copyright 2013 Realtek Semiconductor Corp.
+ * Author: Oder Chiou <oder_chiou@realtek.com>
+ *
+ * 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/input.h>
+#include <linux/spi/spi.h>
+#include <linux/device.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/slab.h>
+#include <linux/gpio.h>
+#include <linux/sched.h>
+#include <linux/kthread.h>
+#include <linux/uaccess.h>
+#include <linux/miscdevice.h>
+#include <linux/regulator/consumer.h>
+#include <linux/pm_qos.h>
+#include <linux/sysfs.h>
+#include <linux/clk.h>
+#include <linux/firmware.h>
+
+#include "rt5677-spi.h"
+
+static struct spi_device *g_spi;
+
+/**
+ * rt5677_spi_write - Write data to SPI.
+ * @txbuf: Data Buffer for writing.
+ * @len: Data length.
+ *
+ *
+ * Returns true for success.
+ */
+int rt5677_spi_write(u8 *txbuf, size_t len)
+{
+	static DEFINE_MUTEX(lock);
+	int status;
+
+	mutex_lock(&lock);
+
+	status = spi_write(g_spi, txbuf, len);
+
+	mutex_unlock(&lock);
+
+	if (status)
+		dev_err(&g_spi->dev, "rt5677_spi_write error %d\n", status);
+
+	return status;
+}
+
+/**
+ * rt5677_spi_burst_write - Write data to SPI by rt5677 dsp memory address.
+ * @addr: Start address.
+ * @txbuf: Data Buffer for writng.
+ * @len: Data length, it must be a multiple of 8.
+ *
+ *
+ * Returns true for success.
+ */
+int rt5677_spi_burst_write(u32 addr, const struct firmware *fw)
+{
+	u8 spi_cmd = 0x05;
+	u8 *write_buf;
+	unsigned int i, end, offset = 0;
+
+	write_buf = kmalloc(8 * 30 + 6, GFP_KERNEL);
+
+	if (write_buf == NULL)
+		return -ENOMEM;
+
+	while (offset < fw->size) {
+		if (offset + SPI_BUF_LEN <= fw->size)
+			end = SPI_BUF_LEN;
+		else
+			end = fw->size % SPI_BUF_LEN;
+
+		write_buf[0] = spi_cmd;
+		write_buf[1] = ((addr + offset) & 0xff000000) >> 24;
+		write_buf[2] = ((addr + offset) & 0x00ff0000) >> 16;
+		write_buf[3] = ((addr + offset) & 0x0000ff00) >> 8;
+		write_buf[4] = ((addr + offset) & 0x000000ff) >> 0;
+
+		for (i = 0; i < end; i += 8) {
+			write_buf[i + 12] = fw->data[offset + i + 0];
+			write_buf[i + 11] = fw->data[offset + i + 1];
+			write_buf[i + 10] = fw->data[offset + i + 2];
+			write_buf[i +  9] = fw->data[offset + i + 3];
+			write_buf[i +  8] = fw->data[offset + i + 4];
+			write_buf[i +  7] = fw->data[offset + i + 5];
+			write_buf[i +  6] = fw->data[offset + i + 6];
+			write_buf[i +  5] = fw->data[offset + i + 7];
+		}
+
+		write_buf[end + 5] = spi_cmd;
+
+		rt5677_spi_write(write_buf, end + 6);
+
+		offset += SPI_BUF_LEN;
+	}
+
+	kfree(write_buf);
+
+	return 0;
+}
+
+static int rt5677_spi_probe(struct spi_device *spi)
+{
+	g_spi = spi;
+	return 0;
+}
+
+static int rt5677_spi_remove(struct spi_device *spi)
+{
+	return 0;
+}
+
+static struct spi_driver rt5677_spi_driver = {
+	.driver = {
+			.name = "rt5677_spidev",
+			.bus = &spi_bus_type,
+			.owner = THIS_MODULE,
+	},
+	.probe = rt5677_spi_probe,
+	.remove = rt5677_spi_remove,
+};
+
+static int __init rt5677_spi_init(void)
+{
+	return spi_register_driver(&rt5677_spi_driver);
+}
+module_init(rt5677_spi_init);
+
+static void __exit rt5677_spi_exit(void)
+{
+	spi_unregister_driver(&rt5677_spi_driver);
+}
+module_exit(rt5677_spi_exit);
+
+MODULE_DESCRIPTION("ASoC RT5677 SPI driver");
+MODULE_AUTHOR("Oder Chiou <oder_chiou@realtek.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/codecs/rt5677-spi.h b/sound/soc/codecs/rt5677-spi.h
new file mode 100644
index 0000000..0bfefae
--- /dev/null
+++ b/sound/soc/codecs/rt5677-spi.h
@@ -0,0 +1,20 @@ 
+/*
+ * rt5677-spi.h  --  RT5677 ALSA SoC audio codec driver
+ *
+ * Copyright 2013 Realtek Semiconductor Corp.
+ * Author: Oder Chiou <oder_chiou@realtek.com>
+ *
+ * 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 __RT5671_SPI_H__
+#define __RT5671_SPI_H__
+
+#define SPI_BUF_LEN 240
+
+int rt5677_spi_write(u8 *txbuf, size_t len);
+int rt5677_spi_burst_write(u32 addr, const struct firmware *fw);
+
+#endif /* __RT5677_SPI_H__ */
diff --git a/sound/soc/codecs/rt5677.c b/sound/soc/codecs/rt5677.c
index 67f1455..a29be9a 100644
--- a/sound/soc/codecs/rt5677.c
+++ b/sound/soc/codecs/rt5677.c
@@ -19,6 +19,7 @@ 
 #include <linux/i2c.h>
 #include <linux/platform_device.h>
 #include <linux/spi/spi.h>
+#include <linux/firmware.h>
 #include <sound/core.h>
 #include <sound/pcm.h>
 #include <sound/pcm_params.h>
@@ -29,6 +30,7 @@ 
 
 #include "rl6231.h"
 #include "rt5677.h"
+#include "rt5677-spi.h"
 
 #define RT5677_DEVICE_ID 0x6327
 
@@ -535,6 +537,134 @@  static bool rt5677_readable_register(struct device *dev, unsigned int reg)
 	}
 }
 
+/**
+ * rt5677_dsp_mode_i2c_write - Write register on DSP mode.
+ * @codec: SoC audio codec device.
+ * @reg: Register index.
+ * @value: Register data.
+ *
+ *
+ * Returns 0 for success or negative error code.
+ */
+static int rt5677_dsp_mode_i2c_write(struct snd_soc_codec *codec,
+		unsigned int reg, unsigned int value)
+{
+	struct rt5677_priv *rt5677 = snd_soc_codec_get_drvdata(codec);
+	int ret;
+
+	mutex_lock(&rt5677->dsp_cmd_lock);
+
+	ret = regmap_write(rt5677->regmap, RT5677_DSP_I2C_ADDR_MSB, 0x1802);
+	if (ret < 0) {
+		dev_err(codec->dev, "Failed to set addr msb value: %d\n", ret);
+		goto err;
+	}
+
+	ret = regmap_write(rt5677->regmap, RT5677_DSP_I2C_ADDR_LSB, reg * 2);
+	if (ret < 0) {
+		dev_err(codec->dev, "Failed to set addr lsb value: %d\n", ret);
+		goto err;
+	}
+
+	ret = regmap_write(rt5677->regmap, RT5677_DSP_I2C_DATA_LSB, value);
+	if (ret < 0) {
+		dev_err(codec->dev, "Failed to set data lsb value: %d\n", ret);
+		goto err;
+	}
+
+	ret = regmap_write(rt5677->regmap, RT5677_DSP_I2C_OP_CODE, 0x0001);
+	if (ret < 0) {
+		dev_err(codec->dev, "Failed to set op code value: %d\n", ret);
+		goto err;
+	}
+
+err:
+	mutex_unlock(&rt5677->dsp_cmd_lock);
+
+	return ret;
+}
+
+/**
+ * rt5677_dsp_mode_i2c_read - Read register on DSP mode.
+ * @codec: SoC audio codec device.
+ * @reg: Register index.
+ *
+ *
+ * Returns Register value or negative error code.
+ */
+static unsigned int rt5677_dsp_mode_i2c_read(
+	struct snd_soc_codec *codec, unsigned int reg)
+{
+	struct rt5677_priv *rt5677 = snd_soc_codec_get_drvdata(codec);
+	int ret;
+
+	mutex_lock(&rt5677->dsp_cmd_lock);
+
+	ret = regmap_write(rt5677->regmap, RT5677_DSP_I2C_ADDR_MSB, 0x1802);
+	if (ret < 0) {
+		dev_err(codec->dev, "Failed to set addr msb value: %d\n", ret);
+		goto err;
+	}
+
+	ret = regmap_write(rt5677->regmap, RT5677_DSP_I2C_ADDR_LSB, reg * 2);
+	if (ret < 0) {
+		dev_err(codec->dev, "Failed to set addr lsb value: %d\n", ret);
+		goto err;
+	}
+
+	ret = regmap_write(rt5677->regmap, RT5677_DSP_I2C_OP_CODE, 0x0002);
+	if (ret < 0) {
+		dev_err(codec->dev, "Failed to set op code value: %d\n", ret);
+		goto err;
+	}
+
+	regmap_read(rt5677->regmap, RT5677_DSP_I2C_DATA_LSB, &ret);
+
+err:
+	mutex_unlock(&rt5677->dsp_cmd_lock);
+
+	return ret;
+}
+
+/**
+ * rt5677_dsp_mode_i2c_update_bits - update register on DSP mode.
+ * @codec: audio codec
+ * @reg: register index.
+ * @mask: register mask
+ * @value: new value
+ *
+ *
+ * Returns 1 for change, 0 for no change, or negative error code.
+ */
+static int rt5677_dsp_mode_i2c_update_bits(struct snd_soc_codec *codec,
+	unsigned int reg, unsigned int mask, unsigned int value)
+{
+	unsigned int old, new;
+	int change, ret;
+
+	ret = rt5677_dsp_mode_i2c_read(codec, reg);
+	if (ret < 0) {
+		dev_err(codec->dev, "Failed to read private reg: %d\n", ret);
+		goto err;
+	}
+
+	old = ret;
+	new = (old & ~mask) | (value & mask);
+	change = old != new;
+	if (change) {
+		ret = rt5677_dsp_mode_i2c_write(codec, reg, new);
+		if (ret < 0) {
+			dev_err(codec->dev,
+				"Failed to write private reg: %d\n", ret);
+			goto err;
+		}
+	}
+	return change;
+
+err:
+	return ret;
+}
+
 static const DECLARE_TLV_DB_SCALE(out_vol_tlv, -4650, 150, 0);
 static const DECLARE_TLV_DB_SCALE(dac_vol_tlv, -65625, 375, 0);
 static const DECLARE_TLV_DB_SCALE(in_vol_tlv, -3450, 150, 0);
@@ -553,6 +683,69 @@  static unsigned int bst_tlv[] = {
 	8, 8, TLV_DB_SCALE_ITEM(5200, 0, 0),
 };
 
+static int rt5677_dsp_get(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct rt5677_priv *rt5677 = snd_soc_codec_get_drvdata(codec);
+
+	ucontrol->value.integer.value[0] = rt5677->dsp_en;
+
+	return 0;
+}
+
+static int rt5677_dsp_put(struct snd_kcontrol *kcontrol,
+		struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct rt5677_priv *rt5677 = snd_soc_codec_get_drvdata(codec);
+
+	if (ucontrol->value.integer.value[0] != 0 && rt5677->dsp_en == false) {
+		rt5677->dsp_en = true;
+
+		regmap_update_bits(rt5677->regmap, RT5677_PWR_ANLG1,
+			RT5677_LDO1_SEL_MASK, 0x0);
+		regmap_update_bits(rt5677->regmap, RT5677_PWR_ANLG2,
+			RT5677_PWR_LDO1, RT5677_PWR_LDO1);
+		regmap_write(rt5677->regmap, RT5677_GLB_CLK2, 0x0080);
+		regmap_write(rt5677->regmap, RT5677_PWR_DSP2, 0x07ff);
+		regmap_write(rt5677->regmap, RT5677_PWR_DSP1, 0x07ff);
+
+		if (rt5677->fw1)
+			rt5677_spi_burst_write(0x50000000, rt5677->fw1);
+
+		if (rt5677->fw2)
+			rt5677_spi_burst_write(0x60000000, rt5677->fw2);
+
+		regcache_cache_bypass(rt5677->regmap, true);
+		rt5677_dsp_mode_i2c_update_bits(codec, RT5677_PWR_DSP1, 0x1,
+			0x0);
+		rt5677_dsp_mode_i2c_write(codec, RT5677_PRIV_INDEX, 0x003e);
+		rt5677_dsp_mode_i2c_write(codec, RT5677_PRIV_DATA, 0x2208);
+		regcache_cache_bypass(rt5677->regmap, false);
+	} else if (ucontrol->value.integer.value[0] == 0 &&
+		rt5677->dsp_en == true) {
+		rt5677->dsp_en = false;
+
+		regmap_write(rt5677->regmap, RT5677_PR_BASE + 0x3e, 0x2008);
+		regcache_cache_bypass(rt5677->regmap, true);
+		rt5677_dsp_mode_i2c_update_bits(codec, RT5677_PWR_DSP1, 0x1,
+			0x1);
+		rt5677_dsp_mode_i2c_write(codec, RT5677_PWR_DSP1, 0x0001);
+		regcache_cache_bypass(rt5677->regmap, false);
+
+		regmap_write(rt5677->regmap, RT5677_PWR_DSP1, 0x0001);
+		regmap_write(rt5677->regmap, RT5677_PWR_DSP2, 0x0c00);
+		regmap_write(rt5677->regmap, RT5677_GLB_CLK2, 0x0000);
+		regmap_update_bits(rt5677->regmap, RT5677_PWR_ANLG2,
+			RT5677_PWR_LDO1, 0);
+		regmap_update_bits(rt5677->regmap, RT5677_PWR_ANLG1,
+			RT5677_LDO1_SEL_MASK, 0x1);
+	}
+
+	return 0;
+}
+
 static const struct snd_kcontrol_new rt5677_snd_controls[] = {
 	/* OUTPUT Control */
 	SOC_SINGLE("OUT1 Playback Switch", RT5677_LOUT1,
@@ -620,6 +813,9 @@  static const struct snd_kcontrol_new rt5677_snd_controls[] = {
 	SOC_DOUBLE_TLV("Mono ADC Boost Volume", RT5677_ADC_BST_CTRL2,
 		RT5677_MONO_ADC_L_BST_SFT, RT5677_MONO_ADC_R_BST_SFT, 3, 0,
 		adc_bst_tlv),
+
+	SOC_SINGLE_EXT("DSP Switch", 0, 0, 1, 0, rt5677_dsp_get,
+		rt5677_dsp_put),
 };
 
 /**
@@ -3138,6 +3334,24 @@  static int rt5677_set_bias_level(struct snd_soc_codec *codec,
 	return 0;
 }
 
+static void rt5677_fw1_loaded(const struct firmware *fw, void *context)
+{
+	struct snd_soc_codec *codec = context;
+	struct rt5677_priv *rt5677 = snd_soc_codec_get_drvdata(codec);
+
+	if (fw)
+		rt5677->fw1 = fw;
+}
+
+static void rt5677_fw2_loaded(const struct firmware *fw, void *context)
+{
+	struct snd_soc_codec *codec = context;
+	struct rt5677_priv *rt5677 = snd_soc_codec_get_drvdata(codec);
+
+	if (fw)
+		rt5677->fw2 = fw;
+}
+
 static int rt5677_probe(struct snd_soc_codec *codec)
 {
 	struct rt5677_priv *rt5677 = snd_soc_codec_get_drvdata(codec);
@@ -3149,6 +3363,16 @@  static int rt5677_probe(struct snd_soc_codec *codec)
 	regmap_write(rt5677->regmap, RT5677_DIG_MISC, 0x0020);
 	regmap_write(rt5677->regmap, RT5677_PWR_DSP2, 0x0c00);
 
+	mutex_init(&rt5677->dsp_cmd_lock);
+
+	request_firmware_nowait(THIS_MODULE, FW_ACTION_HOTPLUG,
+		RT5677_FIRMWARE1, codec->dev, GFP_KERNEL, codec,
+		rt5677_fw1_loaded);
+
+	request_firmware_nowait(THIS_MODULE, FW_ACTION_HOTPLUG,
+		RT5677_FIRMWARE2, codec->dev, GFP_KERNEL, codec,
+		rt5677_fw2_loaded);
+
 	return 0;
 }
 
@@ -3158,6 +3382,9 @@  static int rt5677_remove(struct snd_soc_codec *codec)
 
 	regmap_write(rt5677->regmap, RT5677_RESET, 0x10ec);
 
+	release_firmware(rt5677->fw1);
+	release_firmware(rt5677->fw2);
+
 	return 0;
 }
 
diff --git a/sound/soc/codecs/rt5677.h b/sound/soc/codecs/rt5677.h
index 863393e..32cbbf0 100644
--- a/sound/soc/codecs/rt5677.h
+++ b/sound/soc/codecs/rt5677.h
@@ -1393,6 +1393,9 @@ 
 #define RT5677_DSP_IB_9_L			(0x1 << 1)
 #define RT5677_DSP_IB_9_L_SFT			1
 
+#define RT5677_FIRMWARE1	"rt5677_dsp_fw1.bin"
+#define RT5677_FIRMWARE2	"rt5677_dsp_fw2.bin"
+
 /* System Clock Source */
 enum {
 	RT5677_SCLK_S_MCLK,
@@ -1422,6 +1425,8 @@  struct rt5677_priv {
 	struct snd_soc_codec *codec;
 	struct rt5677_platform_data pdata;
 	struct regmap *regmap;
+	const struct firmware *fw1, *fw2;
+	struct mutex dsp_cmd_lock;
 
 	int sysclk;
 	int sysclk_src;
@@ -1431,6 +1436,7 @@  struct rt5677_priv {
 	int pll_src;
 	int pll_in;
 	int pll_out;
+	bool dsp_en;
 };
 
 #endif /* __RT5677_H__ */