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
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__ */