[alsa-devel] [PATCH v6] ASoC: add RT286 CODEC driver
From: Bard Liao bardliao@realtek.com
ALC286 is a dual mode codec, which can run as HD-A or I2S mode. It is controlled by HD-A verb commands via I2C protocol. The following is the I/O difference between ALC286 and general I2S codecs. 1. A HD-A verb command contains three parts, NID, VID, and PID. And an I2S command contains only two parts: address and data. 2. Not only the register address is written, but the read command also includes the entire write command. 3. rt286 uses different registers for read and write the same bits.
As a result, regmap is difficult to be used on ALC286. We don't request a standard I/O by snd_soc_codec_set_cache_io anymore. Now we have specific write and read functions for ALC286's I/O. A verb command contains three part: NID, VID, and PID. We combine NID and VID as reg address, and PID as reg value. And do corresponding change in rt286_hw_read to get the data properly.
Signed-off-by: Bard Liao bardliao@realtek.com Signed-off-by: Gustaw Lewandowski gustaw.lewandowski@intel.com
--- The jack detection function is done by Lewandowski Gustaw. So I add Gustaw's Signed-off-by line in this patch
The difference between this version and previous version is listed below. * Implement specific read/write functions rather than use regmap. * Use dapm widget to control power bit instead of setting it in _event * Add device ID check
Verb command use two bit to set power state(D0, D1, D2, D3). But dapm widget use only one bit to set power up/down. So we use dapm widget to switch each node between D0 and D1 power state. And will set audio group to D3 when bias level = STANDBY.
--- include/sound/rt286.h | 19 + sound/soc/codecs/Kconfig | 4 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/rt286.c | 1054 +++++++++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/rt286.h | 186 ++++++++ 5 files changed, 1265 insertions(+) create mode 100644 include/sound/rt286.h create mode 100644 sound/soc/codecs/rt286.c create mode 100644 sound/soc/codecs/rt286.h
diff --git a/include/sound/rt286.h b/include/sound/rt286.h new file mode 100644 index 0000000..eb773d1 --- /dev/null +++ b/include/sound/rt286.h @@ -0,0 +1,19 @@ +/* + * linux/sound/rt286.h -- Platform data for RT286 + * + * Copyright 2013 Realtek Microelectronics + * + * 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 __LINUX_SND_RT286_H +#define __LINUX_SND_RT286_H + +struct rt286_platform_data { + bool cbj_en; /*combo jack enable*/ + bool gpio2_en; /*GPIO2 enable*/ +}; + +#endif diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index f0e8401..5818391 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -69,6 +69,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_PCM3008 select SND_SOC_PCM512x_I2C if I2C select SND_SOC_PCM512x_SPI if SPI_MASTER + select SND_SOC_RT286 if I2C select SND_SOC_RT5631 if I2C select SND_SOC_RT5640 if I2C select SND_SOC_SGTL5000 if I2C @@ -390,6 +391,9 @@ config SND_SOC_PCM512x_SPI select SND_SOC_PCM512x select REGMAP_SPI
+config SND_SOC_RT286 + tristate + config SND_SOC_RT5631 tristate
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 3c4d275..5ef8adc 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -58,6 +58,7 @@ snd-soc-pcm3008-objs := pcm3008.o snd-soc-pcm512x-objs := pcm512x.o snd-soc-pcm512x-i2c-objs := pcm512x-i2c.o snd-soc-pcm512x-spi-objs := pcm512x-spi.o +snd-soc-rt286-objs := rt286.o snd-soc-rt5631-objs := rt5631.o snd-soc-rt5640-objs := rt5640.o snd-soc-sgtl5000-objs := sgtl5000.o @@ -209,6 +210,7 @@ obj-$(CONFIG_SND_SOC_PCM3008) += snd-soc-pcm3008.o obj-$(CONFIG_SND_SOC_PCM512x) += snd-soc-pcm512x.o obj-$(CONFIG_SND_SOC_PCM512x_I2C) += snd-soc-pcm512x-i2c.o obj-$(CONFIG_SND_SOC_PCM512x_SPI) += snd-soc-pcm512x-spi.o +obj-$(CONFIG_SND_SOC_RT286) += snd-soc-rt286.o obj-$(CONFIG_SND_SOC_RT5631) += snd-soc-rt5631.o obj-$(CONFIG_SND_SOC_RT5640) += snd-soc-rt5640.o obj-$(CONFIG_SND_SOC_SGTL5000) += snd-soc-sgtl5000.o diff --git a/sound/soc/codecs/rt286.c b/sound/soc/codecs/rt286.c new file mode 100644 index 0000000..a88b3500 --- /dev/null +++ b/sound/soc/codecs/rt286.c @@ -0,0 +1,1054 @@ +/* + * 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. + */ + +#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 <sound/jack.h> +#include <linux/workqueue.h> +#include <sound/rt286.h> +#include <sound/hda_verbs.h> + +#include "rt286.h" + +#define RT286_VENDOR_ID 0x10ec0286 + +#define RT286_INDEX_REG_BASE 0x100 +#define RT286_INDEX_REG_RANGE 0xff +#define RT286_INDEX_REG_MAX (RT286_INDEX_REG_BASE + RT286_INDEX_REG_RANGE) + +struct rt286_priv { + struct snd_soc_codec *codec; + struct rt286_platform_data pdata; + struct i2c_client *i2c; + struct snd_soc_jack *jack; + struct delayed_work jack_detect_work; + int sys_clk; +}; + +static unsigned int rt286_hw_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + struct rt286_priv *rt286 = snd_soc_codec_get_drvdata(codec); + struct i2c_client *client = rt286->i2c; + struct i2c_msg xfer[2]; + int ret; + __be32 be_reg; + unsigned int index, vid, buf = 0x0; + + /*index registers*/ + if (reg >= RT286_INDEX_REG_BASE && reg < RT286_INDEX_REG_MAX) { + ret = snd_soc_write(codec, + VERB_CMD(AC_VERB_SET_COEF_INDEX, + RT286_VENDOR_REGISTERS, 0), + reg - RT286_INDEX_REG_BASE); + + if (ret < 0) { + dev_err(codec->dev, + "Failed to set private addr: %d\n", ret); + return ret; + } + + reg = VERB_CMD(AC_VERB_GET_PROC_COEF, + RT286_VENDOR_REGISTERS, 0); + } + reg = reg | 0x80000; + vid = (reg >> 8) & 0xfff; + + if (AC_VERB_GET_AMP_GAIN_MUTE == (vid & 0xf00)) { + index = (reg >> 8) & 0xf; + reg = (reg & ~0xf0f) | index; + } + be_reg = cpu_to_be32(reg); + + /* Write register */ + xfer[0].addr = client->addr; + xfer[0].flags = 0; + xfer[0].len = 4; + xfer[0].buf = (u8 *)&be_reg; + + /* Read data */ + xfer[1].addr = client->addr; + xfer[1].flags = I2C_M_RD; + xfer[1].len = 4; + xfer[1].buf = (u8 *)&buf; + + ret = i2c_transfer(client->adapter, xfer, 2); + if (ret < 0) + return ret; + else if (ret != 2) + return -EIO; + + return be32_to_cpu(buf); +} + +static int rt286_hw_write(struct snd_soc_codec *codec, + unsigned int reg, unsigned int value) +{ + struct rt286_priv *rt286 = snd_soc_codec_get_drvdata(codec); + struct i2c_client *client = rt286->i2c; + u8 data[4]; + int ret; + + /*index registers*/ + if (reg >= RT286_INDEX_REG_BASE && reg < RT286_INDEX_REG_MAX) { + ret = snd_soc_write(codec, + VERB_CMD(AC_VERB_SET_COEF_INDEX, + RT286_VENDOR_REGISTERS, 0), + reg - RT286_INDEX_REG_BASE); + if (ret < 0) { + dev_err(codec->dev, + "Failed to set private addr: %d\n", ret); + return -EIO; + } + reg = VERB_CMD(AC_VERB_SET_PROC_COEF, + RT286_VENDOR_REGISTERS, 0); + } + + data[0] = (reg >> 24) & 0xff; + data[1] = (reg >> 16) & 0xff; + /* + * 4 bit VID: reg should be 0 + * 12 bit VID: value should be 0 + * So we use an OR operator to handle it rather than use if condition. + */ + data[2] = ((reg >> 8) & 0xff) | ((value >> 8) & 0xff); + data[3] = value & 0xff; + + ret = i2c_master_send(client, data, 4); + if (ret == 4) + return 0; + if (ret < 0) + return ret; + else + return -EIO; +} + +static int rt286_support_power_controls[] = { + RT286_DAC_OUT1, + RT286_DAC_OUT2, + RT286_ADC_IN1, + RT286_ADC_IN2, + RT286_MIC1, + RT286_DMIC1, + RT286_DMIC2, + RT286_SPK_OUT, + RT286_HP_OUT, +}; +#define RT286_POWER_REG_LEN ARRAY_SIZE(rt286_support_power_controls) + +static int rt286_jack_detect(struct snd_soc_codec *codec, bool *hp, bool *mic) +{ + struct rt286_priv *rt286 = snd_soc_codec_get_drvdata(codec); + unsigned int val, buf; + int i; + + *hp = false; + *mic = false; + + if (rt286->pdata.cbj_en) { + buf = snd_soc_read(codec, RT286_GET_HP_SENSE); + *hp = buf & 0x80000000; + if (*hp) { + /* power on HV,VERF */ + snd_soc_update_bits(codec, RT286_INDEX_REG_BASE + + RT286_POWER_CTRL1, 0x1001, 0x0); + /* power LDO1 */ + snd_soc_update_bits(codec, RT286_INDEX_REG_BASE + + RT286_POWER_CTRL2, 0x4, 0x4); + snd_soc_write(codec, RT286_SET_MIC1, 0x24); + val = snd_soc_read(codec, RT286_INDEX_REG_BASE + + RT286_CBJ_CTRL2); + + msleep(200); + i = 40; + while (((val & 0x0800) == 0) && (i > 0)) { + val = snd_soc_read(codec, + RT286_INDEX_REG_BASE + + RT286_CBJ_CTRL2); + i--; + msleep(20); + } + + if (0x0400 == (val & 0x0700)) { + *mic = false; + + snd_soc_write(codec, + RT286_SET_MIC1, 0x20); + /* power off HV,VERF */ + snd_soc_update_bits(codec, + RT286_INDEX_REG_BASE + + RT286_POWER_CTRL1, 0x1001, 0x1001); + snd_soc_update_bits(codec, + RT286_INDEX_REG_BASE + + RT286_A_BIAS_CTRL3, 0xc000, 0x0000); + snd_soc_update_bits(codec, + RT286_INDEX_REG_BASE + + RT286_CBJ_CTRL1, 0x0030, 0x0000); + snd_soc_update_bits(codec, + RT286_INDEX_REG_BASE + + RT286_A_BIAS_CTRL2, 0xc000, 0x0000); + } else if ((0x0200 == (val & 0x0700)) || + (0x0100 == (val & 0x0700))) { + *mic = true; + snd_soc_update_bits(codec, + RT286_INDEX_REG_BASE + + RT286_A_BIAS_CTRL3, 0xc000, 0x8000); + snd_soc_update_bits(codec, + RT286_INDEX_REG_BASE + + RT286_CBJ_CTRL1, 0x0030, 0x0020); + snd_soc_update_bits(codec, + RT286_INDEX_REG_BASE + + RT286_A_BIAS_CTRL2, 0xc000, 0x8000); + } else { + *mic = false; + } + + snd_soc_update_bits(codec, RT286_INDEX_REG_BASE + + RT286_MISC_CTRL1, + 0x0060, 0x0000); + } else { + snd_soc_update_bits(codec, RT286_INDEX_REG_BASE + + RT286_MISC_CTRL1, + 0x0060, 0x0020); + snd_soc_update_bits(codec, RT286_INDEX_REG_BASE + + RT286_A_BIAS_CTRL3, + 0xc000, 0x8000); + snd_soc_update_bits(codec, RT286_INDEX_REG_BASE + + RT286_CBJ_CTRL1, + 0x0030, 0x0020); + snd_soc_update_bits(codec, RT286_INDEX_REG_BASE + + RT286_A_BIAS_CTRL2, + 0xc000, 0x8000); + + *mic = false; + } + } else { + buf = snd_soc_read(codec, RT286_GET_HP_SENSE); + *hp = buf & 0x80000000; + buf = snd_soc_read(codec, RT286_GET_MIC1_SENSE); + *mic = buf & 0x80000000; + } + + return 0; +} + +static void rt286_jack_detect_work(struct work_struct *work) +{ + struct rt286_priv *rt286 = + container_of(work, struct rt286_priv, jack_detect_work.work); + int status = 0; + + bool hp = false; + bool mic = false; + + rt286_jack_detect(rt286->codec, &hp, &mic); + + if (hp == true) + status |= SND_JACK_HEADPHONE; + + if (mic == true) + status |= SND_JACK_MICROPHONE; + + snd_soc_jack_report(rt286->jack, status, + SND_JACK_MICROPHONE | SND_JACK_HEADPHONE); +} + +int rt286_mic_detect(struct snd_soc_codec *codec, struct snd_soc_jack *jack) +{ + struct rt286_priv *rt286 = snd_soc_codec_get_drvdata(codec); + rt286->jack = jack; + + /* Send an initial empty report */ + snd_soc_jack_report(rt286->jack, 0, + SND_JACK_MICROPHONE | SND_JACK_HEADPHONE); + + return 0; +} +EXPORT_SYMBOL_GPL(rt286_mic_detect); + +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 const struct snd_kcontrol_new rt286_snd_controls[] = { + SOC_DOUBLE_R_TLV("DAC0 Playback Volume", RT286_DACL_GAIN, + RT286_DACR_GAIN, 0, 0x7f, 0, out_vol_tlv), + SOC_DOUBLE_R_TLV("ADC0 Capture Volume", RT286_ADCL_GAIN, + RT286_ADCR_GAIN, 0, 0x7f, 0, out_vol_tlv), + SOC_SINGLE_TLV("AMIC Volume", RT286_MIC_GAIN, + 0, 0x3, 0, mic_vol_tlv), + SOC_DOUBLE_R("Speaker Playback Switch", RT286_SPOL_GAIN, + RT286_SPOR_GAIN, RT286_MUTE_SFT, 1, 1), +}; + +/* Digital Mixer */ +static const struct snd_kcontrol_new rt286_front_mix[] = { + SOC_DAPM_SINGLE("DAC Switch", RT286_F_DAC_SWITCH, + RT286_MUTE_SFT, 1, 1), + SOC_DAPM_SINGLE("RECMIX Switch", RT286_F_RECMIX_SWITCH, + RT286_MUTE_SFT, 1, 1), +}; + +/* Analog Input Mixer */ +static const struct snd_kcontrol_new rt286_rec_mix[] = { + SOC_DAPM_SINGLE("Mic1 Switch", RT286_REC_MIC_SWITCH, + RT286_MUTE_SFT, 1, 1), + SOC_DAPM_SINGLE("I2S Switch", RT286_REC_I2S_SWITCH, + RT286_MUTE_SFT, 1, 1), + SOC_DAPM_SINGLE("Line1 Switch", RT286_REC_LINE_SWITCH, + RT286_MUTE_SFT, 1, 1), + SOC_DAPM_SINGLE("Beep Switch", RT286_REC_BEEP_SWITCH, + RT286_MUTE_SFT, 1, 1), +}; + +static const struct snd_kcontrol_new spo_enable_control = + SOC_DAPM_SINGLE("Switch", RT286_SET_PIN_SPK, + RT286_SET_PIN_SFT, 1, 0); + +static const struct snd_kcontrol_new hpol_enable_control = + SOC_DAPM_SINGLE_AUTODISABLE("Switch", RT286_HPOL_GAIN, + RT286_MUTE_SFT, 1, 1); + +static const struct snd_kcontrol_new hpor_enable_control = + SOC_DAPM_SINGLE_AUTODISABLE("Switch", RT286_HPOR_GAIN, + RT286_MUTE_SFT, 1, 1); + +/* ADC0 source */ +static const char * const rt286_adc_src[] = { + "Mic", "RECMIX", "Dmic" +}; + +static const int rt286_adc_values[] = { + 0, 4, 5, +}; + +static SOC_VALUE_ENUM_SINGLE_DECL( + rt286_adc0_enum, RT286_ADC0_MUX, 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 SOC_VALUE_ENUM_SINGLE_DECL( + rt286_adc1_enum, RT286_ADC1_MUX, 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); + +static const char * const rt286_dac_src[] = { + "Front", "Surround" +}; +/* HP-OUT source */ +static SOC_ENUM_SINGLE_DECL(rt286_hpo_enum, RT286_HPO_MUX, + 0, rt286_dac_src); + +static const struct snd_kcontrol_new rt286_hpo_mux = +SOC_DAPM_ENUM("HPO source", rt286_hpo_enum); + +/* SPK-OUT source */ +static SOC_ENUM_SINGLE_DECL(rt286_spo_enum, RT286_SPK_MUX, + 0, rt286_dac_src); + +static const struct snd_kcontrol_new rt286_spo_mux = +SOC_DAPM_ENUM("SPO source", rt286_spo_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: + snd_soc_write(codec, + RT286_SPK_EAPD, RT286_SET_EAPD_HIGH); + break; + case SND_SOC_DAPM_PRE_PMD: + snd_soc_write(codec, + RT286_SPK_EAPD, RT286_SET_EAPD_LOW); + 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: + snd_soc_write(codec, RT286_SET_PIN_DMIC1, 0x20); + break; + case SND_SOC_DAPM_PRE_PMD: + snd_soc_write(codec, RT286_SET_PIN_DMIC1, 0); + break; + default: + return 0; + } + + return 0; +} + +static int rt286_adc_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + unsigned int nid; + + nid = (w->reg >> 20) & 0xff; + switch (event) { + case SND_SOC_DAPM_POST_PMU: + snd_soc_update_bits(codec, + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, nid, 0), + 0x7080, 0x7000); + break; + case SND_SOC_DAPM_PRE_PMD: + snd_soc_update_bits(codec, + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, nid, 0), + 0x7080, 0x7080); + 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", RT286_SET_POWER(RT286_DMIC1), 0, 1, + NULL, 0, rt286_set_dmic1_event, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_PGA("DMIC2", RT286_SET_POWER(RT286_DMIC2), 0, 1, + NULL, 0), + 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", RT286_SET_POWER(RT286_ADC_IN1), 0, 1, + &rt286_adc0_mux, rt286_adc_event, SND_SOC_DAPM_PRE_PMD | + SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_MUX_E("ADC 1 Mux", RT286_SET_POWER(RT286_ADC_IN2), 0, 1, + &rt286_adc1_mux, rt286_adc_event, SND_SOC_DAPM_PRE_PMD | + SND_SOC_DAPM_POST_PMU), + + /* 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("DAC 0", NULL, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_DAC("DAC 1", NULL, SND_SOC_NOPM, 0, 0), + + /* Output Mux */ + SND_SOC_DAPM_MUX("SPK Mux", SND_SOC_NOPM, 0, 0, &rt286_spo_mux), + SND_SOC_DAPM_MUX("HPO Mux", SND_SOC_NOPM, 0, 0, &rt286_hpo_mux), + + SND_SOC_DAPM_SUPPLY("HP Power", RT286_SET_PIN_HPO, + RT286_SET_PIN_SFT, 0, NULL, 0), + + /* Output Mixer */ + SND_SOC_DAPM_MIXER("Front", RT286_SET_POWER(RT286_DAC_OUT1), 0, 1, + rt286_front_mix, ARRAY_SIZE(rt286_front_mix)), + SND_SOC_DAPM_PGA("Surround", RT286_SET_POWER(RT286_DAC_OUT2), 0, 1, + NULL, 0), + + /* Output Pga */ + SND_SOC_DAPM_SWITCH_E("SPO", SND_SOC_NOPM, 0, 0, + &spo_enable_control, rt286_spk_event, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_SWITCH("HPO L", SND_SOC_NOPM, 0, 0, + &hpol_enable_control), + SND_SOC_DAPM_SWITCH("HPO R", SND_SOC_NOPM, 0, 0, + &hpor_enable_control), + + /* 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", "RECMIX", "RECMIX"}, + {"ADC 0 Mux", "Mic", "MIC1"}, + {"ADC 1 Mux", "Dmic", "DMIC2"}, + {"ADC 1 Mux", "RECMIX", "RECMIX"}, + {"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"}, + + {"Surround", NULL, "DAC 1"}, + + {"SPK Mux", "Front", "Front"}, + {"SPK Mux", "Surround", "Surround"}, + + {"HPO Mux", "Front", "Front"}, + {"HPO Mux", "Surround", "Surround"}, + + {"SPO", "Switch", "SPK Mux"}, + {"HPO L", "Switch", "HPO Mux"}, + {"HPO R", "Switch", "HPO Mux"}, + {"HPO L", NULL, "HP Power"}, + {"HPO R", NULL, "HP Power"}, + + {"SPOL", NULL, "SPO"}, + {"SPOR", NULL, "SPO"}, + {"HPO Pin", NULL, "HPO L"}, + {"HPO Pin", NULL, "HPO R"}, +}; + +static int rt286_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct rt286_priv *rt286 = snd_soc_codec_get_drvdata(codec); + unsigned int val = 0; + int d_len_code; + + switch (params_rate(params)) { + /* bit 14 0:48K 1:44.1K */ + case 44100: + val |= 0x4000; + break; + case 48000: + break; + default: + dev_err(codec->dev, "Unsupported sample rate %d\n", + params_rate(params)); + return -EINVAL; + } + switch (rt286->sys_clk) { + case 12288000: + case 24576000: + if (params_rate(params) != 48000) { + dev_err(codec->dev, "Sys_clk is not matched (%d %d)\n", + params_rate(params), rt286->sys_clk); + return -EINVAL; + } + break; + case 11289600: + case 22579200: + if (params_rate(params) != 44100) { + dev_err(codec->dev, "Sys_clk is not matched (%d %d)\n", + params_rate(params), rt286->sys_clk); + return -EINVAL; + } + break; + } + + if (params_channels(params) <= 16) { + /* bit 3:0 Number of Channel */ + val |= (params_channels(params) - 1); + } else { + dev_err(codec->dev, "Unsupported channels %d\n", + params_channels(params)); + return -EINVAL; + } + + 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; + } + snd_soc_update_bits(codec, RT286_INDEX_REG_BASE + + RT286_I2S_CTRL1, 0x0018, d_len_code << 3); + dev_dbg(codec->dev, "format val = 0x%x\n", val); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + snd_soc_update_bits(codec, RT286_DAC_FORMAT, 0x407f, val); + else + snd_soc_update_bits(codec, RT286_ADC_FORMAT, 0x407f, val); + + return 0; +} + +static int rt286_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_codec *codec = dai->codec; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + snd_soc_update_bits(codec, RT286_INDEX_REG_BASE + + RT286_I2S_CTRL1, 0x800, 0x800); + break; + case SND_SOC_DAIFMT_CBS_CFS: + snd_soc_update_bits(codec, RT286_INDEX_REG_BASE + + RT286_I2S_CTRL1, 0x800, 0x0); + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + snd_soc_update_bits(codec, RT286_INDEX_REG_BASE + + RT286_I2S_CTRL1, 0x300, 0x0); + break; + case SND_SOC_DAIFMT_LEFT_J: + snd_soc_update_bits(codec, RT286_INDEX_REG_BASE + + RT286_I2S_CTRL1, 0x300, 0x1 << 8); + break; + case SND_SOC_DAIFMT_DSP_A: + snd_soc_update_bits(codec, RT286_INDEX_REG_BASE + + RT286_I2S_CTRL1, 0x300, 0x2 << 8); + break; + case SND_SOC_DAIFMT_DSP_B: + snd_soc_update_bits(codec, RT286_INDEX_REG_BASE + + RT286_I2S_CTRL1, 0x300, 0x3 << 8); + break; + default: + return -EINVAL; + } + /* bit 15 Stream Type 0:PCM 1:Non-PCM */ + snd_soc_update_bits(codec, RT286_DAC_FORMAT, 0x8000, 0); + snd_soc_update_bits(codec, RT286_ADC_FORMAT, 0x8000, 0); + + 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; + struct rt286_priv *rt286 = snd_soc_codec_get_drvdata(codec); + + dev_dbg(codec->dev, "%s freq=%d\n", __func__, freq); + + if (RT286_SCLK_S_MCLK == clk_id) { + snd_soc_update_bits(codec, RT286_INDEX_REG_BASE + + RT286_I2S_CTRL2, 0x0100, 0x0); + snd_soc_update_bits(codec, RT286_INDEX_REG_BASE + + RT286_PLL_CTRL1, 0x20, 0x20); + } else { + snd_soc_update_bits(codec, RT286_INDEX_REG_BASE + + RT286_I2S_CTRL2, 0x0100, 0x0100); + snd_soc_update_bits(codec, RT286_INDEX_REG_BASE + + RT286_PLL_CTRL, 0x4, 0x4); + snd_soc_update_bits(codec, RT286_INDEX_REG_BASE + + RT286_PLL_CTRL1, 0x20, 0x0); + } + + switch (freq) { + case 19200000: + if (RT286_SCLK_S_MCLK == clk_id) { + dev_err(codec->dev, "Should not use MCLK\n"); + return -EINVAL; + } + snd_soc_update_bits(codec, RT286_INDEX_REG_BASE + + RT286_I2S_CTRL2, 0x40, 0x40); + break; + case 24000000: + if (RT286_SCLK_S_MCLK == clk_id) { + dev_err(codec->dev, "Should not use MCLK\n"); + return -EINVAL; + } + snd_soc_update_bits(codec, RT286_INDEX_REG_BASE + + RT286_I2S_CTRL2, 0x40, 0x0); + break; + case 12288000: + case 11289600: + snd_soc_update_bits(codec, RT286_INDEX_REG_BASE + + RT286_I2S_CTRL2, 0x8, 0x0); + snd_soc_update_bits(codec, RT286_INDEX_REG_BASE + + RT286_CLK_DIV, 0xfc1e, 0x0004); + break; + case 24576000: + case 22579200: + snd_soc_update_bits(codec, RT286_INDEX_REG_BASE + + RT286_I2S_CTRL2, 0x8, 0x8); + snd_soc_update_bits(codec, RT286_INDEX_REG_BASE + + RT286_CLK_DIV, 0xfc1e, 0x5406); + break; + default: + dev_err(codec->dev, "Unsupported system clock\n"); + return -EINVAL; + } + + rt286->sys_clk = freq; + + return 0; +} + +static int rt286_set_bclk_ratio(struct snd_soc_dai *dai, unsigned int ratio) +{ + struct snd_soc_codec *codec = dai->codec; + + dev_dbg(codec->dev, "%s ratio=%d\n", __func__, ratio); + if (50 == ratio) + snd_soc_update_bits(codec, RT286_INDEX_REG_BASE + + RT286_I2S_CTRL1, 0x1000, 0x1000); + else + snd_soc_update_bits(codec, RT286_INDEX_REG_BASE + + RT286_I2S_CTRL1, 0x1000, 0x0); + + + return 0; +} + +static int rt286_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + switch (level) { + case SND_SOC_BIAS_PREPARE: + if (SND_SOC_BIAS_STANDBY == codec->dapm.bias_level) + snd_soc_write(codec, + RT286_SET_AUDIO_POWER, AC_PWRST_D0); + break; + + case SND_SOC_BIAS_STANDBY: + snd_soc_write(codec, + RT286_SET_AUDIO_POWER, AC_PWRST_D3); + break; + + default: + break; + } + codec->dapm.bias_level = level; + + return 0; +} + +static irqreturn_t rt286_irq(int irq, void *data) +{ + struct rt286_priv *rt286 = data; + bool hp = false; + bool mic = false; + int status = 0; + + rt286_jack_detect(rt286->codec, &hp, &mic); + + /* Clear IRQ */ + snd_soc_update_bits(rt286->codec, RT286_INDEX_REG_BASE + + RT286_IRQ_CTRL, 0x1, 0x1); + + if (hp == true) + status |= SND_JACK_HEADPHONE; + + if (mic == true) + status |= SND_JACK_MICROPHONE; + + snd_soc_jack_report(rt286->jack, status, + SND_JACK_MICROPHONE | SND_JACK_HEADPHONE); + + pm_wakeup_event(&rt286->i2c->dev, 300); + + return IRQ_HANDLED; +} + +static int rt286_probe(struct snd_soc_codec *codec) +{ + struct rt286_priv *rt286 = snd_soc_codec_get_drvdata(codec); + int i, ret; + + ret = snd_soc_read(codec, + RT286_GET_PARAM(AC_NODE_ROOT, AC_PAR_VENDOR_ID)); + if (ret != RT286_VENDOR_ID) { + dev_err(codec->dev, + "Device with ID register %x is not rt286\n", ret); + return -ENODEV; + } + + snd_soc_write(codec, RT286_SET_AUDIO_POWER, AC_PWRST_D3); + + for (i = 0; i < RT286_POWER_REG_LEN; i++) + snd_soc_write(codec, + RT286_SET_POWER(rt286_support_power_controls[i]), + AC_PWRST_D1); + + if (!rt286->pdata.cbj_en) { + snd_soc_write(codec, RT286_INDEX_REG_BASE + + RT286_CBJ_CTRL2, 0x0000); + snd_soc_write(codec, RT286_INDEX_REG_BASE + + RT286_MIC1_DET_CTRL, 0x0816); + snd_soc_write(codec, RT286_INDEX_REG_BASE + + RT286_MISC_CTRL1, 0x0000); + snd_soc_update_bits(codec, RT286_INDEX_REG_BASE + + RT286_CBJ_CTRL1, 0xf000, 0xb000); + } else { + snd_soc_update_bits(codec, RT286_INDEX_REG_BASE + + RT286_CBJ_CTRL1, 0xf000, 0x5000); + } + + mdelay(10); + + if (!rt286->pdata.gpio2_en) + snd_soc_write(codec, RT286_SET_DMIC2_DEFAULT, 0x4000); + else + snd_soc_write(codec, RT286_SET_DMIC2_DEFAULT, 0); + + mdelay(10); + + /*Power down LDO2*/ + snd_soc_update_bits(codec, RT286_INDEX_REG_BASE + + RT286_POWER_CTRL2, 0x8, 0x0); + + codec->dapm.bias_level = SND_SOC_BIAS_OFF; + rt286->codec = codec; + + if (rt286->i2c->irq) { + snd_soc_update_bits(codec, RT286_INDEX_REG_BASE + + RT286_IRQ_CTRL, 0x2, 0x2); + + INIT_DELAYED_WORK(&rt286->jack_detect_work, + rt286_jack_detect_work); + schedule_delayed_work(&rt286->jack_detect_work, + msecs_to_jiffies(1250)); + + ret = request_threaded_irq(rt286->i2c->irq, NULL, rt286_irq, + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, "rt286", rt286); + if (ret != 0) { + dev_err(codec->dev, + "Failed to reguest IRQ: %d\n", ret); + return ret; + } + } + + return 0; +} + +static int rt286_remove(struct snd_soc_codec *codec) +{ + struct rt286_priv *rt286 = snd_soc_codec_get_drvdata(codec); + cancel_delayed_work_sync(&rt286->jack_detect_work); + + return 0; +} + +#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) + +static const 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_bclk_ratio = rt286_set_bclk_ratio, +}; + +static 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, + .symmetric_rates = 1, + }, + { + .name = "rt286-aif2", + .id = RT286_AIF2, + .playback = { + .stream_name = "AIF2 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = RT286_STEREO_RATES, + .formats = RT286_FORMATS, + }, + .capture = { + .stream_name = "AIF2 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = RT286_STEREO_RATES, + .formats = RT286_FORMATS, + }, + .ops = &rt286_aif_dai_ops, + .symmetric_rates = 1, + }, + +}; + +static struct snd_soc_codec_driver soc_codec_dev_rt286 = { + .probe = rt286_probe, + .remove = rt286_remove, + .set_bias_level = rt286_set_bias_level, + .idle_bias_off = true, + .write = rt286_hw_write, + .read = rt286_hw_read, + .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 i2c_device_id rt286_i2c_id[] = { + {"rt286", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, rt286_i2c_id); + +static const 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_platform_data *pdata = dev_get_platdata(&i2c->dev); + struct rt286_priv *rt286; + int ret; + + rt286 = devm_kzalloc(&i2c->dev, sizeof(*rt286), + GFP_KERNEL); + if (NULL == rt286) + return -ENOMEM; + + rt286->i2c = i2c; + i2c_set_clientdata(i2c, rt286); + + if (pdata) + rt286->pdata = *pdata; + + ret = snd_soc_register_codec(&i2c->dev, &soc_codec_dev_rt286, + rt286_dai, ARRAY_SIZE(rt286_dai)); + + return ret; +} + +static int rt286_i2c_remove(struct i2c_client *i2c) +{ + struct rt286_priv *rt286 = i2c_get_clientdata(i2c); + + if (i2c->irq) + free_irq(i2c->irq, rt286); + snd_soc_unregister_codec(&i2c->dev); + + return 0; +} + + +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, + .id_table = rt286_i2c_id, +}; + +module_i2c_driver(rt286_i2c_driver); + +MODULE_DESCRIPTION("ASoC RT286 driver"); +MODULE_AUTHOR("Bard Liao bardliao@realtek.com"); +MODULE_LICENSE("GPL"); + +/* + * ALC286 is a dual mode codec, which can run as HD-A or I2S mode. + * It is controlled by HD-A verb commands via I2C protocol. + * The following is the I/O difference between ALC286 and general I2S codecs. + * 1. A HD-A verb command contains three parts, NID, VID, and PID. + * And an I2S command contains only two parts: address and data. + * 2. Not only the register address is written, but the read command also + includes the entire write command. + * 3. rt286 uses different registers for read and write the same bits. + * As a result, regmap is difficult to be used on ALC286. + * We don't request a standard I/O by snd_soc_codec_set_cache_io anymore. + * Now we have specific write and read functions for ALC286's I/O. + * A verb command contains three part: NID, VID, and PID. + * We combine NID and VID as reg address, and PID as reg value. + * And do corresponding change in rt286_hw_read to get the data properly. + */ diff --git a/sound/soc/codecs/rt286.h b/sound/soc/codecs/rt286.h new file mode 100644 index 0000000..eaa093f --- /dev/null +++ b/sound/soc/codecs/rt286.h @@ -0,0 +1,186 @@ +/* + * 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 VERB_CMD(V, N, D) ((N << 20) | (V << 8) | D) + +#define RT286_AUDIO_FUNCTION_GROUP 0x01 +#define RT286_DAC_OUT1 0x02 +#define RT286_DAC_OUT2 0x03 +#define RT286_ADC_IN1 0x09 +#define RT286_ADC_IN2 0x08 +#define RT286_MIXER_IN 0x0b +#define RT286_MIXER_OUT1 0x0c +#define RT286_MIXER_OUT2 0x0d +#define RT286_DMIC1 0x12 +#define RT286_DMIC2 0x13 +#define RT286_SPK_OUT 0x14 +#define RT286_MIC1 0x18 +#define RT286_LINE1 0x1a +#define RT286_BEEP 0x1d +#define RT286_SPDIF 0x1e +#define RT286_VENDOR_REGISTERS 0x20 +#define RT286_HP_OUT 0x21 +#define RT286_MIXER_IN1 0x22 +#define RT286_MIXER_IN2 0x23 + +#define RT286_SET_PIN_SFT 6 +#define RT286_SET_PIN_ENABLE 0x40 +#define RT286_SET_PIN_DISABLE 0 +#define RT286_SET_EAPD_HIGH 0x2 +#define RT286_SET_EAPD_LOW 0 + +#define RT286_MUTE_SFT 7 + +/* Verb commands */ +#define RT286_GET_PARAM(NID, PARAM) VERB_CMD(AC_VERB_PARAMETERS, NID, PARAM) +#define RT286_SET_POWER(NID) VERB_CMD(AC_VERB_SET_POWER_STATE, NID, 0) +#define RT286_SET_AUDIO_POWER RT286_SET_POWER(RT286_AUDIO_FUNCTION_GROUP) +#define RT286_SET_HPO_POWER RT286_SET_POWER(RT286_HP_OUT) +#define RT286_SET_SPK_POWER RT286_SET_POWER(RT286_SPK_OUT) +#define RT286_SET_DMIC1_POWER RT286_SET_POWER(RT286_DMIC1) +#define RT286_SPK_MUX\ + VERB_CMD(AC_VERB_SET_CONNECT_SEL, RT286_SPK_OUT, 0) +#define RT286_HPO_MUX\ + VERB_CMD(AC_VERB_SET_CONNECT_SEL, RT286_HP_OUT, 0) +#define RT286_ADC0_MUX\ + VERB_CMD(AC_VERB_SET_CONNECT_SEL, RT286_MIXER_IN1, 0) +#define RT286_ADC1_MUX\ + VERB_CMD(AC_VERB_SET_CONNECT_SEL, RT286_MIXER_IN2, 0) +#define RT286_SET_MIC1\ + VERB_CMD(AC_VERB_SET_PIN_WIDGET_CONTROL, RT286_MIC1, 0) +#define RT286_SET_PIN_HPO\ + VERB_CMD(AC_VERB_SET_PIN_WIDGET_CONTROL, RT286_HP_OUT, 0) +#define RT286_SET_PIN_SPK\ + VERB_CMD(AC_VERB_SET_PIN_WIDGET_CONTROL, RT286_SPK_OUT, 0) +#define RT286_SET_PIN_DMIC1\ + VERB_CMD(AC_VERB_SET_PIN_WIDGET_CONTROL, RT286_DMIC1, 0) +#define RT286_SPK_EAPD\ + VERB_CMD(AC_VERB_SET_EAPD_BTLENABLE, RT286_SPK_OUT, 0) +#define RT286_SET_AMP_GAIN_HPO\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT286_HP_OUT, 0) +#define RT286_GET_HP_SENSE\ + VERB_CMD(AC_VERB_GET_PIN_SENSE, RT286_HP_OUT, 0) +#define RT286_GET_MIC1_SENSE\ + VERB_CMD(AC_VERB_GET_PIN_SENSE, RT286_MIC1, 0) +#define RT286_SET_DMIC2_DEFAULT\ + VERB_CMD(AC_VERB_SET_CONFIG_DEFAULT_BYTES_3, RT286_DMIC2, 0) +#define RT286_DACL_GAIN\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT286_DAC_OUT1, 0xa000) +#define RT286_DACR_GAIN\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT286_DAC_OUT1, 0x9000) +#define RT286_ADCL_GAIN\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT286_ADC_IN1, 0x6000) +#define RT286_ADCR_GAIN\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT286_ADC_IN1, 0x5000) +#define RT286_MIC_GAIN\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT286_MIC1, 0x7000) +#define RT286_SPOL_GAIN\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT286_SPK_OUT, 0xa000) +#define RT286_SPOR_GAIN\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT286_SPK_OUT, 0x9000) +#define RT286_HPOL_GAIN\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT286_HP_OUT, 0xa000) +#define RT286_HPOR_GAIN\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT286_HP_OUT, 0x9000) +#define RT286_F_DAC_SWITCH\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT286_MIXER_OUT1, 0x7000) +#define RT286_F_RECMIX_SWITCH\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT286_MIXER_OUT1, 0x7100) +#define RT286_REC_MIC_SWITCH\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT286_MIXER_IN, 0x7000) +#define RT286_REC_I2S_SWITCH\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT286_MIXER_IN, 0x7100) +#define RT286_REC_LINE_SWITCH\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT286_MIXER_IN, 0x7200) +#define RT286_REC_BEEP_SWITCH\ + VERB_CMD(AC_VERB_SET_AMP_GAIN_MUTE, RT286_MIXER_IN, 0x7300) +#define RT286_DAC_FORMAT\ + VERB_CMD(AC_VERB_SET_STREAM_FORMAT, RT286_DAC_OUT1, 0) +#define RT286_ADC_FORMAT\ + VERB_CMD(AC_VERB_SET_STREAM_FORMAT, RT286_ADC_IN1, 0) + + +/* Index registers */ +#define RT286_A_BIAS_CTRL1 0x01 +#define RT286_A_BIAS_CTRL2 0x02 +#define RT286_POWER_CTRL1 0x03 +#define RT286_A_BIAS_CTRL3 0x04 +#define RT286_POWER_CTRL2 0x08 +#define RT286_I2S_CTRL1 0x09 +#define RT286_I2S_CTRL2 0x0a +#define RT286_CLK_DIV 0x0b +#define RT286_POWER_CTRL3 0x0f +#define RT286_MIC1_DET_CTRL 0x19 +#define RT286_MISC_CTRL1 0x20 +#define RT286_IRQ_CTRL 0x33 +#define RT286_PLL_CTRL1 0x49 +#define RT286_CBJ_CTRL1 0x4f +#define RT286_CBJ_CTRL2 0x50 +#define RT286_PLL_CTRL 0x63 + +/* 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_M_SPK_MUX_SFT 14 +#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_M_HP_MUX_SFT 14 +#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 + +#define RT286_SCLK_S_MCLK 0 +#define RT286_SCLK_S_PLL 1 + +enum { + RT286_AIF1, + RT286_AIF2, + RT286_AIFS, +}; + +int rt286_mic_detect(struct snd_soc_codec *codec, struct snd_soc_jack *jack); + +#endif /* __RT286_H__ */ +
On Mon, Apr 14, 2014 at 01:59:30PM +0800, bardliao@realtek.com wrote:
+static unsigned int rt286_hw_read(struct snd_soc_codec *codec,
unsigned int reg)
+{
+static int rt286_hw_write(struct snd_soc_codec *codec,
unsigned int reg, unsigned int value)
+{
To repeat what's been said before if you are implementing register I/O you *MUST* use regmap to do this. What's not been clear is if this device has a control interface that looks enough like a register map to be implemented like one but since we're just guessing based on a combination of the code and the HDA spec it's not entirely clear.
-----Original Message----- From: Mark Brown [mailto:broonie@kernel.org] Sent: Tuesday, April 15, 2014 8:04 PM To: Bard Liao Cc: lgirdwood@gmail.com; alsa-devel@alsa-project.org; lars@metafoo.de; Flove; Oder Chiou; Gustaw Lewandowski Subject: Re: [PATCH v6] ASoC: add RT286 CODEC driver
On Mon, Apr 14, 2014 at 01:59:30PM +0800, bardliao@realtek.com wrote:
+static unsigned int rt286_hw_read(struct snd_soc_codec *codec,
unsigned int reg)
+{
+static int rt286_hw_write(struct snd_soc_codec *codec,
unsigned int reg, unsigned int value) {
To repeat what's been said before if you are implementing register I/O you *MUST* use regmap to do this. What's not been clear is if this device has a control interface that looks enough like a register map to be implemented like one but since we're just guessing based on a combination of the code and the HDA spec it's not entirely clear.
I am a little bit confused about it. I thought you told me not using regmap. Actually, I don't know how to use dapm without implementing register I/O. Can I just convert rt286_hw_read/write to regmap?
------Please consider the environment before printing this e-mail.
On Wed, Apr 16, 2014 at 01:26:41PM +0000, Bard Liao wrote:
*MUST* use regmap to do this. What's not been clear is if this device has a control interface that looks enough like a register map to be implemented like one but since we're just guessing based on a combination of the code and the HDA spec it's not entirely clear.
I am a little bit confused about it. I thought you told me not using regmap. Actually, I don't know how to use dapm without implementing register I/O. Can I just convert rt286_hw_read/write to regmap?
As far as we've been able to tell thus far (based on inferring stuff from the HDA spec) you shouldn't be doing register I/O at all, the device doesn't really have things that look enough like registers.
You can implement non-register DAPM widgets - look at the virtual controls in the core, or things using SND_SOC_NOPM.
-----Original Message----- From: Mark Brown [mailto:broonie@kernel.org] Sent: Thursday, April 17, 2014 5:12 AM To: Bard Liao Cc: lgirdwood@gmail.com; alsa-devel@alsa-project.org; lars@metafoo.de; Flove; Oder Chiou; Gustaw Lewandowski Subject: Re: [PATCH v6] ASoC: add RT286 CODEC driver
On Wed, Apr 16, 2014 at 01:26:41PM +0000, Bard Liao wrote:
*MUST* use regmap to do this. What's not been clear is if this device has a control interface that looks enough like a register map to be implemented like one but since we're just guessing based on a combination of the code and the HDA spec it's not entirely clear.
I am a little bit confused about it. I thought you told me not using regmap. Actually, I don't know how to use dapm without implementing register I/O. Can I just convert rt286_hw_read/write to regmap?
As far as we've been able to tell thus far (based on inferring stuff from the HDA spec) you shouldn't be doing register I/O at all, the device doesn't really have things that look enough like registers.
You can implement non-register DAPM widgets - look at the virtual controls in the core, or things using SND_SOC_NOPM.
It means dapm widget will not control the codec directly, right? I.e. Everything will be done by _event? For simplest the driver, can I not define .dapm_widgets and .dapm_routes? All controls will be done in .controls.
------Please consider the environment before printing this e-mail.
On Thu, Apr 17, 2014 at 05:39:08AM +0000, Bard Liao wrote:
You can implement non-register DAPM widgets - look at the virtual controls in the core, or things using SND_SOC_NOPM.
It means dapm widget will not control the codec directly, right? I.e. Everything will be done by _event?
The framework won't do anything directly, yes.
For simplest the driver, can I not define .dapm_widgets and .dapm_routes? All controls will be done in .controls.
It is mandatory to have at least some DAPM support, not having DAPM was such a constant source of bugs that we stopped supporting it.
-----Original Message----- From: Mark Brown [mailto:broonie@kernel.org] Sent: Friday, April 18, 2014 6:37 PM To: Bard Liao Cc: lgirdwood@gmail.com; alsa-devel@alsa-project.org; lars@metafoo.de; Flove; Oder Chiou; Gustaw Lewandowski Subject: Re: [PATCH v6] ASoC: add RT286 CODEC driver
On Thu, Apr 17, 2014 at 05:39:08AM +0000, Bard Liao wrote:
You can implement non-register DAPM widgets - look at the virtual controls in the core, or things using SND_SOC_NOPM.
It means dapm widget will not control the codec directly, right? I.e. Everything will be done by _event?
The framework won't do anything directly, yes.
I have a question about the virtual controls.
Virtual controls assume there is no related register related to the controls, so codec doesn't need to set anything when the control's value is changed in general cases. But rt286 need to do something when the control's value is changed especially mux settings.
I have no idea when should codec driver set the corresponding registers of the controls. The SND_SOC_DAPM_POST_REG event seems not works in virtual controls. Also, I didn't find a suitable API to get a virtual control's value. Look like dapm_kcontrol_get_value can do that, but dapm_kcontrol_get_value is not exported.
I am trying to define a .put function in snd_kcontrol_new, so that I can set corresponding registers to the control. But I don't know how to update dapm path's connect status. I don't know if snd_soc_dapm_mux_update_power can do that.
Do you have any suggestion for setting corresponding registers of a virtual control?
Thanks.
------Please consider the environment before printing this e-mail.
On Tue, May 06, 2014 at 12:04:27PM +0000, Bard Liao wrote:
-----Original Message----- From: Mark Brown [mailto:broonie@kernel.org]
As I'm fairly sure has been requested before please fix your mailer to word wrap within paragraphs, it is very hard to read your mails since the lines are longer than 80 columns.
Virtual controls assume there is no related register related to the controls, so codec doesn't need to set anything when the control's value is changed in general cases. But rt286 need to do something when the control's value is changed especially mux settings.
...
Do you have any suggestion for setting corresponding registers of a virtual control?
Please extend the APIs for virtual controls if you find they are missing things you need, they've not been widely used in this sort of context - I think this is the first substantial device anyone has tried to merge without a register map so it's likely there are gaps.
On 05/07/2014 07:21 PM, Mark Brown wrote:
On Tue, May 06, 2014 at 12:04:27PM +0000, Bard Liao wrote:
-----Original Message----- From: Mark Brown [mailto:broonie@kernel.org]
As I'm fairly sure has been requested before please fix your mailer to word wrap within paragraphs, it is very hard to read your mails since the lines are longer than 80 columns.
Virtual controls assume there is no related register related to the controls, so codec doesn't need to set anything when the control's value is changed in general cases. But rt286 need to do something when the control's value is changed especially mux settings.
...
Do you have any suggestion for setting corresponding registers of a virtual control?
Please extend the APIs for virtual controls if you find they are missing things you need, they've not been widely used in this sort of context - I think this is the first substantial device anyone has tried to merge without a register map so it's likely there are gaps.
I don't think virtual controls are the right approach here. Virtual controls are for controls where the state is completely in software, but here the state is still in hardware. It's just that there is no uniform hardware map. But there is still some structure. So you'd have controls with custom put and get handlers and for DAPM widgets use the event handler.
But I still think that the best and easiest solution is to have custom regmap read/write function that do the translation of logical registers to physical read/write operations.
- Lars
On Wed, May 07, 2014 at 07:46:09PM +0200, Lars-Peter Clausen wrote:
I don't think virtual controls are the right approach here. Virtual controls are for controls where the state is completely in software, but here the state is still in hardware. It's just that there is no uniform hardware map. But there is still some structure. So you'd have controls with custom put and get handlers and for DAPM widgets use the event handler.
What you just described is what I'd consider a virtual control - I don't consider the fact that the put callbacks end up writing to the hardware particularly substantial, as far as the framework is concerned some driver specific thing goes off and does something but it could be setting a variable just as well as writing to hardware.
But I still think that the best and easiest solution is to have custom regmap read/write function that do the translation of logical registers to physical read/write operations.
That's the first time this idea has been mentioned that I recall. I have to say I'm really not keen on pushing non-physical registers through regmap, all the drivers that have done that previously have ended up causing problems though if the registers were *all* fake then there would be less issue (most of the issues have been due to having extra registers that don't really exist). It does still feel like it's a layering problem though.
On 05/07/2014 08:07 PM, Mark Brown wrote:
On Wed, May 07, 2014 at 07:46:09PM +0200, Lars-Peter Clausen wrote:
I don't think virtual controls are the right approach here. Virtual controls are for controls where the state is completely in software, but here the state is still in hardware. It's just that there is no uniform hardware map. But there is still some structure. So you'd have controls with custom put and get handlers and for DAPM widgets use the event handler.
What you just described is what I'd consider a virtual control - I don't consider the fact that the put callbacks end up writing to the hardware particularly substantial, as far as the framework is concerned some driver specific thing goes off and does something but it could be setting a variable just as well as writing to hardware.
Ok, but we have controls that have virtual in their name, so I think it should be made clear that you are not talking about those.
But I still think that the best and easiest solution is to have custom regmap read/write function that do the translation of logical registers to physical read/write operations.
That's the first time this idea has been mentioned that I recall. I have to say I'm really not keen on pushing non-physical registers through regmap, all the drivers that have done that previously have ended up causing problems though if the registers were *all* fake then there would be less issue (most of the issues have been due to having extra registers that don't really exist). It does still feel like it's a layering problem though.
I think the drivers you are thinking of are those which had physical register which were backed by hardware and virtual register which were backed by software. For the later we introduced the virtual controls, so that such hacks are no longer necessary in driver. But in the case of the RT286 all the register all still backed by hardware except that the way they are accessed is not very uniform (different types of registers use a different message format, there is a asymmetry between reading and writing the same data). In a sense this is similar to devices which have registers with different sizes, we also support these with custom regmap callbacks.
- Lars
On Wed, May 07, 2014 at 08:21:02PM +0200, Lars-Peter Clausen wrote:
On 05/07/2014 08:07 PM, Mark Brown wrote:
What you just described is what I'd consider a virtual control - I don't consider the fact that the put callbacks end up writing to the hardware particularly substantial, as far as the framework is concerned some driver specific thing goes off and does something but it could be setting a variable just as well as writing to hardware.
Ok, but we have controls that have virtual in their name, so I think it should be made clear that you are not talking about those.
My first thought would be to extend those so we have callbacks when required, to be honest I'd forgotten they weren't there already.
I think the drivers you are thinking of are those which had physical register which were backed by hardware and virtual register which were backed by software. For the later we introduced the virtual controls, so that such hacks are no longer necessary in driver. But in the case of the RT286 all the register all still backed by hardware except that the way they are accessed is not very uniform (different types of registers use a different message format, there is a asymmetry between reading and writing
Right, to me that's not actually a register map at all since it's missing so many of the assumptions that back up register maps.
the same data). In a sense this is similar to devices which have registers with different sizes, we also support these with custom regmap callbacks.
But those are only breaking that one assumption so they fit into the idea of a register map much more readily - it's really only the physical I/O code that actually notices anything, all the cache and other operations can carry on uninterrupted.
On 05/07/2014 09:49 PM, Mark Brown wrote:
On Wed, May 07, 2014 at 08:21:02PM +0200, Lars-Peter Clausen wrote:
On 05/07/2014 08:07 PM, Mark Brown wrote:
What you just described is what I'd consider a virtual control - I don't consider the fact that the put callbacks end up writing to the hardware particularly substantial, as far as the framework is concerned some driver specific thing goes off and does something but it could be setting a variable just as well as writing to hardware.
Ok, but we have controls that have virtual in their name, so I think it should be made clear that you are not talking about those.
My first thought would be to extend those so we have callbacks when required, to be honest I'd forgotten they weren't there already.
But that's just the normal ..._EXT() controls with custom put/get handlers.
Having virtual controls with custom put/get handlers makes no sense, since the virtual controls already have put/get function in which virtual-ness of the control is implemented.
I think the drivers you are thinking of are those which had physical register which were backed by hardware and virtual register which were backed by software. For the later we introduced the virtual controls, so that such hacks are no longer necessary in driver. But in the case of the RT286 all the register all still backed by hardware except that the way they are accessed is not very uniform (different types of registers use a different message format, there is a asymmetry between reading and writing
Right, to me that's not actually a register map at all since it's missing so many of the assumptions that back up register maps.
Well there is a register map, there is just no linear address space. It's more hierarchical, but you can still map that hierarchical addressing scheme to a linear one. I think what best describes HDA is object oriented RPC. You have your objects, called nodes, each node has a unique identifier, called the node ID (NID). Then you have functions, called verbs, each verb has a specific ID. A node can support a certain set of verbs. And each verb has a verb specific payload, which is the functions signature. Most of the verbs are some kind of read/write register functions.
E.g. you have the set amplifier gain verb, whose payload looks like:
bool output, bool input, bool left, bool right, int index, bool mute, int gain
output, input, left, right, index select for which amplifier to set the parameters and mute and gain are the parameters that should be set.
So the address of the register in which the amplifier gain is stored is the NID + It's a amplifier you want to access + whether it's a input or output amplifier + whether it's left or right amplifier + amplifier index. You can map that onto a linear address space and in the regmap read/write callback do the mapping to the appropriate verb payload layout.
the same data). In a sense this is similar to devices which have registers with different sizes, we also support these with custom regmap callbacks.
But those are only breaking that one assumption so they fit into the idea of a register map much more readily - it's really only the physical I/O code that actually notices anything, all the cache and other operations can carry on uninterrupted.
This is one of the reasons why I suggest using regmap. You probably still want caching, you probably still be able to sync the register cache, etc. If you don't use regmap you'd have to implement this on your own.
On Thu, May 08, 2014 at 09:05:01AM +0200, Lars-Peter Clausen wrote:
On 05/07/2014 09:49 PM, Mark Brown wrote:
My first thought would be to extend those so we have callbacks when required, to be honest I'd forgotten they weren't there already.
But that's just the normal ..._EXT() controls with custom put/get handlers.
Hrm, yes. That's the issue - I have never really differentiated between virtual and external enums here, the external controls are (as far as DAPM is concerned) also virtual. To be honest I'd forgotten that there were external controls in DAPM, there's only one user.
Right, to me that's not actually a register map at all since it's missing so many of the assumptions that back up register maps.
Well there is a register map, there is just no linear address space. It's more hierarchical, but you can still map that hierarchical addressing scheme to a linear one. I think what best describes HDA is object oriented RPC. You
The non-linear addressing isn't the worst bit, I'd say the worst bit is that the read and write formats for the data aren't consistent with each other. That's really the fundamental assumption here.
have your objects, called nodes, each node has a unique identifier, called the node ID (NID). Then you have functions, called verbs, each verb has a specific ID. A node can support a certain set of verbs. And each verb has a verb specific payload, which is the functions signature. Most of the verbs are some kind of read/write register functions.
Yes, it makes sense as RPC (and hence as external controls as I have been suggesting).
So the address of the register in which the amplifier gain is stored is the NID + It's a amplifier you want to access + whether it's a input or output amplifier + whether it's left or right amplifier + amplifier index. You can map that onto a linear address space and in the regmap read/write callback do the mapping to the appropriate verb payload layout.
It is of course always going to be possible to map any set of settings onto a register map, the question is if it accomplishes anything to do so. Once you're talking about having a mapping function like that which understands the contents of all the registers it sounds like the contortions to fit into a register map are more effort than is being saved and doing nothing for comprehensibility.
the same data). In a sense this is similar to devices which have registers with different sizes, we also support these with custom regmap callbacks.
But those are only breaking that one assumption so they fit into the idea of a register map much more readily - it's really only the physical I/O code that actually notices anything, all the cache and other operations can carry on uninterrupted.
This is one of the reasons why I suggest using regmap. You probably still want caching, you probably still be able to sync the register cache, etc. If you don't use regmap you'd have to implement this on your own.
So how does HDA handle this? We can obviously keep recording settings in the same way as we do for virtual enums, writing them out shouldn't be so hard. The cache code isn't going to buy us much if we have to write things out control by control anyway, it essentially just boils down to a fancy list walk.
If you really want to reuse regmap having a write only regmap internal to the driver (not presented to ASoC) which just remembers the last value written to every NID/VID combination might work and at least avoids the ugly bits with trying to convince ASoC there are registers since you don't need to worry about reading the data back and can just pretend that read values match written values since we never look at them except to write them back out.
On 05/08/2014 10:00 AM, Mark Brown wrote: [...]
So the address of the register in which the amplifier gain is stored is the NID + It's a amplifier you want to access + whether it's a input or output amplifier + whether it's left or right amplifier + amplifier index. You can map that onto a linear address space and in the regmap read/write callback do the mapping to the appropriate verb payload layout.
It is of course always going to be possible to map any set of settings onto a register map, the question is if it accomplishes anything to do so. Once you're talking about having a mapping function like that which understands the contents of all the registers it sounds like the contortions to fit into a register map are more effort than is being saved and doing nothing for comprehensibility.
There is still structure, the mapping function does not have to understand each register it only has to understand each verb and the driver uses maybe 6-7 different verbs, so it is not that bad. The code would basically look like this:
write_reg(addr, val) { verb = EXTRACT_VERB(addr); nid = EXTRACT_NID(addr); pid = EXTRACT_PID(addr);
switch (verb) { case VERB1: return write_verb1(nid, pid, val): case VERB2: return write_verb2(nid, pid, val): .... } }
Where pid is verb specific additional addressing information that is used in the verbs payload.
the same data). In a sense this is similar to devices which have registers with different sizes, we also support these with custom regmap callbacks.
But those are only breaking that one assumption so they fit into the idea of a register map much more readily - it's really only the physical I/O code that actually notices anything, all the cache and other operations can carry on uninterrupted.
This is one of the reasons why I suggest using regmap. You probably still want caching, you probably still be able to sync the register cache, etc. If you don't use regmap you'd have to implement this on your own.
So how does HDA handle this? We can obviously keep recording settings in the same way as we do for virtual enums, writing them out shouldn't be so hard. The cache code isn't going to buy us much if we have to write things out control by control anyway, it essentially just boils down to a fancy list walk.
It's not just the DAPM stuff, but also the normal controls, for which we do not have per control caching.
If you really want to reuse regmap having a write only regmap internal to the driver (not presented to ASoC) which just remembers the last value written to every NID/VID combination might work and at least avoids the ugly bits with trying to convince ASoC there are registers since you don't need to worry about reading the data back and can just pretend that read values match written values since we never look at them except to write them back out.
Yes kind of.
2014-05-08 16:57 GMT+08:00 Lars-Peter Clausen lars@metafoo.de:
On 05/08/2014 10:00 AM, Mark Brown wrote:
[...]
Just for confirm.
From the mail thread, what I need to to are:
1. Don't present register map to alsa, use virtrul controls instead. I may need to extend the APIs to accomplish the goal. 2. I can (or should?) use regmap internal to codec driver to set the codec physically. Did I miss anything?
Thanks.
------Please consider the environment before printing this e-mail.
On 05/12/2014 01:08 PM, Bard Liao wrote:
2014-05-08 16:57 GMT+08:00 Lars-Peter Clausen lars@metafoo.de:
On 05/08/2014 10:00 AM, Mark Brown wrote:
[...]
Just for confirm. From the mail thread, what I need to to are:
- Don't present register map to alsa, use virtrul controls instead. I may need to extend the APIs to accomplish the goal.
To avoid confusion we should not call them virtual controls, but controls with custom put/get handlers.
- I can (or should?) use regmap internal to codec driver to set the
codec physically. Did I miss anything?
There are essentially two options.
a) Have custom put/get handlers for different types of verbs. This means you'll re-implement things like caching.
b) Have a mapping that maps the hierarchical HDA register addressing structure onto a linear address map and use regmap.
- Lars
On Thu, May 08, 2014 at 10:57:44AM +0200, Lars-Peter Clausen wrote:
There is still structure, the mapping function does not have to understand each register it only has to understand each verb and the driver uses maybe 6-7 different verbs, so it is not that bad. The code would basically look like this:
write_reg(addr, val) { verb = EXTRACT_VERB(addr); nid = EXTRACT_NID(addr); pid = EXTRACT_PID(addr);
switch (verb) { case VERB1: return write_verb1(nid, pid, val): case VERB2: return write_verb2(nid, pid, val): .... } }
Where pid is verb specific additional addressing information that is used in the verbs payload.
Yeah, it's not exactly a model of niceness though. It's looking an awful lot like we didn't manage to abstract things properly and setting off alarm bells as a result.
So how does HDA handle this? We can obviously keep recording settings in the same way as we do for virtual enums, writing them out shouldn't be so hard. The cache code isn't going to buy us much if we have to write things out control by control anyway, it essentially just boils down to a fancy list walk.
It's not just the DAPM stuff, but also the normal controls, for which we do not have per control caching.
That seems like a simple matter of programming to resolve.
If you really want to reuse regmap having a write only regmap internal to the driver (not presented to ASoC) which just remembers the last value written to every NID/VID combination might work and at least avoids the ugly bits with trying to convince ASoC there are registers since you don't need to worry about reading the data back and can just pretend that read values match written values since we never look at them except to write them back out.
Yes kind of.
Kind of? It solves the cache sync case and if it's write only then we won't need to go to the register map for reads anyway (outside of interrupt handling and so on).
-----Original Message----- From: Mark Brown [mailto:broonie@kernel.org] Sent: Tuesday, May 13, 2014 4:29 AM To: Lars-Peter Clausen Cc: Bard Liao; lgirdwood@gmail.com; alsa-devel@alsa-project.org; Flove; Oder Chiou; Gustaw Lewandowski Subject: Re: [PATCH v6] ASoC: add RT286 CODEC driver
If you really want to reuse regmap having a write only regmap internal to the driver (not presented to ASoC) which just remembers the last value written to every NID/VID combination might work and at least avoids the ugly bits with trying to convince ASoC there are registers since you don't need to worry about reading the data back and can just pretend that read values match written values since we never look at them except to write them back out.
Yes kind of.
Kind of? It solves the cache sync case and if it's write only then we won't need to go to the register map for reads anyway (outside of interrupt handling and so on).
Does "write only regmap" mean read (almost) everything from cache? If so, we don't need to care about the asymmetry read/write issue. In that case, can we use regmap for mixer/mux controls just like other drivers do?
------Please consider the environment before printing this e-mail.
On Wed, Jun 04, 2014 at 12:08:41PM +0000, Bard Liao wrote:
Kind of? It solves the cache sync case and if it's write only then we won't need to go to the register map for reads anyway (outside of interrupt handling and so on).
Does "write only regmap" mean read (almost) everything from cache? If so, we don't need to care about the asymmetry read/write issue. In that case, can we use regmap for mixer/mux controls just like other drivers do?
Yes, exactly - most things should be able to get away with only using the cache I'd expect.
participants (5)
-
Bard Liao
-
Bard Liao
-
bardliao@realtek.com
-
Lars-Peter Clausen
-
Mark Brown