Skip to content

Commit 325883a

Browse files
committed
drivers: adc: add a driver for the CH32V003 ADC
The CH32V003 has a 8 channel, 10 bit onboard ADC. Add an immediate mode driver and the appropriate pinctrl bindings. Note that the CH32V003 GPIO pins have both a floating input and an analogue input mode, and the pinctrl is needed to put the pin in analogue mode. Signed-off-by: Michael Hope <[email protected]>
1 parent 207a048 commit 325883a

File tree

8 files changed

+243
-0
lines changed

8 files changed

+243
-0
lines changed

drivers/adc/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,4 @@ zephyr_library_sources_ifdef(CONFIG_ADC_MAX32 adc_max32.c)
6060
zephyr_library_sources_ifdef(CONFIG_ADC_AD4114 adc_ad4114.c)
6161
zephyr_library_sources_ifdef(CONFIG_ADC_AD7124 adc_ad7124.c)
6262
zephyr_library_sources_ifdef(CONFIG_ADC_AD405X adc_ad405x.c)
63+
zephyr_library_sources_ifdef(CONFIG_ADC_CH32V00X adc_ch32v00x.c)

drivers/adc/Kconfig

+2
Original file line numberDiff line numberDiff line change
@@ -144,4 +144,6 @@ source "drivers/adc/Kconfig.ad7124"
144144

145145
source "drivers/adc/Kconfig.ad405x"
146146

147+
source "drivers/adc/Kconfig.ch32v00x"
148+
147149
endif # ADC

drivers/adc/Kconfig.ch32v00x

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Copyright (c) Michael Hope <[email protected]>
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
config ADC_CH32V00X
5+
bool "WCH CH32V00x ADC Driver"
6+
default y
7+
depends on DT_HAS_WCH_ADC_ENABLED
8+
help
9+
Enable the WCH CH32V00x family Analog-to-Digital Converter (ADC) driver.

drivers/adc/adc_ch32v00x.c

