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