1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Logging driver for ChromeOS EC based USBPD Charger. |
4 | * |
5 | * Copyright 2018 Google LLC. |
6 | */ |
7 | |
8 | #include <linux/ktime.h> |
9 | #include <linux/math64.h> |
10 | #include <linux/module.h> |
11 | #include <linux/platform_data/cros_ec_commands.h> |
12 | #include <linux/platform_data/cros_ec_proto.h> |
13 | #include <linux/platform_device.h> |
14 | #include <linux/rtc.h> |
15 | |
16 | #define DRV_NAME "cros-usbpd-logger" |
17 | |
18 | #define CROS_USBPD_MAX_LOG_ENTRIES 30 |
19 | #define CROS_USBPD_LOG_UPDATE_DELAY msecs_to_jiffies(60000) |
20 | #define CROS_USBPD_DATA_SIZE 16 |
21 | #define CROS_USBPD_LOG_RESP_SIZE (sizeof(struct ec_response_pd_log) + \ |
22 | CROS_USBPD_DATA_SIZE) |
23 | #define CROS_USBPD_BUFFER_SIZE (sizeof(struct cros_ec_command) + \ |
24 | CROS_USBPD_LOG_RESP_SIZE) |
25 | /* Buffer for building the PDLOG string */ |
26 | #define BUF_SIZE 80 |
27 | |
28 | struct logger_data { |
29 | struct device *dev; |
30 | struct cros_ec_dev *ec_dev; |
31 | u8 ec_buffer[CROS_USBPD_BUFFER_SIZE]; |
32 | struct delayed_work log_work; |
33 | struct workqueue_struct *log_workqueue; |
34 | }; |
35 | |
36 | static const char * const chg_type_names[] = { |
37 | "None" , "PD" , "Type-C" , "Proprietary" , "DCP" , "CDP" , "SDP" , |
38 | "Other" , "VBUS" |
39 | }; |
40 | |
41 | static const char * const role_names[] = { |
42 | "Disconnected" , "SRC" , "SNK" , "SNK (not charging)" |
43 | }; |
44 | |
45 | static const char * const fault_names[] = { |
46 | "---" , "OCP" , "fast OCP" , "OVP" , "Discharge" |
47 | }; |
48 | |
49 | __printf(3, 4) |
50 | static int append_str(char *buf, int pos, const char *fmt, ...) |
51 | { |
52 | va_list args; |
53 | int i; |
54 | |
55 | va_start(args, fmt); |
56 | i = vsnprintf(buf: buf + pos, BUF_SIZE - pos, fmt, args); |
57 | va_end(args); |
58 | |
59 | return i; |
60 | } |
61 | |
62 | static struct ec_response_pd_log *ec_get_log_entry(struct logger_data *logger) |
63 | { |
64 | struct cros_ec_dev *ec_dev = logger->ec_dev; |
65 | struct cros_ec_command *msg; |
66 | int ret; |
67 | |
68 | msg = (struct cros_ec_command *)logger->ec_buffer; |
69 | |
70 | msg->command = ec_dev->cmd_offset + EC_CMD_PD_GET_LOG_ENTRY; |
71 | msg->insize = CROS_USBPD_LOG_RESP_SIZE; |
72 | |
73 | ret = cros_ec_cmd_xfer_status(ec_dev: ec_dev->ec_dev, msg); |
74 | if (ret < 0) |
75 | return ERR_PTR(error: ret); |
76 | |
77 | return (struct ec_response_pd_log *)msg->data; |
78 | } |
79 | |
80 | static void cros_usbpd_print_log_entry(struct ec_response_pd_log *r, |
81 | ktime_t tstamp) |
82 | { |
83 | const char *fault, *role, *chg_type; |
84 | struct usb_chg_measures *meas; |
85 | struct mcdp_info *minfo; |
86 | int role_idx, type_idx; |
87 | char buf[BUF_SIZE + 1]; |
88 | struct rtc_time rt; |
89 | int len = 0; |
90 | s32 rem; |
91 | int i; |
92 | |
93 | /* The timestamp is the number of 1024th of seconds in the past */ |
94 | tstamp = ktime_sub_us(kt: tstamp, usec: r->timestamp << PD_LOG_TIMESTAMP_SHIFT); |
95 | rt = rtc_ktime_to_tm(kt: tstamp); |
96 | |
97 | switch (r->type) { |
98 | case PD_EVENT_MCU_CHARGE: |
99 | if (r->data & CHARGE_FLAGS_OVERRIDE) |
100 | len += append_str(buf, pos: len, fmt: "override " ); |
101 | |
102 | if (r->data & CHARGE_FLAGS_DELAYED_OVERRIDE) |
103 | len += append_str(buf, pos: len, fmt: "pending_override " ); |
104 | |
105 | role_idx = r->data & CHARGE_FLAGS_ROLE_MASK; |
106 | role = role_idx < ARRAY_SIZE(role_names) ? |
107 | role_names[role_idx] : "Unknown" ; |
108 | |
109 | type_idx = (r->data & CHARGE_FLAGS_TYPE_MASK) |
110 | >> CHARGE_FLAGS_TYPE_SHIFT; |
111 | |
112 | chg_type = type_idx < ARRAY_SIZE(chg_type_names) ? |
113 | chg_type_names[type_idx] : "???" ; |
114 | |
115 | if (role_idx == USB_PD_PORT_POWER_DISCONNECTED || |
116 | role_idx == USB_PD_PORT_POWER_SOURCE) { |
117 | len += append_str(buf, pos: len, fmt: "%s" , role); |
118 | break; |
119 | } |
120 | |
121 | meas = (struct usb_chg_measures *)r->payload; |
122 | len += append_str(buf, pos: len, fmt: "%s %s %s %dmV max %dmV / %dmA" , |
123 | role, r->data & CHARGE_FLAGS_DUAL_ROLE ? |
124 | "DRP" : "Charger" , |
125 | chg_type, meas->voltage_now, |
126 | meas->voltage_max, meas->current_max); |
127 | break; |
128 | case PD_EVENT_ACC_RW_FAIL: |
129 | len += append_str(buf, pos: len, fmt: "RW signature check failed" ); |
130 | break; |
131 | case PD_EVENT_PS_FAULT: |
132 | fault = r->data < ARRAY_SIZE(fault_names) ? fault_names[r->data] |
133 | : "???" ; |
134 | len += append_str(buf, pos: len, fmt: "Power supply fault: %s" , fault); |
135 | break; |
136 | case PD_EVENT_VIDEO_DP_MODE: |
137 | len += append_str(buf, pos: len, fmt: "DP mode %sabled" , r->data == 1 ? |
138 | "en" : "dis" ); |
139 | break; |
140 | case PD_EVENT_VIDEO_CODEC: |
141 | minfo = (struct mcdp_info *)r->payload; |
142 | len += append_str(buf, pos: len, fmt: "HDMI info: family:%04x chipid:%04x " , |
143 | MCDP_FAMILY(minfo->family), |
144 | MCDP_CHIPID(minfo->chipid)); |
145 | len += append_str(buf, pos: len, fmt: "irom:%d.%d.%d fw:%d.%d.%d" , |
146 | minfo->irom.major, minfo->irom.minor, |
147 | minfo->irom.build, minfo->fw.major, |
148 | minfo->fw.minor, minfo->fw.build); |
149 | break; |
150 | default: |
151 | len += append_str(buf, pos: len, fmt: "Event %02x (%04x) [" , r->type, |
152 | r->data); |
153 | |
154 | for (i = 0; i < PD_LOG_SIZE(r->size_port); i++) |
155 | len += append_str(buf, pos: len, fmt: "%02x " , r->payload[i]); |
156 | |
157 | len += append_str(buf, pos: len, fmt: "]" ); |
158 | break; |
159 | } |
160 | |
161 | div_s64_rem(dividend: ktime_to_ms(kt: tstamp), MSEC_PER_SEC, remainder: &rem); |
162 | pr_info("PDLOG %d/%02d/%02d %02d:%02d:%02d.%03d P%d %s\n" , |
163 | rt.tm_year + 1900, rt.tm_mon + 1, rt.tm_mday, |
164 | rt.tm_hour, rt.tm_min, rt.tm_sec, rem, |
165 | PD_LOG_PORT(r->size_port), buf); |
166 | } |
167 | |
168 | static void cros_usbpd_log_check(struct work_struct *work) |
169 | { |
170 | struct logger_data *logger = container_of(to_delayed_work(work), |
171 | struct logger_data, |
172 | log_work); |
173 | struct device *dev = logger->dev; |
174 | struct ec_response_pd_log *r; |
175 | int entries = 0; |
176 | ktime_t now; |
177 | |
178 | while (entries++ < CROS_USBPD_MAX_LOG_ENTRIES) { |
179 | r = ec_get_log_entry(logger); |
180 | now = ktime_get_real(); |
181 | if (IS_ERR(ptr: r)) { |
182 | dev_dbg(dev, "Cannot get PD log %ld\n" , PTR_ERR(r)); |
183 | break; |
184 | } |
185 | if (r->type == PD_EVENT_NO_ENTRY) |
186 | break; |
187 | |
188 | cros_usbpd_print_log_entry(r, tstamp: now); |
189 | } |
190 | |
191 | queue_delayed_work(wq: logger->log_workqueue, dwork: &logger->log_work, |
192 | CROS_USBPD_LOG_UPDATE_DELAY); |
193 | } |
194 | |
195 | static int cros_usbpd_logger_probe(struct platform_device *pd) |
196 | { |
197 | struct cros_ec_dev *ec_dev = dev_get_drvdata(dev: pd->dev.parent); |
198 | struct device *dev = &pd->dev; |
199 | struct logger_data *logger; |
200 | |
201 | logger = devm_kzalloc(dev, size: sizeof(*logger), GFP_KERNEL); |
202 | if (!logger) |
203 | return -ENOMEM; |
204 | |
205 | logger->dev = dev; |
206 | logger->ec_dev = ec_dev; |
207 | |
208 | platform_set_drvdata(pdev: pd, data: logger); |
209 | |
210 | /* Retrieve PD event logs periodically */ |
211 | INIT_DELAYED_WORK(&logger->log_work, cros_usbpd_log_check); |
212 | logger->log_workqueue = create_singlethread_workqueue("cros_usbpd_log" ); |
213 | if (!logger->log_workqueue) |
214 | return -ENOMEM; |
215 | |
216 | queue_delayed_work(wq: logger->log_workqueue, dwork: &logger->log_work, |
217 | CROS_USBPD_LOG_UPDATE_DELAY); |
218 | |
219 | return 0; |
220 | } |
221 | |
222 | static void cros_usbpd_logger_remove(struct platform_device *pd) |
223 | { |
224 | struct logger_data *logger = platform_get_drvdata(pdev: pd); |
225 | |
226 | cancel_delayed_work_sync(dwork: &logger->log_work); |
227 | destroy_workqueue(wq: logger->log_workqueue); |
228 | } |
229 | |
230 | static int __maybe_unused cros_usbpd_logger_resume(struct device *dev) |
231 | { |
232 | struct logger_data *logger = dev_get_drvdata(dev); |
233 | |
234 | queue_delayed_work(wq: logger->log_workqueue, dwork: &logger->log_work, |
235 | CROS_USBPD_LOG_UPDATE_DELAY); |
236 | |
237 | return 0; |
238 | } |
239 | |
240 | static int __maybe_unused cros_usbpd_logger_suspend(struct device *dev) |
241 | { |
242 | struct logger_data *logger = dev_get_drvdata(dev); |
243 | |
244 | cancel_delayed_work_sync(dwork: &logger->log_work); |
245 | |
246 | return 0; |
247 | } |
248 | |
249 | static SIMPLE_DEV_PM_OPS(cros_usbpd_logger_pm_ops, cros_usbpd_logger_suspend, |
250 | cros_usbpd_logger_resume); |
251 | |
252 | static struct platform_driver cros_usbpd_logger_driver = { |
253 | .driver = { |
254 | .name = DRV_NAME, |
255 | .pm = &cros_usbpd_logger_pm_ops, |
256 | }, |
257 | .probe = cros_usbpd_logger_probe, |
258 | .remove_new = cros_usbpd_logger_remove, |
259 | }; |
260 | |
261 | module_platform_driver(cros_usbpd_logger_driver); |
262 | |
263 | MODULE_LICENSE("GPL v2" ); |
264 | MODULE_DESCRIPTION("Logging driver for ChromeOS EC USBPD Charger." ); |
265 | MODULE_ALIAS("platform:" DRV_NAME); |
266 | |