From 505892f1bb72350b4893e38b4c27633911b85ff8 Mon Sep 17 00:00:00 2001 From: Sam Hurst Date: Thu, 23 Jun 2022 10:15:00 -0700 Subject: [PATCH] usb-c: Add USB-C VBUS ADC driver This commit adds a VBUS driver the uses an ADC connected to a voltage divider to measure VBUS. Signed-off-by: Sam Hurst --- drivers/usb_c/vbus/CMakeLists.txt | 5 + drivers/usb_c/vbus/Kconfig | 19 ++ drivers/usb_c/vbus/Kconfig.usbc_vbus_adc | 11 + drivers/usb_c/vbus/usbc_vbus_adc.c | 219 ++++++++++++++++++ drivers/usb_c/vbus/usbc_vbus_adc_priv.h | 32 +++ dts/bindings/usb-c/zephyr,usb-c-vbus-adc.yaml | 15 ++ 6 files changed, 301 insertions(+) create mode 100644 drivers/usb_c/vbus/CMakeLists.txt create mode 100644 drivers/usb_c/vbus/Kconfig create mode 100644 drivers/usb_c/vbus/Kconfig.usbc_vbus_adc create mode 100644 drivers/usb_c/vbus/usbc_vbus_adc.c create mode 100644 drivers/usb_c/vbus/usbc_vbus_adc_priv.h create mode 100644 dts/bindings/usb-c/zephyr,usb-c-vbus-adc.yaml diff --git a/drivers/usb_c/vbus/CMakeLists.txt b/drivers/usb_c/vbus/CMakeLists.txt new file mode 100644 index 00000000000..24c9151a41e --- /dev/null +++ b/drivers/usb_c/vbus/CMakeLists.txt @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: Apache-2.0 + +zephyr_library() + +zephyr_library_sources_ifdef(CONFIG_USBC_VBUS_ADC usbc_vbus_adc.c) diff --git a/drivers/usb_c/vbus/Kconfig b/drivers/usb_c/vbus/Kconfig new file mode 100644 index 00000000000..36e20e3c382 --- /dev/null +++ b/drivers/usb_c/vbus/Kconfig @@ -0,0 +1,19 @@ +# USB-C VBUS Measurement configuration options + +# Copyright 2022 The Chromium OS Authors +# SPDX-License-Identifier: Apache-2.0 + +menuconfig USBC_VBUS_DRIVER + bool "USB-C VBUS drivers" + help + Enable USB-C drivers + +if USBC_VBUS_DRIVER + +source "drivers/usb_c/vbus/Kconfig.usbc_vbus_adc" + +endif # USBC_VBUS_DRIVER + +module = USBC +module-str = usbc +source "subsys/logging/Kconfig.template.log_config" diff --git a/drivers/usb_c/vbus/Kconfig.usbc_vbus_adc b/drivers/usb_c/vbus/Kconfig.usbc_vbus_adc new file mode 100644 index 00000000000..abc1cd15088 --- /dev/null +++ b/drivers/usb_c/vbus/Kconfig.usbc_vbus_adc @@ -0,0 +1,11 @@ +# USB-C VBUS device configuration options + +# Copyright 2022 The Chromium OS Authors +# SPDX-License-Identifier: Apache-2.0 + +config USBC_VBUS_ADC + bool "USB-C VBUS ADC" + default y + depends on DT_HAS_ZEPHYR_USB_C_VBUS_ADC_ENABLED + help + Measure VBUS with an ADC through a voltage divider diff --git a/drivers/usb_c/vbus/usbc_vbus_adc.c b/drivers/usb_c/vbus/usbc_vbus_adc.c new file mode 100644 index 00000000000..557dafdff62 --- /dev/null +++ b/drivers/usb_c/vbus/usbc_vbus_adc.c @@ -0,0 +1,219 @@ +/* + * Copyright 2022 The Chromium OS Authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT zephyr_usb_c_vbus_adc + +#include +LOG_MODULE_REGISTER(usbc_vbus_adc, CONFIG_USBC_LOG_LEVEL); + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "usbc_vbus_adc_priv.h" + +/** + * @brief Reads and returns VBUS measured in mV + * + * @retval 0 on success + * @retval -EIO on failure + */ +static int adc_vbus_measure(const struct device *dev, int *meas) +{ + const struct usbc_vbus_config *const config = dev->config; + struct usbc_vbus_data *data = dev->data; + int value; + int ret; + + __ASSERT(meas != NULL, "ADC VBUS meas must not be NULL"); + + ret = adc_read(config->adc_channel.dev, &data->sequence); + if (ret != 0) { + LOG_INF("ADC reading failed with error %d.", ret); + return ret; + } + + value = data->sample; + ret = adc_raw_to_millivolts_dt(&config->adc_channel, &value); + if (ret != 0) { + LOG_INF("Scaling ADC failed with error %d.", ret); + return ret; + } + + if (config->full_ohm > 0) { + /* VBUS is scaled down though a voltage divider */ + value = (value * 1000) / ((config->output_ohm * 1000) / config->full_ohm); + } + *meas = value; + + return 0; +} + +/** + * @brief Checks if VBUS is at a particular level + * + * @retval true if VBUS is at the level voltage, else false + */ +static bool adc_vbus_check_level(const struct device *dev, + enum tc_vbus_level level) +{ + int meas; + int ret; + + ret = adc_vbus_measure(dev, &meas); + if (ret) { + return false; + } + + switch (level) { + case TC_VBUS_SAFE0V: + return (meas < PD_V_SAFE_0V_MAX_MV); + case TC_VBUS_PRESENT: + return (meas >= PD_V_SAFE_5V_MIN_MV); + case TC_VBUS_REMOVED: + return (meas < TC_V_SINK_DISCONNECT_MAX_MV); + } + + return false; +} + +/** + * @brief Sets pin to discharge VBUS + * + * @retval 0 on success + * @retval -EIO on failure + * @retval -ENOENT if enable pin isn't defined + */ +static int adc_vbus_discharge(const struct device *dev, + bool enable) +{ + const struct usbc_vbus_config *const config = dev->config; + const struct gpio_dt_spec *gcd = &config->discharge_gpios; + int ret = -ENOENT; + + if (gcd->port) { + ret = gpio_pin_set_dt(gcd, enable); + } + return ret; +} + +/** + * @brief Sets pin to enable VBUS measurments + * + * @retval 0 on success + * @retval -EIO on failure + * @retval -ENOENT if enable pin isn't defined + */ +static int adc_vbus_enable(const struct device *dev, + bool enable) +{ + const struct usbc_vbus_config *const config = dev->config; + const struct gpio_dt_spec *gcp = &config->power_gpios; + int ret = -ENOENT; + + if (gcp->port) { + ret = gpio_pin_set_dt(gcp, enable); + } + return ret; +} + +/** + * @brief Initializes the ADC VBUS Driver + * + * @retval 0 on success + * @retval -EIO on failure + */ +static int adc_vbus_init(const struct device *dev) +{ + const struct usbc_vbus_config *const config = dev->config; + struct usbc_vbus_data *data = dev->data; + const struct gpio_dt_spec *gcp = &config->power_gpios; + const struct gpio_dt_spec *gcd = &config->discharge_gpios; + int ret; + + /* Configure VBUS Measurement enable pin if defined */ + if (gcp->port) { + ret = device_is_ready(gcp->port); + if (ret < 0) { + LOG_ERR("%s: device not ready", gcp->port->name); + return ret; + } + ret = gpio_pin_configure_dt(gcp, GPIO_OUTPUT_INACTIVE); + if (ret < 0) { + LOG_ERR("Failed to control feed %s.%u: %d", + gcp->port->name, gcp->pin, ret); + return ret; + } + } + + /* Configure VBUS Discharge pin if defined */ + if (gcd->port) { + ret = device_is_ready(gcd->port); + if (ret == false) { + LOG_ERR("%s: device not ready", gcd->port->name); + return ret; + } + ret = gpio_pin_configure_dt(gcd, GPIO_OUTPUT_INACTIVE); + if (ret < 0) { + LOG_ERR("Failed to control feed %s.%u: %d", + gcd->port->name, gcd->pin, ret); + return ret; + } + + } + + data->sequence.buffer = &data->sample; + data->sequence.buffer_size = sizeof(data->sample); + + ret = adc_channel_setup_dt(&config->adc_channel); + if (ret < 0) { + LOG_INF("Could not setup channel (%d)\n", ret); + return ret; + } + + ret = adc_sequence_init_dt(&config->adc_channel, &data->sequence); + if (ret < 0) { + LOG_INF("Could not init sequence (%d)\n", ret); + return ret; + } + + return 0; +} + +static const struct usbc_vbus_driver_api driver_api = { + .measure = adc_vbus_measure, + .check_level = adc_vbus_check_level, + .discharge = adc_vbus_discharge, + .enable = adc_vbus_enable +}; + +BUILD_ASSERT(DT_NUM_INST_STATUS_OKAY(DT_DRV_COMPAT) > 0, + "No compatible USB-C VBUS Measurement instance found"); + +#define DRIVER_INIT(inst) \ + static struct usbc_vbus_data drv_data_##inst; \ + static const struct usbc_vbus_config drv_config_##inst = { \ + .output_ohm = DT_INST_PROP(inst, output_ohms), \ + .full_ohm = DT_INST_PROP_OR(inst, full_ohms, 0), \ + .adc_channel = ADC_DT_SPEC_INST_GET(inst), \ + .discharge_gpios = GPIO_DT_SPEC_INST_GET_OR(inst, discharge_gpios, {}), \ + .power_gpios = GPIO_DT_SPEC_INST_GET_OR(inst, power_gpios, {}), \ + }; \ + DEVICE_DT_INST_DEFINE(inst, \ + &adc_vbus_init, \ + NULL, \ + &drv_data_##inst, \ + &drv_config_##inst, \ + POST_KERNEL, \ + CONFIG_USBC_INIT_PRIORITY, \ + &driver_api); + +DT_INST_FOREACH_STATUS_OKAY(DRIVER_INIT) diff --git a/drivers/usb_c/vbus/usbc_vbus_adc_priv.h b/drivers/usb_c/vbus/usbc_vbus_adc_priv.h new file mode 100644 index 00000000000..cc8a51bb109 --- /dev/null +++ b/drivers/usb_c/vbus/usbc_vbus_adc_priv.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2022 The Chromium OS Authors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_DRIVERS_USBC_VBUS_ADC_PRIV_H_ +#define ZEPHYR_DRIVERS_USBC_VBUS_ADC_PRIV_H_ + +#include +#include + +/** + * @brief Driver config + */ +struct usbc_vbus_config { + uint32_t output_ohm; + uint32_t full_ohm; + struct adc_dt_spec adc_channel; + const struct gpio_dt_spec power_gpios; + const struct gpio_dt_spec discharge_gpios; +}; + +/** + * @brief Driver data + */ +struct usbc_vbus_data { + int sample; + struct adc_sequence sequence; +}; + +#endif /* ZEPHYR_DRIVERS_USBC_VBUS_ADC_PRIV_H_ */ diff --git a/dts/bindings/usb-c/zephyr,usb-c-vbus-adc.yaml b/dts/bindings/usb-c/zephyr,usb-c-vbus-adc.yaml new file mode 100644 index 00000000000..4ed4f8ebd7d --- /dev/null +++ b/dts/bindings/usb-c/zephyr,usb-c-vbus-adc.yaml @@ -0,0 +1,15 @@ +# Copyright 2022 The Chromium OS Authors +# SPDX-License-Identifier: Apache-2.0 + +description: | + This device is used to measure VBUS on a Type-C Port and to + discharge VBUS when needed. + +compatible: "zephyr,usb-c-vbus-adc" + +include: [base.yaml, voltage-divider.yaml] + +properties: + discharge-gpios: + type: phandle-array + required: false