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