Skip to content

drivers: adc: add a driver for the CH32V003 ADC #88203

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions drivers/adc/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,4 @@ zephyr_library_sources_ifdef(CONFIG_ADC_AD7124 adc_ad7124.c)
zephyr_library_sources_ifdef(CONFIG_ADC_AD405X adc_ad405x.c)
zephyr_library_sources_ifdef(CONFIG_ADC_AD4130 adc_ad4130.c)
zephyr_library_sources_ifdef(CONFIG_ADC_REALTEK_RTS5912 adc_realtek_rts5912.c)
zephyr_library_sources_ifdef(CONFIG_ADC_CH32V00X adc_ch32v00x.c)
2 changes: 2 additions & 0 deletions drivers/adc/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -150,4 +150,6 @@ source "drivers/adc/Kconfig.ad4130"

source "drivers/adc/Kconfig.rts5912"

source "drivers/adc/Kconfig.ch32v00x"

endif # ADC
9 changes: 9 additions & 0 deletions drivers/adc/Kconfig.ch32v00x
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Copyright (c) Michael Hope <[email protected]>
# SPDX-License-Identifier: Apache-2.0

config ADC_CH32V00X
bool "WCH CH32V00x ADC Driver"
default y
depends on DT_HAS_WCH_ADC_ENABLED
help
Enable the WCH CH32V00x family Analog-to-Digital Converter (ADC) driver.
163 changes: 163 additions & 0 deletions drivers/adc/adc_ch32v00x.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
/*
* Copyright (c) 2025 Michael Hope
*
* SPDX-License-Identifier: Apache-2.0
*/

#define DT_DRV_COMPAT wch_adc

#include <ch32fun.h>

#include <zephyr/drivers/adc.h>
#include <zephyr/drivers/pinctrl.h>
#include <zephyr/drivers/clock_control.h>

struct adc_ch32v00x_data {
};

struct adc_ch32v00x_config {
ADC_TypeDef *regs;
const struct pinctrl_dev_config *pin_cfg;
const struct device *clock_dev;
uint8_t clock_id;
};

static int adc_ch32v00x_channel_setup(const struct device *dev,
const struct adc_channel_cfg *channel_cfg)
{
if (channel_cfg->gain != ADC_GAIN_1) {
return -EINVAL;
}
if (channel_cfg->reference != ADC_REF_INTERNAL) {
return -EINVAL;
}
if (channel_cfg->acquisition_time != ADC_ACQ_TIME_DEFAULT) {
return -EINVAL;
}
if (channel_cfg->differential) {
return -EINVAL;
}
if (channel_cfg->channel_id >= 10) {
return -EINVAL;
}

return 0;
}

static int adc_ch32v00x_read(const struct device *dev, const struct adc_sequence *sequence)
{
const struct adc_ch32v00x_config *config = dev->config;
ADC_TypeDef *regs = config->regs;
uint32_t channels = sequence->channels;
int rsqr = 2;
int sequence_id = 0;
int total_channels = 0;
int i;
uint16_t *samples = sequence->buffer;

if (sequence->options != NULL) {
return -ENOTSUP;
}
if (sequence->resolution != 10) {
return -EINVAL;
}
if (sequence->oversampling != 0) {
return -ENOTSUP;
}
if (sequence->channels >= (1 << 10)) {
return -EINVAL;
}

if (sequence->calibrate) {
regs->CTLR2 |= ADC_RSTCAL;
while ((regs->CTLR2 & ADC_RSTCAL) != 0) {
}
regs->CTLR2 |= ADC_CAL;
while ((regs->CTLR2 & ADC_CAL) != 0) {
}
}

/*
* Build the sample sequence. The channel IDs are packed 5 bits at a time starting in RSQR3
* and working down in memory to RSQR1.
*/
regs->RSQR1 = 0;
regs->RSQR2 = 0;
regs->RSQR3 = 0;

for (i = 0; channels != 0; i++, channels >>= 1) {
if ((channels & 1) != 0) {
total_channels++;
(&regs->RSQR1)[rsqr] |= i << sequence_id;
sequence_id += ADC_SQ2_0;
if (sequence_id >= 32) {
/* Move on to the next RSQRn register, i.e. RSQR(n-1) */
sequence_id = 0;
rsqr--;
}
}
}
if (total_channels == 0) {
return 0;
}
if (sequence->buffer_size < total_channels * sizeof(*samples)) {
return -ENOMEM;
}

/* Set the number of channels to read. Note that '0' means 'one channel'. */
regs->RSQR1 |= (total_channels - 1) * ADC_L_0;
regs->CTLR2 |= ADC_SWSTART;
for (i = 0; i < total_channels; i++) {
while ((regs->STATR & ADC_EOC) == 0) {
}
*samples++ = regs->RDATAR;
}

return 0;
}

