1 /*
2  * Copyright 2022 The Chromium OS Authors
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #define DT_DRV_COMPAT zephyr_usb_c_vbus_adc
8 
9 #include <zephyr/logging/log.h>
10 LOG_MODULE_REGISTER(usbc_vbus_adc, CONFIG_USBC_LOG_LEVEL);
11 
12 #include <zephyr/device.h>
13 #include <zephyr/sys/util.h>
14 #include <zephyr/kernel.h>
15 #include <zephyr/drivers/adc.h>
16 #include <zephyr/drivers/usb_c/usbc_pd.h>
17 #include <zephyr/drivers/usb_c/usbc_vbus.h>
18 #include <soc.h>
19 #include <stddef.h>
20 
21 #include "usbc_vbus_adc_priv.h"
22 
23 /**
24  * @brief Reads and returns VBUS measured in mV
25  *
26  * @retval 0 on success
27  * @retval -EIO on failure
28  */
adc_vbus_measure(const struct device * dev,int * meas)29 static int adc_vbus_measure(const struct device *dev, int *meas)
30 {
31 	const struct usbc_vbus_config *const config = dev->config;
32 	struct usbc_vbus_data *data = dev->data;
33 	int value;
34 	int ret;
35 
36 	__ASSERT(meas != NULL, "ADC VBUS meas must not be NULL");
37 
38 	ret = adc_read(config->adc_channel.dev, &data->sequence);
39 	if (ret != 0) {
40 		LOG_INF("ADC reading failed with error %d.", ret);
41 		return ret;
42 	}
43 
44 	value = data->sample;
45 	ret = adc_raw_to_millivolts_dt(&config->adc_channel, &value);
46 	if (ret != 0) {
47 		LOG_INF("Scaling ADC failed with error %d.", ret);
48 		return ret;
49 	}
50 
51 	if (config->full_ohm > 0) {
52 		/* VBUS is scaled down though a voltage divider */
53 		value = (value * 1000) / ((config->output_ohm * 1000) / config->full_ohm);
54 	}
55 	*meas = value;
56 
57 	return 0;
58 }
59 
60 /**
61  * @brief Checks if VBUS is at a particular level
62  *
63  * @retval true if VBUS is at the level voltage, else false
64  */
adc_vbus_check_level(const struct device * dev,enum tc_vbus_level level)65 static bool adc_vbus_check_level(const struct device *dev,
66 				 enum tc_vbus_level level)
67 {
68 	int meas;
69 	int ret;
70 
71 	ret = adc_vbus_measure(dev, &meas);
72 	if (ret) {
73 		return false;
74 	}
75 
76 	switch (level) {
77 	case TC_VBUS_SAFE0V:
78 		return (meas < PD_V_SAFE_0V_MAX_MV);
79 	case TC_VBUS_PRESENT:
80 		return (meas >= PD_V_SAFE_5V_MIN_MV);
81 	case TC_VBUS_REMOVED:
82 		return (meas < TC_V_SINK_DISCONNECT_MAX_MV);
83 	}
84 
85 	return false;
86 }
87 
88 /**
89  * @brief Sets pin to discharge VBUS
90  *
91  * @retval 0 on success
92  * @retval -EIO on failure
93  * @retval -ENOENT if enable pin isn't defined
94  */
adc_vbus_discharge(const struct device * dev,bool enable)95 static int adc_vbus_discharge(const struct device *dev,
96 			      bool enable)
97 {
98 	const struct usbc_vbus_config *const config = dev->config;
99 	const struct gpio_dt_spec *gcd = &config->discharge_gpios;
100 	int ret = -ENOENT;
101 
102 	if (gcd->port) {
103 		ret = gpio_pin_set_dt(gcd, enable);
104 	}
105 	return ret;
106 }
107 
108 /**
109  * @brief Sets pin to enable VBUS measurments
110  *
111  * @retval 0 on success
112  * @retval -EIO on failure
113  * @retval -ENOENT if enable pin isn't defined
114  */
adc_vbus_enable(const struct device * dev,bool enable)115 static int adc_vbus_enable(const struct device *dev,
116 			   bool enable)
117 {
118 	const struct usbc_vbus_config *const config = dev->config;
119 	const struct gpio_dt_spec *gcp = &config->power_gpios;
120 	int ret = -ENOENT;
121 
122 	if (gcp->port) {
123 		ret = gpio_pin_set_dt(gcp, enable);
124 	}
125 	return ret;
126 }
127 
128 /**
129  * @brief Initializes the ADC VBUS Driver
130  *
131  * @retval 0 on success
132  * @retval -EIO on failure
133  */
adc_vbus_init(const struct device * dev)134 static int adc_vbus_init(const struct device *dev)
135 {
136 	const struct usbc_vbus_config *const config = dev->config;
137 	struct usbc_vbus_data *data = dev->data;
138 	const struct gpio_dt_spec *gcp = &config->power_gpios;
139 	const struct gpio_dt_spec *gcd = &config->discharge_gpios;
140 	int ret;
141 
142 	if (!adc_is_ready_dt(&config->adc_channel)) {
143 		LOG_ERR("ADC controller device is not ready");
144 		return -ENODEV;
145 	}
146 
147 	/* Configure VBUS Measurement enable pin if defined */
148 	if (gcp->port) {
149 		ret = device_is_ready(gcp->port);
150 		if (ret < 0) {
151 			LOG_ERR("%s: device not ready", gcp->port->name);
152 			return ret;
153 		}
154 		ret = gpio_pin_configure_dt(gcp, GPIO_OUTPUT_INACTIVE);
155 		if (ret < 0) {
156 			LOG_ERR("Failed to control feed %s.%u: %d",
157 				gcp->port->name, gcp->pin, ret);
158 			return ret;
159 		}
160 	}
161 
162 	/* Configure VBUS Discharge pin if defined */
163 	if (gcd->port) {
164 		ret = device_is_ready(gcd->port);
165 		if (ret == false) {
166 			LOG_ERR("%s: device not ready", gcd->port->name);
167 			return ret;
168 		}
169 		ret = gpio_pin_configure_dt(gcd, GPIO_OUTPUT_INACTIVE);
170 		if (ret < 0) {
171 			LOG_ERR("Failed to control feed %s.%u: %d",
172 				gcd->port->name, gcd->pin, ret);
173 			return ret;
174 		}
175 
176 	}
177 
178 	data->sequence.buffer = &data->sample;
179 	data->sequence.buffer_size = sizeof(data->sample);
180 
181 	ret = adc_channel_setup_dt(&config->adc_channel);
182 	if (ret < 0) {
183 		LOG_INF("Could not setup channel (%d)\n", ret);
184 		return ret;
185 	}
186 
187 	ret = adc_sequence_init_dt(&config->adc_channel, &data->sequence);
188 	if (ret < 0) {
189 		LOG_INF("Could not init sequence (%d)\n", ret);
190 		return ret;
191 	}
192 
193 	return 0;
194 }
195 
196 static const struct usbc_vbus_driver_api driver_api = {
197 	.measure = adc_vbus_measure,
198 	.check_level = adc_vbus_check_level,
199 	.discharge = adc_vbus_discharge,
200 	.enable = adc_vbus_enable
201 };
202 
203 BUILD_ASSERT(DT_NUM_INST_STATUS_OKAY(DT_DRV_COMPAT) > 0,
204 	     "No compatible USB-C VBUS Measurement instance found");
205 
206 #define DRIVER_INIT(inst)								\
207 	static struct usbc_vbus_data drv_data_##inst;					\
208 	static const struct usbc_vbus_config drv_config_##inst = {			\
209 		.output_ohm = DT_INST_PROP(inst, output_ohms),				\
210 		.full_ohm = DT_INST_PROP_OR(inst, full_ohms, 0),			\
211 		.adc_channel = ADC_DT_SPEC_INST_GET(inst),				\
212 		.discharge_gpios = GPIO_DT_SPEC_INST_GET_OR(inst, discharge_gpios, {}), \
213 		.power_gpios = GPIO_DT_SPEC_INST_GET_OR(inst, power_gpios, {}),		\
214 	};										\
215 	DEVICE_DT_INST_DEFINE(inst,							\
216 			      &adc_vbus_init,						\
217 			      NULL,							\
218 			      &drv_data_##inst,						\
219 			      &drv_config_##inst,					\
220 			      POST_KERNEL,						\
221 			      CONFIG_USBC_VBUS_INIT_PRIORITY,				\
222 			      &driver_api);
223 
224 DT_INST_FOREACH_STATUS_OKAY(DRIVER_INIT)
225