From nobody Tue Feb 10 03:45:10 2026 Received: from mail-wm1-f49.google.com (mail-wm1-f49.google.com [209.85.128.49]) (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 17E8430C378 for ; Mon, 19 Jan 2026 16:31:01 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.49 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1768840264; cv=none; b=LBbZ/D6fIPmQVNvegJo2mf7bEpXewplTkx6TxO4CJa3/nsL3GBKoE737d8MRdIv061P5on1oUkgNTW/Nx97xc55lvqkerL0Pt50TZaDlCzqHiY40XcaCvcV5NB8ZcR8//yhnO3alUvZEoEGqZ3NF47NiCKwFzd6Gbmfkk6aMH54= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1768840264; c=relaxed/simple; bh=FhxRnJeE+n492blGdhTMJDOvXT2t5M+yRqAueV+wnBE=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=cL6qJuN89Qedqm1rcRyRuoTnIb5DQCK3ISprVKTeB5f9CUo4eo0SdEq8+qxiM2TYcg9nDWskDVmowSzqmMDVJWGzaR4rrcaoo1/n8lpBJR0cWRJTDQpR9jhUqXT7WFN9cmjEP8DzTWH7T1Djowp+aOGqBHHGEO3bHBjs94hPrb8= 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=hZYzMpHF; arc=none smtp.client-ip=209.85.128.49 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="hZYzMpHF" Received: by mail-wm1-f49.google.com with SMTP id 5b1f17b1804b1-47edd9024b1so27453105e9.3 for ; Mon, 19 Jan 2026 08:31:01 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1768840260; x=1769445060; 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=67n3hQj7YZy5SOWtPvVJYLwDNXmEE9p7Iv/fRjacYhE=; b=hZYzMpHFFfGcTzU+yA64Zyp88T/zsLltZnELAPK/s9IcfghHTfGRJGfzgbe6/MtTE3 dMmSqIxbjaydBPss6X0wQQNTP8JZO/v+cxZyMYrQE66VyhvYO4ltyDtJ6ZHtp3kXcK4a nZaEIMGdLZYAAyilli6Rm7ABh0siZp0pVGwf+ji3ON3E0vlHXVLe1j6QWuKU1R8KVbPt kXoIX/t3RbnsTjtLmkUSl/Thn5QKc1FsiYQthbzHy0yoQUrY/JkJzdWraEcEnV6oGhTQ AuU7myGSIptXi1Sr7WLCJG0mdtUudY2DGGSgic1Vcun7H5Iff2xQk9h2S58H9xTK5Ad/ KzDg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1768840260; x=1769445060; 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=67n3hQj7YZy5SOWtPvVJYLwDNXmEE9p7Iv/fRjacYhE=; b=sCxgiqXZZpl1ZPxxBXJ55sV0zIw22hC3HV3b+O1jxVdngK2guwgT/evixbTzLozWK/ w3vk4STCddAErWuvWkV+dETOY760l5/kuDGXKVIbVki7GwaA03tnFwH44oPH8gyOn1Rz IsdM/GBF2YUjNuln/AxqpyUthzZdQlk6i7z0k5+MlUx+cLad9XOGbfO9pfxk01qZGuQn LZiXz++ZWGYqjSbo/87E/s/hYIzY7r02uKYh7fgaV0LMdz4OtrWA7S7zpN2eC35TKGy5 AXxokcI5FrGhU5FLQTLENH7dC+weRhxJOd7eHIRH3aM1T1ZCMwEQdzk8mbmCiGHEKmwi TL7Q== X-Forwarded-Encrypted: i=1; AJvYcCUu8Bb342mueat3g88xnnHy1gey2QRykUCc0FtUKehiQetAY4zWhzmyFQ+WUdOeAWkbZBRVTtjQQic9k0I=@vger.kernel.org X-Gm-Message-State: AOJu0YyU3LPB5aY/QxbEDx5OnMZwzXDADdw/qAqIGNGV3rsi4W9HOJuE 1Tv1s/T5q0gKMMkMvzICodpz1gtyTC2V945TcX2V/x1/n/yffjOXSWmx X-Gm-Gg: AY/fxX6nZFQO4mq8F/bxwxENicY15vA3TSKs9uz9gTvccBNB0PKBkE9dRMZTrxfoVfc 26z2X8cCmQtLnnw59PiusPb2C7/zZx61wWDTNlKqo4qmcUB4OkqGAMaxpYH6KAkk2uiNZANGjYq Eq80zq/ZK1yJdWSIyMN6oAtOWRxA8NwKvFDPPmBQPUodlQ92ScbxAnHR5ZyKT7o4h9Isl47yE7u Nc9G9DcVO1q2/lieWTYNkYViMo2/57AqVo+dMjjQaUVhdS9qnbxcw4ruPPNhHzJqcS8I2FYPNtt ro6cabXuZMQq3Lse8ci70AgLRc/wKC5D6StGrLvSn0yqXVYX5ezQUjZEYh/tv+YO1RdD6LXYhWv ufV7aOBZK/snE+ChbmXWCJ/BP4pxBNHpZTNeE272B6MqVrGAq822pLFFJVQ6vnLi5irC5tC2Q0W sXcMm4MivbOoy9ikh7TTz/XJSZ8EuSpBCpG1gtCvE+mKNj X-Received: by 2002:a05:600c:870c:b0:47d:25ac:3a94 with SMTP id 5b1f17b1804b1-4801eb092demr155865715e9.17.1768840260071; Mon, 19 Jan 2026 08:31:00 -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-4801e879537sm201666795e9.5.2026.01.19.08.30.56 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 19 Jan 2026 08:30:59 -0800 (PST) From: "Anton D. Stavinskii" Date: Mon, 19 Jan 2026 20:30:45 +0400 Subject: [PATCH v3 4/6] 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: <20260119-cv1800b-i2s-driver-v3-4-04006f4111d7@gmail.com> References: <20260119-cv1800b-i2s-driver-v3-0-04006f4111d7@gmail.com> In-Reply-To: <20260119-cv1800b-i2s-driver-v3-0-04006f4111d7@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=1768840243; l=11247; i=stavinsky@gmail.com; s=20260115; h=from:subject:message-id; bh=FhxRnJeE+n492blGdhTMJDOvXT2t5M+yRqAueV+wnBE=; b=rlorVl7lp7truTnHwuiw4wwPmmRrcQE2E0O+s4VLbfASsmz+Mfa7saIE+5+n/QJlcZYkEa30m ZAif8FFntDhAw/YdmBcPole/fQUWmn/P7dmiUWahPxLHWApVc6lpYGT 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 9495ab49f042..12d1a57ea308 100644 --- a/sound/soc/sophgo/Kconfig +++ b/sound/soc/sophgo/Kconfig @@ -22,4 +22,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