1 /*
2  * Copyright (c) 2018-2019 Peter Bigot Consulting, LLC
3  * Copyright (c) 2019-2020 Nordic Semiconductor ASA
4  *
5  * SPDX-License-Identifier: Apache-2.0
6  */
7 
8 #include <math.h>
9 #include <stdio.h>
10 #include <stdlib.h>
11 
12 #include <zephyr/kernel.h>
13 #include <zephyr/init.h>
14 #include <zephyr/drivers/gpio.h>
15 #include <zephyr/drivers/adc.h>
16 #include <zephyr/drivers/sensor.h>
17 #include <zephyr/logging/log.h>
18 
19 #include "battery.h"
20 
21 LOG_MODULE_REGISTER(BATTERY, CONFIG_ADC_LOG_LEVEL);
22 
23 #define VBATT DT_PATH(vbatt)
24 #define ZEPHYR_USER DT_PATH(zephyr_user)
25 
26 #ifdef CONFIG_BOARD_THINGY52_NRF52832
27 /* This board uses a divider that reduces max voltage to
28  * reference voltage (600 mV).
29  */
30 #define BATTERY_ADC_GAIN ADC_GAIN_1
31 #else
32 /* Other boards may use dividers that only reduce battery voltage to
33  * the maximum supported by the hardware (3.6 V)
34  */
35 #define BATTERY_ADC_GAIN ADC_GAIN_1_6
36 #endif
37 
38 struct io_channel_config {
39 	uint8_t channel;
40 };
41 
42 struct divider_config {
43 	struct io_channel_config io_channel;
44 	struct gpio_dt_spec power_gpios;
45 	/* output_ohm is used as a flag value: if it is nonzero then
46 	 * the battery is measured through a voltage divider;
47 	 * otherwise it is assumed to be directly connected to Vdd.
48 	 */
49 	uint32_t output_ohm;
50 	uint32_t full_ohm;
51 };
52 
53 static const struct divider_config divider_config = {
54 #if DT_NODE_HAS_STATUS_OKAY(VBATT)
55 	.io_channel = {
56 		DT_IO_CHANNELS_INPUT(VBATT),
57 	},
58 	.power_gpios = GPIO_DT_SPEC_GET_OR(VBATT, power_gpios, {}),
59 	.output_ohm = DT_PROP(VBATT, output_ohms),
60 	.full_ohm = DT_PROP(VBATT, full_ohms),
61 #else /* /vbatt exists */
62 	.io_channel = {
63 		DT_IO_CHANNELS_INPUT(ZEPHYR_USER),
64 	},
65 #endif /* /vbatt exists */
66 };
67 
68 struct divider_data {
69 	const struct device *adc;
70 	struct adc_channel_cfg adc_cfg;
71 	struct adc_sequence adc_seq;
72 	int16_t raw;
73 };
74 static struct divider_data divider_data = {
75 #if DT_NODE_HAS_STATUS_OKAY(VBATT)
76 	.adc = DEVICE_DT_GET(DT_IO_CHANNELS_CTLR(VBATT)),
77 #else
78 	.adc = DEVICE_DT_GET(DT_IO_CHANNELS_CTLR(ZEPHYR_USER)),
79 #endif
80 };
81 
divider_setup(void)82 static int divider_setup(void)
83 {
84 	const struct divider_config *cfg = &divider_config;
85 	const struct io_channel_config *iocp = &cfg->io_channel;
86 	const struct gpio_dt_spec *gcp = &cfg->power_gpios;
87 	struct divider_data *ddp = &divider_data;
88 	struct adc_sequence *asp = &ddp->adc_seq;
89 	struct adc_channel_cfg *accp = &ddp->adc_cfg;
90 	int rc;
91 
92 	if (!device_is_ready(ddp->adc)) {
93 		LOG_ERR("ADC device is not ready %s", ddp->adc->name);
94 		return -ENOENT;
95 	}
96 
97 	if (gcp->port) {
98 		if (!device_is_ready(gcp->port)) {
99 			LOG_ERR("%s: device not ready", gcp->port->name);
100 			return -ENOENT;
101 		}
102 		rc = gpio_pin_configure_dt(gcp, GPIO_OUTPUT_INACTIVE);
103 		if (rc != 0) {
104 			LOG_ERR("Failed to control feed %s.%u: %d",
105 				gcp->port->name, gcp->pin, rc);
106 			return rc;
107 		}
108 	}
109 
110 	*asp = (struct adc_sequence){
111 		.channels = BIT(0),
112 		.buffer = &ddp->raw,
113 		.buffer_size = sizeof(ddp->raw),
114 		.oversampling = 4,
115 		.calibrate = true,
116 	};
117 
118 #ifdef CONFIG_ADC_NRFX_SAADC
119 	*accp = (struct adc_channel_cfg){
120 		.gain = BATTERY_ADC_GAIN,
121 		.reference = ADC_REF_INTERNAL,
122 		.acquisition_time = ADC_ACQ_TIME(ADC_ACQ_TIME_MICROSECONDS, 40),
123 	};
124 
125 	if (cfg->output_ohm != 0) {
126 		accp->input_positive = SAADC_CH_PSELP_PSELP_AnalogInput0
127 			+ iocp->channel;
128 	} else {
129 		accp->input_positive = SAADC_CH_PSELP_PSELP_VDD;
130 	}
131 
132 	asp->resolution = 14;
133 #else /* CONFIG_ADC_var */
134 #error Unsupported ADC
135 #endif /* CONFIG_ADC_var */
136 
137 	rc = adc_channel_setup(ddp->adc, accp);
138 	LOG_INF("Setup AIN%u got %d", iocp->channel, rc);
139 
140 	return rc;
141 }
142 
143 static bool battery_ok;
144 
battery_setup(void)145 static int battery_setup(void)
146 {
147 	int rc = divider_setup();
148 
149 	battery_ok = (rc == 0);
150 	LOG_INF("Battery setup: %d %d", rc, battery_ok);
151 	return rc;
152 }
153 
154 SYS_INIT(battery_setup, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY);
155 
battery_measure_enable(bool enable)156 int battery_measure_enable(bool enable)
157 {
158 	int rc = -ENOENT;
159 
160 	if (battery_ok) {
161 		const struct gpio_dt_spec *gcp = &divider_config.power_gpios;
162 
163 		rc = 0;
164 		if (gcp->port) {
165 			rc = gpio_pin_set_dt(gcp, enable);
166 		}
167 	}
168 	return rc;
169 }
170 
battery_sample(void)171 int battery_sample(void)
172 {
173 	int rc = -ENOENT;
174 
175 	if (battery_ok) {
176 		struct divider_data *ddp = &divider_data;
177 		const struct divider_config *dcp = &divider_config;
178 		struct adc_sequence *sp = &ddp->adc_seq;
179 
180 		rc = adc_read(ddp->adc, sp);
181 		sp->calibrate = false;
182 		if (rc == 0) {
183 			int32_t val = ddp->raw;
184 
185 			adc_raw_to_millivolts(adc_ref_internal(ddp->adc),
186 					      ddp->adc_cfg.gain,
187 					      sp->resolution,
188 					      &val);
189 
190 			if (dcp->output_ohm != 0) {
191 				rc = val * (uint64_t)dcp->full_ohm
192 					/ dcp->output_ohm;
193 				LOG_INF("raw %u ~ %u mV => %d mV\n",
194 					ddp->raw, val, rc);
195 			} else {
196 				rc = val;
197 				LOG_INF("raw %u ~ %u mV\n", ddp->raw, val);
198 			}
199 		}
200 	}
201 
202 	return rc;
203 }
204 
battery_level_pptt(unsigned int batt_mV,const struct battery_level_point * curve)205 unsigned int battery_level_pptt(unsigned int batt_mV,
206 				const struct battery_level_point *curve)
207 {
208 	const struct battery_level_point *pb = curve;
209 
210 	if (batt_mV >= pb->lvl_mV) {
211 		/* Measured voltage above highest point, cap at maximum. */
212 		return pb->lvl_pptt;
213 	}
214 	/* Go down to the last point at or below the measured voltage. */
215 	while ((pb->lvl_pptt > 0)
216 	       && (batt_mV < pb->lvl_mV)) {
217 		++pb;
218 	}
219 	if (batt_mV < pb->lvl_mV) {
220 		/* Below lowest point, cap at minimum */
221 		return pb->lvl_pptt;
222 	}
223 
224 	/* Linear interpolation between below and above points. */
225 	const struct battery_level_point *pa = pb - 1;
226 
227 	return pb->lvl_pptt
228 	       + ((pa->lvl_pptt - pb->lvl_pptt)
229 		  * (batt_mV - pb->lvl_mV)
230 		  / (pa->lvl_mV - pb->lvl_mV));
231 }
232