[alsa-devel] [PATCH] ASoC: add RT286 CODEC driver
From: Bard Liao bardliao@realtek.com
This patch adds the ALC286 codec driver.
Signed-off-by: Bard Liao bardliao@realtek.com ---
This is the initial codec driver of ALC586. I tested DMCI/Headset recording and Speaker/Headphone playback.
--- sound/soc/codecs/Kconfig | 3 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/rt286.c | 1609 +++++++++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/rt286.h | 113 ++++ 4 files changed, 1727 insertions(+) create mode 100644 sound/soc/codecs/rt286.c create mode 100644 sound/soc/codecs/rt286.h
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 983d087a..56748d4 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -313,6 +313,9 @@ config SND_SOC_PCM1792A config SND_SOC_PCM3008 tristate
+config SND_SOC_RT286 + tristate + config SND_SOC_RT5631 tristate
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index bc12676..b31615c 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -48,6 +48,7 @@ snd-soc-pcm1792a-codec-objs := pcm1792a.o snd-soc-pcm3008-objs := pcm3008.o snd-soc-rt5631-objs := rt5631.o snd-soc-rt5640-objs := rt5640.o +snd-soc-rt286-objs := rt286.o snd-soc-sgtl5000-objs := sgtl5000.o snd-soc-alc5623-objs := alc5623.o snd-soc-alc5632-objs := alc5632.o @@ -181,6 +182,7 @@ obj-$(CONFIG_SND_SOC_PCM1792A) += snd-soc-pcm1792a-codec.o obj-$(CONFIG_SND_SOC_PCM3008) += snd-soc-pcm3008.o obj-$(CONFIG_SND_SOC_RT5631) += snd-soc-rt5631.o obj-$(CONFIG_SND_SOC_RT5640) += snd-soc-rt5640.o +obj-$(CONFIG_SND_SOC_RT286) += snd-soc-rt286.o obj-$(CONFIG_SND_SOC_SGTL5000) += snd-soc-sgtl5000.o obj-$(CONFIG_SND_SOC_SIGMADSP) += snd-soc-sigmadsp.o obj-$(CONFIG_SND_SOC_SI476X) += snd-soc-si476x.o diff --git a/sound/soc/codecs/rt286.c b/sound/soc/codecs/rt286.c new file mode 100644 index 0000000..e95f76c --- /dev/null +++ b/sound/soc/codecs/rt286.c @@ -0,0 +1,1609 @@ +/* + * rt286.c -- RT286 ALSA SoC audio codec driver + * + * Copyright 2013 Realtek Semiconductor Corp. + * Author: Bard Liao bardliao@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. + */ +#define DEBUG +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/platform_device.h> +#include <linux/spi/spi.h> +#include <linux/acpi.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> +#include <sound/tlv.h> + +#include "../../pci/hda/hda_codec.h" +#include "rt286.h" + +static int hp_amp_time = 100; +module_param(hp_amp_time, int, 0644); + +#define RT_SUPPORT_COMBO_JACK 1 +#define VERSION "0.0.1-beta3 alsa 1.0.25" + +struct rt286_priv { + struct snd_soc_codec *codec; + struct regmap *regmap; + struct i2c_client *i2c; +}; + +static unsigned long rt286_read(struct snd_soc_codec *codec, + unsigned int vid, + unsigned int nid, + unsigned int data) +{ + struct i2c_msg xfer[2]; + int ret; + unsigned long verb; + unsigned long buf = 0x0; + struct rt286_priv *rt286 = snd_soc_codec_get_drvdata(codec); + + verb = nid << 20 | vid << 8 | data; + verb = cpu_to_be32(verb); + /* Write register */ + xfer[0].addr = rt286->i2c->addr; + xfer[0].flags = 0; + xfer[0].len = 4; + xfer[0].buf = (u8 *)&verb; + + /* Read data */ + xfer[1].addr = rt286->i2c->addr; + xfer[1].flags = I2C_M_RD; + xfer[1].len = 4; + xfer[1].buf = (u8 *)&buf; + + ret = i2c_transfer(rt286->i2c->adapter, xfer, 2); + if (ret != 2) { + dev_err(&rt286->i2c->dev, "i2c_transfer() returned %d\n", ret); + return 0; + } + + return be32_to_cpu(buf); +} + +static int rt286_write(struct snd_soc_codec *codec, + unsigned int vid, + unsigned int nid, + unsigned long value) +{ + u8 data[4]; + int ret; + struct rt286_priv *rt286 = snd_soc_codec_get_drvdata(codec); + + if ((vid & 0xff) == 0) { /* 4bit VID */ + vid = vid >> 8; + data[0] = (nid >> 4) & 0xf; + data[1] = ((nid & 0xf) << 4) | vid; + data[2] = (value >> 8) & 0xff; + data[3] = value & 0xff; + } else { /* 12bit VID */ + data[0] = (nid >> 4) & 0xf; + data[1] = ((nid & 0xf) << 4) | ((vid >> 8) & 0xf); + data[2] = vid & 0xff; + data[3] = value & 0xff; + } + + ret = i2c_master_send(rt286->i2c, data, 4); + pr_debug("write 0x%x 0x%x 0x%lx\n", vid, nid, value); + if (ret == 4) + return 0; + if (ret < 0) + return ret; + else + return -EIO; +} + +static int rt286_index_write(struct snd_soc_codec *codec, + unsigned int WidgetID, unsigned int index, unsigned int data) +{ + int ret; + + ret = rt286_write(codec, AC_VERB_SET_COEF_INDEX, + WidgetID, index); + if (ret < 0) { + dev_err(codec->dev, "Failed to set private addr: %d\n", ret); + goto err; + } + ret = rt286_write(codec, AC_VERB_SET_PROC_COEF, + WidgetID, data); + if (ret < 0) { + dev_err(codec->dev, "Failed to set private value: %d\n", ret); + goto err; + } + + return 0; +err: + return ret; +} + +static unsigned long rt286_index_read(struct snd_soc_codec *codec, + unsigned int WidgetID, unsigned int index) +{ + int ret; + + ret = rt286_write(codec, AC_VERB_SET_COEF_INDEX, + WidgetID, index); + if (ret < 0) { + dev_err(codec->dev, "Failed to set private addr: %d\n", ret); + return ret; + } + return rt286_read(codec, AC_VERB_GET_PROC_COEF, + WidgetID, index); +} + +static int rt286_index_update_bits(struct snd_soc_codec *codec, + unsigned int WidgetID, unsigned int index, + unsigned int mask, unsigned int data) +{ + unsigned long old, new; + int change, ret; + + ret = rt286_index_read(codec, WidgetID, index); + if (ret < 0) { + dev_err(codec->dev, "Failed to read private reg: %d\n", ret); + goto err; + } + + old = ret; + new = (old & ~mask) | (data & mask); + change = old != new; + + if (change) { + ret = rt286_index_write(codec, WidgetID, index, new); + if (ret < 0) { + dev_err(codec->dev, + "Failed to write private reg: %d\n", ret); + goto err; + } + } + return change; + +err: + return ret; +} + +static int rt286_support_power_controls[] = { + NODE_ID_DAC_OUT1, + NODE_ID_DAC_OUT2, + NODE_ID_ADC_IN1, + NODE_ID_ADC_IN2, + NODE_ID_MIC1, + NODE_ID_DMIC1, + NODE_ID_DMIC2, + NODE_ID_SPK_OUT, + NODE_ID_HP_OUT, +}; +#define RT286_POWER_REG_LEN ARRAY_SIZE(rt286_support_power_controls) + +static const struct reg_default rt286_reg[TOTAL_NODE_ID + 1] = { + { NODE_ID_DAC_OUT1, 0x7f7f }, + { NODE_ID_DAC_OUT2, 0x7f7f }, + { NODE_ID_SPDIF, 0x0000 }, + { NODE_ID_ADC_IN1, 0x4343 }, + { NODE_ID_ADC_IN2, 0x4343 }, + { NODE_ID_MIC1, 0x0002 }, + { NODE_ID_MIXER_IN, 0x000b }, + { NODE_ID_MIXER_OUT1, 0x0002 }, + { NODE_ID_MIXER_OUT2, 0x0000 }, + { NODE_ID_SPK_OUT, 0x0000 }, + { NODE_ID_HP_OUT, 0x0000 }, + { NODE_ID_MIXER_IN1, 0x0005 }, + { NODE_ID_MIXER_IN2, 0x0005 }, +}; + +static bool rt286_volatile_register(struct device *dev, unsigned int reg) +{ + return 0; +} + +static bool rt286_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case NODE_ID_DAC_OUT1: + case NODE_ID_DAC_OUT2: + case NODE_ID_ADC_IN1: + case NODE_ID_ADC_IN2: + case NODE_ID_MIC1: + case NODE_ID_SPDIF: + case NODE_ID_MIXER_IN: + case NODE_ID_MIXER_OUT1: + case NODE_ID_MIXER_OUT2: + case NODE_ID_DMIC1: + case NODE_ID_DMIC2: + case NODE_ID_SPK_OUT: + case NODE_ID_HP_OUT: + case NODE_ID_MIXER_IN1: + case NODE_ID_MIXER_IN2: + return 1; + default: + return 0; + } +} + +int rt286_jack_detect(struct snd_soc_codec *codec, bool *bHP, bool *bMIC) +{ + unsigned int val; + int i; + + *bHP = false; + *bMIC = false; + +#if RT_SUPPORT_COMBO_JACK + *bHP = rt286_read(codec, AC_VERB_GET_PIN_SENSE, NODE_ID_HP_OUT, 0); + if (*bHP) { + /* power on HV,VERF */ + rt286_index_update_bits(codec, NODE_ID_VENDOR_REGISTERS, + 0x03, 0x1001, 0x0000); + /* power LDO1 */ + rt286_index_update_bits(codec, NODE_ID_VENDOR_REGISTERS, + 0x08, 0x0004, 0x0004); + rt286_write(codec, AC_VERB_SET_PIN_WIDGET_CONTROL, + NODE_ID_MIC1, 0x24); + rt286_write(codec, AC_VERB_SET_COEF_INDEX, + 0x20, 0x50); + val = rt286_read(codec, AC_VERB_GET_PROC_COEF, + 0x20, 0x50); + + msleep(200); + i = 40; + while (((val & 0x0800) == 0) && (i > 0)) { + rt286_write(codec, AC_VERB_SET_COEF_INDEX, + 0x20, 0x50); + val = rt286_read(codec, + AC_VERB_GET_PROC_COEF, 0x20, 0x50); + i--; + msleep(20); + } + + if (0x0400 == (val & 0x0700)) { + *bMIC = false; + + rt286_write(codec, AC_VERB_SET_PIN_WIDGET_CONTROL, + NODE_ID_MIC1, 0x20); + /* power off HV,VERF */ + rt286_index_update_bits(codec, + NODE_ID_VENDOR_REGISTERS, + 0x03, 0x1001, 0x1001); + rt286_index_update_bits(codec, + NODE_ID_VENDOR_REGISTERS, + 0x04, 0xC000, 0x0000); + rt286_index_update_bits(codec, + NODE_ID_VENDOR_REGISTERS, + 0x4f, 0x0030, 0x0000); + rt286_index_update_bits(codec, + NODE_ID_VENDOR_REGISTERS, + 0x02, 0xc000, 0x0000); + } else if ((0x0200 == (val & 0x0700)) || + (0x0100 == (val & 0x0700))) { + *bMIC = true; + } else { + *bMIC = false; + } + + rt286_index_update_bits(codec, NODE_ID_VENDOR_REGISTERS, + 0x20, 0x0060, 0x0000); + } else { + rt286_index_update_bits(codec, NODE_ID_VENDOR_REGISTERS, + 0x20, 0x0060, 0x0020); + rt286_index_update_bits(codec, NODE_ID_VENDOR_REGISTERS, + 0x04, 0xC000, 0x8000); + rt286_index_update_bits(codec, NODE_ID_VENDOR_REGISTERS, + 0x4f, 0x0030, 0x0020); + rt286_index_update_bits(codec, NODE_ID_VENDOR_REGISTERS, + 0x02, 0xc000, 0x8000); + + *bMIC = false; + } + +#else + *bHP = rt286_read(codec, AC_VERB_GET_PIN_SENSE, NODE_ID_HP_OUT, 0); + *bMIC = rt286_read(codec, AC_VERB_GET_PIN_SENSE, NODE_ID_MIC1, 0); +#endif + + + /* Clear IRQ */ + rt286_index_update_bits(codec, NODE_ID_VENDOR_REGISTERS, + 0x33, 0x1, 0x1); + return 0; +} + + +static const DECLARE_TLV_DB_SCALE(out_vol_tlv, -6350, 50, 0); +static const DECLARE_TLV_DB_SCALE(mic_vol_tlv, 0, 1000, 0); + +static int rt286_playback_vol_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + + ucontrol->value.integer.value[0] = + rt286_read(codec, AC_VERB_GET_AMP_GAIN_MUTE, + mc->reg, 0xa000) & 0x7f; + ucontrol->value.integer.value[1] = + rt286_read(codec, AC_VERB_GET_AMP_GAIN_MUTE, + mc->reg, 0x8000) & 0x7f; + + return 0; +} + +static int rt286_playback_vol_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + unsigned int vall, valr; + + vall = ucontrol->value.integer.value[0]; + valr = ucontrol->value.integer.value[1]; + if (vall == valr) { + vall = vall | 0xb000; + rt286_write(codec, AC_VERB_SET_AMP_GAIN_MUTE, mc->reg, vall); + } else { + vall = vall | 0xa000; + rt286_write(codec, AC_VERB_SET_AMP_GAIN_MUTE, mc->reg, vall); + valr = valr | 0x9000; + rt286_write(codec, AC_VERB_SET_AMP_GAIN_MUTE, mc->reg, valr); + } + + return 0; +} + +static int rt286_capture_vol_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + + ucontrol->value.integer.value[0] = + rt286_read(codec, AC_VERB_GET_AMP_GAIN_MUTE, + mc->reg, 0x0) & 0x7f; + ucontrol->value.integer.value[1] = + rt286_read(codec, AC_VERB_GET_AMP_GAIN_MUTE, + mc->reg, 0x2000) & 0x7f; + + return 0; +} + +static int rt286_capture_vol_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + unsigned int vall, valr; + + vall = ucontrol->value.integer.value[0]; + valr = ucontrol->value.integer.value[1]; + if (vall == valr) { + vall = vall | 0x7000; + rt286_write(codec, AC_VERB_SET_AMP_GAIN_MUTE, mc->reg, vall); + } else { + vall = vall | 0x6000; + rt286_write(codec, AC_VERB_SET_AMP_GAIN_MUTE, mc->reg, vall); + valr = valr | 0x5000; + rt286_write(codec, AC_VERB_SET_AMP_GAIN_MUTE, mc->reg, valr); + } + + return 0; +} + +static int rt286_mic_gain_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = + rt286_read(codec, AC_VERB_GET_AMP_GAIN_MUTE, + mc->reg, 0x0) & 0x3; + + return 0; +} + +static int rt286_mic_gain_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + unsigned int val; + + val = ucontrol->value.integer.value[0]; + val = val | 0x7000; + rt286_write(codec, AC_VERB_SET_AMP_GAIN_MUTE, mc->reg, val); + + return 0; +} + +static int rt286_playback_switch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + + ucontrol->value.integer.value[0] = + !(rt286_read(codec, AC_VERB_GET_AMP_GAIN_MUTE, + mc->reg, 0xa000) & 0x80); + ucontrol->value.integer.value[1] = + !(rt286_read(codec, AC_VERB_GET_AMP_GAIN_MUTE, + mc->reg, 0x8000) & 0x80); + + return 0; +} + +static int rt286_playback_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + unsigned int vall, valr; + + vall = (!ucontrol->value.integer.value[0] << 7); + valr = (!ucontrol->value.integer.value[1] << 7); + + if (vall == valr) { + vall = vall | 0xb000; + rt286_write(codec, AC_VERB_SET_AMP_GAIN_MUTE, mc->reg, vall); + } else { + vall = vall | 0xa000; + rt286_write(codec, AC_VERB_SET_AMP_GAIN_MUTE, mc->reg, vall); + valr = valr | 0x9000; + rt286_write(codec, AC_VERB_SET_AMP_GAIN_MUTE, mc->reg, valr); + } + + return 0; +} + +static const struct snd_kcontrol_new rt286_snd_controls[] = { + SOC_DOUBLE_EXT_TLV("DAC0 Playback Volume", NODE_ID_DAC_OUT1, + 8, 0, 0x7f, 0, + rt286_playback_vol_get, rt286_playback_vol_put, + out_vol_tlv), + SOC_DOUBLE_EXT_TLV("ADC0 Capture Volume", NODE_ID_ADC_IN1, + 8, 0, 0x7f, 0, + rt286_capture_vol_get, rt286_capture_vol_put, + out_vol_tlv), + SOC_SINGLE_EXT_TLV("AMIC Gain", NODE_ID_MIC1, 0, 0x3, 0, + rt286_mic_gain_get, rt286_mic_gain_put, + mic_vol_tlv), + SOC_DOUBLE_EXT("Headphone Playback Switch", NODE_ID_HP_OUT, + 15, 8, 1, 1, rt286_playback_switch_get, + rt286_playback_switch_put), + SOC_DOUBLE_EXT("Speaker Playback Switch", NODE_ID_SPK_OUT, + 15, 8, 1, 1, rt286_playback_switch_get, + rt286_playback_switch_put), +}; + +static void hwCodecConfigure(struct snd_soc_codec *codec) +{ + struct rt286_priv *rt286 = snd_soc_codec_get_drvdata(codec); + unsigned int val; + int i; + + rt286_write(codec, AC_VERB_SET_POWER_STATE, + NODE_ID_AUDIO_FUNCTION_GROUP, AC_PWRST_D3); + for (i = 0; i < RT286_POWER_REG_LEN; i++) { + rt286_write(codec, AC_VERB_SET_POWER_STATE, + rt286_support_power_controls[i], AC_PWRST_D3); + } + + rt286_index_write(codec, NODE_ID_VENDOR_REGISTERS, 0x4F, 0xb029); + rt286_index_write(codec, NODE_ID_VENDOR_REGISTERS, 0x09, 0xc400); + rt286_index_write(codec, NODE_ID_VENDOR_REGISTERS, 0x0A, 0x0120); + rt286_index_write(codec, NODE_ID_VENDOR_REGISTERS, 0x33, 0x020A); + +#if !RT_SUPPORT_COMBO_JACK + rt286_index_write(codec, NODE_ID_VENDOR_REGISTERS, 0x50, 0x0000); + rt286_index_write(codec, NODE_ID_VENDOR_REGISTERS, 0x19, 0x0816); + rt286_index_write(codec, NODE_ID_VENDOR_REGISTERS, 0x20, 0x0000); +#endif + + mdelay(10); + + /* Set configuration default */ + /* MIC1 node 0x18 */ + rt286_write(codec, AC_VERB_SET_CONFIG_DEFAULT_BYTES_3, + NODE_ID_MIC1, 0x00); + + /* DMIC1/2 */ + rt286_write(codec, AC_VERB_SET_CONFIG_DEFAULT_BYTES_3, + NODE_ID_DMIC1, 0x00); + /* DMIC3/4 */ + rt286_write(codec, AC_VERB_SET_CONFIG_DEFAULT_BYTES_3, + NODE_ID_DMIC2, 0x00); + + mdelay(10); + +#if RT_SUPPORT_COMBO_JACK + rt286_index_write(codec, NODE_ID_VENDOR_REGISTERS, 0x4f, 0x5029); +#endif + + rt286_index_update_bits(codec, + NODE_ID_VENDOR_REGISTERS, 0x08, 0x0008, 0x0000); + rt286_index_update_bits(codec, + NODE_ID_VENDOR_REGISTERS, 0x62, 0x0007, 0x0007); + rt286_index_update_bits(codec, + NODE_ID_VENDOR_REGISTERS, 0x0e, 0x00c0, 0x0040); + /* Sync volume */ + regmap_read(rt286->regmap, NODE_ID_ADC_IN1, &val); + val = (val >> 8) & 0x7f; + rt286_write(codec, AC_VERB_SET_AMP_GAIN_MUTE, + NODE_ID_ADC_IN1, 0x6000 | val); + regmap_read(rt286->regmap, NODE_ID_ADC_IN1, &val); + val = val & 0x7f; + rt286_write(codec, AC_VERB_SET_AMP_GAIN_MUTE, + NODE_ID_ADC_IN1, 0x5000 | val); + regmap_read(rt286->regmap, NODE_ID_ADC_IN2, &val); + val = (val >> 8) & 0x7f; + rt286_write(codec, AC_VERB_SET_AMP_GAIN_MUTE, + NODE_ID_ADC_IN2, 0x6000 | val); + regmap_read(rt286->regmap, NODE_ID_ADC_IN2, &val); + val = val & 0x7f; + rt286_write(codec, AC_VERB_SET_AMP_GAIN_MUTE, + NODE_ID_ADC_IN2, 0x5000 | val); + regmap_read(rt286->regmap, NODE_ID_DAC_OUT1, &val); + val = (val >> 8) & 0x7f; + rt286_write(codec, AC_VERB_SET_AMP_GAIN_MUTE, + NODE_ID_DAC_OUT1, 0xa000 | val); + regmap_read(rt286->regmap, NODE_ID_DAC_OUT1, &val); + val = val & 0x7f; + rt286_write(codec, AC_VERB_SET_AMP_GAIN_MUTE, + NODE_ID_DAC_OUT1, 0x9000 | val); + regmap_read(rt286->regmap, NODE_ID_DAC_OUT2, &val); + val = (val >> 8) & 0x7f; + rt286_write(codec, AC_VERB_SET_AMP_GAIN_MUTE, + NODE_ID_DAC_OUT2, 0xa000 | val); + regmap_read(rt286->regmap, NODE_ID_DAC_OUT2, &val); + val = val & 0x7f; + rt286_write(codec, AC_VERB_SET_AMP_GAIN_MUTE, + NODE_ID_DAC_OUT2, 0x9000 | val); + regmap_read(rt286->regmap, NODE_ID_MIC1, &val); + val = val & 0x3; + rt286_write(codec, AC_VERB_SET_AMP_GAIN_MUTE, + NODE_ID_MIC1, 0x7000 | val); + /* Sync the mux selection */ + regmap_read(rt286->regmap, NODE_ID_MIXER_IN1, &val); + rt286_write(codec, AC_VERB_SET_CONNECT_SEL, + NODE_ID_MIXER_IN1, val & RT286_ADC_SEL_MASK); + regmap_read(rt286->regmap, NODE_ID_MIXER_IN2, &val); + rt286_write(codec, AC_VERB_SET_CONNECT_SEL, + NODE_ID_MIXER_IN2, val & RT286_ADC_SEL_MASK); + +} + +/* Digital Mixer */ +static const struct snd_kcontrol_new rt286_front_mix[] = { + SOC_DAPM_SINGLE("DAC Switch", NODE_ID_MIXER_OUT1, + RT286_M_FRONT_DAC_SFT, 1, 1), + SOC_DAPM_SINGLE("RECMIX Switch", NODE_ID_MIXER_OUT1, + RT286_M_FRONT_REC_SFT, 1, 1), +}; + +/* Analog Input Mixer */ +static const struct snd_kcontrol_new rt286_rec_mix[] = { + SOC_DAPM_SINGLE("Beep Switch", NODE_ID_MIXER_IN, + RT286_M_REC_BEEP_SFT, 1, 1), + SOC_DAPM_SINGLE("Line1 Switch", NODE_ID_MIXER_IN, + RT286_M_REC_LINE1_SFT, 1, 1), + SOC_DAPM_SINGLE("Mic1 Switch", NODE_ID_MIXER_IN, + RT286_M_REC_MIC1_SFT, 1, 1), + SOC_DAPM_SINGLE("I2S Switch", NODE_ID_MIXER_IN, + RT286_M_REC_I2S_SFT, 1, 1), +}; + +/* ADC0 source */ +static const char * const rt286_adc_src[] = { + "Mic", "Dmic" +}; + +static int rt286_adc_values[] = { + 0, 5, +}; + +static const SOC_VALUE_ENUM_SINGLE_DECL( + rt286_adc0_enum, NODE_ID_MIXER_IN1, RT286_ADC_SEL_SFT, + RT286_ADC_SEL_MASK, rt286_adc_src, rt286_adc_values); + +static const struct snd_kcontrol_new rt286_adc0_mux = + SOC_DAPM_VALUE_ENUM("ADC 0 source", rt286_adc0_enum); + +static const SOC_VALUE_ENUM_SINGLE_DECL( + rt286_adc1_enum, NODE_ID_MIXER_IN2, RT286_ADC_SEL_SFT, + RT286_ADC_SEL_MASK, rt286_adc_src, rt286_adc_values); + +static const struct snd_kcontrol_new rt286_adc1_mux = + SOC_DAPM_VALUE_ENUM("ADC 1 source", rt286_adc1_enum); + +/* HP-OUT source */ +static const char * const rt286_hpo_src[] = { + "Front", "Surr" +}; + +static const SOC_ENUM_SINGLE_DECL(rt286_hpo_enum, NODE_ID_HP_OUT, + RT286_HP_SEL_SFT, rt286_hpo_src); + +static const struct snd_kcontrol_new rt286_hpo_mux = +SOC_DAPM_ENUM("HPO source", rt286_hpo_enum); + +/* SPK-OUT source */ +static const char * const rt286_spo_src[] = { + "Front", "Surr" +}; + +static const SOC_ENUM_SINGLE_DECL(rt286_spo_enum, NODE_ID_SPK_OUT, + RT286_SPK_SEL_SFT, rt286_spo_src); + +static const struct snd_kcontrol_new rt286_spo_mux = +SOC_DAPM_ENUM("SPO source", rt286_spo_enum); + +/* SPDIF source */ +static const char * const rt286_spdif_src[] = { + "PCM-IN 0", "PCM-IN 1", "SP-OUT", "PP" +}; + +static const SOC_ENUM_SINGLE_DECL(rt286_spdif_enum, NODE_ID_SPDIF, + RT286_SPDIF_SEL_SFT, rt286_spdif_src); + +static const struct snd_kcontrol_new rt286_spdif_mux = +SOC_DAPM_ENUM("SPDIF source", rt286_spdif_enum); + + +static int rt286_spk_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + rt286_write(codec, AC_VERB_SET_POWER_STATE, + NODE_ID_SPK_OUT, AC_PWRST_D0); + rt286_write(codec, AC_VERB_SET_PIN_WIDGET_CONTROL, + NODE_ID_SPK_OUT, 0x40); + rt286_write(codec, AC_VERB_SET_EAPD_BTLENABLE, + NODE_ID_SPK_OUT, 0x0002); + rt286_write(codec, AC_VERB_SET_AMP_GAIN_MUTE, + NODE_ID_SPK_OUT, 0xb000); + break; + case SND_SOC_DAPM_PRE_PMD: + rt286_write(codec, AC_VERB_SET_AMP_GAIN_MUTE, + NODE_ID_SPK_OUT, 0xb080); + rt286_write(codec, AC_VERB_SET_EAPD_BTLENABLE, + NODE_ID_SPK_OUT, 0x0000); + rt286_write(codec, AC_VERB_SET_PIN_WIDGET_CONTROL, + NODE_ID_SPK_OUT, 0x00); + rt286_write(codec, AC_VERB_SET_POWER_STATE, + NODE_ID_SPK_OUT, AC_PWRST_D3); + break; + + default: + return 0; + } + + return 0; +} + +static int rt286_set_dmic1_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + rt286_write(codec, AC_VERB_SET_PIN_WIDGET_CONTROL, + NODE_ID_DMIC1, 0x20); + rt286_write(codec, AC_VERB_SET_POWER_STATE, + NODE_ID_DMIC1, AC_PWRST_D0); + break; + case SND_SOC_DAPM_PRE_PMD: + rt286_write(codec, AC_VERB_SET_POWER_STATE, + NODE_ID_DMIC1, AC_PWRST_D3); + rt286_write(codec, AC_VERB_SET_PIN_WIDGET_CONTROL, + NODE_ID_DMIC1, 0x0); + break; + default: + return 0; + } + + return 0; +} + +static int rt286_set_dmic2_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + rt286_write(codec, AC_VERB_SET_POWER_STATE, + NODE_ID_DMIC2, AC_PWRST_D0); + break; + case SND_SOC_DAPM_PRE_PMD: + rt286_write(codec, AC_VERB_SET_POWER_STATE, + NODE_ID_DMIC2, AC_PWRST_D3); + break; + default: + return 0; + } + + return 0; +} + +static int rt286_hp_pow_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + rt286_write(codec, AC_VERB_SET_POWER_STATE, + NODE_ID_HP_OUT, AC_PWRST_D0); + rt286_write(codec, AC_VERB_SET_PIN_WIDGET_CONTROL, + NODE_ID_HP_OUT, 0x40); + break; + + case SND_SOC_DAPM_PRE_PMD: + rt286_write(codec, AC_VERB_SET_PIN_WIDGET_CONTROL, + NODE_ID_HP_OUT, 0x00); + rt286_write(codec, AC_VERB_SET_POWER_STATE, + NODE_ID_HP_OUT, AC_PWRST_D3); + break; + + default: + return 0; + } + + return 0; +} + +static int rt286_hp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + rt286_write(codec, AC_VERB_SET_AMP_GAIN_MUTE, + NODE_ID_HP_OUT, 0xb000); + mdelay(hp_amp_time); + break; + + case SND_SOC_DAPM_PRE_PMD: + rt286_write(codec, AC_VERB_SET_AMP_GAIN_MUTE, + NODE_ID_HP_OUT, 0xb080); + break; + + default: + return 0; + } + + return 0; +} + +static int rt286_dac0_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + rt286_write(codec, AC_VERB_SET_POWER_STATE, + NODE_ID_DAC_OUT1, AC_PWRST_D0); + break; + case SND_SOC_DAPM_PRE_PMD: + rt286_write(codec, AC_VERB_SET_POWER_STATE, + NODE_ID_DAC_OUT1, AC_PWRST_D3); + break; + default: + return 0; + } + + return 0; +} + +static int rt286_dac1_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + rt286_write(codec, AC_VERB_SET_POWER_STATE, + NODE_ID_DAC_OUT2, AC_PWRST_D0); + break; + case SND_SOC_DAPM_PRE_PMD: + rt286_write(codec, AC_VERB_SET_POWER_STATE, + NODE_ID_DAC_OUT2, AC_PWRST_D3); + break; + default: + return 0; + } + + return 0; +} + +static int rt286_adc0_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + + switch (event) { + case SND_SOC_DAPM_POST_PMU: +#if (RT_SUPPORT_COMBO_JACK == 1) + rt286_index_update_bits(codec, NODE_ID_VENDOR_REGISTERS, + 0x04, 0xC000, 0x8000); + rt286_index_update_bits(codec, NODE_ID_VENDOR_REGISTERS, + 0x4f, 0x0030, 0x0020); + rt286_index_update_bits(codec, NODE_ID_VENDOR_REGISTERS, + 0x02, 0xc000, 0x8000); +#endif + rt286_index_update_bits(codec, NODE_ID_VENDOR_REGISTERS, + 0x0f, 0x0020, 0x0000); + rt286_write(codec, AC_VERB_SET_POWER_STATE, + NODE_ID_ADC_IN1, AC_PWRST_D0); + break; + case SND_SOC_DAPM_PRE_PMD: + rt286_write(codec, AC_VERB_SET_POWER_STATE, + NODE_ID_ADC_IN1, AC_PWRST_D3); + break; + case SND_SOC_DAPM_POST_REG: + rt286_write(codec, AC_VERB_SET_CONNECT_SEL, + NODE_ID_MIXER_IN1, snd_soc_read(codec, + NODE_ID_MIXER_IN1) & RT286_ADC_SEL_MASK); + break; + default: + return 0; + } + + return 0; +} + +static int rt286_adc1_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + +#if (RT_SUPPORT_COMBO_JACK == 1) + rt286_index_update_bits(codec, NODE_ID_VENDOR_REGISTERS, + 0x04, 0xC000, 0x8000); + rt286_index_update_bits(codec, NODE_ID_VENDOR_REGISTERS, + 0x4f, 0x0030, 0x0020); + rt286_index_update_bits(codec, NODE_ID_VENDOR_REGISTERS, + 0x02, 0xc000, 0x8000); +#endif + rt286_write(codec, AC_VERB_SET_CHANNEL_STREAMID, + NODE_ID_ADC_IN1, 0x10); + rt286_index_update_bits(codec, NODE_ID_VENDOR_REGISTERS, + 0x0f, 0x0020, 0x0000); + rt286_write(codec, AC_VERB_SET_POWER_STATE, + NODE_ID_ADC_IN2, AC_PWRST_D0); + + break; + case SND_SOC_DAPM_PRE_PMD: + rt286_write(codec, AC_VERB_SET_POWER_STATE, + NODE_ID_ADC_IN2, AC_PWRST_D3); + break; + case SND_SOC_DAPM_POST_REG: + rt286_write(codec, AC_VERB_SET_CONNECT_SEL, + NODE_ID_MIXER_IN2, snd_soc_read(codec, + NODE_ID_MIXER_IN2) & RT286_ADC_SEL_MASK); + break; + default: + return 0; + } + + return 0; +} + +static int rt286_spk_mux_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + + switch (event) { + case SND_SOC_DAPM_POST_REG: + rt286_write(codec, AC_VERB_SET_CONNECT_SEL, + NODE_ID_SPK_OUT, snd_soc_read(codec, + NODE_ID_SPK_OUT) & RT286_SPK_SEL_MASK); + break; + default: + return 0; + } + + return 0; +} + +static int rt286_hpo_mux_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + + switch (event) { + case SND_SOC_DAPM_POST_REG: + rt286_write(codec, AC_VERB_SET_CONNECT_SEL, + NODE_ID_HP_OUT, snd_soc_read(codec, + NODE_ID_HP_OUT) & RT286_HP_SEL_MASK); + break; + default: + return 0; + } + + return 0; +} + +static const struct snd_soc_dapm_widget rt286_dapm_widgets[] = { + /* Input Lines */ + SND_SOC_DAPM_INPUT("DMIC1 Pin"), + SND_SOC_DAPM_INPUT("DMIC2 Pin"), + SND_SOC_DAPM_INPUT("MIC1"), + SND_SOC_DAPM_INPUT("LINE1"), + SND_SOC_DAPM_INPUT("Beep"), + + /* DMIC */ + SND_SOC_DAPM_PGA_E("DMIC1", SND_SOC_NOPM, 0, 0, + NULL, 0, rt286_set_dmic1_event, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_PGA_E("DMIC2", SND_SOC_NOPM, 0, 0, + NULL, 0, rt286_set_dmic2_event, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_SUPPLY("DMIC Receiver", SND_SOC_NOPM, + 0, 0, NULL, 0), + + /* REC Mixer */ + SND_SOC_DAPM_MIXER("RECMIX", SND_SOC_NOPM, 0, 0, + rt286_rec_mix, ARRAY_SIZE(rt286_rec_mix)), + + /* ADCs */ + SND_SOC_DAPM_ADC("ADC 0", NULL, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_ADC("ADC 1", NULL, SND_SOC_NOPM, 0, 0), + + /* ADC Mux */ + SND_SOC_DAPM_MUX_E("ADC 0 Mux", SND_SOC_NOPM, 0, 0, + &rt286_adc0_mux, rt286_adc0_event, SND_SOC_DAPM_PRE_PMD | + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_REG), + SND_SOC_DAPM_MUX_E("ADC 1 Mux", SND_SOC_NOPM, 0, 0, + &rt286_adc1_mux, rt286_adc1_event, SND_SOC_DAPM_PRE_PMD | + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_REG), + + + /* Audio Interface */ + SND_SOC_DAPM_AIF_IN("AIF1RX", "AIF1 Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AIF1TX", "AIF1 Capture", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("AIF2RX", "AIF2 Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("AIF2TX", "AIF2 Capture", 0, SND_SOC_NOPM, 0, 0), + + + /* Output Side */ + + /* DACs */ + SND_SOC_DAPM_DAC_E("DAC 0", NULL, SND_SOC_NOPM, + 0, 0, rt286_dac0_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_DAC_E("DAC 1", NULL, SND_SOC_NOPM, + 0, 0, rt286_dac1_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + + /* Output Mux */ + SND_SOC_DAPM_MUX_E("SPK Mux", SND_SOC_NOPM, 0, 0, + &rt286_spo_mux, + rt286_spk_mux_event, + SND_SOC_DAPM_POST_REG), + + SND_SOC_DAPM_MUX_E("HPO Mux", SND_SOC_NOPM, 0, 0, + &rt286_hpo_mux, + rt286_hpo_mux_event, + SND_SOC_DAPM_POST_REG), + + SND_SOC_DAPM_SUPPLY("HP Power", SND_SOC_NOPM, + 0, 0, rt286_hp_pow_event, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU), + + SND_SOC_DAPM_MUX("SPDIF Mux", SND_SOC_NOPM, 0, 0, + &rt286_spdif_mux), + + /* Output Mixer */ + SND_SOC_DAPM_MIXER("Front", SND_SOC_NOPM, 0, 0, + rt286_front_mix, ARRAY_SIZE(rt286_front_mix)), + SND_SOC_DAPM_PGA("Surr", SND_SOC_NOPM, 0, 0, NULL, 0), + + /* Output Pga */ + SND_SOC_DAPM_PGA_E("SPO", SND_SOC_NOPM, 0, 0, + NULL, 0, rt286_spk_event, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_PGA_E("HPO", SND_SOC_NOPM, 0, 0, + NULL, 0, rt286_hp_event, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU), + /* Output Lines */ + SND_SOC_DAPM_OUTPUT("SPOL"), + SND_SOC_DAPM_OUTPUT("SPOR"), + SND_SOC_DAPM_OUTPUT("HPO Pin"), + SND_SOC_DAPM_OUTPUT("SPDIF"), +}; + +static const struct snd_soc_dapm_route rt286_dapm_routes[] = { + {"DMIC1", NULL, "DMIC1 Pin"}, + {"DMIC2", NULL, "DMIC2 Pin"}, + {"DMIC1", NULL, "DMIC Receiver"}, + {"DMIC2", NULL, "DMIC Receiver"}, + + {"RECMIX", "Beep Switch", "Beep"}, + {"RECMIX", "Line1 Switch", "LINE1"}, + {"RECMIX", "Mic1 Switch", "MIC1"}, + + {"ADC 0 Mux", "Dmic", "DMIC1"}, + {"ADC 0 Mux", "Mic", "MIC1"}, + {"ADC 1 Mux", "Dmic", "DMIC2"}, + {"ADC 1 Mux", "Mic", "MIC1"}, + + {"ADC 0", NULL, "ADC 0 Mux"}, + {"ADC 1", NULL, "ADC 1 Mux"}, + + {"AIF1TX", NULL, "ADC 0"}, + {"AIF2TX", NULL, "ADC 1"}, + + {"DAC 0", NULL, "AIF1RX"}, + {"DAC 1", NULL, "AIF2RX"}, + + {"Front", "DAC Switch", "DAC 0"}, + {"Front", "RECMIX Switch", "RECMIX"}, + + {"Surr", NULL, "DAC 1"}, + + {"SPK Mux", "Front", "Front"}, + {"SPK Mux", "Surr", "Surr"}, + + {"HPO Mux", "Front", "Front"}, + {"HPO Mux", "Surr", "Surr"}, + + {"SPO", NULL, "SPK Mux"}, + {"HPO", NULL, "HPO Mux"}, + {"HPO", NULL, "HP Power"}, + + {"SPOL", NULL, "SPO"}, + {"SPOR", NULL, "SPO"}, + {"HPO Pin", NULL, "HPO"}, +}; + +static int rt286_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_codec *codec = rtd->codec; + unsigned long val; + int d_len_code; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + val = rt286_read(codec, AC_VERB_GET_STREAM_FORMAT, + NODE_ID_DAC_OUT1, 0); + else + val = rt286_read(codec, AC_VERB_GET_STREAM_FORMAT, + NODE_ID_ADC_IN1, 0); + + switch (params_rate(params)) { + /* bit 14 0:48K 1:44.1K */ + case 48000: + val &= ~0x4000; + break; + case 44100: + val |= 0x4000; + break; + default: + pr_err("unsportted sample rate %d\n", params_rate(params)); + return -EINVAL; + } + + if (params_channels(params) <= 16) { + /* bit 3:0 Number of Channel */ + val &= ~0xf; + val |= (params_channels(params) - 1); + } else { + pr_err("unsportted channels %d\n", params_channels(params)); + return -EINVAL; + } + + val &= ~0x70; + d_len_code = 0; + switch (params_format(params)) { + /* bit 6:4 Bits per Sample */ + case SNDRV_PCM_FORMAT_S16_LE: + d_len_code = 0; + val |= (0x1 << 4); + break; + case SNDRV_PCM_FORMAT_S32_LE: + d_len_code = 2; + val |= (0x4 << 4); + break; + case SNDRV_PCM_FORMAT_S20_3LE: + d_len_code = 1; + val |= (0x2 << 4); + break; + case SNDRV_PCM_FORMAT_S24_LE: + d_len_code = 2; + val |= (0x3 << 4); + break; + case SNDRV_PCM_FORMAT_S8: + break; + default: + return -EINVAL; + } + rt286_index_update_bits(codec, + NODE_ID_VENDOR_REGISTERS, 0x09, 0x0018, d_len_code << 3); + pr_debug("format val = 0x%lx\n", val); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + rt286_write(codec, AC_VERB_SET_STREAM_FORMAT, + NODE_ID_DAC_OUT1, val); + else + rt286_write(codec, AC_VERB_SET_STREAM_FORMAT, + NODE_ID_ADC_IN1, val); + + return 0; +} + +static int rt286_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_codec *codec = dai->codec; + unsigned long val_dac, val_adc; + + val_dac = rt286_read(codec, AC_VERB_GET_STREAM_FORMAT, + NODE_ID_DAC_OUT1, 0); + val_adc = rt286_read(codec, AC_VERB_GET_STREAM_FORMAT, + NODE_ID_ADC_IN1, 0); + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + rt286_index_update_bits(codec, + NODE_ID_VENDOR_REGISTERS, 0x09, 0x0300, 0x0); + break; + case SND_SOC_DAIFMT_LEFT_J: + rt286_index_update_bits(codec, + NODE_ID_VENDOR_REGISTERS, 0x09, 0x0300, 0x1 << 8); + break; + case SND_SOC_DAIFMT_DSP_A: + rt286_index_update_bits(codec, + NODE_ID_VENDOR_REGISTERS, 0x09, 0x0300, 0x2 << 8); + break; + case SND_SOC_DAIFMT_DSP_B: + rt286_index_update_bits(codec, + NODE_ID_VENDOR_REGISTERS, 0x09, 0x0300, 0x3 << 8); + break; + default: + return -EINVAL; + } + /* bit 15 Stream Type 0:PCM 1:Non-PCM */ + val_dac &= ~0x8000; + val_adc &= ~0x8000; + rt286_write(codec, AC_VERB_SET_STREAM_FORMAT, + NODE_ID_DAC_OUT1, val_dac); + rt286_write(codec, AC_VERB_SET_STREAM_FORMAT, + NODE_ID_ADC_IN1, val_adc); + + return 0; +} + +static int rt286_set_dai_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = dai->codec; + + pr_debug("%s freq=%d\n", __func__, freq); + + switch (freq) { + case 19200000: + case 24000000: + rt286_index_update_bits(codec, NODE_ID_VENDOR_REGISTERS, + 0x63, 0x4, 0x4); + rt286_index_update_bits(codec, NODE_ID_VENDOR_REGISTERS, + 0x49, 0x20, 0x0); + break; + case 12288000: + case 11289600: + rt286_index_update_bits(codec, NODE_ID_VENDOR_REGISTERS, + 0x0a, 0x0108, 0x0000); + rt286_index_update_bits(codec, NODE_ID_VENDOR_REGISTERS, + 0x0b, 0xfc1e, 0x0004); + rt286_index_update_bits(codec, NODE_ID_VENDOR_REGISTERS, + 0x49, 0x0020, 0x0020); + break; + case 24576000: + case 22579200: + rt286_index_update_bits(codec, NODE_ID_VENDOR_REGISTERS, + 0x0a, 0x0108, 0x0008); + rt286_index_update_bits(codec, NODE_ID_VENDOR_REGISTERS, + 0x0b, 0xfc1e, 0x5406); + rt286_index_update_bits(codec, NODE_ID_VENDOR_REGISTERS, + 0x49, 0x0020, 0x0020); + break; + default: + pr_err("unsported system clock\n"); + return -EINVAL; + } + + return 0; +} + +static int rt286_set_dai_dfs(struct snd_soc_dai *dai, unsigned int fs) +{ + struct snd_soc_codec *codec = dai->codec; + + pr_debug("%s fs=%d\n", __func__, fs); + if (50 == fs) + rt286_index_update_bits(codec, + NODE_ID_VENDOR_REGISTERS, 0x09, 0x1000, 0x1000); + + + return 0; +} + +static ssize_t rt286_codec_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct rt286_priv *rt286 = i2c_get_clientdata(client); + struct snd_soc_codec *codec = rt286->codec; + unsigned long val; + int cnt = 0, i; + + cnt += sprintf(buf, "RT286 codec register\n"); + for (i = 0; i <= TOTAL_NODE_ID; i++) { + if (cnt + 31 >= PAGE_SIZE) + break; + if (!rt286_readable_register(dev, i)) + continue; + val = rt286_read(codec, AC_VERB_GET_POWER_STATE, i, 0); + + cnt += snprintf(buf + cnt, 31, + "vid: 0x%x nid: %02x val: %04lx\n", + AC_VERB_GET_POWER_STATE, i, val); + } + for (i = 0; i <= TOTAL_NODE_ID; i++) { + if (cnt + 31 >= PAGE_SIZE) + break; + if (!rt286_readable_register(dev, i)) + continue; + val = rt286_read(codec, AC_VERB_GET_PIN_WIDGET_CONTROL, i, 0); + + cnt += snprintf(buf + cnt, 31, + "vid: 0x%x nid: %02x val: %04lx\n", + AC_VERB_GET_PIN_WIDGET_CONTROL, i, val); + } + for (i = 0; i <= TOTAL_NODE_ID; i++) { + if (cnt + 31 >= PAGE_SIZE) + break; + if (!rt286_readable_register(dev, i)) + continue; + val = rt286_read(codec, AC_VERB_GET_AMP_GAIN_MUTE, i, 0); + + cnt += snprintf(buf + cnt, 31, + "vid: 0x%x nid: %02x val: %04lx\n", + AC_VERB_GET_AMP_GAIN_MUTE, i, val); + } + + val = rt286_index_read(codec, NODE_ID_VENDOR_REGISTERS, 0x09); + cnt += snprintf(buf + cnt, 31, "Index: 0x%x val: %04lx\n", 0x09, val); + + + if (cnt >= PAGE_SIZE) + cnt = PAGE_SIZE - 1; + + return cnt; +} + +static ssize_t rt286_codec_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct rt286_priv *rt286 = i2c_get_clientdata(client); + struct snd_soc_codec *codec = rt286->codec; + unsigned int nid = 0, vid = 0; + unsigned long data = 0; + int i; + + pr_debug("register "%s" count=%ld\n", buf, count); + + for (i = 0; i < count; i++) { + if (*(buf + i) <= '9' && *(buf + i) >= '0') + vid = (vid << 4) | (*(buf + i) - '0'); + else if (*(buf + i) <= 'f' && *(buf + i) >= 'a') + vid = (vid << 4) | ((*(buf + i) - 'a') + 0xa); + else if (*(buf + i) <= 'F' && *(buf + i) >= 'A') + vid = (vid << 4) | ((*(buf + i) - 'A') + 0xa); + else + break; + } + + for (i = i + 1; i < count; i++) { + if (*(buf + i) <= '9' && *(buf + i) >= '0') + nid = (nid << 4) | (*(buf + i) - '0'); + else if (*(buf + i) <= 'f' && *(buf + i) >= 'a') + nid = (nid << 4) | ((*(buf + i) - 'a') + 0xa); + else if (*(buf + i) <= 'F' && *(buf + i) >= 'A') + nid = (nid << 4) | ((*(buf + i) - 'A') + 0xa); + else + break; + } + + for (i = i + 1; i < count; i++) { + if (*(buf + i) <= '9' && *(buf + i) >= '0') + data = (data << 4) | (*(buf + i) - '0'); + else if (*(buf + i) <= 'f' && *(buf + i) >= 'a') + data = (data << 4) | ((*(buf + i) - 'a') + 0xa); + else if (*(buf + i) <= 'F' && *(buf + i) >= 'A') + data = (data << 4) | ((*(buf + i) - 'A') + 0xa); + else + break; + } + + if (*(buf + i + 1) == 'r') { + pr_info("vid=0x%x nid=0x%x data=0x%lx\n", + vid, nid, rt286_read(codec, vid, nid, data)); + } else { + rt286_write(codec, vid, nid, data); + pr_debug("vid=0x%x nid=0x%x data=0x%lx\n", vid, nid, data); + } + + return count; +} + +static DEVICE_ATTR(codec_reg, 0666, rt286_codec_show, rt286_codec_store); + +static int rt286_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + switch (level) { + case SND_SOC_BIAS_ON: + pr_debug("In case SND_SOC_BIAS_ON:\n"); + break; + + case SND_SOC_BIAS_PREPARE: + rt286_write(codec, AC_VERB_SET_POWER_STATE, + NODE_ID_AUDIO_FUNCTION_GROUP, AC_PWRST_D0); + pr_debug("In case SND_SOC_BIAS_PREPARE:\n"); + break; + + case SND_SOC_BIAS_STANDBY: + pr_debug("In case SND_SOC_BIAS_STANDBY:\n"); + break; + + case SND_SOC_BIAS_OFF: + pr_debug("In case SND_SOC_BIAS_OFF:\n"); + rt286_write(codec, AC_VERB_SET_POWER_STATE, + NODE_ID_AUDIO_FUNCTION_GROUP, AC_PWRST_D3); + break; + + default: + break; + } + codec->dapm.bias_level = level; + + return 0; +} + +static int rt286_probe(struct snd_soc_codec *codec) +{ + struct rt286_priv *rt286 = snd_soc_codec_get_drvdata(codec); + int ret; + + pr_info("Codec driver version %s\n", VERSION); + + ret = snd_soc_codec_set_cache_io(codec, 8, 16, SND_SOC_REGMAP); + if (ret != 0) { + dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret); + return ret; + } + + hwCodecConfigure(codec); + codec->dapm.bias_level = SND_SOC_BIAS_OFF; + rt286->codec = codec; + + ret = device_create_file(codec->dev, &dev_attr_codec_reg); + if (ret != 0) { + dev_err(codec->dev, + "Failed to create codec_reg sysfs files: %d\n", ret); + return ret; + } + + return 0; +} + +static int rt286_remove(struct snd_soc_codec *codec) +{ + rt286_set_bias_level(codec, SND_SOC_BIAS_OFF); + return 0; +} + +#ifdef CONFIG_PM +static int rt286_suspend(struct snd_soc_codec *codec) +{ + return 0; +} + +static int rt286_resume(struct snd_soc_codec *codec) +{ + return 0; +} +#else +#define rt286_suspend NULL +#define rt286_resume NULL +#endif + +#define RT286_STEREO_RATES (SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000) +#define RT286_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S8) + +struct snd_soc_dai_ops rt286_aif_dai_ops = { + .hw_params = rt286_hw_params, + .set_fmt = rt286_set_dai_fmt, + .set_sysclk = rt286_set_dai_sysclk, + .set_dfs = rt286_set_dai_dfs, +}; + +struct snd_soc_dai_driver rt286_dai[] = { + { + .name = "rt286-aif1", + .id = RT286_AIF1, + .playback = { + .stream_name = "AIF1 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = RT286_STEREO_RATES, + .formats = RT286_FORMATS, + }, + .capture = { + .stream_name = "AIF1 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = RT286_STEREO_RATES, + .formats = RT286_FORMATS, + }, + .ops = &rt286_aif_dai_ops, + }, +}; + +static struct snd_soc_codec_driver soc_codec_dev_rt286 = { + .probe = rt286_probe, + .remove = rt286_remove, + .suspend = rt286_suspend, + .resume = rt286_resume, + .set_bias_level = rt286_set_bias_level, + .idle_bias_off = true, + .controls = rt286_snd_controls, + .num_controls = ARRAY_SIZE(rt286_snd_controls), + .dapm_widgets = rt286_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(rt286_dapm_widgets), + .dapm_routes = rt286_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(rt286_dapm_routes), +}; + +static const struct regmap_config rt286_regmap = { + .reg_bits = 8, + .val_bits = 16, + + .max_register = TOTAL_NODE_ID + 1, + .volatile_reg = rt286_volatile_register, + .readable_reg = rt286_readable_register, + + .cache_type = REGCACHE_RBTREE, + .reg_defaults = rt286_reg, + .num_reg_defaults = ARRAY_SIZE(rt286_reg), +}; + +static const struct i2c_device_id rt286_i2c_id[] = { + {"rt286", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, rt286_i2c_id); + +static struct acpi_device_id rt286_acpi_match[] = { + {"INT343A", 0}, + {}, +}; +MODULE_DEVICE_TABLE(acpi, rt286_acpi_match); + +static int rt286_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct rt286_priv *rt286; + int ret; + + rt286 = devm_kzalloc(&i2c->dev, + sizeof(struct rt286_priv), + GFP_KERNEL); + if (NULL == rt286) + return -ENOMEM; + + rt286->i2c = i2c; + i2c_set_clientdata(i2c, rt286); + + rt286->regmap = devm_regmap_init_i2c(i2c, &rt286_regmap); + if (IS_ERR(rt286->regmap)) { + ret = PTR_ERR(rt286->regmap); + dev_err(&i2c->dev, "Failed to allocate register map: %d\n", + ret); + return ret; + } + + regcache_cache_only(rt286->regmap, true); + ret = snd_soc_register_codec(&i2c->dev, &soc_codec_dev_rt286, + rt286_dai, ARRAY_SIZE(rt286_dai)); + if (ret < 0) + goto err; + + return 0; +err: + return ret; +} + +static int rt286_i2c_remove(struct i2c_client *i2c) +{ + snd_soc_unregister_codec(&i2c->dev); + + return 0; +} + +static void rt286_i2c_shutdown(struct i2c_client *client) +{ + struct rt286_priv *rt286 = i2c_get_clientdata(client); + struct snd_soc_codec *codec = rt286->codec; + + if (codec != NULL) + rt286_set_bias_level(codec, SND_SOC_BIAS_OFF); +} + +struct i2c_driver rt286_i2c_driver = { + .driver = { + .name = "rt286", + .owner = THIS_MODULE, + .acpi_match_table = ACPI_PTR(rt286_acpi_match), + }, + .probe = rt286_i2c_probe, + .remove = rt286_i2c_remove, + .shutdown = rt286_i2c_shutdown, + .id_table = rt286_i2c_id, +}; + +static int __init rt286_modinit(void) +{ + return i2c_add_driver(&rt286_i2c_driver); +} + +module_init(rt286_modinit); + +static void __exit rt286_modexit(void) +{ + i2c_del_driver(&rt286_i2c_driver); +} + +module_exit(rt286_modexit); + +MODULE_DESCRIPTION("ASoC RT286 driver"); +MODULE_AUTHOR("Bard Liao bardliao@realtek.com"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/rt286.h b/sound/soc/codecs/rt286.h new file mode 100644 index 0000000..3702d9a --- /dev/null +++ b/sound/soc/codecs/rt286.h @@ -0,0 +1,113 @@ +/* + * rt286.h -- RT286 ALSA SoC audio driver + * + * Copyright 2011 Realtek Microelectronics + * Author: Johnny Hsu johnnyhsu@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 __RT286_H__ +#define __RT286_H__ + +#define NODE_ID_AUDIO_FUNCTION_GROUP 0x01 +#define NODE_ID_DAC_OUT1 0x02 +#define NODE_ID_DAC_OUT2 0x03 +#define NODE_ID_ADC_IN1 0x09 +#define NODE_ID_ADC_IN2 0x08 +#define NODE_ID_MIXER_IN 0x0b +#define NODE_ID_MIXER_OUT1 0x0c +#define NODE_ID_MIXER_OUT2 0x0d +#define NODE_ID_DMIC1 0x12 +#define NODE_ID_DMIC2 0x13 +#define NODE_ID_SPK_OUT 0x14 +#define NODE_ID_MIC1 0x18 +#define NODE_ID_LINE1 0x1a +#define NODE_ID_BEEP 0x1d +#define NODE_ID_SPDIF 0x1e +#define NODE_ID_VENDOR_REGISTERS 0x20 +#define NODE_ID_HP_OUT 0x21 +#define NODE_ID_MIXER_IN1 0x22 +#define NODE_ID_MIXER_IN2 0x23 +#define TOTAL_NODE_ID 0x23 + +#define CONNECTION_INDEX_MIC1 0X0 +#define CONNECTION_INDEX_DMIC 0X5 + +/* SPDIF (0x06) */ +#define RT286_SPDIF_SEL_SFT 0 +#define RT286_SPDIF_SEL_PCM0 0 +#define RT286_SPDIF_SEL_PCM1 1 +#define RT286_SPDIF_SEL_SPOUT 2 +#define RT286_SPDIF_SEL_PP 3 + +/* RECMIX (0x0b) */ +#define RT286_M_REC_BEEP_SFT 0 +#define RT286_M_REC_LINE1_SFT 1 +#define RT286_M_REC_MIC1_SFT 2 +#define RT286_M_REC_I2S_SFT 3 + +/* Front (0x0c) */ +#define RT286_M_FRONT_DAC_SFT 0 +#define RT286_M_FRONT_REC_SFT 1 + +/* SPK-OUT (0x14) */ +#define RT286_SPK_SEL_MASK 0x1 +#define RT286_SPK_SEL_SFT 0 +#define RT286_SPK_SEL_F 0 +#define RT286_SPK_SEL_S 1 + +/* HP-OUT (0x21) */ +#define RT286_HP_SEL_MASK 0x1 +#define RT286_HP_SEL_SFT 0 +#define RT286_HP_SEL_F 0 +#define RT286_HP_SEL_S 1 + +/* ADC (0x22) (0x23) */ +#define RT286_ADC_SEL_MASK 0x7 +#define RT286_ADC_SEL_SFT 0 +#define RT286_ADC_SEL_SURR 0 +#define RT286_ADC_SEL_FRONT 1 +#define RT286_ADC_SEL_DMIC 2 +#define RT286_ADC_SEL_BEEP 4 +#define RT286_ADC_SEL_LINE1 5 +#define RT286_ADC_SEL_I2S 6 +#define RT286_ADC_SEL_MIC1 7 + +/* System Clock Source */ +enum { + RT286_SCLK_S_MCLK, + RT286_SCLK_S_PLL1, + RT286_SCLK_S_RCCLK, +}; + +enum { + RT286_AIF1, + RT286_AIF2, + RT286_AIF3, + RT286_AIFS, +}; + +enum { + RT286_DMIC_DIS, + RT286_DMIC1, + RT286_DMIC2, +}; + +enum { + RT286_J_IN_EVENT, /* Jack insert */ + RT286_J_OUT_EVENT, /* Jack evulse */ + RT286_BP_EVENT, /* Button Press */ + RT286_BR_EVENT, /* Button Release */ + RT286_UN_EVENT, /* Unknown */ +}; + +struct rt286_pll_code { + bool m_bp; /* Indicates bypass m code or not. */ + int m_code; + int n_code; + int k_code; +}; +#endif /* __RT286_H__ */
At Tue, 26 Nov 2013 17:11:31 +0800, bardliao@realtek.com wrote:
From: Bard Liao bardliao@realtek.com
This patch adds the ALC286 codec driver.
Signed-off-by: Bard Liao bardliao@realtek.com
This is the initial codec driver of ALC586. I tested DMCI/Headset recording and Speaker/Headphone playback.
sound/soc/codecs/Kconfig | 3 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/rt286.c | 1609 +++++++++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/rt286.h | 113 ++++ 4 files changed, 1727 insertions(+) create mode 100644 sound/soc/codecs/rt286.c create mode 100644 sound/soc/codecs/rt286.h
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 983d087a..56748d4 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -313,6 +313,9 @@ config SND_SOC_PCM1792A config SND_SOC_PCM3008 tristate
+config SND_SOC_RT286
- tristate
config SND_SOC_RT5631 tristate
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index bc12676..b31615c 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -48,6 +48,7 @@ snd-soc-pcm1792a-codec-objs := pcm1792a.o snd-soc-pcm3008-objs := pcm3008.o snd-soc-rt5631-objs := rt5631.o snd-soc-rt5640-objs := rt5640.o +snd-soc-rt286-objs := rt286.o snd-soc-sgtl5000-objs := sgtl5000.o snd-soc-alc5623-objs := alc5623.o snd-soc-alc5632-objs := alc5632.o @@ -181,6 +182,7 @@ obj-$(CONFIG_SND_SOC_PCM1792A) += snd-soc-pcm1792a-codec.o obj-$(CONFIG_SND_SOC_PCM3008) += snd-soc-pcm3008.o obj-$(CONFIG_SND_SOC_RT5631) += snd-soc-rt5631.o obj-$(CONFIG_SND_SOC_RT5640) += snd-soc-rt5640.o +obj-$(CONFIG_SND_SOC_RT286) += snd-soc-rt286.o obj-$(CONFIG_SND_SOC_SGTL5000) += snd-soc-sgtl5000.o obj-$(CONFIG_SND_SOC_SIGMADSP) += snd-soc-sigmadsp.o obj-$(CONFIG_SND_SOC_SI476X) += snd-soc-si476x.o diff --git a/sound/soc/codecs/rt286.c b/sound/soc/codecs/rt286.c new file mode 100644 index 0000000..e95f76c --- /dev/null +++ b/sound/soc/codecs/rt286.c @@ -0,0 +1,1609 @@ +/*
- rt286.c -- RT286 ALSA SoC audio codec driver
- Copyright 2013 Realtek Semiconductor Corp.
- Author: Bard Liao bardliao@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.
- */
+#define DEBUG +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/platform_device.h> +#include <linux/spi/spi.h> +#include <linux/acpi.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> +#include <sound/tlv.h>
+#include "../../pci/hda/hda_codec.h"
This is bad. If you need the definitions of HD-audio verbs, we can split them into a public header instead.
Takashi
On Tue, Nov 26, 2013 at 05:11:31PM +0800, bardliao@realtek.com wrote:
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 983d087a..56748d4 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -313,6 +313,9 @@ config SND_SOC_PCM1792A config SND_SOC_PCM3008 tristate
+config SND_SOC_RT286
- tristate
This needs to be added to ALL_CODECS too.
+#define DEBUG
No, not in mainline.
+static int hp_amp_time = 100; +module_param(hp_amp_time, int, 0644);
No module parameters. Make a sysfs tunable for this if it's required, or make it platform data.
+#define RT_SUPPORT_COMBO_JACK 1
No compile time configuration please, platform data.
+#define VERSION "0.0.1-beta3 alsa 1.0.25"
The kernel is already versioned, don't try to maintian an individual driver version.
+static unsigned long rt286_read(struct snd_soc_codec *codec,
unsigned int vid,
unsigned int nid,
unsigned int data)
You should be implementing this in regmap using the reg_read and reg_write operations.
+static int rt286_index_write(struct snd_soc_codec *codec,
- unsigned int WidgetID, unsigned int index, unsigned int data)
Coding style - no CamelCase.
+static const struct reg_default rt286_reg[TOTAL_NODE_ID + 1] = {
- { NODE_ID_DAC_OUT1, 0x7f7f },
- { NODE_ID_DAC_OUT2, 0x7f7f },
- { NODE_ID_SPDIF, 0x0000 },
- { NODE_ID_ADC_IN1, 0x4343 },
- { NODE_ID_ADC_IN2, 0x4343 },
- { NODE_ID_MIC1, 0x0002 },
- { NODE_ID_MIXER_IN, 0x000b },
- { NODE_ID_MIXER_OUT1, 0x0002 },
- { NODE_ID_MIXER_OUT2, 0x0000 },
- { NODE_ID_SPK_OUT, 0x0000 },
- { NODE_ID_HP_OUT, 0x0000 },
- { NODE_ID_MIXER_IN1, 0x0005 },
- { NODE_ID_MIXER_IN2, 0x0005 },
+};
Why is there a size specified for this array?
+static bool rt286_volatile_register(struct device *dev, unsigned int reg) +{
- return 0;
+}
This is the default when caching, no need to have this function.
+static bool rt286_readable_register(struct device *dev, unsigned int reg) +{
- case NODE_ID_MIXER_IN2:
return 1;
- default:
return 0;
Return true and false for booleans.
+int rt286_jack_detect(struct snd_soc_codec *codec, bool *bHP, bool *bMIC) +{
Again, coding style. This should also be a static function (which presumably will be used by some jack detection code further down)?
+static int rt286_playback_vol_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
+static int rt286_playback_vol_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
+static int rt286_capture_vol_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
+static int rt286_capture_vol_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
There should be some code reuse between all these things, they seem to just differ in bits of data from a quick scan through.
- SOC_SINGLE_EXT_TLV("AMIC Gain", NODE_ID_MIC1, 0, 0x3, 0,
rt286_mic_gain_get, rt286_mic_gain_put,
mic_vol_tlv),
Volume.
+static void hwCodecConfigure(struct snd_soc_codec *codec)
Coding style yet again.
- rt286_write(codec, AC_VERB_SET_POWER_STATE,
NODE_ID_AUDIO_FUNCTION_GROUP, AC_PWRST_D3);
- for (i = 0; i < RT286_POWER_REG_LEN; i++) {
rt286_write(codec, AC_VERB_SET_POWER_STATE,
rt286_support_power_controls[i], AC_PWRST_D3);
- }
- rt286_index_write(codec, NODE_ID_VENDOR_REGISTERS, 0x4F, 0xb029);
- rt286_index_write(codec, NODE_ID_VENDOR_REGISTERS, 0x09, 0xc400);
- rt286_index_write(codec, NODE_ID_VENDOR_REGISTERS, 0x0A, 0x0120);
- rt286_index_write(codec, NODE_ID_VENDOR_REGISTERS, 0x33, 0x020A);
+#if !RT_SUPPORT_COMBO_JACK
- rt286_index_write(codec, NODE_ID_VENDOR_REGISTERS, 0x50, 0x0000);
- rt286_index_write(codec, NODE_ID_VENDOR_REGISTERS, 0x19, 0x0816);
- rt286_index_write(codec, NODE_ID_VENDOR_REGISTERS, 0x20, 0x0000);
+#endif
This is writing a bunch of apparently configuration dependant magic numbers into the device. The configuration should come from platform data or be done at runtime and we should have some visibility of what the magic numbers are up to.
- /* Sync volume */
- regmap_read(rt286->regmap, NODE_ID_ADC_IN1, &val);
- val = (val >> 8) & 0x7f;
- rt286_write(codec, AC_VERB_SET_AMP_GAIN_MUTE,
NODE_ID_ADC_IN1, 0x6000 | val);
It's not clear what any of this is doing but the general policy is to use the hardware defaults.
+/* HP-OUT source */ +static const char * const rt286_hpo_src[] = {
- "Front", "Surr"
+};
Spell out Surround fully to help readabiity.
rt286_write(codec, AC_VERB_SET_AMP_GAIN_MUTE,
NODE_ID_SPK_OUT, 0xb000);
Wasn't there a separate speaker mute control?
+static int rt286_hp_pow_event(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event)
It looks like a lot of these events should be sharing code as well.
- switch (params_rate(params)) {
- /* bit 14 0:48K 1:44.1K */
- case 48000:
val &= ~0x4000;
break;
- case 44100:
val |= 0x4000;
break;
Use snd_soc_update_bits() when writing out.
- default:
pr_err("unsportted sample rate %d\n", params_rate(params));
return -EINVAL;
dev_err().
- } else {
pr_err("unsportted channels %d\n", params_channels(params));
Unsupported.
- /* bit 15 Stream Type 0:PCM 1:Non-PCM */
- val_dac &= ~0x8000;
- val_adc &= ~0x8000;
- rt286_write(codec, AC_VERB_SET_STREAM_FORMAT,
NODE_ID_DAC_OUT1, val_dac);
- rt286_write(codec, AC_VERB_SET_STREAM_FORMAT,
NODE_ID_ADC_IN1, val_adc);
update_bits here too.
- switch (freq) {
- case 19200000:
- case 24000000:
rt286_index_update_bits(codec, NODE_ID_VENDOR_REGISTERS,
0x63, 0x4, 0x4);
rt286_index_update_bits(codec, NODE_ID_VENDOR_REGISTERS,
0x49, 0x20, 0x0);
break;
This doesn't look right - you've got multiple clock rates but are writing the same value out (and similarly for everything else). Do you need to add a constraint limiting what sample rates can be used depending on the system clock?
Too many magic numbers too as with the rest of the code.
+static int rt286_set_dai_dfs(struct snd_soc_dai *dai, unsigned int fs) +{
- struct snd_soc_codec *codec = dai->codec;
- pr_debug("%s fs=%d\n", __func__, fs);
- if (50 == fs)
rt286_index_update_bits(codec,
NODE_ID_VENDOR_REGISTERS, 0x09, 0x1000, 0x1000);
- return 0;
+}
This looks buggy, if the fs value happens to be 50 you write something otherwise you write nothing - what happens if we move from 50 to another value?
+static ssize_t rt286_codec_show(struct device *dev,
struct device_attribute *attr, char *buf)
Use the framework support for this.
+static int rt286_set_bias_level(struct snd_soc_codec *codec,
enum snd_soc_bias_level level)
+{
- switch (level) {
- case SND_SOC_BIAS_ON:
pr_debug("In case SND_SOC_BIAS_ON:\n");
Remove noisy stuff like this, you're duplicating the core.
+#ifdef CONFIG_PM +static int rt286_suspend(struct snd_soc_codec *codec) +{
- return 0;
+}
Remove empty functions.
- regcache_cache_only(rt286->regmap, true);
Nothing ever clears the cache_only flag in the driver so register writes won't be happening. Has this code been tested?
- ret = snd_soc_register_codec(&i2c->dev, &soc_codec_dev_rt286,
rt286_dai, ARRAY_SIZE(rt286_dai));
devm_snd_soc_register_codec().
+static void rt286_i2c_shutdown(struct i2c_client *client) +{
- struct rt286_priv *rt286 = i2c_get_clientdata(client);
- struct snd_soc_codec *codec = rt286->codec;
- if (codec != NULL)
rt286_set_bias_level(codec, SND_SOC_BIAS_OFF);
+}
The core should be dealing with this.
participants (3)
-
bardliao@realtek.com
-
Mark Brown
-
Takashi Iwai