1 /*
2 * Copyright 2024 Google LLC
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6 #define DT_DRV_COMPAT zephyr_usb_c_vbus_tcpci
7
8 #include <zephyr/logging/log.h>
9 LOG_MODULE_REGISTER(usbc_vbus_tcpci, CONFIG_USBC_LOG_LEVEL);
10
11 #include <zephyr/device.h>
12 #include <zephyr/kernel.h>
13 #include <zephyr/drivers/i2c.h>
14 #include <zephyr/drivers/usb_c/tcpci_priv.h>
15 #include <zephyr/drivers/usb_c/usbc_vbus.h>
16 #include <zephyr/drivers/usb_c/usbc_pd.h>
17 #include <zephyr/usb_c/tcpci.h>
18
19 /** Configuration structure for device instances */
20 struct vbus_tcpci_cfg {
21 /** I2C bus and address used for communication, set from parent node of device. */
22 const struct i2c_dt_spec i2c;
23 };
24
tcpci_measure(const struct device * dev,int * vbus_meas)25 static int tcpci_measure(const struct device *dev, int *vbus_meas)
26 {
27 const struct vbus_tcpci_cfg *cfg = dev->config;
28 uint16_t measure;
29 int ret;
30
31 __ASSERT(vbus_meas != NULL, "TCPCI VBUS meas must not be NULL");
32
33 ret = tcpci_read_reg16(&cfg->i2c, TCPC_REG_VBUS_VOLTAGE, &measure);
34 if (ret != 0) {
35 return ret;
36 }
37
38 *vbus_meas = TCPC_REG_VBUS_VOLTAGE_VBUS(measure);
39
40 return 0;
41 }
42
tcpci_check_level(const struct device * dev,enum tc_vbus_level level)43 static bool tcpci_check_level(const struct device *dev, enum tc_vbus_level level)
44 {
45 int measure;
46 int ret;
47
48 ret = tcpci_measure(dev, &measure);
49 if (ret != 0) {
50 return false;
51 }
52
53 switch (level) {
54 case TC_VBUS_SAFE0V:
55 return (measure < PD_V_SAFE_0V_MAX_MV);
56 case TC_VBUS_PRESENT:
57 return (measure >= PD_V_SAFE_5V_MIN_MV);
58 case TC_VBUS_REMOVED:
59 return (measure < TC_V_SINK_DISCONNECT_MAX_MV);
60 }
61
62 return false;
63 }
64
tcpci_discharge(const struct device * dev,bool enable)65 static int tcpci_discharge(const struct device *dev, bool enable)
66 {
67 const struct vbus_tcpci_cfg *cfg = dev->config;
68
69 return tcpci_update_reg8(&cfg->i2c, TCPC_REG_POWER_CTRL,
70 TCPC_REG_POWER_CTRL_FORCE_DISCHARGE,
71 (enable) ? TCPC_REG_POWER_CTRL_FORCE_DISCHARGE : 0);
72 }
73
tcpci_enable(const struct device * dev,bool enable)74 static int tcpci_enable(const struct device *dev, bool enable)
75 {
76 const struct vbus_tcpci_cfg *cfg = dev->config;
77
78 return tcpci_update_reg8(&cfg->i2c, TCPC_REG_POWER_CTRL,
79 TCPC_REG_POWER_CTRL_VBUS_VOL_MONITOR_DIS,
80 (enable) ? 0 : TCPC_REG_POWER_CTRL_VBUS_VOL_MONITOR_DIS);
81 }
82
83 static DEVICE_API(usbc_vbus, vbus_tcpci_api) = {
84 .measure = tcpci_measure,
85 .check_level = tcpci_check_level,
86 .discharge = tcpci_discharge,
87 .enable = tcpci_enable,
88 };
89
tcpci_init(const struct device * dev)90 static int tcpci_init(const struct device *dev)
91 {
92 return 0;
93 }
94
95 BUILD_ASSERT(DT_NUM_INST_STATUS_OKAY(DT_DRV_COMPAT) > 0,
96 "No compatible USB-C VBUS Measurement instance found");
97
98 #define VBUS_TCPCI_INIT_CFG(node) \
99 { \
100 .i2c = {I2C_DT_SPEC_GET_ON_I2C(DT_PARENT(node))}, \
101 }
102
103 #define DRIVER_INIT(inst) \
104 static const struct vbus_tcpci_cfg drv_config_##inst = \
105 VBUS_TCPCI_INIT_CFG(DT_DRV_INST(inst)); \
106 DEVICE_DT_INST_DEFINE(inst, &tcpci_init, NULL, NULL, &drv_config_##inst, POST_KERNEL, \
107 CONFIG_USBC_VBUS_INIT_PRIORITY, &vbus_tcpci_api);
108
109 DT_INST_FOREACH_STATUS_OKAY(DRIVER_INIT)
110