2023-05-15 07:50:39

by Aidan MacDonald

[permalink] [raw]
Subject: [PATCH v2 1/3] ASoC: Add ESS ES9218P codec driver

The ES9218P is a 32-bit stereo codec featuring an integrated
headphone amp with analog volume control and up to 2.0 V rms
output, and a low-power bypass path for passing through an
analog signal from an alternate source.

The digital audio interface supports 16/24/32-bit I2S inputs
ranging from 8 KHz to 384 KHz, and DSD inputs. This driver
only supports I2S operation.

Signed-off-by: Aidan MacDonald <[email protected]>
---

Notes:
v1->v2

- Drop clock names, add __maybe_unused to OF match table
- Add comment explaining why ess,max-clock-div exists

sound/soc/codecs/Kconfig | 6 +
sound/soc/codecs/Makefile | 2 +
sound/soc/codecs/ess-es9218p.c | 805 +++++++++++++++++++++++++++++++++
3 files changed, 813 insertions(+)
create mode 100644 sound/soc/codecs/ess-es9218p.c

diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 8020097d4e4c..973ce164e1e5 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -108,6 +108,7 @@ config SND_SOC_ALL_CODECS
imply SND_SOC_ES8328_I2C
imply SND_SOC_ES7134
imply SND_SOC_ES7241
+ imply SND_SOC_ESS_ES9218P
imply SND_SOC_GTM601
imply SND_SOC_HDAC_HDMI
imply SND_SOC_HDAC_HDA
@@ -1017,6 +1018,11 @@ config SND_SOC_ES8328_SPI
depends on SPI_MASTER
select SND_SOC_ES8328

+config SND_SOC_ESS_ES9218P
+ tristate "ESS Technology ES9218P CODEC"
+ depends on I2C
+ select REGMAP_I2C
+
config SND_SOC_GTM601
tristate 'GTM601 UMTS modem audio codec'

diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index 5cdbae88e6e3..1c119f1d9cb1 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -114,6 +114,7 @@ snd-soc-es8326-objs := es8326.o
snd-soc-es8328-objs := es8328.o
snd-soc-es8328-i2c-objs := es8328-i2c.o
snd-soc-es8328-spi-objs := es8328-spi.o
+snd-soc-ess-es9218p-objs := ess-es9218p.o
snd-soc-gtm601-objs := gtm601.o
snd-soc-hdac-hdmi-objs := hdac_hdmi.o
snd-soc-hdac-hda-objs := hdac_hda.o
@@ -488,6 +489,7 @@ obj-$(CONFIG_SND_SOC_ES8326) += snd-soc-es8326.o
obj-$(CONFIG_SND_SOC_ES8328) += snd-soc-es8328.o
obj-$(CONFIG_SND_SOC_ES8328_I2C)+= snd-soc-es8328-i2c.o
obj-$(CONFIG_SND_SOC_ES8328_SPI)+= snd-soc-es8328-spi.o
+obj-$(CONFIG_SND_SOC_ESS_ES9218P) += snd-soc-ess-es9218p.o
obj-$(CONFIG_SND_SOC_GTM601) += snd-soc-gtm601.o
obj-$(CONFIG_SND_SOC_HDAC_HDMI) += snd-soc-hdac-hdmi.o
obj-$(CONFIG_SND_SOC_HDAC_HDA) += snd-soc-hdac-hda.o
diff --git a/sound/soc/codecs/ess-es9218p.c b/sound/soc/codecs/ess-es9218p.c
new file mode 100644
index 000000000000..bcaacd9cdc4b
--- /dev/null
+++ b/sound/soc/codecs/ess-es9218p.c
@@ -0,0 +1,805 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2022-2023 Aidan MacDonald
+ */
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/tlv.h>
+
+/* Read/write registers */
+#define ES9218P_SYSTEM 0x00
+#define ES9218P_INPUT_SEL 0x01
+#define ES9218P_MIX_AMUTE 0x02
+#define ES9218P_ANALOGVOL 0x03
+#define ES9218P_AMUTE_TIME 0x04
+#define ES9218P_AMUTE_LEVEL 0x05
+#define ES9218P_DOP_VOL_RAMP 0x06
+#define ES9218P_FBW_MUTE 0x07
+#define ES9218P_GPIO_CONFIG 0x08
+#define ES9218P_MODE_SYNC 0x0a
+#define ES9218P_OC_PROTECT 0x0b
+#define ES9218P_DPLL_BANDWITH 0x0c
+#define ES9218P_THD_MONO_MODE 0x0d
+#define ES9218P_SS_CONFIG 0x0e
+#define ES9218P_VOL_CH1 0x0f
+#define ES9218P_VOL_CH2 0x10
+#define ES9218P_MASTER_TRIM 0x11 /* 32-bit */
+#define ES9218P_GPIO_INPUT_SEL 0x15
+#define ES9218P_THD_C2 0x16 /* 16-bit */
+#define ES9218P_THD_C3 0x18 /* 16-bit */
+#define ES9218P_CP_SS_DELAY 0x1a
+#define ES9218P_GEN_CONFIG 0x1b
+#define ES9218P_GPIO_INV_ACG 0x1c
+#define ES9218P_CP_CLOCK 0x1e /* 16-bit */
+#define ES9218P_AMP_CONFIG 0x20
+#define ES9218P_IMASK 0x21
+#define ES9218P_NCO 0x22 /* 32-bit */
+#define ES9218P_FIR_ADDR 0x28
+#define ES9218P_FIR_DATA 0x29 /* 24-bit */
+#define ES9218P_FIR_CONFIG 0x2c
+#define ES9218P_ANALOG_OV_1 0x2d
+#define ES9218P_ANALOG_OV_2 0x2e
+#define ES9218P_ANALOG_OV_3 0x2f
+#define ES9218P_ANALOG_CTRL 0x30
+#define ES9218P_ACG_THRESH 0x31 /* 24-bit */
+#define ES9218P_THD_C2_CH2 0x35 /* 16-bit */
+#define ES9218P_THD_C3_CH2 0x37 /* 16-bit */
+
+/* Read only registers */
+#define ES9218P_CHIP_STATUS 0x40
+#define ES9218P_GPIO_READBACK 0x41
+#define ES9218P_DPLL_NUMBER 0x42 /* 32-bit */
+#define ES9218P_INPUT_STATUS 0x48
+#define ES9218P_RAM_READBACK 0x49 /* 24-bit */
+
+#define ES9218P_MAX_REGISTER (ES9218P_RAM_READBACK + 2)
+
+#define ES9218P_SYSTEM_OSC_DRV GENMASK(7, 4)
+#define ES9218P_SYSTEM_CLK_GEAR GENMASK(3, 2)
+
+#define ES9218P_INPUT_SEL_SER_LEN GENMASK(7, 6)
+#define ES9218P_INPUT_SEL_SER_MODE GENMASK(5, 4)
+#define ES9218P_INPUT_SEL_AUTOSELECT GENMASK(3, 2)
+#define ES9218P_INPUT_SEL_DECODER GENMASK(1, 0)
+
+#define ES9218P_MODE_SYNC_MASTER BIT(7)
+
+#define ES9218P_SS_CONFIG_QUAD_RAMP BIT(5)
+#define ES9218P_SS_CONFIG_TIME GENMASK(4, 0)
+
+#define ES9218P_GEN_CONFIG_ASRC_EN BIT(7)
+
+#define ES9218P_AMP_CONFIG_MODE GENMASK(1, 0)
+#define ES9218P_AMP_MODE_CORE_ON 0
+#define ES9218P_AMP_MODE_LOWFI 1
+#define ES9218P_AMP_MODE_HIFI_1V 2
+#define ES9218P_AMP_MODE_HIFI_2V 3
+
+enum es9218p_supply_id {
+ ES9218P_SUPPLY_AVDD,
+ ES9218P_SUPPLY_VCCA,
+ ES9218P_SUPPLY_AVCC3V3,
+ ES9218P_SUPPLY_AVCC1V8,
+ ES9218P_NUM_SUPPLIES
+};
+
+static const char * const es9218p_supply_names[ES9218P_NUM_SUPPLIES] = {
+ "avdd",
+ "vcca",
+ "avcc3v3",
+ "avcc1v8",
+};
+
+/**
+ * struct es9218p_priv - private data for ES9218P codec
+ * @supplies: number of supplies
+ * @mclk: clock for the MCLK/XI input
+ * @reset_gpio: GPIO for the RESETB pin
+ * @max_clk_div: maximum safe clock divider for MCLK
+ * @osc_drv_bias: oscillator drive pad bias
+ * @soft_start_time: requested soft start time (0-20)
+ * @output_2v: requested output voltage (true -> 2Vrms, false -> 1Vrms)
+ * @analog_bypass: requested audio path (true -> AUX input, false -> I2S input)
+ * @fsr: current sample rate
+ * @master_mode: true if the codec is supplying the bit and frame clocks
+ * @powered: true if the codec is powered up
+ * @out_mux_powered: true if the output mux is powered
+ * @dac_powered: true if the DAC is powered
+ */
+struct es9218p_priv {
+ struct regmap *regmap;
+ struct regulator_bulk_data supplies[ES9218P_NUM_SUPPLIES];
+ struct clk *mclk;
+ struct gpio_desc *reset_gpio;
+ unsigned int max_clk_div;
+ unsigned int osc_drv_bias;
+
+ unsigned int soft_start_time;
+ bool output_2v;
+ bool analog_bypass;
+
+ unsigned int fsr;
+ bool master_mode;
+ bool powered;
+ bool out_mux_powered;
+ bool dac_powered;
+};
+
+static int es9218p_wide_write(struct regmap *regmap, unsigned int reg,
+ int count, unsigned int value)
+{
+ u8 data[4];
+ int i;
+
+ for (i = 0; i < count; i++) {
+ data[i] = value & 0xff;
+ value >>= 8;
+ }
+
+ return regmap_raw_write(regmap, reg, data, count);
+}
+
+static void es9218p_apply_clk_params(struct snd_soc_component *component)
+{
+ struct es9218p_priv *priv = snd_soc_component_get_drvdata(component);
+ unsigned int clk_div, clk_gear, nco, ss_time, max_ss_time;
+ unsigned long mclk;
+
+ if (!priv->powered)
+ return;
+
+ /*
+ * Calculate the maximum safe clock gear setting based on the
+ * constraint CLK >= 128*FSR and the platform-specific maximum.
+ */
+ mclk = clk_get_rate(priv->mclk);
+
+ clk_div = mclk / (128 * priv->fsr);
+ if (clk_div > priv->max_clk_div)
+ clk_div = priv->max_clk_div;
+
+ if (clk_div >= 8)
+ clk_gear = 3;
+ else if (clk_div >= 4)
+ clk_gear = 2;
+ else if (clk_div >= 2)
+ clk_gear = 1;
+ else
+ clk_gear = 0;
+
+ snd_soc_component_write_field(component, ES9218P_SYSTEM,
+ ES9218P_SYSTEM_CLK_GEAR, clk_gear);
+
+ /*
+ * Calculate soft start time, adjusting for clock gear and clamping
+ * to ensure the time does not exceed 250ms. That limit is arbitrary,
+ * but we need to impose _some_ limit because the maximum timeout can
+ * be absurdly long (minutes to hours).
+ */
+ if (priv->soft_start_time > clk_gear)
+ ss_time = priv->soft_start_time - clk_gear;
+ else
+ ss_time = 0;
+
+ max_ss_time = ilog2((mclk >> clk_gear) / 4096 / 2 / 4);
+ if (ss_time > max_ss_time)
+ ss_time = max_ss_time;
+
+ snd_soc_component_write_field(component, ES9218P_SS_CONFIG,
+ ES9218P_SS_CONFIG_TIME, ss_time);
+
+ /*
+ * NCO sets the frame clock when in master mode, otherwise it's unused.
+ */
+ if (priv->master_mode)
+ nco = div_u64((u64)priv->fsr << 32, mclk >> clk_gear);
+ else
+ nco = 0;
+
+ snd_soc_component_write_field(component, ES9218P_MODE_SYNC,
+ ES9218P_MODE_SYNC_MASTER, priv->master_mode);
+ snd_soc_component_write_field(component, ES9218P_GEN_CONFIG,
+ ES9218P_GEN_CONFIG_ASRC_EN, !priv->master_mode);
+ es9218p_wide_write(priv->regmap, ES9218P_NCO, 4, nco);
+}
+
+static int es9218p_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+ struct snd_soc_component *component = dai->component;
+ struct es9218p_priv *priv = snd_soc_component_get_drvdata(component);
+
+ /* Select I2S decoder, disable decoder autoselection */
+ snd_soc_component_write_field(component, ES9218P_INPUT_SEL,
+ ES9218P_INPUT_SEL_AUTOSELECT, 0);
+ snd_soc_component_write_field(component, ES9218P_INPUT_SEL,
+ ES9218P_INPUT_SEL_DECODER, 0);
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ snd_soc_component_write_field(component, ES9218P_INPUT_SEL,
+ ES9218P_INPUT_SEL_SER_MODE, 0);
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ snd_soc_component_write_field(component, ES9218P_INPUT_SEL,
+ ES9218P_INPUT_SEL_SER_MODE, 1);
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ snd_soc_component_write_field(component, ES9218P_INPUT_SEL,
+ ES9218P_INPUT_SEL_SER_MODE, 2);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
+ case SND_SOC_DAIFMT_BP_FP:
+ priv->master_mode = true;
+ break;
+ case SND_SOC_DAIFMT_BC_FC:
+ priv->master_mode = false;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ es9218p_apply_clk_params(component);
+ return 0;
+}
+
+static int es9218p_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_component *component = dai->component;
+ struct es9218p_priv *priv = snd_soc_component_get_drvdata(component);
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ snd_soc_component_write_field(component, ES9218P_INPUT_SEL,
+ ES9218P_INPUT_SEL_SER_LEN, 0);
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ snd_soc_component_write_field(component, ES9218P_INPUT_SEL,
+ ES9218P_INPUT_SEL_SER_LEN, 1);
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ snd_soc_component_write_field(component, ES9218P_INPUT_SEL,
+ ES9218P_INPUT_SEL_SER_LEN, 2);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ priv->fsr = params_rate(params);
+
+ es9218p_apply_clk_params(component);
+ return 0;
+}
+
+static int es9218p_mute_stream(struct snd_soc_dai *dai, int mute, int direction)
+{
+ struct snd_soc_component *component = dai->component;
+
+ if (mute)
+ snd_soc_component_update_bits(component, ES9218P_FBW_MUTE, 0x01, 0x01);
+ else
+ snd_soc_component_update_bits(component, ES9218P_FBW_MUTE, 0x01, 0);
+
+ return 0;
+}
+
+static const struct snd_soc_dai_ops es9218p_dai_ops = {
+ .set_fmt = es9218p_set_fmt,
+ .hw_params = es9218p_hw_params,
+ .mute_stream = es9218p_mute_stream,
+};
+
+static struct snd_soc_dai_driver es9218p_dai = {
+ .name = "es9218p",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_CONTINUOUS,
+ .rate_min = 8000,
+ .rate_max = 384000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S24_LE |
+ SNDRV_PCM_FMTBIT_S32_LE,
+ },
+ .ops = &es9218p_dai_ops,
+};
+
+static int es9218p_power_up(struct snd_soc_component *component)
+{
+ struct es9218p_priv *priv = snd_soc_component_get_drvdata(component);
+ int ret;
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(priv->supplies), priv->supplies);
+ if (ret) {
+ dev_err(component->dev, "Cannot enable supplies: %d\n", ret);
+ return ret;
+ }
+
+ ret = clk_prepare_enable(priv->mclk);
+ if (ret) {
+ dev_err(component->dev, "Cannot enable mclk: %d\n", ret);
+ goto err_regulator;
+ }
+
+ gpiod_set_value_cansleep(priv->reset_gpio, 1);
+ usleep_range(1500, 2000);
+
+ priv->powered = true;
+
+ snd_soc_component_write_field(component, ES9218P_AMP_CONFIG,
+ ES9218P_AMP_CONFIG_MODE, ES9218P_AMP_MODE_CORE_ON);
+ snd_soc_component_write_field(component, ES9218P_SYSTEM,
+ ES9218P_SYSTEM_OSC_DRV, priv->osc_drv_bias);
+ es9218p_apply_clk_params(component);
+
+ regcache_cache_only(priv->regmap, false);
+ ret = regcache_sync(priv->regmap);
+ if (ret) {
+ regcache_cache_only(priv->regmap, true);
+ goto err_clk_reset;
+ }
+
+ return 0;
+
+err_clk_reset:
+ priv->powered = false;
+ gpiod_set_value_cansleep(priv->reset_gpio, 0);
+ clk_disable_unprepare(priv->mclk);
+err_regulator:
+ regulator_bulk_disable(ARRAY_SIZE(priv->supplies), priv->supplies);
+ return ret;
+}
+
+static void es9218p_power_down(struct snd_soc_component *component)
+{
+ struct es9218p_priv *priv = snd_soc_component_get_drvdata(component);
+
+ regcache_cache_only(priv->regmap, true);
+ regcache_mark_dirty(priv->regmap);
+
+ priv->powered = false;
+
+ gpiod_set_value_cansleep(priv->reset_gpio, 0);
+
+ clk_disable_unprepare(priv->mclk);
+ regulator_bulk_disable(ARRAY_SIZE(priv->supplies), priv->supplies);
+}
+
+static int es9218p_set_bias_level(struct snd_soc_component *component,
+ enum snd_soc_bias_level level)
+{
+ int ret = 0;
+
+ switch (level) {
+ case SND_SOC_BIAS_STANDBY:
+ if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF)
+ ret = es9218p_power_up(component);
+ break;
+
+ case SND_SOC_BIAS_OFF:
+ es9218p_power_down(component);
+ break;
+
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+/* Returns soft start delay in microseconds */
+static unsigned long es9218p_get_soft_start_delay(struct snd_soc_component *component)
+{
+ struct es9218p_priv *priv = snd_soc_component_get_drvdata(component);
+ unsigned long mclk, clk_gear, ss_time;
+
+ if (!priv->powered)
+ return 0;
+
+ mclk = clk_get_rate(priv->mclk);
+ clk_gear = snd_soc_component_read_field(component, ES9218P_SYSTEM,
+ ES9218P_SYSTEM_CLK_GEAR);
+ ss_time = snd_soc_component_read_field(component, ES9218P_SS_CONFIG,
+ ES9218P_SS_CONFIG_TIME);
+
+ return div_u64((1000000ull * 4096ull) << (1 + ss_time), mclk >> clk_gear);
+}
+
+static void es9218p_set_amp_mode(struct snd_soc_component *component, int new_mode)
+{
+ unsigned long ss_delay;
+ int cur_mode, ret;
+
+ cur_mode = snd_soc_component_read_field(component, ES9218P_AMP_CONFIG,
+ ES9218P_AMP_CONFIG_MODE);
+ if (cur_mode == new_mode)
+ return;
+
+ ss_delay = es9218p_get_soft_start_delay(component);
+ ret = snd_soc_component_write_field(component, ES9218P_AMP_CONFIG,
+ ES9218P_AMP_CONFIG_MODE,
+ ES9218P_AMP_MODE_CORE_ON);
+ if (ret) {
+ /*
+ * We need an extra ~25ms delay here, otherwise the transition
+ * to the new mode doesn't work.
+ */
+ usleep_range(ss_delay + 25000, ss_delay + 26000);
+ }
+
+ ret = snd_soc_component_write_field(component, ES9218P_AMP_CONFIG,
+ ES9218P_AMP_CONFIG_MODE, new_mode);
+ if (ret)
+ usleep_range(ss_delay, ss_delay + 1000);
+}
+
+static void es9218p_update_amp_mode(struct snd_soc_component *component)
+{
+ struct es9218p_priv *priv = snd_soc_component_get_drvdata(component);
+ int amp_mode;
+
+ if (!priv->out_mux_powered)
+ amp_mode = ES9218P_AMP_MODE_CORE_ON;
+ else if (priv->analog_bypass)
+ amp_mode = ES9218P_AMP_MODE_LOWFI;
+ else if (!priv->dac_powered)
+ amp_mode = ES9218P_AMP_MODE_CORE_ON;
+ else if (priv->output_2v)
+ amp_mode = ES9218P_AMP_MODE_HIFI_2V;
+ else
+ amp_mode = ES9218P_AMP_MODE_HIFI_1V;
+
+ es9218p_set_amp_mode(component, amp_mode);
+}
+
+static int es9218p_output_dapm_event(struct snd_soc_dapm_widget *widget,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_component *component = snd_soc_dapm_to_component(widget->dapm);
+ struct es9218p_priv *priv = snd_soc_component_get_drvdata(component);
+
+ switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ if (widget->id == snd_soc_dapm_dac)
+ priv->dac_powered = true;
+ else
+ priv->out_mux_powered = true;
+ break;
+
+ case SND_SOC_DAPM_POST_PMD:
+ if (widget->id == snd_soc_dapm_dac)
+ priv->dac_powered = false;
+ else
+ priv->out_mux_powered = false;
+ break;
+
+ default:
+ return 0;
+ }
+
+ es9218p_update_amp_mode(component);
+ return 0;
+}
+
+static int outlevel_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
+ struct es9218p_priv *priv = snd_soc_component_get_drvdata(component);
+
+ ucontrol->value.enumerated.item[0] = priv->output_2v;
+ return 0;
+}
+
+static int outlevel_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
+ struct es9218p_priv *priv = snd_soc_component_get_drvdata(component);
+
+ priv->output_2v = ucontrol->value.enumerated.item[0];
+ es9218p_update_amp_mode(component);
+ return 1;
+}
+
+static int softstart_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
+ struct es9218p_priv *priv = snd_soc_component_get_drvdata(component);
+
+ ucontrol->value.integer.value[0] = priv->soft_start_time;
+ return 0;
+}
+
+static int softstart_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
+ struct es9218p_priv *priv = snd_soc_component_get_drvdata(component);
+
+ priv->soft_start_time = ucontrol->value.integer.value[0];
+ es9218p_apply_clk_params(component);
+ return 1;
+}
+
+static int output_mux_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component = snd_soc_dapm_kcontrol_component(kcontrol);
+ struct es9218p_priv *priv = snd_soc_component_get_drvdata(component);
+
+ ucontrol->value.enumerated.item[0] = priv->analog_bypass;
+ return 0;
+}
+
+static int output_mux_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component = snd_soc_dapm_kcontrol_component(kcontrol);
+ struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kcontrol);
+ struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+ struct es9218p_priv *priv = snd_soc_component_get_drvdata(component);
+ unsigned int value = ucontrol->value.enumerated.item[0];
+
+ priv->analog_bypass = value;
+ es9218p_update_amp_mode(component);
+
+ snd_soc_dapm_mux_update_power(dapm, kcontrol, value, e, NULL);
+ return 1;
+}
+
+static DECLARE_TLV_DB_SCALE(analog_vol_scale, -2400, 100, 0);
+static DECLARE_TLV_DB_SCALE(digital_vol_scale, -12750, 50, 0);
+
+static const char * const digfilter_texts[] = {
+ "Linear Phase Fast",
+ "Linear Phase Slow",
+ "Minimum Phase Fast",
+ "Minimum Phase Slow",
+ "Apodizing Fast Type 1",
+ "Apodizing Fast Type 2",
+ "Hybrid Fast",
+ "Brick Wall"
+};
+static SOC_ENUM_SINGLE_DECL(digfilter_enum, ES9218P_FBW_MUTE, 5, digfilter_texts);
+
+static const char * const outlevel_texts[] = { "1V", "2V" };
+static SOC_ENUM_SINGLE_EXT_DECL(outlevel_enum, outlevel_texts);
+
+static const char * const channel_mux_texts[] = { "CH1", "CH2" };
+static SOC_ENUM_DOUBLE_DECL(channel_mux_enum, ES9218P_MIX_AMUTE, 0, 2, channel_mux_texts);
+
+static const struct snd_kcontrol_new es9218p_controls[] = {
+ SOC_SINGLE_TLV("Headphone Playback Volume",
+ ES9218P_ANALOGVOL, 0, 24, 1, analog_vol_scale),
+ SOC_DOUBLE_R_TLV("Master Playback Volume",
+ ES9218P_VOL_CH1, ES9218P_VOL_CH2, 0, 0xff, 1, digital_vol_scale),
+ SOC_ENUM("Digital Filter Roll-Off", digfilter_enum),
+ SOC_ENUM_EXT("Output Level", outlevel_enum, outlevel_get, outlevel_put),
+ SOC_ENUM("Channel Mux", channel_mux_enum),
+ SOC_SINGLE_EXT("Soft Start Time", SND_SOC_NOPM, 0, 20, 0, softstart_get, softstart_put),
+};
+
+static const char * const output_mux_texts[] = { "Digital Input", "AUX Input" };
+static SOC_ENUM_SINGLE_EXT_DECL(output_mux_enum, output_mux_texts);
+
+static const struct snd_kcontrol_new output_mux_ctl =
+ SOC_DAPM_ENUM_EXT("Output Source", output_mux_enum, output_mux_get, output_mux_put);
+
+static const struct snd_soc_dapm_widget es9218p_dapm_widgets[] = {
+ SND_SOC_DAPM_INPUT("AUX_L"),
+ SND_SOC_DAPM_INPUT("AUX_R"),
+
+ SND_SOC_DAPM_DAC_E("DAC", "Playback", SND_SOC_NOPM, 0, 0,
+ es9218p_output_dapm_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+
+ SND_SOC_DAPM_MUX_E("Output Mux", SND_SOC_NOPM, 0, 0,
+ &output_mux_ctl, es9218p_output_dapm_event,
+ SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
+
+ SND_SOC_DAPM_OUTPUT("OUTL"),
+ SND_SOC_DAPM_OUTPUT("OUTR"),
+
+};
+
+static const struct snd_soc_dapm_route es9218p_dapm_routes[] = {
+ {"Output Mux", "Digital Input", "DAC"},
+ {"Output Mux", "AUX Input", "AUX_L"},
+ {"Output Mux", "AUX Input", "AUX_R"},
+ {"OUTL", NULL, "Output Mux"},
+ {"OUTR", NULL, "Output Mux"},
+};
+
+static const struct snd_soc_component_driver es9218p_component = {
+ .set_bias_level = es9218p_set_bias_level,
+ .controls = es9218p_controls,
+ .num_controls = ARRAY_SIZE(es9218p_controls),
+ .dapm_widgets = es9218p_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(es9218p_dapm_widgets),
+ .dapm_routes = es9218p_dapm_routes,
+ .num_dapm_routes = ARRAY_SIZE(es9218p_dapm_routes),
+};
+
+static const struct regmap_range es9218p_readable_ranges[] = {
+ regmap_reg_range(ES9218P_SYSTEM, ES9218P_THD_C3_CH2+1),
+ regmap_reg_range(ES9218P_CHIP_STATUS, ES9218P_MAX_REGISTER),
+};
+
+static const struct regmap_access_table es9218p_readable_table = {
+ .yes_ranges = es9218p_readable_ranges,
+ .n_yes_ranges = ARRAY_SIZE(es9218p_readable_ranges),
+};
+
+static const struct regmap_range es9218p_writeable_ranges[] = {
+ regmap_reg_range(ES9218P_SYSTEM, ES9218P_THD_C3_CH2+1),
+};
+
+static const struct regmap_access_table es9218p_writeable_table = {
+ .yes_ranges = es9218p_writeable_ranges,
+ .n_yes_ranges = ARRAY_SIZE(es9218p_writeable_ranges),
+};
+
+static const struct regmap_range es9218p_volatile_ranges[] = {
+ regmap_reg_range(ES9218P_FIR_ADDR, ES9218P_FIR_CONFIG),
+ regmap_reg_range(ES9218P_CHIP_STATUS, ES9218P_DPLL_NUMBER+3),
+ regmap_reg_range(ES9218P_INPUT_STATUS, ES9218P_RAM_READBACK+2),
+};
+
+static const struct regmap_access_table es9218p_volatile_table = {
+ .yes_ranges = es9218p_volatile_ranges,
+ .n_yes_ranges = ARRAY_SIZE(es9218p_volatile_ranges),
+};
+
+static const u8 es9218p_reg_defaults[] = {
+ 0x00, 0x8c, 0x34, 0x58, 0x00, 0x68, 0x72, 0x80,
+ 0xdd, 0x18, 0x02, 0x00, 0x5a, 0x40, 0x0a, 0x50,
+ 0x50, 0xff, 0xff, 0xff, 0x7f, 0x0d, 0x00, 0x00,
+ 0x00, 0x00, 0x62, 0xc4, 0xf0, 0x00, 0x00, 0x00,
+ 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcc,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x62, 0xc0, 0x0d, 0x62, 0x00, 0x00, 0x00,
+ 0x00,
+};
+
+static const struct regmap_config es9218p_regmap = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .rd_table = &es9218p_readable_table,
+ .wr_table = &es9218p_writeable_table,
+ .volatile_table = &es9218p_volatile_table,
+ .max_register = ES9218P_MAX_REGISTER,
+ .reg_defaults_raw = es9218p_reg_defaults,
+ .num_reg_defaults_raw = ARRAY_SIZE(es9218p_reg_defaults),
+ .cache_type = REGCACHE_RBTREE,
+};
+
+static int es9218p_read_properties(struct device *dev,
+ struct es9218p_priv *priv)
+{
+ u32 val;
+
+ /*
+ * NOTE: This property determines the maximum value of the MCLK/XI
+ * divider that supplies the codec internal CLK. A lower CLK saves
+ * power so we want to use the highest possible divider at all times.
+ *
+ * We _should_ be able to calculate the clock divider automatically
+ * because CLK is constrained only by the sample rate and I2C bus
+ * frequency:
+ *
+ * MCLK / DIV = CLK >= max { 128 * FSR, 20 * I2C_BUS_FREQ }
+ *
+ * The I2C subsystem doesn't yet provide a mechanism to query the
+ * bus frequency, so we can't implement the automatic calculation.
+ * Instead, ess,max-clock-div sets the maximum value of DIV that
+ * ensures CLK >= 20 * I2C_BUS_FREQ, and we use FSR to determine
+ * the value of DIV within the range [1, ess,max-clock-div].
+ *
+ * If/when there's a way to query I2C bus frequency this property
+ * can be removed.
+ */
+ if (!device_property_read_u32(dev, "ess,max-clock-div", &val))
+ priv->max_clk_div = val;
+
+ if (!device_property_read_u32(dev, "ess,oscillator-pad-bias", &val))
+ priv->osc_drv_bias = val;
+
+ return 0;
+}
+
+static int es9218p_i2c_probe(struct i2c_client *i2c)
+{
+ struct device *dev = &i2c->dev;
+ struct es9218p_priv *priv;
+ int ret, i;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->regmap = devm_regmap_init_i2c(i2c, &es9218p_regmap);
+ if (IS_ERR(priv->regmap))
+ return dev_err_probe(dev, PTR_ERR(priv->regmap), "Failed to create regmap\n");
+
+ /* Codec is only powered when needed */
+ regcache_cache_only(priv->regmap, true);
+
+ for (i = 0; i < ES9218P_NUM_SUPPLIES; i++)
+ priv->supplies[i].supply = es9218p_supply_names[i];
+
+ ret = devm_regulator_bulk_get(dev, ES9218P_NUM_SUPPLIES, priv->supplies);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to request supplies\n");
+
+ priv->mclk = devm_clk_get(dev, NULL);
+ if (IS_ERR(priv->mclk))
+ return dev_err_probe(dev, PTR_ERR(priv->mclk), "Failed to request MCLK\n");
+
+ priv->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
+ if (IS_ERR(priv->reset_gpio))
+ return dev_err_probe(dev, PTR_ERR(priv->reset_gpio),
+ "Failed to request reset GPIO\n");
+
+ ret = es9218p_read_properties(dev, priv);
+ if (ret)
+ return ret;
+
+ /* Arbitrary; must be nonzero */
+ priv->fsr = 48000;
+
+ i2c_set_clientdata(i2c, priv);
+
+ return devm_snd_soc_register_component(dev, &es9218p_component, &es9218p_dai, 1);
+}
+
+static const struct of_device_id __maybe_unused es9218p_of_match[] = {
+ { .compatible = "ess,es9218p" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, es9218p_of_match);
+
+static const struct i2c_device_id es9218p_i2c_id[] = {
+ { "ess-es9218p", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, es9218p_i2c_id);
+
+static struct i2c_driver es9218p_i2c_driver = {
+ .driver = {
+ .name = "ess-es9218p",
+ .of_match_table = of_match_ptr(es9218p_of_match),
+ },
+ .probe = es9218p_i2c_probe,
+ .id_table = es9218p_i2c_id,
+};
+module_i2c_driver(es9218p_i2c_driver);
+
+MODULE_DESCRIPTION("ESS ES9218P ALSA SoC Codec Driver");
+MODULE_AUTHOR("Aidan MacDonald <[email protected]>");
+MODULE_LICENSE("GPL");
--
2.39.2



2023-05-15 07:58:49

by Aidan MacDonald

[permalink] [raw]
Subject: [PATCH v2 3/3] ASoC: dt-bindings: Add ESS ES9218P codec

Device tree bindings for the ESS ES9218P codec, which uses an
I2C control interface.

Signed-off-by: Aidan MacDonald <[email protected]>
---

Notes:
v1->v2

Address Krzysztof's review comments:

- Add missing ref to dai-common.yaml
- Simplify descriptions of supply properties
- Remove unneeded clock names, the device has only one clock input
- Use unevaluatedProperties instead of additionalProperties

.../bindings/sound/ess,es9218p.yaml | 101 ++++++++++++++++++
1 file changed, 101 insertions(+)
create mode 100644 Documentation/devicetree/bindings/sound/ess,es9218p.yaml

diff --git a/Documentation/devicetree/bindings/sound/ess,es9218p.yaml b/Documentation/devicetree/bindings/sound/ess,es9218p.yaml
new file mode 100644
index 000000000000..53ca06539e0a
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/ess,es9218p.yaml
@@ -0,0 +1,101 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/sound/ess,es9218p.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: ESS ES9218P audio codec
+
+maintainers:
+ - Aidan MacDonald <[email protected]>
+
+allOf:
+ - $ref: dai-common.yaml#
+
+properties:
+ compatible:
+ const: ess,es9218p
+
+ reg:
+ maxItems: 1
+
+ "#sound-dai-cells":
+ const: 0
+
+ avdd-supply:
+ description:
+ Digital core power supply.
+
+ vcca-supply:
+ description:
+ Oscillator and DAC power supply.
+
+ avcc3v3-supply:
+ description:
+ 3.3V regulator for amplifier, switch, and charge pumps.
+
+ avcc1v8-supply:
+ description:
+ 1.8V regulator for amplifier, switch, and charge pumps.
+
+ clocks:
+ items:
+ - description: clock for master clock (MCLK)
+
+ reset-gpios:
+ maxItems: 1
+ description:
+ Pin used for codec hardware reset, corresponds to the RESETB pin.
+
+ ess,max-clock-div:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description:
+ Sets the maximum MCLK divider for generating the internal CLK.
+ CLK must be at least 20x the I2C bus speed or I2C transactions
+ will fail. The maximum divider should be chosen to ensure that
+ CLK will not fall below the limit.
+ enum:
+ - 1
+ - 2
+ - 4
+ - 8
+ default: 1
+
+ ess,oscillator-pad-bias:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description: Sets the oscillator pad drive bias. 0 - full bias, 15 - disabled.
+ minimum: 0
+ maximum: 15
+ default: 0
+
+required:
+ - compatible
+ - reg
+ - "#sound-dai-cells"
+ - avdd-supply
+ - vcca-supply
+ - avcc3v3-supply
+ - avcc1v8-supply
+ - clocks
+ - reset-gpios
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/gpio/gpio.h>
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ codec@48 {
+ compatible = "ess,es9218p";
+ reg = <0x48>;
+ #sound-dai-cells = <0>;
+ avdd-supply = <&avdd>;
+ vcca-supply = <&vcca>;
+ avcc3v3-supply = <&avcc3v3>;
+ avcc1v8-supply = <&avcc1v8>;
+ clocks = <&xtal_clock>;
+ reset-gpios = <&gpio 1 GPIO_ACTIVE_LOW>;
+ };
+ };
--
2.39.2


2023-05-15 11:07:43

by Mark Brown

[permalink] [raw]
Subject: Re: [PATCH v2 1/3] ASoC: Add ESS ES9218P codec driver

On Mon, May 15, 2023 at 08:40:19AM +0100, Aidan MacDonald wrote:

> +++ b/sound/soc/codecs/ess-es9218p.c
> @@ -0,0 +1,805 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Copyright (c) 2022-2023 Aidan MacDonald
> + */

Please make the entire comment a C++ one so things look more
intentional.

> +enum es9218p_supply_id {
> + ES9218P_SUPPLY_AVDD,
> + ES9218P_SUPPLY_VCCA,
> + ES9218P_SUPPLY_AVCC3V3,
> + ES9218P_SUPPLY_AVCC1V8,
> + ES9218P_NUM_SUPPLIES
> +};
> +
> +static const char * const es9218p_supply_names[ES9218P_NUM_SUPPLIES] = {
> + "avdd",
> + "vcca",
> + "avcc3v3",
> + "avcc1v8",
> +};

These could easily get out of sync, it would be better to explicitly
assign the names to the slots identified by the constants

[ES9218P_SUPPLY_VCCA] = "vcca",

for example.

> +static int es9218p_wide_write(struct regmap *regmap, unsigned int reg,
> + int count, unsigned int value)
> +{
> + u8 data[4];
> + int i;
> +
> + for (i = 0; i < count; i++) {
> + data[i] = value & 0xff;
> + value >>= 8;
> + }

This needs a bounds check to make sure we don't overflow data.

> +static int outlevel_put(struct snd_kcontrol *kcontrol,
> + struct snd_ctl_elem_value *ucontrol)
> +{
> + struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
> + struct es9218p_priv *priv = snd_soc_component_get_drvdata(component);
> +
> + priv->output_2v = ucontrol->value.enumerated.item[0];
> + es9218p_update_amp_mode(component);
> + return 1;
> +}

Running the mixer-test selftest on a card with this driver will report
that the driver generates spurious events when there is no change in
value and doesn't validate input. Similar issues apply to the other
enums.


Attachments:
(No filename) (1.71 kB)
signature.asc (499.00 B)
Download all attachments

2023-05-15 12:31:58

by Krzysztof Kozlowski

[permalink] [raw]
Subject: Re: [PATCH v2 3/3] ASoC: dt-bindings: Add ESS ES9218P codec

On 15/05/2023 09:40, Aidan MacDonald wrote:
> Device tree bindings for the ESS ES9218P codec, which uses an
> I2C control interface.
>
> Signed-off-by: Aidan MacDonald <[email protected]>
> ---
>
> Notes:
> v1->v2


Reviewed-by: Krzysztof Kozlowski <[email protected]>

Best regards,
Krzysztof