static int adc_ch32v00x_init(const struct device *dev)
{
const struct adc_ch32v00x_config *config = dev->config;
ADC_TypeDef *regs = config->regs;
int err;

clock_control_on(config->clock_dev, (clock_control_subsys_t)(uintptr_t)config->clock_id);

err = pinctrl_apply_state(config->pin_cfg, PINCTRL_STATE_DEFAULT);
if (err != 0) {
return err;
}

/*
* The default sampling time is 3 cycles and shows coupling between channels. Use 15 cycles
* instead. Arbitrary.
*/
regs->SAMPTR2 = ADC_SMP0_1 | ADC_SMP1_1 | ADC_SMP2_1 | ADC_SMP3_1 | ADC_SMP4_1 |
ADC_SMP5_1 | ADC_SMP6_1 | ADC_SMP7_1 | ADC_SMP8_1 | ADC_SMP9_1;

regs->CTLR2 = ADC_ADON | ADC_EXTSEL;

return 0;
}

#define ADC_CH32V00X_DEVICE(n) \
PINCTRL_DT_INST_DEFINE(n); \
\
static const struct adc_driver_api adc_ch32v00x_api_##n = { \
.channel_setup = adc_ch32v00x_channel_setup, \
.read = adc_ch32v00x_read, \
.ref_internal = DT_INST_PROP(n, vref_mv), \
}; \
\
static const struct adc_ch32v00x_config adc_ch32v00x_config_##n = { \
.regs = (ADC_TypeDef *)DT_INST_REG_ADDR(n), \
.pin_cfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n), \
.clock_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(n)), \
.clock_id = DT_INST_CLOCKS_CELL(n, id), \
}; \
\
DEVICE_DT_INST_DEFINE(n, adc_ch32v00x_init, NULL, NULL, &adc_ch32v00x_config_##n, \
POST_KERNEL, CONFIG_ADC_INIT_PRIORITY, &adc_ch32v00x_api_##n);

DT_INST_FOREACH_STATUS_OKAY(ADC_CH32V00X_DEVICE)
8 changes: 8 additions & 0 deletions drivers/pinctrl/pinctrl_wch_afio.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ int pinctrl_configure_pins(const pinctrl_soc_pin_t *pins, uint8_t pin_cnt, uintp
GPIO_TypeDef *regs = wch_afio_pinctrl_regs[port];
uint32_t pcfr1 = AFIO->PCFR1;
uint8_t cfg = 0;
bool is_adc = (bit0 == CH32V003_PINMUX_ADC1_RM);

if (remap != 0) {
RCC->APB2PCENR |= RCC_AFIOEN;
Expand All @@ -41,7 +42,14 @@ int pinctrl_configure_pins(const pinctrl_soc_pin_t *pins, uint8_t pin_cnt, uintp
cfg |= BIT(3);
} else {
if (pins->bias_pull_up || pins->bias_pull_down) {
/* "With pull up and pull down" mode */
cfg |= BIT(3);
} else if (is_adc) {
/* Analog input mode */
cfg = 0;
} else {
/* Floating input mode */
cfg |= BIT(0);
}
}
regs->CFGLR = (regs->CFGLR & ~(0x0F << (pin * 4))) | (cfg << (pin * 4));
Expand Down
36 changes: 36 additions & 0 deletions dts/bindings/adc/wch,adc.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Copyright (c) 2025 Michael Hope <[email protected]>
# SPDX-License-Identifier: Apache-2.0

description: WCH CH32V00x ADC

compatible: "wch,adc"

include: [adc-controller.yaml, pinctrl-device.yaml]

properties:
reg:
required: true

interrupts:
required: true

clocks:
required: true

vref-mv:
type: int
default: 3300
description: |
Reference voltage in mV. This is typically VDD.

pinctrl-0:
required: true

pinctrl-names:
required: true

"#io-channel-cells":
const: 1

io-channel-cells:
- input
10 changes: 10 additions & 0 deletions dts/riscv/wch/ch32v0/ch32v003.dtsi
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,16 @@
interrupts = <22>, <23>, <24>, <25>, <26>, <27>, <28>;
dma-channels = <7>;
};

adc1: adc@40012400 {
compatible = "wch,adc";
reg = <0x40012400 16>;
clocks = <&rcc CH32V00X_CLOCK_ADC1>;
interrupt-parent = <&pfic>;
interrupts = <29>;
#io-channel-cells = <1>;
status = "disabled";
};
};
};