+163
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
/*
2+
* Copyright (c) 2025 Michael Hope
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#define DT_DRV_COMPAT wch_adc
8+
9+
#include <ch32fun.h>
10+
11+
#include <zephyr/drivers/adc.h>
12+
#include <zephyr/drivers/pinctrl.h>
13+
#include <zephyr/drivers/clock_control.h>
14+
15+
struct adc_ch32v00x_data {
16+
};
17+
18+
struct adc_ch32v00x_config {
19+
ADC_TypeDef *regs;
20+
const struct pinctrl_dev_config *pin_cfg;
21+
const struct device *clock_dev;
22+
uint8_t clock_id;
23+
};
24+
25+
static int adc_ch32v00x_channel_setup(const struct device *dev,
26+
const struct adc_channel_cfg *channel_cfg)
27+
{
28+
if (channel_cfg->gain != ADC_GAIN_1) {
29+
return -EINVAL;
30+
}
31+
if (channel_cfg->reference != ADC_REF_INTERNAL) {
32+
return -EINVAL;
33+
}
34+
if (channel_cfg->acquisition_time != ADC_ACQ_TIME_DEFAULT) {
35+
return -EINVAL;
36+
}
37+
if (channel_cfg->differential) {
38+
return -EINVAL;
39+
}
40+
if (channel_cfg->channel_id >= 10) {
41+
return -EINVAL;
42+
}
43+
44+
return 0;
45+
}
46+
47+
static int adc_ch32v00x_read(const struct device *dev, const struct adc_sequence *sequence)
48+
{
49+
const struct adc_ch32v00x_config *config = dev->config;
50+
ADC_TypeDef *regs = config->regs;
51+
uint32_t channels = sequence->channels;
52+
int rsqr = 2;
53+
int sequence_id = 0;
54+
int total_channels = 0;
55+
int i;
56+
uint16_t *samples = sequence->buffer;
57+
58+
if (sequence->options != NULL) {
59+
return -ENOTSUP;
60+
}
61+
if (sequence->resolution != 10) {
62+
return -EINVAL;
63+
}
64+
if (sequence->oversampling != 0) {
65+
return -ENOTSUP;
66+
}
67+
if (sequence->channels >= (1 << 10)) {
68+
return -EINVAL;
69+
}
70+
71+
if (sequence->calibrate) {
72+
regs->CTLR2 |= ADC_RSTCAL;
73+
while ((regs->CTLR2 & ADC_RSTCAL) != 0) {
74+
}
75+
regs->CTLR2 |= ADC_CAL;
76+
while ((ADC1->CTLR2 & ADC_CAL) != 0) {
77+
}
78+
}
79+
80+
/*
81+
* Build the sample sequence. The channel IDs are packed 5 bits at a time starting in RSQR3
82+
* and working down in memory to RSQR1.
83+
*/
84+
regs->RSQR1 = 0;
85+
regs->RSQR2 = 0;
86+
regs->RSQR3 = 0;
87+
88+
for (i = 0; channels != 0; i++, channels >>= 1) {
89+
if ((channels & 1) != 0) {
90+
total_channels++;
91+
(&regs->RSQR1)[rsqr] |= i << sequence_id;
92+
sequence_id += ADC_SQ2_0;
93+
if (sequence_id >= 32) {
94+
/* Move on to the next RSQRn register, i.e. RSQR(n-1) */
95+
sequence_id = 0;
96+
rsqr--;
97+
}
98+
}
99+
}
100+
if (total_channels == 0) {
101+
return 0;
102+
}
103+
if (sequence->buffer_size < total_channels * sizeof(*samples)) {
104+
return -ENOMEM;
105+
}
106+
107+
/* Set the number of channels to read. Note that '0' means 'one channel'. */
108+
regs->RSQR1 |= (total_channels - 1) * ADC_L_0;
109+
regs->CTLR2 |= ADC_SWSTART;
110+
for (i = 0; i < total_channels; i++) {
111+
while ((regs->STATR & ADC_EOC) == 0) {
112+
}
113+
*samples++ = regs->RDATAR;
114+
}
115+
116+
return 0;
117+
}
118+
119+
static int adc_ch32v00x_init(const struct device *dev)
120+
{
121+
const struct adc_ch32v00x_config *config = dev->config;
122+
ADC_TypeDef *regs = config->regs;
123+
int err;
124+
125+
clock_control_on(config->clock_dev, (clock_control_subsys_t)(uintptr_t)config->clock_id);
126+
127+
err = pinctrl_apply_state(config->pin_cfg, PINCTRL_STATE_DEFAULT);
128+
if (err != 0) {
129+
return err;
130+
}
131+
132+
/*
133+
* The default sampling time is 3 cycles and shows coupling between channels. Use 15 cycles
134+
* instead. Arbitrary.
135+
*/
136+
regs->SAMPTR2 = ADC_SMP0_1 | ADC_SMP1_1 | ADC_SMP2_1 | ADC_SMP3_1 | ADC_SMP4_1 |
137+
ADC_SMP5_1 | ADC_SMP6_1 | ADC_SMP7_1 | ADC_SMP8_1 | ADC_SMP9_1;
138+
139+
regs->CTLR2 = ADC_ADON | ADC_EXTSEL;
140+
141+
return 0;
142+
}
143+
144+
#define ADC_CH32V00X_DEVICE(n) \
145+
PINCTRL_DT_INST_DEFINE(n); \
146+
\
147+
static const struct adc_driver_api adc_ch32v00x_api_##n = { \
148+
.channel_setup = adc_ch32v00x_channel_setup, \
149+
.read = adc_ch32v00x_read, \
150+
.ref_internal = DT_INST_PROP(n, vref_mv), \
151+
}; \
152+
\
153+
static const struct adc_ch32v00x_config adc_ch32v00x_config_##n = { \
154+
.regs = (ADC_TypeDef *)DT_INST_REG_ADDR(n), \
155+
.pin_cfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n), \
156+
.clock_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(n)), \
157+
.clock_id = DT_INST_CLOCKS_CELL(n, id), \
158+
}; \
159+
\
160+
DEVICE_DT_INST_DEFINE(n, adc_ch32v00x_init, NULL, NULL, &adc_ch32v00x_config_##n, \
161+
POST_KERNEL, CONFIG_ADC_INIT_PRIORITY, &adc_ch32v00x_api_##n);
162+
163+
DT_INST_FOREACH_STATUS_OKAY(ADC_CH32V00X_DEVICE)

drivers/pinctrl/pinctrl_wch_afio.c

+8
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ int pinctrl_configure_pins(const pinctrl_soc_pin_t *pins, uint8_t pin_cnt, uintp
2727
GPIO_TypeDef *regs = wch_afio_pinctrl_regs[port];
2828
uint32_t pcfr1 = AFIO->PCFR1;
2929
uint8_t cfg = 0;
30+
bool is_adc = (bit0 == CH32V003_PINMUX_ADC1_RM);
3031

3132
if (remap != 0) {
3233
RCC->APB2PCENR |= RCC_AFIOEN;
@@ -41,7 +42,14 @@ int pinctrl_configure_pins(const pinctrl_soc_pin_t *pins, uint8_t pin_cnt, uintp
4142
cfg |= BIT(3);
4243
} else {
4344
if (pins->bias_pull_up || pins->bias_pull_down) {
45+
/* "With pull up and pull down" mode */
4446
cfg |= BIT(3);
47+
} else if (is_adc) {
48+
/* Analog input mode */
49+
cfg = 0;
50+
} else {
51+
/* Floating input mode */
52+
cfg |= BIT(0);
4553
}
4654
}
4755
regs->CFGLR = (regs->CFGLR & ~(0x0F << (pin * 4))) | (cfg << (pin * 4));

dts/bindings/adc/wch,adc.yaml

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Copyright (c) 2025 Michael Hope <[email protected]>
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
description: WCH CH32V00x ADC
5+
6+
compatible: "wch,adc"
7+
8+
include: [adc-controller.yaml, pinctrl-device.yaml]
9+
10+
properties:
11+
reg:
12+
required: true
13+
14+
interrupts:
15+
required: true
16+
17+
clocks:
18+
required: true
19+
20+
vref-mv:
21+
type: int
22+
default: 3300
23+
description: |
24+
Reference voltage in mV. This is typically VDD.
25+
26+
pinctrl-0:
27+
required: true
28+
29+
pinctrl-names:
30+
required: true
31+
32+
"#io-channel-cells":
33+
const: 1
34+
35+
io-channel-cells:
36+
- input

dts/riscv/wch/ch32v0/ch32v003.dtsi

+10
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,16 @@
126126
interrupts = <22>, <23>, <24>, <25>, <26>, <27>, <28>;
127127
dma-channels = <7>;
128128
};
129+
130+
adc1: adc@40012400 {
131+
compatible = "wch,adc";
132+
reg = <0x40012400 16>;
133+
clocks = <&rcc CH32V00X_CLOCK_ADC1>;
134+
interrupt-parent = <&pfic>;
135+
interrupts = <29>;
136+
#io-channel-cells = <1>;
137+
status = "disabled";
138+
};
129139
};
130140
};
131141

