Skip to content

Commit b1cd947

Browse files
nzmichaelhkartben
authored andcommitted
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 <michaelh@juju.nz>
1 parent 5fed196 commit b1cd947

File tree

8 files changed

+242
-0
lines changed

8 files changed

+242
-0
lines changed

drivers/adc/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,4 @@ zephyr_library_sources_ifdef(CONFIG_ADC_AD405X adc_ad405x.c)
6767
zephyr_library_sources_ifdef(CONFIG_ADC_AD4130 adc_ad4130.c)
6868
zephyr_library_sources_ifdef(CONFIG_ADC_REALTEK_RTS5912 adc_realtek_rts5912.c)
6969
zephyr_library_sources_ifdef(CONFIG_ADC_TI_AM335X adc_ti_am335x.c)
70+
zephyr_library_sources_ifdef(CONFIG_ADC_CH32V00X adc_ch32v00x.c)

drivers/adc/Kconfig

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,4 +158,6 @@ source "drivers/adc/Kconfig.rts5912"
158158

159159
source "drivers/adc/Kconfig.ti_am335x"
160160

161+
source "drivers/adc/Kconfig.ch32v00x"
162+
161163
endif # ADC

drivers/adc/Kconfig.ch32v00x

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Copyright (c) Michael Hope <michaelh@juju.nz>
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

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

drivers/pinctrl/pinctrl_wch_afio.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ int pinctrl_configure_pins(const pinctrl_soc_pin_t *pins, uint8_t pin_cnt, uintp
2929
uint8_t remap = (pins->config >> CH32V003_PINCTRL_RM_SHIFT) & 0x3;
3030
GPIO_TypeDef *regs = wch_afio_pinctrl_regs[port];
3131
uint8_t cfg = 0;
32+
bool is_adc = (bit0 == CH32V003_PINMUX_ADC1_RM);
3233

3334
if (pins->output_high || pins->output_low) {
3435
cfg |= (pins->slew_rate + 1);
@@ -39,7 +40,14 @@ int pinctrl_configure_pins(const pinctrl_soc_pin_t *pins, uint8_t pin_cnt, uintp
3940
cfg |= BIT(3);
4041
} else {
4142
if (pins->bias_pull_up || pins->bias_pull_down) {
43+
/* "With pull up and pull down" mode */
4244
cfg |= BIT(3);
45+
} else if (is_adc) {
46+
/* Analog input mode */
47+
cfg = 0;
48+
} else {
49+
/* Floating input mode */
50+
cfg |= BIT(2);
4351
}
4452
}
4553
regs->CFGLR = (regs->CFGLR & ~(0x0F << (pin * 4))) | (cfg << (pin * 4));

dts/bindings/adc/wch,adc.yaml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Copyright (c) 2025 Michael Hope <michaelh@juju.nz>
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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,16 @@
186186
#address-cells = <1>;
187187
#size-cells = <0>;
188188
};
189+
190+
adc1: adc@40012400 {
191+
compatible = "wch,adc";
192+
reg = <0x40012400 16>;
193+
clocks = <&rcc CH32V00X_CLOCK_ADC1>;
194+
interrupt-parent = <&pfic>;
195+
interrupts = <29>;
196+
#io-channel-cells = <1>;
197+
status = "disabled";
198+
};
189199
};
190200
};
191201

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

Lines changed: 14 additions & 0 deletions
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 /* ZEPHYR_INCLUDE_DT_BINDINGS_PINCTRL_CH32V003_PINCTRL_H_ */

0 commit comments

Comments
 (0)