1 /*
2 * Copyright (c) 2023 Kenneth J. Miller <ken@miller.ec>
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #define DT_DRV_COMPAT st_stm32_vref
8
9 #include <zephyr/device.h>
10 #include <zephyr/devicetree.h>
11 #include <zephyr/drivers/sensor.h>
12 #include <zephyr/drivers/adc.h>
13 #include <zephyr/logging/log.h>
14 #include <zephyr/pm/device_runtime.h>
15 #include <stm32_ll_adc.h>
16 #if defined(CONFIG_SOC_SERIES_STM32H5X)
17 #include <stm32_ll_icache.h>
18 #endif /* CONFIG_SOC_SERIES_STM32H5X */
19
20 LOG_MODULE_REGISTER(stm32_vref, CONFIG_SENSOR_LOG_LEVEL);
21
22 /* Resolution used to perform the Vref measurement */
23 #define MEAS_RES (12U)
24
25 struct stm32_vref_data {
26 const struct device *adc;
27 const struct adc_channel_cfg adc_cfg;
28 ADC_TypeDef *adc_base;
29 struct adc_sequence adc_seq;
30 struct k_mutex mutex;
31 int16_t sample_buffer;
32 int16_t raw; /* raw adc Sensor value */
33 };
34
35 struct stm32_vref_config {
36 uint16_t *cal_addr;
37 int cal_mv;
38 uint8_t cal_shift;
39 };
40
stm32_vref_sample_fetch(const struct device * dev,enum sensor_channel chan)41 static int stm32_vref_sample_fetch(const struct device *dev, enum sensor_channel chan)
42 {
43 struct stm32_vref_data *data = dev->data;
44 struct adc_sequence *sp = &data->adc_seq;
45 int rc;
46 uint32_t path;
47
48 if (chan != SENSOR_CHAN_ALL && chan != SENSOR_CHAN_VOLTAGE) {
49 return -ENOTSUP;
50 }
51
52 k_mutex_lock(&data->mutex, K_FOREVER);
53 pm_device_runtime_get(data->adc);
54
55 rc = adc_channel_setup(data->adc, &data->adc_cfg);
56 if (rc) {
57 LOG_DBG("Setup AIN%u got %d", data->adc_cfg.channel_id, rc);
58 goto unlock;
59 }
60
61 path = LL_ADC_GetCommonPathInternalCh(__LL_ADC_COMMON_INSTANCE(data->adc_base));
62 LL_ADC_SetCommonPathInternalCh(__LL_ADC_COMMON_INSTANCE(data->adc_base),
63 LL_ADC_PATH_INTERNAL_VREFINT | path);
64
65 #ifdef LL_ADC_DELAY_VREFINT_STAB_US
66 k_usleep(LL_ADC_DELAY_VREFINT_STAB_US);
67 #endif
68
69 rc = adc_read(data->adc, sp);
70 if (rc == 0) {
71 data->raw = data->sample_buffer;
72 }
73
74 path = LL_ADC_GetCommonPathInternalCh(__LL_ADC_COMMON_INSTANCE(data->adc_base));
75 LL_ADC_SetCommonPathInternalCh(__LL_ADC_COMMON_INSTANCE(data->adc_base),
76 path &= ~LL_ADC_PATH_INTERNAL_VREFINT);
77
78
79 unlock:
80 pm_device_runtime_put(data->adc);
81 k_mutex_unlock(&data->mutex);
82
83 return rc;
84 }
85
stm32_vref_channel_get(const struct device * dev,enum sensor_channel chan,struct sensor_value * val)86 static int stm32_vref_channel_get(const struct device *dev, enum sensor_channel chan,
87 struct sensor_value *val)
88 {
89 struct stm32_vref_data *data = dev->data;
90 const struct stm32_vref_config *cfg = dev->config;
91 int32_t vref;
92
93 if (chan != SENSOR_CHAN_VOLTAGE) {
94 return -ENOTSUP;
95 }
96
97 if (data->raw == 0) {
98 LOG_ERR("Raw ADC value is zero");
99 return -ENODATA;
100 }
101
102 /*
103 * STM32H5X: accesses to flash RO region must be done with caching disabled.
104 */
105 #if defined(CONFIG_SOC_SERIES_STM32H5X)
106 LL_ICACHE_Disable();
107 #endif /* CONFIG_SOC_SERIES_STM32H5X */
108
109 /* Calculate VREF+ using VREFINT bandgap voltage and calibration data */
110 vref = (cfg->cal_mv * ((*cfg->cal_addr) >> cfg->cal_shift)) / data->raw;
111
112 #if defined(CONFIG_SOC_SERIES_STM32H5X)
113 LL_ICACHE_Enable();
114 #endif /* CONFIG_SOC_SERIES_STM32H5X */
115
116 return sensor_value_from_milli(val, vref);
117 }
118
119 static DEVICE_API(sensor, stm32_vref_driver_api) = {
120 .sample_fetch = stm32_vref_sample_fetch,
121 .channel_get = stm32_vref_channel_get,
122 };
123
stm32_vref_init(const struct device * dev)124 static int stm32_vref_init(const struct device *dev)
125 {
126 struct stm32_vref_data *data = dev->data;
127 struct adc_sequence *asp = &data->adc_seq;
128
129 k_mutex_init(&data->mutex);
130
131 if (!device_is_ready(data->adc)) {
132 LOG_ERR("Device %s is not ready", data->adc->name);
133 return -ENODEV;
134 }
135
136 *asp = (struct adc_sequence){
137 .channels = BIT(data->adc_cfg.channel_id),
138 .buffer = &data->sample_buffer,
139 .buffer_size = sizeof(data->sample_buffer),
140 .resolution = MEAS_RES,
141 };
142
143 return 0;
144 }
145
146 /**
147 * Verify that the ADC instance which this driver uses to measure internal
148 * voltage reference is enabled. On STM32 MCUs with more than one ADC, it is
149 * possible to compile this driver even if the ADC used for measurement is
150 * disabled. In such cases, fail build with an explicit error message.
151 */
152 #if !DT_NODE_HAS_STATUS_OKAY(DT_INST_IO_CHANNELS_CTLR(0))
153
154 /* Use BUILD_ASSERT to get preprocessing on the message */
155 BUILD_ASSERT(0, "ADC '" DT_NODE_FULL_NAME(DT_INST_IO_CHANNELS_CTLR(0)) "' needed by "
156 "Vref sensor '" DT_NODE_FULL_NAME(DT_DRV_INST(0)) "' is not enabled");
157
158 /* To reduce noise in the compiler error log, do not attempt
159 * to instantiate device if the sensor's ADC is not enabled.
160 */
161 #else
162
163 static struct stm32_vref_data stm32_vref_dev_data = {
164 .adc = DEVICE_DT_GET(DT_INST_IO_CHANNELS_CTLR(0)),
165 .adc_base = (ADC_TypeDef *)DT_REG_ADDR(DT_INST_IO_CHANNELS_CTLR(0)),
166 .adc_cfg = {.gain = ADC_GAIN_1,
167 .reference = ADC_REF_INTERNAL,
168 .acquisition_time = ADC_ACQ_TIME_MAX,
169 .channel_id = DT_INST_IO_CHANNELS_INPUT(0),
170 .differential = 0},
171 };
172
173 static const struct stm32_vref_config stm32_vref_dev_config = {
174 .cal_addr = (uint16_t *)DT_INST_PROP(0, vrefint_cal_addr),
175 .cal_mv = DT_INST_PROP(0, vrefint_cal_mv),
176 .cal_shift = (DT_INST_PROP(0, vrefint_cal_resolution) - MEAS_RES),
177 };
178
179 /* Make sure no series with unsupported configuration can be added silently */
180 BUILD_ASSERT(DT_INST_PROP(0, vrefint_cal_resolution) >= MEAS_RES,
181 "VREFINT calibration resolution is too low");
182
183 SENSOR_DEVICE_DT_INST_DEFINE(0, stm32_vref_init, NULL, &stm32_vref_dev_data, &stm32_vref_dev_config,
184 POST_KERNEL, CONFIG_SENSOR_INIT_PRIORITY, &stm32_vref_driver_api);
185
186 #endif /* !DT_NODE_HAS_STATUS_OKAY(DT_INST_IO_CHANNELS_CTLR(0)) */
187