Add dsp part of rt5670 codec driver
Signed-off-by: Bard Liao <bardliao(a)realtek.com>
---
sound/soc/codecs/Makefile | 2 +-
sound/soc/codecs/rt5670-dsp.c | 383 ++++++++++++++++++++++++++++++++++++++++++
sound/soc/codecs/rt5670-dsp.h | 11 +-
sound/soc/codecs/rt5670.c | 43 +++++
sound/soc/codecs/rt5670.h | 3 +
5 files changed, 435 insertions(+), 7 deletions(-)
create mode 100644 sound/soc/codecs/rt5670-dsp.c
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index 5dce451..19a4631 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -78,7 +78,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-rt5670-objs := rt5670.o
+snd-soc-rt5670-objs := rt5670.o rt5670-dsp.o
snd-soc-rt5677-objs := rt5677.o
snd-soc-sgtl5000-objs := sgtl5000.o
snd-soc-alc5623-objs := alc5623.o
diff --git a/sound/soc/codecs/rt5670-dsp.c b/sound/soc/codecs/rt5670-dsp.c
new file mode 100644
index 0000000..bbe039d
--- /dev/null
+++ b/sound/soc/codecs/rt5670-dsp.c
@@ -0,0 +1,383 @@
+/*
+ * rt5670-dsp.c -- RT5670 ALSA SoC DSP driver
+ *
+ * Copyright 2014 Realtek Semiconductor Corp.
+ * Author: Bard Liao <bardliao(a)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/delay.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <linux/firmware.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include "rt5670.h"
+#include "rt5670-dsp.h"
+
+#define DSP_CLK_RATE RT5670_DSP_CLK_96K
+
+static const struct firmware *rt5670_dsp_fw;
+
+/**
+ * rt5670_dsp_done - Wait until DSP is ready.
+ * @codec: SoC Audio Codec device.
+ *
+ * To check voice DSP status and confirm it's ready for next work.
+ *
+ * Returns 0 for success or negative error code.
+ */
+static int rt5670_dsp_done(struct snd_soc_codec *codec)
+{
+ unsigned int count = 0, dsp_val;
+
+ dsp_val = snd_soc_read(codec, RT5670_DSP_CTRL1);
+ while (dsp_val & RT5670_DSP_BUSY_MASK) {
+ if (count > 10)
+ return -EBUSY;
+ dsp_val = snd_soc_read(codec, RT5670_DSP_CTRL1);
+ count++;
+ }
+
+ return 0;
+}
+
+/**
+ * rt5670_dsp_write - Write DSP register.
+ * @codec: SoC audio codec device.
+ * @param: DSP parameters.
+ *
+ * Modify voice DSP register for sound effect. The DSP can be controlled
+ * through DSP addr (0xe1), data (0xe2) and cmd (0xe0)
+ * registers. It has to wait until the DSP is ready.
+ *
+ * Returns 0 for success or negative error code.
+ */
+int rt5670_dsp_write(struct snd_soc_codec *codec,
+ unsigned int addr, unsigned int data)
+{
+ unsigned int dsp_val;
+ int ret;
+
+ ret = snd_soc_write(codec, RT5670_DSP_CTRL2, addr);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to write DSP addr reg: %d\n", ret);
+ goto err;
+ }
+ ret = snd_soc_write(codec, RT5670_DSP_CTRL3, data);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to write DSP data reg: %d\n", ret);
+ goto err;
+ }
+ dsp_val = RT5670_DSP_I2C_AL_16 | RT5670_DSP_DL_2 |
+ RT5670_DSP_CMD_MW | DSP_CLK_RATE | RT5670_DSP_CMD_EN;
+
+ ret = snd_soc_write(codec, RT5670_DSP_CTRL1, dsp_val);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to write DSP cmd reg: %d\n", ret);
+ goto err;
+ }
+ ret = rt5670_dsp_done(codec);
+ if (ret < 0) {
+ dev_err(codec->dev, "DSP is busy: %d\n", ret);
+ goto err;
+ }
+
+ return 0;
+
+err:
+ return ret;
+}
+
+/**
+ * rt5670_dsp_read - Read DSP register.
+ * @codec: SoC audio codec device.
+ * @reg: DSP register index.
+ *
+ * Read DSP setting value from voice DSP. The DSP can be controlled
+ * through DSP addr (0xe1), data (0xe2) and cmd (0xe0) registers. Each
+ * command has to wait until the DSP is ready.
+ *
+ * Returns DSP register value or negative error code.
+ */
+unsigned int rt5670_dsp_read(
+ struct snd_soc_codec *codec, unsigned int reg)
+{
+ unsigned int value;
+ unsigned int dsp_val;
+ int ret = 0;
+
+ ret = rt5670_dsp_done(codec);
+ if (ret < 0) {
+ dev_err(codec->dev, "DSP is busy: %d\n", ret);
+ goto err;
+ }
+
+ ret = snd_soc_write(codec, RT5670_DSP_CTRL2, reg);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to write DSP addr reg: %d\n", ret);
+ goto err;
+ }
+ dsp_val = RT5670_DSP_I2C_AL_16 | RT5670_DSP_DL_0 | RT5670_DSP_RW_MASK |
+ RT5670_DSP_CMD_MR | DSP_CLK_RATE | RT5670_DSP_CMD_EN;
+
+ ret = snd_soc_write(codec, RT5670_DSP_CTRL1, dsp_val);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to write DSP cmd reg: %d\n", ret);
+ goto err;
+ }
+
+ ret = rt5670_dsp_done(codec);
+ if (ret < 0) {
+ dev_err(codec->dev, "DSP is busy: %d\n", ret);
+ goto err;
+ }
+
+ ret = snd_soc_write(codec, RT5670_DSP_CTRL2, 0x26);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to write DSP addr reg: %d\n", ret);
+ goto err;
+ }
+ dsp_val = RT5670_DSP_DL_1 | RT5670_DSP_CMD_RR | RT5670_DSP_RW_MASK |
+ DSP_CLK_RATE | RT5670_DSP_CMD_EN;
+
+ ret = snd_soc_write(codec, RT5670_DSP_CTRL1, dsp_val);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to write DSP cmd reg: %d\n", ret);
+ goto err;
+ }
+
+ ret = rt5670_dsp_done(codec);
+ if (ret < 0) {
+ dev_err(codec->dev, "DSP is busy: %d\n", ret);
+ goto err;
+ }
+
+ ret = snd_soc_write(codec, RT5670_DSP_CTRL2, 0x25);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to write DSP addr reg: %d\n", ret);
+ goto err;
+ }
+
+ dsp_val = RT5670_DSP_DL_1 | RT5670_DSP_CMD_RR | RT5670_DSP_RW_MASK |
+ DSP_CLK_RATE | RT5670_DSP_CMD_EN;
+
+ ret = snd_soc_write(codec, RT5670_DSP_CTRL1, dsp_val);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to write DSP cmd reg: %d\n", ret);
+ goto err;
+ }
+
+ ret = rt5670_dsp_done(codec);
+ if (ret < 0) {
+ dev_err(codec->dev, "DSP is busy: %d\n", ret);
+ goto err;
+ }
+
+ ret = snd_soc_read(codec, RT5670_DSP_CTRL5);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to read DSP data reg: %d\n", ret);
+ goto err;
+ }
+
+ value = ret;
+ return value;
+
+err:
+ return ret;
+}
+
+static int rt5670_dsp_mode_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol);
+ struct rt5670_priv *rt5670 = snd_soc_codec_get_drvdata(codec);
+
+ ucontrol->value.integer.value[0] = rt5670->dsp_sw;
+
+ return 0;
+}
+
+static int rt5670_dsp_mode_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol);
+ struct rt5670_priv *rt5670 = snd_soc_codec_get_drvdata(codec);
+
+ if (rt5670->dsp_sw != ucontrol->value.integer.value[0])
+ rt5670->dsp_sw = ucontrol->value.integer.value[0];
+
+ return 0;
+}
+
+/* DSP SRC Control */
+static const char * const rt5670_src_rxdp_mode[] = {
+ "Normal", "Divided by 2", "Divided by 3"
+};
+
+static const SOC_ENUM_SINGLE_DECL(
+ rt5670_src_rxdp_enum, RT5670_DSP_PATH1,
+ RT5670_RXDP_SRC_SFT, rt5670_src_rxdp_mode);
+
+static const char * const rt5670_src_txdp_mode[] = {
+ "Normal", "Multiplied by 2", "Multiplied by 3"
+};
+
+static const SOC_ENUM_SINGLE_DECL(
+ rt5670_src_txdp_enum, RT5670_DSP_PATH1,
+ RT5670_TXDP_SRC_SFT, rt5670_src_txdp_mode);
+
+/* DSP Mode */
+static const char * const rt5670_dsp_mode[] = {
+ "Mode 1", "Mode 2", "Mode 3", "Mode 4", "Mode 5"
+};
+
+static const SOC_ENUM_SINGLE_DECL(rt5670_dsp_enum, 0, 0,
+ rt5670_dsp_mode);
+
+static const struct snd_kcontrol_new rt5670_dsp_snd_controls[] = {
+ SOC_ENUM("RxDP SRC Switch", rt5670_src_rxdp_enum),
+ SOC_ENUM("TxDP SRC Switch", rt5670_src_txdp_enum),
+ SOC_ENUM_EXT("DSP Function Switch", rt5670_dsp_enum,
+ rt5670_dsp_mode_get, rt5670_dsp_mode_put),
+};
+
+/**
+ * rt5670_dsp_set_mode - Set DSP mode parameters.
+ *
+ * @codec: SoC audio codec device.
+ * @mode: DSP mode.
+ *
+ * Set parameters of mode to DSP.
+ *
+ * Returns 0 for success or negative error code.
+ */
+static int rt5670_dsp_set_mode(struct snd_soc_codec *codec, int mode)
+{
+ int tab_num, n, ret = -EINVAL;
+ unsigned int pos = 0;
+
+ if (rt5670_dsp_fw) {
+ n = rt5670_dsp_fw->data[0];
+
+ if (mode <= n) {
+ pos = rt5670_dsp_fw->data[mode * 3 + 2] |
+ rt5670_dsp_fw->data[mode * 3 + 1] << 8;
+
+ tab_num = rt5670_dsp_fw->data[mode * 3 + 3];
+ if (pos + tab_num * 5 > rt5670_dsp_fw->size)
+ return -EINVAL;
+ ret = rt5670_write_fw(codec,
+ rt5670_dsp_fw, pos, tab_num);
+ if (ret < 0)
+ dev_err(codec->dev,
+ "Fail to set mode %d parameters: %d\n",
+ mode, ret);
+ }
+ }
+
+ return ret;
+}
+
+static int rt5670_dsp_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ struct snd_soc_codec *codec = w->codec;
+ struct rt5670_priv *rt5670 = snd_soc_codec_get_drvdata(codec);
+
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMD:
+ rt5670_dsp_write(codec, 0x22f9, 1);
+ break;
+
+ case SND_SOC_DAPM_POST_PMU:
+ snd_soc_update_bits(codec, RT5670_DIG_MISC,
+ RT5670_RST_DSP, RT5670_RST_DSP);
+ snd_soc_update_bits(codec, RT5670_DIG_MISC,
+ RT5670_RST_DSP, 0);
+ mdelay(10);
+ rt5670_dsp_set_mode(codec, rt5670->dsp_sw);
+ break;
+
+ default:
+ return 0;
+ }
+
+ return 0;
+}
+
+static const struct snd_soc_dapm_widget rt5670_dsp_dapm_widgets[] = {
+ SND_SOC_DAPM_SUPPLY_S("Voice DSP", 1, SND_SOC_NOPM,
+ 0, 0, rt5670_dsp_event,
+ SND_SOC_DAPM_POST_PMD | SND_SOC_DAPM_POST_PMU),
+ SND_SOC_DAPM_PGA("DSP Downstream", SND_SOC_NOPM,
+ 0, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("DSP Upstream", SND_SOC_NOPM,
+ 0, 0, NULL, 0),
+};
+
+static const struct snd_soc_dapm_route rt5670_dsp_dapm_routes[] = {
+ {"DSP Downstream", NULL, "Voice DSP"},
+ {"DSP Downstream", NULL, "RxDP Mux"},
+ {"DSP Upstream", NULL, "Voice DSP"},
+ {"DSP Upstream", NULL, "TDM Data Mux"},
+ {"DSP DL Mux", "DSP", "DSP Downstream"},
+ {"DSP UL Mux", "DSP", "DSP Upstream"},
+};
+
+static void rt5670_dsp_fw_loaded(const struct firmware *fw, void *context)
+{
+ if (fw) {
+ rt5670_dsp_fw = fw;
+ pr_debug("fw->size=%d\n", fw->size);
+ }
+
+}
+
+/**
+ * rt5670_dsp_probe - register DSP for rt5670
+ * @codec: audio codec
+ *
+ * To register DSP function for rt5670.
+ *
+ * Returns 0 for success or negative error code.
+ */
+int rt5670_dsp_probe(struct snd_soc_codec *codec)
+{
+ if (codec == NULL)
+ return -EINVAL;
+
+ snd_soc_update_bits(codec, RT5670_PWR_DIG2,
+ RT5670_PWR_I2S_DSP, RT5670_PWR_I2S_DSP);
+
+ snd_soc_update_bits(codec, RT5670_DIG_MISC, RT5670_RST_DSP,
+ RT5670_RST_DSP);
+ snd_soc_update_bits(codec, RT5670_DIG_MISC, RT5670_RST_DSP, 0);
+
+ mdelay(10);
+
+ rt5670_dsp_set_mode(codec, 0);
+ /* power down DSP */
+ mdelay(15);
+ rt5670_dsp_write(codec, 0x22f9, 1);
+
+ snd_soc_update_bits(codec, RT5670_PWR_DIG2,
+ RT5670_PWR_I2S_DSP, 0);
+
+ snd_soc_add_codec_controls(codec, rt5670_dsp_snd_controls,
+ ARRAY_SIZE(rt5670_dsp_snd_controls));
+ snd_soc_dapm_new_controls(&codec->dapm, rt5670_dsp_dapm_widgets,
+ ARRAY_SIZE(rt5670_dsp_dapm_widgets));
+ snd_soc_dapm_add_routes(&codec->dapm, rt5670_dsp_dapm_routes,
+ ARRAY_SIZE(rt5670_dsp_dapm_routes));
+
+ request_firmware_nowait(THIS_MODULE, FW_ACTION_HOTPLUG,
+ "rt567x_dsp.bin", codec->dev, GFP_KERNEL,
+ codec, rt5670_dsp_fw_loaded);
+
+ return 0;
+}
diff --git a/sound/soc/codecs/rt5670-dsp.h b/sound/soc/codecs/rt5670-dsp.h
index a34d0cd..7b789f3 100644
--- a/sound/soc/codecs/rt5670-dsp.h
+++ b/sound/soc/codecs/rt5670-dsp.h
@@ -43,12 +43,11 @@
#define RT5670_DSP_I2C_AL_16 (0x1 << 1)
#define RT5670_DSP_CMD_EN (0x1)
-struct rt5670_dsp_param {
- u16 cmd_fmt;
- u16 addr;
- u16 data;
- u8 cmd;
-};
+int rt5670_dsp_probe(struct snd_soc_codec *codec);
+int rt5670_dsp_write(struct snd_soc_codec *codec,
+ unsigned int addr, unsigned int data);
+unsigned int rt5670_dsp_read(
+ struct snd_soc_codec *codec, unsigned int reg);
#endif /* __RT5670_DSP_H__ */
diff --git a/sound/soc/codecs/rt5670.c b/sound/soc/codecs/rt5670.c
index ba9d9b4..da4b689 100644
--- a/sound/soc/codecs/rt5670.c
+++ b/sound/soc/codecs/rt5670.c
@@ -16,6 +16,7 @@
#include <linux/pm.h>
#include <linux/i2c.h>
#include <linux/platform_device.h>
+#include <linux/firmware.h>
#include <linux/spi/spi.h>
#include <sound/core.h>
#include <sound/pcm.h>
@@ -401,6 +402,44 @@ static bool rt5670_readable_register(struct device *dev, unsigned int reg)
}
}
+int rt5670_write_fw(struct snd_soc_codec *codec, const struct firmware *fw,
+ unsigned int pos, unsigned int num)
+{
+ int i, ret;
+
+ for (i = 0; i < num; i++) {
+ switch (fw->data[pos]) {
+ case 1: /*PR*/
+ ret = snd_soc_write(codec, RT5670_PR_BASE +
+ ((fw->data[pos + 1] << 8) | fw->data[pos + 2]),
+ (fw->data[pos + 3] << 8) | fw->data[pos + 4]);
+ if (ret < 0)
+ return -1;
+ break;
+ case 2: /*dsp*/
+ ret = rt5670_dsp_write(codec,
+ (fw->data[pos + 1] << 8) | fw->data[pos + 2],
+ (fw->data[pos + 3] << 8) | fw->data[pos + 4]);
+ if (ret < 0)
+ return -1;
+ break;
+ default: /*register*/
+ ret = snd_soc_write(codec,
+ (fw->data[pos + 1] << 8) | fw->data[pos + 2],
+ (fw->data[pos + 3] << 8) | fw->data[pos + 4]);
+ if (ret < 0)
+ return -1;
+ break;
+ }
+ pos += 5;
+ if (fw->size < pos)
+ return 0;
+ }
+
+ return 0;
+
+}
+
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);
@@ -2332,6 +2371,7 @@ static int rt5670_probe(struct snd_soc_codec *codec)
struct rt5670_priv *rt5670 = snd_soc_codec_get_drvdata(codec);
rt5670->codec = codec;
+ rt5670_dsp_probe(codec);
return 0;
}
@@ -2623,6 +2663,9 @@ static int rt5670_i2c_probe(struct i2c_client *i2c,
}
+ /*Give sysclk a default value*/
+ rt5670->sysclk = 24576000;
+
ret = snd_soc_register_codec(&i2c->dev, &soc_codec_dev_rt5670,
rt5670_dai, ARRAY_SIZE(rt5670_dai));
if (ret < 0)
diff --git a/sound/soc/codecs/rt5670.h b/sound/soc/codecs/rt5670.h
index a0b5c85..70285c9 100644
--- a/sound/soc/codecs/rt5670.h
+++ b/sound/soc/codecs/rt5670.h
@@ -1997,4 +1997,7 @@ struct rt5670_priv {
int jack_type;
};
+int rt5670_write_fw(struct snd_soc_codec *codec, const struct firmware *fw,
+ unsigned int pos, unsigned int num);
+
#endif /* __RT5670_H__ */
--
1.8.1.1.439.g50a6b54