1 /*
2  * Copyright (c) 2024 Nuvoton Technology Corporation.
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #define DT_DRV_COMPAT nuvoton_numaker_vbus
8 
9 #include <zephyr/kernel.h>
10 #include <zephyr/drivers/usb_c/usbc_vbus.h>
11 #include <zephyr/drivers/clock_control.h>
12 #include <zephyr/drivers/clock_control/clock_control_numaker.h>
13 #include <zephyr/drivers/reset.h>
14 #include <zephyr/drivers/pinctrl.h>
15 
16 #include <zephyr/logging/log.h>
17 LOG_MODULE_REGISTER(vbus_numaker, CONFIG_USBC_LOG_LEVEL);
18 
19 #include <soc.h>
20 #include <NuMicro.h>
21 
22 #include "../tcpc/ucpd_numaker.h"
23 
24 /* Implementation notes on NuMaker TCPC/PPC/VBUS
25  *
26  * PPC and VBUS rely on TCPC/UTCPD and are just pseudo. They are completely
27  * implemented in TCPC/UTCPD.
28  */
29 
30 /**
31  * @brief Immutable device context
32  */
33 struct numaker_vbus_config {
34 	const struct device *tcpc_dev;
35 };
36 
37 /**
38  * @brief Initializes the usb-c vbus driver
39  *
40  * @retval 0 on success
41  * @retval -ENODEV if dependent TCPC device is not ready
42  */
numaker_vbus_init(const struct device * dev)43 static int numaker_vbus_init(const struct device *dev)
44 {
45 	const struct numaker_vbus_config *const config = dev->config;
46 	const struct device *tcpc_dev = config->tcpc_dev;
47 
48 	/* Rely on TCPC */
49 	if (!device_is_ready(tcpc_dev)) {
50 		LOG_ERR("TCPC device not ready");
51 		return -ENODEV;
52 	}
53 
54 	return 0;
55 }
56 
57 /**
58  * @brief Checks if VBUS is at a particular level
59  *
60  * @retval true if VBUS is at the level voltage
61  * @retval false if VBUS is not at that level voltage
62  */
numaker_vbus_check_level(const struct device * dev,enum tc_vbus_level level)63 static bool numaker_vbus_check_level(const struct device *dev, enum tc_vbus_level level)
64 {
65 	const struct numaker_vbus_config *const config = dev->config;
66 	const struct device *tcpc_dev = config->tcpc_dev;
67 
68 	return numaker_tcpc_vbus_check_level(tcpc_dev, level);
69 }
70 
71 /**
72  * @brief Reads and returns VBUS measured in mV
73  *
74  * @retval 0 on success
75  * @retval -EIO on failure
76  */
numaker_vbus_measure(const struct device * dev,int * vbus_meas)77 static int numaker_vbus_measure(const struct device *dev, int *vbus_meas)
78 {
79 	const struct numaker_vbus_config *const config = dev->config;
80 	const struct device *tcpc_dev = config->tcpc_dev;
81 
82 	return numaker_tcpc_vbus_measure(tcpc_dev, vbus_meas);
83 }
84 
85 /**
86  * @brief Controls a pin that discharges VBUS
87  *
88  * @retval 0 on success
89  * @retval -EIO on failure
90  */
numaker_vbus_discharge(const struct device * dev,bool enable)91 static int numaker_vbus_discharge(const struct device *dev, bool enable)
92 {
93 	const struct numaker_vbus_config *const config = dev->config;
94 	const struct device *tcpc_dev = config->tcpc_dev;
95 
96 	return numaker_tcpc_vbus_discharge(tcpc_dev, enable);
97 }
98 
99 /**
100  * @brief Controls a pin that enables VBUS measurments
101  *
102  * @retval 0 on success
103  * @retval -EIO on failure
104  */
numaker_vbus_enable(const struct device * dev,bool enable)105 static int numaker_vbus_enable(const struct device *dev, bool enable)
106 {
107 	const struct numaker_vbus_config *const config = dev->config;
108 	const struct device *tcpc_dev = config->tcpc_dev;
109 
110 	return numaker_tcpc_vbus_enable(tcpc_dev, enable);
111 }
112 
113 static DEVICE_API(usbc_vbus, numaker_vbus_driver_api) = {
114 	.check_level = numaker_vbus_check_level,
115 	.measure = numaker_vbus_measure,
116 	.discharge = numaker_vbus_discharge,
117 	.enable = numaker_vbus_enable,
118 };
119 
120 #define NUMAKER_TCPC(inst) DT_INST_PARENT(inst)
121 
122 #define VBUS_NUMAKER_INIT(inst)                                                                    \
123 	static const struct numaker_vbus_config numaker_vbus_config_##inst = {                     \
124 		.tcpc_dev = DEVICE_DT_GET(NUMAKER_TCPC(inst)),                                     \
125 	};                                                                                         \
126                                                                                                    \
127 	DEVICE_DT_INST_DEFINE(inst, numaker_vbus_init, NULL, NULL, &numaker_vbus_config_##inst,    \
128 			      POST_KERNEL, CONFIG_USBC_VBUS_INIT_PRIORITY,                         \
129 			      &numaker_vbus_driver_api);
130 
131 DT_INST_FOREACH_STATUS_OKAY(VBUS_NUMAKER_INIT);
132