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