Expand Down
14 changes: 14 additions & 0 deletions include/zephyr/dt-bindings/pinctrl/ch32v003-pinctrl.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@
#define CH32V003_PINMUX_TIM1_RM 6
#define CH32V003_PINMUX_TIM1_RM1 23
#define CH32V003_PINMUX_TIM2_RM 8
/*
* ADC1 is not remappable but re-uses the remap field to encode that the pin needs to use
* analog input mode.
*/
#define CH32V003_PINMUX_ADC1_RM 3

/* Port number with 0-2 */
#define CH32V003_PINCTRL_PORT_SHIFT 0
Expand Down Expand Up @@ -134,4 +139,13 @@
#define I2C1_SDA_PD0_1 CH32V003_PINMUX_DEFINE(PD, 0, I2C1, 1)
#define I2C1_SDA_PC6_2 CH32V003_PINMUX_DEFINE(PC, 6, I2C1, 2)

#define ADC1_A0_PA2_0 CH32V003_PINMUX_DEFINE(PA, 2, ADC1, 0)
#define ADC1_A1_PA1_0 CH32V003_PINMUX_DEFINE(PA, 1, ADC1, 0)
#define ADC1_A2_PC4_0 CH32V003_PINMUX_DEFINE(PC, 4, ADC1, 0)
#define ADC1_A4_PD3_0 CH32V003_PINMUX_DEFINE(PD, 3, ADC1, 0)
#define ADC1_A3_PD2_0 CH32V003_PINMUX_DEFINE(PD, 2, ADC1, 0)
#define ADC1_A5_PD5_0 CH32V003_PINMUX_DEFINE(PD, 5, ADC1, 0)
#define ADC1_A7_PD4_0 CH32V003_PINMUX_DEFINE(PD, 4, ADC1, 0)
#define ADC1_A6_PD6_0 CH32V003_PINMUX_DEFINE(PD, 6, ADC1, 0)

#endif /* __CH32V003_PINCTRL_H__ */
56 changes: 56 additions & 0 deletions samples/drivers/adc/adc_dt/boards/ch32v003evt.overlay
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Copyright (c) 2025 Michael Hope <[email protected]>
* SPDX-License-Identifier: Apache-2.0
*/

#include <zephyr/dt-bindings/adc/adc.h>

/ {
zephyr,user {
io-channels = <&adc1 0>, <&adc1 8>, <&adc1 9>;
};
};

&pinctrl {
adc1_default: adc1_default {
group1 {
pinmux = <ADC1_A0_PA2_0>;
};
};
};

&adc1 {
#address-cells = <1>;
#size-cells = <0>;
status = "okay";

pinctrl-0 = <&adc1_default>;
pinctrl-names = "default";

/* Channel 0 is floating */
channel@0 {
reg = <0>;
zephyr,gain = "ADC_GAIN_1";
zephyr,reference = "ADC_REF_INTERNAL";
zephyr,acquisition-time = <ADC_ACQ_TIME_DEFAULT>;
zephyr,resolution = <10>;
};

/* Channel 8 is connected to Vref, which is between 1.17 and 1.23 V */
channel@8 {
reg = <8>;
zephyr,gain = "ADC_GAIN_1";
zephyr,reference = "ADC_REF_INTERNAL";
zephyr,acquisition-time = <ADC_ACQ_TIME_DEFAULT>;
zephyr,resolution = <10>;
};

/* Channel 9 is connected to Vcal, which is half of Avdd, i.e ~1.65 V */
channel@9 {
reg = <9>;
zephyr,gain = "ADC_GAIN_1";
zephyr,reference = "ADC_REF_INTERNAL";
zephyr,acquisition-time = <ADC_ACQ_TIME_DEFAULT>;
zephyr,resolution = <10>;
};
};