include/zephyr/dt-bindings/pinctrl/ch32v003-pinctrl.h

+14
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@
2323
#define CH32V003_PINMUX_TIM1_RM 6
2424
#define CH32V003_PINMUX_TIM1_RM1 23
2525
#define CH32V003_PINMUX_TIM2_RM 8
26+
/*
27+
* ADc1 is not remappable but re-uses the remap field to encode that the pin needs to use
28+
* analog input mode.
29+
*/
30+
#define CH32V003_PINMUX_ADC1_RM 3
2631

2732
/* Port number with 0-2 */
2833
#define CH32V003_PINCTRL_PORT_SHIFT 0
@@ -134,4 +139,13 @@
134139
#define I2C1_SDA_PD0_1 CH32V003_PINMUX_DEFINE(PD, 0, I2C1, 1)
135140
#define I2C1_SDA_PC6_2 CH32V003_PINMUX_DEFINE(PC, 6, I2C1, 2)
136141

142+
#define ADC1_A0_PA2_0 CH32V003_PINMUX_DEFINE(PA, 2, ADC1, 0)
143+
#define ADC1_A1_PA1_0 CH32V003_PINMUX_DEFINE(PA, 1, ADC1, 0)
144+
#define ADC1_A2_PC4_0 CH32V003_PINMUX_DEFINE(PC, 4, ADC1, 0)
145+
#define ADC1_A4_PD3_0 CH32V003_PINMUX_DEFINE(PD, 3, ADC1, 0)
146+
#define ADC1_A3_PD2_0 CH32V003_PINMUX_DEFINE(PD, 2, ADC1, 0)
147+
#define ADC1_A5_PD5_0 CH32V003_PINMUX_DEFINE(PD, 5, ADC1, 0)
148+
#define ADC1_A7_PD4_0 CH32V003_PINMUX_DEFINE(PD, 4, ADC1, 0)
149+
#define ADC1_A6_PD6_0 CH32V003_PINMUX_DEFINE(PD, 6, ADC1, 0)
150+
137151
#endif /* __CH32V003_PINCTRL_H__ */

0 commit comments

Comments
 (0)