From nobody Mon Feb 9 12:42:31 2026 Received: from mail-wm1-f54.google.com (mail-wm1-f54.google.com [209.85.128.54]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id A0EA82E7F0A for ; Sat, 17 Jan 2026 20:19:40 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.54 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1768681183; cv=none; b=e37S2vBcnQpZdXmB6NY5Z60RQaHBgUAAzqfsQ7oIasWd/6AuQrg7yoyNLM01fuHg3Y7nUMfcXLcE6jIv8G5IOOFowgfwSsjZvs0ZKmaBVegF3hJH4YBfVIpbCsXsFQ5KgaP8qKilZQ2lB1A65cfBzqDTKARZEv+VPMWnXoWFwG4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1768681183; c=relaxed/simple; bh=yoqDZc5ZVlZgdU+bd4E5tzHezHO66wUqbgpf8bFY4E4=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=hSHImcaVq521V1Rmsjcsg/veoUZxNFkEdW19fS6i4daQs7P4iHP3JIKh1UtpsnB1qvJPsuRxuQKv1TBcW8gUwPPuKBRzHAeBabIKS+YiijwEgC52Y4N0c/4rUdDJcoFSTRlTB7rHwCxUwKiAjpLmCgnTq71KIHUpiYMwV0MiWSA= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=nNZWdZi8; arc=none smtp.client-ip=209.85.128.54 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="nNZWdZi8" Received: by mail-wm1-f54.google.com with SMTP id 5b1f17b1804b1-4801d98cf39so11815765e9.1 for ; Sat, 17 Jan 2026 12:19:40 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1768681177; x=1769285977; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=M+UkR1HZFum2uQlZYRs91yhhWbIXUBFNThSz2GGApOk=; b=nNZWdZi8SU/dlCIEuO1egPGoswDy6CucbgOha+hOqot0PrXtLwsTMAvO03eTxkAG9O BdtP15FrlHbvUnZJULJSCLh5v9S7hxJ/PSXgVZt3l3zlC2Z8dQ2FHnmC5vv+0PCpgAKl Rt1Hu6iwp+BkLcmy8SN0OV3Ftipr7/bEZyMTUIrvVtCyjqopO8T8/kUbcI6Zoghb6e5i kOOqutUGyT2+3Xctjuw/0bfQqUDfA7PT4cuIuMUcEk+f7fJ5NYZGYiwgtYjjpK6/nzdb 650Z41sG03inwm3Otku9nkkRSyia0Sir2ftLnoeiTBZ818hY83wfvmojzbmPpR0l0Vw3 Yi/A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1768681177; x=1769285977; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-gg:x-gm-message-state:from:to :cc:subject:date:message-id:reply-to; bh=M+UkR1HZFum2uQlZYRs91yhhWbIXUBFNThSz2GGApOk=; b=PE81cw0Cnd8dj0y2U3wG0ORVn39Jo7wtnWB++ZLRgSKvZ3jToeKWbRYF/520DefvqW phwpXpnJFHfODhjZmBIVKvRJih/wOca5Du/wJn3mQ8p3iMfofGwfEePN6qQ+m2FATB2H 4xNoZpGR12gNNvW64ODe5uC8LrTEjPQScKGVTxogcmR0noCpDT4It3Zwv3ilkjF3WfPk /zBOlWOn/7joIcozzS/zSDvTIK2G113ZZezRlYQLSCb6bu8L7WMFfA0inhLunpk/nJ/G Ldxt12OQiVPRq9+SNLqcnXsWX0DpcW6WHCNK3FLq5m2y3ud0bGt0pyOR0yUMtiBix0NQ lE1g== X-Forwarded-Encrypted: i=1; AJvYcCVQQ3c7zyKTkke2uFnRxRVQuzUEYX3zPElJzJQCNDjBC7Xvw/DWw9mQrOdqDt+VKg1I+2dBz+LCLaojW1w=@vger.kernel.org X-Gm-Message-State: AOJu0Yw3HPYvyJjnVqIgXlSL4Se/8Nj2geYOJplAUfIjd76rpZbLXiw0 G+8IMRap9zTw9QHRPhPRZQ2DQzdd8eqBuDsNlU3pClh9bXxeoH/CkPJUDXstyDjw X-Gm-Gg: AY/fxX5BE/r6cEkYu9Yr5vPuMRU1fQ6rHMD+D7iFoWWeFtEzMvb8j6hlX1zHMIpMHSF x5An9IKbyOinQ0ednk+L1V8eMx974uW0fH8KkoUh5TxxCXTvMAZitwh5T/GQuLxmpjgZzNqL0Zf TZ+XHiim5AfNStX0fY5MehTDVP5MsAMoXWmyORuXIPVvm86oIa94KkyLUpILDwtDW/5CIuSCcqL FaiIyfRf4OIttAp0bsGWDlh5DrQUf0kDv5wmyzdSbCchum/pyk/cBOjR8yZUjraf0XPa8dR5rGD Rsl3Wdxyh3LzYgCrwD8ucowXA0vMTVRJ+U79Baixy9urpVONyfsURJVBHzJHCv5xT9/qkFQtxss PdcsfoO9iCT9/IyxoEO5mnU8JNMvRhWXeDTE1Q6dYocHBhsuzx5waKlUeIX6CNqmSOAuiwKOljA dPa+h/joagRXvBrXhfD4rzespqQhitKzyj9/0AwymBIii35i4QZME+Q1A= X-Received: by 2002:a05:600c:a009:b0:475:dd9a:f791 with SMTP id 5b1f17b1804b1-4801eb0ef0bmr62487135e9.28.1768681176483; Sat, 17 Jan 2026 12:19:36 -0800 (PST) Received: from [127.0.1.1] (bba-94-59-215-181.alshamil.net.ae. [94.59.215.181]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-4801e879542sm112402295e9.4.2026.01.17.12.19.33 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 17 Jan 2026 12:19:36 -0800 (PST) From: "Anton D. Stavinskii" Date: Sun, 18 Jan 2026 00:18:56 +0400 Subject: [PATCH v2 4/7] ASoC: sophgo: add CV1800B internal ADC codec driver Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260118-cv1800b-i2s-driver-v2-4-d10055f68368@gmail.com> References: <20260118-cv1800b-i2s-driver-v2-0-d10055f68368@gmail.com> In-Reply-To: <20260118-cv1800b-i2s-driver-v2-0-d10055f68368@gmail.com> To: Liam Girdwood , Mark Brown , Rob Herring , Krzysztof Kozlowski , Conor Dooley , Chen Wang , Inochi Amaoto , Jaroslav Kysela , Takashi Iwai , Paul Walmsley , Palmer Dabbelt , Albert Ou , Alexandre Ghiti Cc: linux-sound@vger.kernel.org, devicetree@vger.kernel.org, sophgo@lists.linux.dev, linux-kernel@vger.kernel.org, linux-riscv@lists.infradead.org, "Anton D. Stavinskii" X-Mailer: b4 0.13.0 X-Developer-Signature: v=1; a=ed25519-sha256; t=1768681160; l=11247; i=stavinsky@gmail.com; s=20260115; h=from:subject:message-id; bh=yoqDZc5ZVlZgdU+bd4E5tzHezHO66wUqbgpf8bFY4E4=; b=Kh3fdLaX4mnHZSrSNmx5C/yL+afTAzTi84PHGn/Q2uCiR3cSfuRe7KJiZvTBMw0s+/RANKNEz qLuXaWzmsl8CQcS8U2Xh8OCgN/Ht/G5jgeywp1j+hpJtSs2i2nBGF5Y X-Developer-Key: i=stavinsky@gmail.com; a=ed25519; pk=2WxGZ1zd1vQwSPFCSks6zrADqUDBUdtq39lElk4ZE7Q= Codec DAI endpoint for RXADC + basic controls. THe codec have basic volume control. Which is imlemented by lookup table for simplicity. The codec expects set_sysclk callback to adjust internal mclk divider. Signed-off-by: Anton D. Stavinskii --- sound/soc/sophgo/Kconfig | 12 ++ sound/soc/sophgo/Makefile | 1 + sound/soc/sophgo/cv1800b-sound-adc.c | 322 +++++++++++++++++++++++++++++++= ++++ 3 files changed, 335 insertions(+) diff --git a/sound/soc/sophgo/Kconfig b/sound/soc/sophgo/Kconfig index 70f07d46c810..813ab30d6da3 100644 --- a/sound/soc/sophgo/Kconfig +++ b/sound/soc/sophgo/Kconfig @@ -17,4 +17,16 @@ config SND_SOC_CV1800B_TDM To compile the driver as a module, choose M here: the module will be called cv1800b_tdm. =20 +config SND_SOC_CV1800B_ADC_CODEC + tristate "Sophgo CV1800B/SG2002 internal ADC codec" + depends on SND_SOC + help + This driver provides an ASoC codec DAI for capture and basic + control of the RXADC registers. + + Say Y or M to build support for the Sophgo CV1800B + internal analog ADC codec block (RXADC). + The module will be called cv1800b-sound-adc + + endmenu diff --git a/sound/soc/sophgo/Makefile b/sound/soc/sophgo/Makefile index 3f9f1d07227a..c654d6059cbd 100644 --- a/sound/soc/sophgo/Makefile +++ b/sound/soc/sophgo/Makefile @@ -1,3 +1,4 @@ # SPDX-License-Identifier: GPL-2.0 # Sophgo Platform Support obj-$(CONFIG_SND_SOC_CV1800B_TDM) +=3D cv1800b-tdm.o +obj-$(CONFIG_SND_SOC_CV1800B_ADC_CODEC) +=3D cv1800b-sound-adc.o diff --git a/sound/soc/sophgo/cv1800b-sound-adc.c b/sound/soc/sophgo/cv1800= b-sound-adc.c new file mode 100644 index 000000000000..794030b713e9 --- /dev/null +++ b/sound/soc/sophgo/cv1800b-sound-adc.c @@ -0,0 +1,322 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Internal adc codec for cv1800b compatible SoC + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CV1800B_RXADC_WORD_LEN 16 +#define CV1800B_RXADC_CHANNELS 2 + +#define CV1800B_RXADC_CTRL0 0x00 +#define CV1800B_RXADCC_CTRL1 0x04 +#define CV1800B_RXADC_STATUS 0x08 +#define CV1800B_RXADC_CLK 0x0c +#define CV1800B_RXADC_ANA0 0x10 +#define CV1800B_RXADC_ANA1 0x14 +#define CV1800B_RXADC_ANA2 0x18 +#define CV1800B_RXADC_ANA3 0x1c +#define CV1800B_RXADC_ANA4 0x20 + +/* CV1800B_RXADC_CTRL0 */ +#define REG_RXADC_EN GENMASK(0, 0) +#define REG_I2S_TX_EN GENMASK(1, 1) + +/* CV1800B_RXADCC_CTRL1 */ +#define REG_RXADC_CIC_OPT GENMASK(1, 0) +#define REG_RXADC_IGR_INIT GENMASK(8, 8) + +/* CV1800B_RXADC_ANA0 */ +#define REG_GSTEPL_RXPGA GENMASK(12, 0) +#define REG_G6DBL_RXPGA GENMASK(13, 13) +#define REG_GAINL_RXADC GENMASK(15, 14) +#define REG_GSTEPR_RXPGA GENMASK(28, 16) +#define REG_G6DBR_RXPGA GENMASK(29, 29) +#define REG_GAINR_RXADC GENMASK(31, 30) +#define REG_COMB_LEFT_VOLUME GENMASK(15, 0) +#define REG_COMB_RIGHT_VOLUME GENMASK(31, 16) + +/* CV1800B_RXADC_ANA2 */ +#define REG_MUTEL_RXPGA GENMASK(0, 0) +#define REG_MUTER_RXPGA GENMASK(1, 1) + +/* CV1800B_RXADC_CLK */ +#define REG_RXADC_CLK_INV GENMASK(0, 0) +#define REG_RXADC_SCK_DIV GENMASK(15, 8) +#define REG_RXADC_DLYEN GENMASK(23, 16) + +enum decimation_values { + DECIMATION_64 =3D 0, + DECIMATION_128, + DECIMATION_256, + DECIMATION_512, +}; + +static const u32 cv1800b_gains[] =3D { + 0x0001, /* 0dB */ + 0x0002, /* 2dB */ + 0x0004, /* 4dB */ + 0x0008, /* 6dB */ + 0x0010, /* 8dB */ + 0x0020, /* 10dB */ + 0x0040, /* 12dB */ + 0x0080, /* 14dB */ + 0x0100, /* 16dB */ + 0x0200, /* 18dB */ + 0x0400, /* 20dB */ + 0x0800, /* 22dB */ + 0x1000, /* 24dB */ + 0x2400, /* 26dB */ + 0x2800, /* 28dB */ + 0x3000, /* 30dB */ + 0x6400, /* 32dB */ + 0x6800, /* 34dB */ + 0x7000, /* 36dB */ + 0xA400, /* 38dB */ + 0xA800, /* 40dB */ + 0xB000, /* 42dB */ + 0xE400, /* 44dB */ + 0xE800, /* 46dB */ + 0xF000, /* 48dB */ +}; + +struct cv1800b_priv { + void __iomem *regs; + struct device *dev; + unsigned int mclk_rate; +}; + +static int cv1800b_adc_setbclk_div(struct cv1800b_priv *priv, unsigned int= rate) +{ + u32 val; + u32 bclk_div; + u64 tmp; + + if (!priv->mclk_rate || !rate) + return -EINVAL; + + tmp =3D priv->mclk_rate; + tmp /=3D CV1800B_RXADC_WORD_LEN; + tmp /=3D CV1800B_RXADC_CHANNELS; + tmp /=3D rate; + tmp /=3D 2; + + if (!tmp) { + dev_err(priv->dev, "computed BCLK divider is zero\n"); + return -EINVAL; + } + + if (tmp > 256) { + dev_err(priv->dev, "BCLK divider %llu out of range\n", tmp); + return -EINVAL; + } + + bclk_div =3D tmp - 1; + val =3D readl(priv->regs + CV1800B_RXADC_CLK); + val =3D u32_replace_bits(val, bclk_div, REG_RXADC_SCK_DIV); + /* Vendor value for 48kHz, tested on SG2000/SG2002 */ + val =3D u32_replace_bits(val, 0x19, REG_RXADC_DLYEN); + writel(val, priv->regs + CV1800B_RXADC_CLK); + + return 0; +} + +static void cv1800b_adc_enable(struct cv1800b_priv *priv, bool enable) +{ + u32 val; + + val =3D readl(priv->regs + CV1800B_RXADC_CTRL0); + val =3D u32_replace_bits(val, enable, REG_RXADC_EN); + val =3D u32_replace_bits(val, enable, REG_I2S_TX_EN); + writel(val, priv->regs + CV1800B_RXADC_CTRL0); +} + +static unsigned int cv1800b_adc_calc_db(u32 ana0, bool right) +{ + u32 step_mask =3D right ? FIELD_GET(REG_GSTEPR_RXPGA, ana0) : + FIELD_GET(REG_GSTEPL_RXPGA, ana0); + u32 coarse =3D right ? FIELD_GET(REG_GAINR_RXADC, ana0) : + FIELD_GET(REG_GAINL_RXADC, ana0); + bool g6db =3D right ? FIELD_GET(REG_G6DBR_RXPGA, ana0) : + FIELD_GET(REG_G6DBL_RXPGA, ana0); + + u32 step =3D step_mask ? __ffs(step_mask) : 0; + + step =3D min(step, 12U); + coarse =3D min(coarse, 3U); + + return 2 * step + 6 * coarse + (g6db ? 6 : 0); +} + +static int cv1800b_adc_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct cv1800b_priv *priv =3D snd_soc_dai_get_drvdata(dai); + unsigned int rate =3D params_rate(params); + u32 val; + int ret; + + ret =3D cv1800b_adc_setbclk_div(priv, rate); + if (ret) { + dev_err(priv->dev, + "could not set rate, check DT node for fixed clock\n"); + return ret; + } + + /* init adc */ + val =3D readl(priv->regs + CV1800B_RXADCC_CTRL1); + val =3D u32_replace_bits(val, 1, REG_RXADC_IGR_INIT); + val =3D u32_replace_bits(val, DECIMATION_64, REG_RXADC_CIC_OPT); + writel(val, priv->regs + CV1800B_RXADCC_CTRL1); + return 0; +} + +static int cv1800b_adc_dai_trigger(struct snd_pcm_substream *substream, in= t cmd, + struct snd_soc_dai *dai) +{ + struct cv1800b_priv *priv =3D snd_soc_dai_get_drvdata(dai); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + cv1800b_adc_enable(priv, true); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + cv1800b_adc_enable(priv, false); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int cv1800b_adc_dai_set_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) +{ + struct cv1800b_priv *priv =3D snd_soc_dai_get_drvdata(dai); + + priv->mclk_rate =3D freq; + dev_dbg(priv->dev, "mclk is set to %u\n", freq); + return 0; +} + +static const struct snd_soc_dai_ops cv1800b_adc_dai_ops =3D { + .hw_params =3D cv1800b_adc_hw_params, + .set_sysclk =3D cv1800b_adc_dai_set_sysclk, + .trigger =3D cv1800b_adc_dai_trigger, +}; + +static struct snd_soc_dai_driver cv1800b_adc_dai =3D { + .name =3D "adc-hifi", + .capture =3D { .stream_name =3D "ADC Capture", + .channels_min =3D 1, + .channels_max =3D 2, + .rates =3D SNDRV_PCM_RATE_48000, + .formats =3D SNDRV_PCM_FMTBIT_S16_LE }, + .ops =3D &cv1800b_adc_dai_ops, +}; + +static int cv1800b_adc_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component =3D snd_kcontrol_chip(kcontrol); + struct cv1800b_priv *priv =3D snd_soc_component_get_drvdata(component); + u32 ana0 =3D readl(priv->regs + CV1800B_RXADC_ANA0); + + unsigned int left =3D cv1800b_adc_calc_db(ana0, false); + unsigned int right =3D cv1800b_adc_calc_db(ana0, true); + + ucontrol->value.integer.value[0] =3D min(left / 2, 24U); + ucontrol->value.integer.value[1] =3D min(right / 2, 24U); + return 0; +} + +static int cv1800b_adc_volume_set(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component =3D snd_kcontrol_chip(kcontrol); + struct cv1800b_priv *priv =3D snd_soc_component_get_drvdata(component); + + u32 v_left =3D clamp_t(u32, ucontrol->value.integer.value[0], 0, 24); + u32 v_right =3D clamp_t(u32, ucontrol->value.integer.value[1], 0, 24); + u32 val; + + val =3D readl(priv->regs + CV1800B_RXADC_ANA0); + val =3D u32_replace_bits(val, cv1800b_gains[v_left], + REG_COMB_LEFT_VOLUME); + val =3D u32_replace_bits(val, cv1800b_gains[v_right], + REG_COMB_RIGHT_VOLUME); + writel(val, priv->regs + CV1800B_RXADC_ANA0); + + return 0; +} + +static DECLARE_TLV_DB_SCALE(cv1800b_volume_tlv, 0, 200, 0); + +static const struct snd_kcontrol_new cv1800b_adc_controls[] =3D { + SOC_DOUBLE_EXT_TLV("Internal I2S Capture Volume", SND_SOC_NOPM, 0, 16, 24= , false, + cv1800b_adc_volume_get, cv1800b_adc_volume_set, + cv1800b_volume_tlv), +}; + +static const struct snd_soc_component_driver cv1800b_adc_component =3D { + .name =3D "cv1800b-adc-codec", + .controls =3D cv1800b_adc_controls, + .num_controls =3D ARRAY_SIZE(cv1800b_adc_controls), +}; + +static int cv1800b_adc_probe(struct platform_device *pdev) +{ + struct device *dev =3D &pdev->dev; + struct cv1800b_priv *priv; + + priv =3D devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->dev =3D dev; + priv->regs =3D devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(priv->regs)) + return PTR_ERR(priv->regs); + + platform_set_drvdata(pdev, priv); + return devm_snd_soc_register_component(&pdev->dev, + &cv1800b_adc_component, + &cv1800b_adc_dai, 1); +} + +static const struct of_device_id cv1800b_adc_of_match[] =3D { + { .compatible =3D "sophgo,cv1800b-sound-adc" }, + { /* sentinel */ } +}; + +MODULE_DEVICE_TABLE(of, cv1800b_adc_of_match); + +static struct platform_driver cv1800b_adc_driver =3D { + .probe =3D cv1800b_adc_probe, + .driver =3D { + .name =3D "cv1800b-sound-adc", + .of_match_table =3D cv1800b_adc_of_match, + }, +}; + +module_platform_driver(cv1800b_adc_driver); + +MODULE_DESCRIPTION("ADC codec for CV1800B"); +MODULE_AUTHOR("Anton D. Stavinskii "); +MODULE_LICENSE("GPL"); --=20 2.43.0