From e0dd45ba317237a90873b5b51aeb10d1c4d65073 Mon Sep 17 00:00:00 2001 From: Keith Short Date: Tue, 6 Dec 2022 10:53:16 -0700 Subject: [PATCH] bc12: API and 1st driver implementation. Add portable-device mode to the Diodes PI3USB9201 USB charging detector. Signed-off-by: Keith Short --- drivers/usb/CMakeLists.txt | 1 + drivers/usb/Kconfig | 1 + drivers/usb/bc12/CMakeLists.txt | 5 + drivers/usb/bc12/Kconfig | 17 + drivers/usb/bc12/Kconfig.pi3usb9201 | 14 + drivers/usb/bc12/bc12_handlers.c | 24 ++ drivers/usb/bc12/bc12_pi3usb9201.c | 432 ++++++++++++++++++++++++ drivers/usb/bc12/bc12_pi3usb9201.h | 45 +++ dts/bindings/usb/diodes,pi3usb9201.yaml | 15 + dts/bindings/usb/usb-bc12.yaml | 6 + include/zephyr/drivers/usb/usb_bc12.h | 173 ++++++++++ 11 files changed, 733 insertions(+) create mode 100644 drivers/usb/bc12/CMakeLists.txt create mode 100644 drivers/usb/bc12/Kconfig create mode 100644 drivers/usb/bc12/Kconfig.pi3usb9201 create mode 100644 drivers/usb/bc12/bc12_handlers.c create mode 100644 drivers/usb/bc12/bc12_pi3usb9201.c create mode 100644 drivers/usb/bc12/bc12_pi3usb9201.h create mode 100644 dts/bindings/usb/diodes,pi3usb9201.yaml create mode 100644 dts/bindings/usb/usb-bc12.yaml create mode 100644 include/zephyr/drivers/usb/usb_bc12.h diff --git a/drivers/usb/CMakeLists.txt b/drivers/usb/CMakeLists.txt index 1d78712b93..7a0570f146 100644 --- a/drivers/usb/CMakeLists.txt +++ b/drivers/usb/CMakeLists.txt @@ -3,4 +3,5 @@ add_subdirectory_ifdef(CONFIG_UDC_DRIVER udc) add_subdirectory_ifdef(CONFIG_UHC_DRIVER uhc) add_subdirectory_ifdef(CONFIG_UVB uvb) +add_subdirectory_ifdef(CONFIG_USB_BC12 bc12) add_subdirectory_ifdef(CONFIG_USB_DEVICE_DRIVER device) diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig index 423c797ef6..369b40f6fa 100644 --- a/drivers/usb/Kconfig +++ b/drivers/usb/Kconfig @@ -3,6 +3,7 @@ # Copyright (c) 2016 Wind River Systems, Inc. # SPDX-License-Identifier: Apache-2.0 +source "drivers/usb/bc12/Kconfig" source "drivers/usb/udc/Kconfig" source "drivers/usb/uhc/Kconfig" source "drivers/usb/uvb/Kconfig" diff --git a/drivers/usb/bc12/CMakeLists.txt b/drivers/usb/bc12/CMakeLists.txt new file mode 100644 index 0000000000..ade1d10802 --- /dev/null +++ b/drivers/usb/bc12/CMakeLists.txt @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: Apache-2.0 + +zephyr_library() + +zephyr_library_sources_ifdef(CONFIG_USB_BC12_PI3USB9201 bc12_pi3usb9201.c) diff --git a/drivers/usb/bc12/Kconfig b/drivers/usb/bc12/Kconfig new file mode 100644 index 0000000000..65f75966ac --- /dev/null +++ b/drivers/usb/bc12/Kconfig @@ -0,0 +1,17 @@ +# Copyright (c) 2022 Google LLC +# SPDX-License-Identifier: Apache-2.0 + +menuconfig USB_BC12 + bool "USB BC1.2 Drivers" + help + Enable USB BC1.2 (battery charging detection) drivers. + +if USB_BC12 + +module = USB_BC12 +module-str = usb_bc12 +source "subsys/logging/Kconfig.template.log_config" + +source "drivers/usb/bc12/Kconfig.pi3usb9201" + +endif # USB_BC12 diff --git a/drivers/usb/bc12/Kconfig.pi3usb9201 b/drivers/usb/bc12/Kconfig.pi3usb9201 new file mode 100644 index 0000000000..fae4f89428 --- /dev/null +++ b/drivers/usb/bc12/Kconfig.pi3usb9201 @@ -0,0 +1,14 @@ +# Copyright (c) 2022 Google LLC +# SPDX-License-Identifier: Apache-2.0 + +config USB_BC12_PI3USB9201 + bool "Diodes PI3USB9201" + default y + depends on DT_HAS_DIODES_PI3USB9201_ENABLED + help + This is a Dual-Role USB Charging-Type Detector. It can operate in + host or client mode. It supports Battery Charging Specification, rev + 1.2 (BC1.2) with Standard/Charging/Dedicated downstream port + (SDP/CDP/DCP) advertisement when in host mode. In portable device or + client mode it starts BC1.2 detection to detect the attached host + type. It provides an I2C interface to report detection results. diff --git a/drivers/usb/bc12/bc12_handlers.c b/drivers/usb/bc12/bc12_handlers.c new file mode 100644 index 0000000000..bf7ff8fcf3 --- /dev/null +++ b/drivers/usb/bc12/bc12_handlers.c @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2023 Google LLC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +static inline int z_vrfy_bc12_set_role(const struct device *dev, enum bc12_role role) +{ + Z_OOPS(Z_SYSCALL_DRIVER_BC12(dev, set_role)); + + return z_impl_bc12_set_role(dev, role); +} + +static inline int z_vrfy_bc12_set_result_cb(const struct device *dev, bc12_callback_t cb, + void *user_data) +{ + Z_OOPS(Z_SYSCALL_DRIVER_BC12(dev, set_result_cb)); + Z_OOPS(Z_SYSCALL_VERIFY_MSG(cb == NULL, "callbacks may not be set from user mode")); + + return z_impl_bc12_set_result_cb(dev, cb, user_data); +} diff --git a/drivers/usb/bc12/bc12_pi3usb9201.c b/drivers/usb/bc12/bc12_pi3usb9201.c new file mode 100644 index 0000000000..7053433fa9 --- /dev/null +++ b/drivers/usb/bc12/bc12_pi3usb9201.c @@ -0,0 +1,432 @@ +/* + * Copyright (c) 2022 Google LLC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* PI3USB9201 USB BC 1.2 Charger Detector driver. */ + +#define DT_DRV_COMPAT diodes_pi3usb9201 + +#include +#include +#include +#include +#include +#include "bc12_pi3usb9201.h" + +LOG_MODULE_REGISTER(PI3USB9201, CONFIG_USB_BC12_LOG_LEVEL); + +/* Constant configuration data */ +struct pi3usb9201_config { + struct i2c_dt_spec i2c; + + struct gpio_dt_spec intb_gpio; + + enum bc12_type charging_mode; +}; + +/* Run-time configuration data */ +struct pi3usb9201_data { + const struct device *dev; + struct k_work work; + enum bc12_type partner_type; + struct gpio_callback gpio_cb; + + bc12_callback_t result_cb; + void *result_cb_data; +}; + +enum pi3usb9201_client_sts { + CHG_OTHER = 0, + CHG_2_4A = 1, + CHG_2_0A = 2, + CHG_1_0A = 3, + CHG_RESERVED = 4, + CHG_CDP = 5, + CHG_SDP = 6, + CHG_DCP = 7, +}; + +struct bc12_status { + enum bc12_type partner_type; + int current_limit; +}; + +/* + * The USB Type-C specification limits the maximum amount of current from BC 1.2 + * suppliers to 1.5A. Technically, proprietary methods are not allowed, but we + * will continue to allow those. + */ +static const struct bc12_status bc12_chg_limits[] = { + /* For unknown chargers return Isusp. */ + [CHG_OTHER] = {BC12_TYPE_PROPRIETARY, BC12_CURR_UA(BC12_CHARGER_MIN_CURR_UA)}, + [CHG_2_4A] = {BC12_TYPE_PROPRIETARY, BC12_CURR_UA(2400000)}, + [CHG_2_0A] = {BC12_TYPE_PROPRIETARY, BC12_CURR_UA(2000000)}, + [CHG_1_0A] = {BC12_TYPE_PROPRIETARY, BC12_CURR_UA(1000000)}, + [CHG_RESERVED] = {BC12_TYPE_NONE, 0}, + [CHG_CDP] = {BC12_TYPE_CDP, BC12_CURR_UA(1500000)}, + /* BC1.2 driver contract specifies to return Isusp for SDP ports. */ + [CHG_SDP] = {BC12_TYPE_SDP, BC12_CURR_UA(BC12_CHARGER_MIN_CURR_UA)}, + [CHG_DCP] = {BC12_TYPE_DCP, BC12_CURR_UA(1500000)}, +}; + +static int pi3usb9201_interrupt_enable(const struct device *dev, const bool enable) +{ + const struct pi3usb9201_config *cfg = dev->config; + + /* Clear the interrupt mask bit to enable the interrupt */ + return i2c_reg_update_byte_dt(&cfg->i2c, PI3USB9201_REG_CTRL_1, + PI3USB9201_REG_CTRL_1_INT_MASK, + enable ? 0 : PI3USB9201_REG_CTRL_1_INT_MASK); +} + +static int pi3usb9201_bc12_detect_ctrl(const struct device *dev, const bool enable) +{ + const struct pi3usb9201_config *cfg = dev->config; + + return i2c_reg_update_byte_dt(&cfg->i2c, PI3USB9201_REG_CTRL_2, + PI3USB9201_REG_CTRL_2_START_DET, + enable ? PI3USB9201_REG_CTRL_2_START_DET : 0); +} + +static int pi3usb9201_bc12_usb_switch(const struct device *dev, bool enable) +{ + const struct pi3usb9201_config *cfg = dev->config; + + /* USB data switch enabled when PI3USB9201_REG_CTRL_2_AUTO_SW is clear */ + return i2c_reg_update_byte_dt(&cfg->i2c, PI3USB9201_REG_CTRL_2, + PI3USB9201_REG_CTRL_2_START_DET, + enable ? 0 : PI3USB9201_REG_CTRL_2_AUTO_SW); +} + +static int pi3usb9201_set_mode(const struct device *dev, enum pi3usb9201_mode mode) +{ + const struct pi3usb9201_config *cfg = dev->config; + + return i2c_reg_update_byte_dt(&cfg->i2c, PI3USB9201_REG_CTRL_1, + PI3USB9201_REG_CTRL_1_MODE_MASK + << PI3USB9201_REG_CTRL_1_MODE_SHIFT, + mode << PI3USB9201_REG_CTRL_1_MODE_SHIFT); +} + +static int pi3usb9201_get_status(const struct device *dev, uint8_t *const client, + uint8_t *const host) +{ + const struct pi3usb9201_config *cfg = dev->config; + uint8_t status; + int rv; + + rv = i2c_reg_read_byte_dt(&cfg->i2c, PI3USB9201_REG_CLIENT_STS, &status); + if (rv < 0) { + return rv; + } + + if (client != NULL) { + *client = status; + } + + rv = i2c_reg_read_byte_dt(&cfg->i2c, PI3USB9201_REG_HOST_STS, &status); + if (rv < 0) { + return rv; + } + + if (host != NULL) { + *host = status; + } + + return 0; +} + +static void pi3usb9201_notify_callback(const struct device *dev, + struct bc12_partner_state *const state) +{ + struct pi3usb9201_data *pi3usb9201_data = dev->data; + + if (pi3usb9201_data->result_cb) { + pi3usb9201_data->result_cb(dev, state, pi3usb9201_data->result_cb_data); + } +} + +static void pi3usb9201_update_charging_partner(const struct device *dev, + struct bc12_partner_state *const state) +{ + struct pi3usb9201_data *pi3usb9201_data = dev->data; + + if (state && state->type == pi3usb9201_data->partner_type) { + /* No change to the charging partner */ + return; + } + + if (state && state->type != BC12_TYPE_NONE) { + /* Now update the current charger type */ + pi3usb9201_data->partner_type = state->type; + pi3usb9201_notify_callback(dev, state); + } else { + pi3usb9201_data->partner_type = BC12_TYPE_NONE; + pi3usb9201_notify_callback(dev, NULL); + } +} + +static int pi3usb9201_client_detect_start(const struct device *dev) +{ + int rv; + + /* + * Read both status registers to ensure that all interrupt indications + * are cleared prior to starting bc1.2 detection. + */ + pi3usb9201_get_status(dev, NULL, NULL); + + /* Put pi3usb9201 into client mode */ + rv = pi3usb9201_set_mode(dev, PI3USB9201_CLIENT_MODE); + if (rv < 0) { + return rv; + } + + /* Have pi3usb9201 start bc1.2 detection */ + rv = pi3usb9201_bc12_detect_ctrl(dev, true); + if (rv < 0) { + return rv; + } + + /* Enable interrupt to wake task when detection completes */ + return pi3usb9201_interrupt_enable(dev, true); +} + +static void pi3usb9201_client_detect_finish(const struct device *dev, const int status) +{ + struct bc12_partner_state new_chg; + int bit_pos; + bool enable_usb_data; + + /* Set charge voltage to 5V */ + new_chg.voltage_uv = BC12_CHARGER_VOLTAGE_UV; + + /* + * Find set bit position. Note that this function is only called if a + * bit was set in client_status, so bit_pos won't be negative. + */ + bit_pos = __builtin_ffs(status) - 1; + + new_chg.current_ua = bc12_chg_limits[bit_pos].current_limit; + new_chg.type = bc12_chg_limits[bit_pos].partner_type; + + LOG_DBG("client status = 0x%x, current = %d mA, type = %d", + status, new_chg.current_ua, new_chg.type); + + /* bc1.2 is complete and start bit does not auto clear */ + if (pi3usb9201_bc12_detect_ctrl(dev, false) < 0) { + LOG_ERR("failed to clear client detect"); + } + + /* If DCP mode, disable USB swtich */ + if (status & BIT(CHG_DCP)) { + enable_usb_data = false; + } else { + enable_usb_data = true; + } + if (pi3usb9201_bc12_usb_switch(dev, enable_usb_data) < 0) { + LOG_ERR("failed to set USB data mode"); + } + + /* Inform charge manager of new supplier type and current limit */ + pi3usb9201_update_charging_partner(dev, &new_chg); +} + +static int pi3usb9201_disconnect(const struct device *dev) +{ + int rv; + + /* Ensure USB switch auto-on is enabled */ + rv = pi3usb9201_bc12_usb_switch(dev, true); + if (rv < 0) { + return rv; + } + + /* Put pi3usb9201 into its power down mode */ + rv = pi3usb9201_set_mode(dev, PI3USB9201_POWER_DOWN); + if (rv < 0) { + return rv; + } + + /* The start bc1.2 bit does not auto clear */ + rv = pi3usb9201_bc12_detect_ctrl(dev, false); + if (rv < 0) { + return rv; + } + + /* Mask interrupts until next bc1.2 detection event */ + rv = pi3usb9201_interrupt_enable(dev, false); + if (rv < 0) { + return rv; + } + + /* + * Let the application know there's no more charge available for the + * supplier type that was most recently detected. + */ + pi3usb9201_update_charging_partner(dev, NULL); + + return 0; +} + +static int pi3usb9201_set_portable_device(const struct device *dev) +{ + int rv; + + /* Disable interrupts during mode change */ + rv = pi3usb9201_interrupt_enable(dev, false); + if (rv < 0) { + return rv; + } + + if (pi3usb9201_client_detect_start(dev) < 0) { + struct bc12_partner_state new_result; + + /* + * VBUS is present, but starting bc1.2 detection failed + * for some reason. Set the partner type to unknown limit + * current to the minimum allowed for a suspended USB device. + */ + new_result.voltage_uv = BC12_CHARGER_VOLTAGE_UV; + new_result.current_ua = BC12_CHARGER_MIN_CURR_UA; + new_result.type = BC12_TYPE_UNKNOWN; + /* Save supplier type and notify callbacks */ + pi3usb9201_update_charging_partner(dev, &new_result); + LOG_ERR("bc1.2 detection failed, using defaults"); + + return -EIO; + } + + return 0; +} + +static void pi3usb9201_isr_work(struct k_work *item) +{ + struct pi3usb9201_data *pi3usb9201_data = CONTAINER_OF(item, struct pi3usb9201_data, work); + const struct device *dev = pi3usb9201_data->dev; + uint8_t client; + uint8_t host; + int rv; + + rv = pi3usb9201_get_status(dev, &client, &host); + if (rv < 0) { + LOG_ERR("Failed to get host/client status"); + return; + } + + if (client != 0) { + /* + * Any bit set in client status register indicates that + * BC1.2 detection has completed. + */ + pi3usb9201_client_detect_finish(dev, client); + } +} + +static void pi3usb9201_gpio_callback(const struct device *dev, struct gpio_callback *cb, + uint32_t pins) +{ + struct pi3usb9201_data *pi3usb9201_data = CONTAINER_OF(cb, struct pi3usb9201_data, gpio_cb); + + k_work_submit(&pi3usb9201_data->work); +} + +static int pi3usb9201_set_role(const struct device *dev, const enum bc12_role role) +{ + switch (role) { + case BC12_DISCONNECTED: + return pi3usb9201_disconnect(dev); + case BC12_PORTABLE_DEVICE: + return pi3usb9201_set_portable_device(dev); + + default: + LOG_ERR("unsupported BC12 role: %d", role); + return -EINVAL; + } + + return 0; +} + +int pi3usb9201_set_result_cb(const struct device *dev, bc12_callback_t cb, void *const user_data) +{ + struct pi3usb9201_data *pi3usb9201_data = dev->data; + + pi3usb9201_data->result_cb = cb; + pi3usb9201_data->result_cb_data = user_data; + + return 0; +} + +static const struct bc12_driver_api pi3usb9201_driver_api = { + .set_role = pi3usb9201_set_role, + .set_result_cb = pi3usb9201_set_result_cb, +}; + +static int pi3usb9201_init(const struct device *dev) +{ + const struct pi3usb9201_config *cfg = dev->config; + struct pi3usb9201_data *pi3usb9201_data = dev->data; + int rv; + + if (!i2c_is_ready_dt(&cfg->i2c)) { + LOG_ERR("Bus device is not ready."); + return -ENODEV; + } + + if (!gpio_is_ready_dt(&cfg->intb_gpio)) { + LOG_ERR("intb_gpio device is not ready."); + return -ENODEV; + } + + pi3usb9201_data->dev = dev; + + /* + * Set most recent bc1.2 detection type result to + * BC12_TYPE_NONE for the port. + */ + pi3usb9201_data->partner_type = BC12_TYPE_NONE; + + rv = gpio_pin_configure_dt(&cfg->intb_gpio, GPIO_INPUT); + if (rv < 0) { + LOG_DBG("Failed to set gpio callback."); + return rv; + } + + gpio_init_callback(&pi3usb9201_data->gpio_cb, pi3usb9201_gpio_callback, + BIT(cfg->intb_gpio.pin)); + k_work_init(&pi3usb9201_data->work, pi3usb9201_isr_work); + + rv = gpio_add_callback(cfg->intb_gpio.port, &pi3usb9201_data->gpio_cb); + if (rv < 0) { + LOG_DBG("Failed to set gpio callback."); + return rv; + } + + rv = gpio_pin_interrupt_configure_dt(&cfg->intb_gpio, GPIO_INT_EDGE_FALLING); + if (rv < 0) { + LOG_DBG("Failed to configure gpio interrupt."); + return rv; + } + + /* + * The is no specific initialization required for the pi3usb9201 other + * than disabling the interrupt. + */ + return pi3usb9201_interrupt_enable(dev, false); +} + +#define PI2USB9201_DEFINE(inst) \ + static struct pi3usb9201_data pi3usb9201_data_##inst; \ + \ + static const struct pi3usb9201_config pi3usb9201_config_##inst = { \ + .i2c = I2C_DT_SPEC_INST_GET(inst), \ + .intb_gpio = GPIO_DT_SPEC_INST_GET(inst, intb_gpios), \ + }; \ + \ + DEVICE_DT_INST_DEFINE(inst, pi3usb9201_init, NULL, &pi3usb9201_data_##inst, \ + &pi3usb9201_config_##inst, POST_KERNEL, \ + CONFIG_APPLICATION_INIT_PRIORITY, &pi3usb9201_driver_api); + +DT_INST_FOREACH_STATUS_OKAY(PI2USB9201_DEFINE) diff --git a/drivers/usb/bc12/bc12_pi3usb9201.h b/drivers/usb/bc12/bc12_pi3usb9201.h new file mode 100644 index 0000000000..3d1855a4dc --- /dev/null +++ b/drivers/usb/bc12/bc12_pi3usb9201.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2022 Google LLC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* PI3USB9201 USB BC 1.2 Charger Detector driver definitions */ + +#ifndef ZEPHYR_INCLUDE_USB_BC12_PI3USB9201_H +#define ZEPHYR_INCLUDE_USB_BC12_PI3USB9201_H + +#define PI3USB9201_REG_CTRL_1 0x0 +#define PI3USB9201_REG_CTRL_2 0x1 +#define PI3USB9201_REG_CLIENT_STS 0x2 +#define PI3USB9201_REG_HOST_STS 0x3 + +/* Flags */ +#define PI3USB9201_ALWAYS_POWERED BIT(0) + +/* Control_1 register bit definitions */ +#define PI3USB9201_REG_CTRL_1_INT_MASK BIT(0) +#define PI3USB9201_REG_CTRL_1_MODE_SHIFT 1 +#define PI3USB9201_REG_CTRL_1_MODE_MASK BIT_MASK(3) + +/* Control_2 register bit definitions */ +#define PI3USB9201_REG_CTRL_2_AUTO_SW BIT(1) +#define PI3USB9201_REG_CTRL_2_START_DET BIT(3) + +/* Host status register bit definitions */ +#define PI3USB9201_REG_HOST_STS_BC12_DET BIT(0) +#define PI3USB9201_REG_HOST_STS_DEV_PLUG BIT(1) +#define PI3USB9201_REG_HOST_STS_DEV_UNPLUG BIT(2) + +enum pi3usb9201_mode { + PI3USB9201_POWER_DOWN, + PI3USB9201_SDP_HOST_MODE, + PI3USB9201_DCP_HOST_MODE, + PI3USB9201_CDP_HOST_MODE, + PI3USB9201_CLIENT_MODE, + PI3USB9201_RESERVED_1, + PI3USB9201_RESERVED_2, + PI3USB9201_USB_PATH_ON, +}; + +#endif /* ZEPHYR_INCLUDE_USB_BC12_PI3USB9201_H */ diff --git a/dts/bindings/usb/diodes,pi3usb9201.yaml b/dts/bindings/usb/diodes,pi3usb9201.yaml new file mode 100644 index 0000000000..2119774bd0 --- /dev/null +++ b/dts/bindings/usb/diodes,pi3usb9201.yaml @@ -0,0 +1,15 @@ +# Copyright (c) 2022 Google LLC +# SPDX-License-Identifier: Apache-2.0 + +description: Diodes PI3USB9201, Dual-Role USB Charging-Type Detector + +compatible: "diodes,pi3usb9201" + +include: [i2c-device.yaml, usb-bc12.yaml] + +properties: + intb-gpios: + type: phandle-array + required: true + description: | + GPIO input connected to the active low interrupt INTB pin on the PI3USB9201. diff --git a/dts/bindings/usb/usb-bc12.yaml b/dts/bindings/usb/usb-bc12.yaml new file mode 100644 index 0000000000..2ea119b53d --- /dev/null +++ b/dts/bindings/usb/usb-bc12.yaml @@ -0,0 +1,6 @@ +# Copyright (c) 2022 Google LLC +# SPDX-License-Identifier: Apache-2.0 + +# Common fields for USB BC1.2 devices + +include: base.yaml diff --git a/include/zephyr/drivers/usb/usb_bc12.h b/include/zephyr/drivers/usb/usb_bc12.h new file mode 100644 index 0000000000..2a5d6a9511 --- /dev/null +++ b/include/zephyr/drivers/usb/usb_bc12.h @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2022 Google LLC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Public APIs for the USB BC1.2 battery charging detect drivers. + */ + +#ifndef ZEPHYR_INCLUDE_DRIVERS_USB_USB_BC12_H_ +#define ZEPHYR_INCLUDE_DRIVERS_USB_USB_BC12_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief BC1.2 driver APIs + * @defgroup b12_interface BC1.2 driver APIs + * @ingroup io_interfaces + * @{ + */ + +/* FIXME - make these Kconfig options */ + +/** + * @name BC1.2 constants + * @{ + */ + +/** BC1.2 USB charger voltage. */ +#define BC12_CHARGER_VOLTAGE_UV 5000 * 1000 +/** + * BC1.2 USB charger minimum current. Set to match the Isusp of 2.5 mA parameter. + * This is returned by the driver when either BC1.2 detection fails, or the + * attached partner is a SDP (standard downstream port). + * + * The application may increase the current draw after determing the USB device + * state of suspended/unconfigured/configured. + * Suspended: 2.5 mA + * Unconfigured: 100 mA + * Configured: 500 mA (USB 2.0) + */ +#define BC12_CHARGER_MIN_CURR_UA 2500 +/** BC1.2 USB charger maximum current. */ +#define BC12_CHARGER_MAX_CURR_UA 1500 * 1000 + +/** @} */ + +/** @cond INTERNAL_HIDDEN + * @brief Helper macro for setting a BC1.2 current limit + * + * @param val Current limit value, in uA. + * @return A valid BC1.2 current limit, in uA, clamped between the BC1.2 minimum + * and maximum values. + */ +#define BC12_CURR_UA(val) CLAMP(val, BC12_CHARGER_MIN_CURR_UA, BC12_CHARGER_MAX_CURR_UA) +/** @endcond */ + +/** @brief BC1.2 device role. */ +enum bc12_role { + BC12_DISCONNECTED, + BC12_PORTABLE_DEVICE, +}; + +/** @brief BC1.2 charging partner type. */ +enum bc12_type { + /** No partner connected. */ + BC12_TYPE_NONE, + /** Standard Downstream Port */ + BC12_TYPE_SDP, + /** Dedicated Charging Port */ + BC12_TYPE_DCP, + /** Charging Downstream Port */ + BC12_TYPE_CDP, + /** Proprietary charging port */ + BC12_TYPE_PROPRIETARY, + /** Unknown charging port, BC1.2 detection failed. */ + BC12_TYPE_UNKNOWN, +}; + +/** + * @brief BC1.2 detected partner state. + * + * @param type Charging partner type. + * @param current_ma Current, in uA, that the charging partner provides. + * @param voltage_mv Voltage, in uV, that the charging partner provides. + */ +struct bc12_partner_state { + enum bc12_type type; + int current_ua; + int voltage_uv; +}; + +/** + * @brief BC1.2 callback for charger configuration + * + * @param dev BC1.2 device which is notifying of the new charger state. + * @param state Current state of the BC1.2 client, including BC1.2 type + * detected, voltage, and current limits. + * If NULL, then the partner charger is disconnected or the BC1.2 device is + * operating in host mode. + * @param user_data Requester supplied data which is passed along to the callback. + */ +typedef void (*bc12_callback_t)(const struct device *dev, struct bc12_partner_state *state, + void *user_data); + +/** + * @cond INTERNAL_HIDDEN + * + * These are for internal use only, so skip these in public documentation. + */ +__subsystem struct bc12_driver_api { + int (*set_role)(const struct device *dev, enum bc12_role role); + int (*set_result_cb)(const struct device *dev, bc12_callback_t cb, void *user_data); +}; +/** + * @endcond + */ + +/** + * @brief Set the BC1.2 role. + * + * @param dev Pointer to the device structure for the BC1.2 driver instance. + * @param role New role for the BC1.2 device. + * + * @retval 0 If successful. + * @retval -EIO general input/output error. + */ +__syscall int bc12_set_role(const struct device *dev, enum bc12_role role); + +static inline int z_impl_bc12_set_role(const struct device *dev, enum bc12_role role) +{ + const struct bc12_driver_api *api = (const struct bc12_driver_api *)dev->api; + + return api->set_role(dev, role); +} + +/** + * @brief Register a callback for BC1.2 results. + * + * @param dev Pointer to the device structure for the BC1.2 driver instance. + * @param cb Function pointer for the result callback. + * @param user_data Requester supplied data which is passed along to the callback. + * + * @retval 0 If successful. + * @retval -EIO general input/output error. + */ +__syscall int bc12_set_result_cb(const struct device *dev, bc12_callback_t cb, void *user_data); + +static inline int z_impl_bc12_set_result_cb(const struct device *dev, bc12_callback_t cb, + void *user_data) +{ + const struct bc12_driver_api *api = (const struct bc12_driver_api *)dev->api; + + return api->set_result_cb(dev, cb, user_data); +} + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#include + +#endif /* ZEPHYR_INCLUDE_DRIVERS_USB_USB_BC12_H_ */