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