18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Logging driver for ChromeOS EC based USBPD Charger. 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright 2018 Google LLC. 68c2ecf20Sopenharmony_ci */ 78c2ecf20Sopenharmony_ci 88c2ecf20Sopenharmony_ci#include <linux/ktime.h> 98c2ecf20Sopenharmony_ci#include <linux/math64.h> 108c2ecf20Sopenharmony_ci#include <linux/module.h> 118c2ecf20Sopenharmony_ci#include <linux/platform_data/cros_ec_commands.h> 128c2ecf20Sopenharmony_ci#include <linux/platform_data/cros_ec_proto.h> 138c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 148c2ecf20Sopenharmony_ci#include <linux/rtc.h> 158c2ecf20Sopenharmony_ci 168c2ecf20Sopenharmony_ci#define DRV_NAME "cros-usbpd-logger" 178c2ecf20Sopenharmony_ci 188c2ecf20Sopenharmony_ci#define CROS_USBPD_MAX_LOG_ENTRIES 30 198c2ecf20Sopenharmony_ci#define CROS_USBPD_LOG_UPDATE_DELAY msecs_to_jiffies(60000) 208c2ecf20Sopenharmony_ci#define CROS_USBPD_DATA_SIZE 16 218c2ecf20Sopenharmony_ci#define CROS_USBPD_LOG_RESP_SIZE (sizeof(struct ec_response_pd_log) + \ 228c2ecf20Sopenharmony_ci CROS_USBPD_DATA_SIZE) 238c2ecf20Sopenharmony_ci#define CROS_USBPD_BUFFER_SIZE (sizeof(struct cros_ec_command) + \ 248c2ecf20Sopenharmony_ci CROS_USBPD_LOG_RESP_SIZE) 258c2ecf20Sopenharmony_ci/* Buffer for building the PDLOG string */ 268c2ecf20Sopenharmony_ci#define BUF_SIZE 80 278c2ecf20Sopenharmony_ci 288c2ecf20Sopenharmony_cistruct logger_data { 298c2ecf20Sopenharmony_ci struct device *dev; 308c2ecf20Sopenharmony_ci struct cros_ec_dev *ec_dev; 318c2ecf20Sopenharmony_ci u8 ec_buffer[CROS_USBPD_BUFFER_SIZE]; 328c2ecf20Sopenharmony_ci struct delayed_work log_work; 338c2ecf20Sopenharmony_ci struct workqueue_struct *log_workqueue; 348c2ecf20Sopenharmony_ci}; 358c2ecf20Sopenharmony_ci 368c2ecf20Sopenharmony_cistatic const char * const chg_type_names[] = { 378c2ecf20Sopenharmony_ci "None", "PD", "Type-C", "Proprietary", "DCP", "CDP", "SDP", 388c2ecf20Sopenharmony_ci "Other", "VBUS" 398c2ecf20Sopenharmony_ci}; 408c2ecf20Sopenharmony_ci 418c2ecf20Sopenharmony_cistatic const char * const role_names[] = { 428c2ecf20Sopenharmony_ci "Disconnected", "SRC", "SNK", "SNK (not charging)" 438c2ecf20Sopenharmony_ci}; 448c2ecf20Sopenharmony_ci 458c2ecf20Sopenharmony_cistatic const char * const fault_names[] = { 468c2ecf20Sopenharmony_ci "---", "OCP", "fast OCP", "OVP", "Discharge" 478c2ecf20Sopenharmony_ci}; 488c2ecf20Sopenharmony_ci 498c2ecf20Sopenharmony_ci__printf(3, 4) 508c2ecf20Sopenharmony_cistatic int append_str(char *buf, int pos, const char *fmt, ...) 518c2ecf20Sopenharmony_ci{ 528c2ecf20Sopenharmony_ci va_list args; 538c2ecf20Sopenharmony_ci int i; 548c2ecf20Sopenharmony_ci 558c2ecf20Sopenharmony_ci va_start(args, fmt); 568c2ecf20Sopenharmony_ci i = vsnprintf(buf + pos, BUF_SIZE - pos, fmt, args); 578c2ecf20Sopenharmony_ci va_end(args); 588c2ecf20Sopenharmony_ci 598c2ecf20Sopenharmony_ci return i; 608c2ecf20Sopenharmony_ci} 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_cistatic struct ec_response_pd_log *ec_get_log_entry(struct logger_data *logger) 638c2ecf20Sopenharmony_ci{ 648c2ecf20Sopenharmony_ci struct cros_ec_dev *ec_dev = logger->ec_dev; 658c2ecf20Sopenharmony_ci struct cros_ec_command *msg; 668c2ecf20Sopenharmony_ci int ret; 678c2ecf20Sopenharmony_ci 688c2ecf20Sopenharmony_ci msg = (struct cros_ec_command *)logger->ec_buffer; 698c2ecf20Sopenharmony_ci 708c2ecf20Sopenharmony_ci msg->command = ec_dev->cmd_offset + EC_CMD_PD_GET_LOG_ENTRY; 718c2ecf20Sopenharmony_ci msg->insize = CROS_USBPD_LOG_RESP_SIZE; 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_ci ret = cros_ec_cmd_xfer_status(ec_dev->ec_dev, msg); 748c2ecf20Sopenharmony_ci if (ret < 0) 758c2ecf20Sopenharmony_ci return ERR_PTR(ret); 768c2ecf20Sopenharmony_ci 778c2ecf20Sopenharmony_ci return (struct ec_response_pd_log *)msg->data; 788c2ecf20Sopenharmony_ci} 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_cistatic void cros_usbpd_print_log_entry(struct ec_response_pd_log *r, 818c2ecf20Sopenharmony_ci ktime_t tstamp) 828c2ecf20Sopenharmony_ci{ 838c2ecf20Sopenharmony_ci const char *fault, *role, *chg_type; 848c2ecf20Sopenharmony_ci struct usb_chg_measures *meas; 858c2ecf20Sopenharmony_ci struct mcdp_info *minfo; 868c2ecf20Sopenharmony_ci int role_idx, type_idx; 878c2ecf20Sopenharmony_ci char buf[BUF_SIZE + 1]; 888c2ecf20Sopenharmony_ci struct rtc_time rt; 898c2ecf20Sopenharmony_ci int len = 0; 908c2ecf20Sopenharmony_ci s32 rem; 918c2ecf20Sopenharmony_ci int i; 928c2ecf20Sopenharmony_ci 938c2ecf20Sopenharmony_ci /* The timestamp is the number of 1024th of seconds in the past */ 948c2ecf20Sopenharmony_ci tstamp = ktime_sub_us(tstamp, r->timestamp << PD_LOG_TIMESTAMP_SHIFT); 958c2ecf20Sopenharmony_ci rt = rtc_ktime_to_tm(tstamp); 968c2ecf20Sopenharmony_ci 978c2ecf20Sopenharmony_ci switch (r->type) { 988c2ecf20Sopenharmony_ci case PD_EVENT_MCU_CHARGE: 998c2ecf20Sopenharmony_ci if (r->data & CHARGE_FLAGS_OVERRIDE) 1008c2ecf20Sopenharmony_ci len += append_str(buf, len, "override "); 1018c2ecf20Sopenharmony_ci 1028c2ecf20Sopenharmony_ci if (r->data & CHARGE_FLAGS_DELAYED_OVERRIDE) 1038c2ecf20Sopenharmony_ci len += append_str(buf, len, "pending_override "); 1048c2ecf20Sopenharmony_ci 1058c2ecf20Sopenharmony_ci role_idx = r->data & CHARGE_FLAGS_ROLE_MASK; 1068c2ecf20Sopenharmony_ci role = role_idx < ARRAY_SIZE(role_names) ? 1078c2ecf20Sopenharmony_ci role_names[role_idx] : "Unknown"; 1088c2ecf20Sopenharmony_ci 1098c2ecf20Sopenharmony_ci type_idx = (r->data & CHARGE_FLAGS_TYPE_MASK) 1108c2ecf20Sopenharmony_ci >> CHARGE_FLAGS_TYPE_SHIFT; 1118c2ecf20Sopenharmony_ci 1128c2ecf20Sopenharmony_ci chg_type = type_idx < ARRAY_SIZE(chg_type_names) ? 1138c2ecf20Sopenharmony_ci chg_type_names[type_idx] : "???"; 1148c2ecf20Sopenharmony_ci 1158c2ecf20Sopenharmony_ci if (role_idx == USB_PD_PORT_POWER_DISCONNECTED || 1168c2ecf20Sopenharmony_ci role_idx == USB_PD_PORT_POWER_SOURCE) { 1178c2ecf20Sopenharmony_ci len += append_str(buf, len, "%s", role); 1188c2ecf20Sopenharmony_ci break; 1198c2ecf20Sopenharmony_ci } 1208c2ecf20Sopenharmony_ci 1218c2ecf20Sopenharmony_ci meas = (struct usb_chg_measures *)r->payload; 1228c2ecf20Sopenharmony_ci len += append_str(buf, len, "%s %s %s %dmV max %dmV / %dmA", 1238c2ecf20Sopenharmony_ci role, r->data & CHARGE_FLAGS_DUAL_ROLE ? 1248c2ecf20Sopenharmony_ci "DRP" : "Charger", 1258c2ecf20Sopenharmony_ci chg_type, meas->voltage_now, 1268c2ecf20Sopenharmony_ci meas->voltage_max, meas->current_max); 1278c2ecf20Sopenharmony_ci break; 1288c2ecf20Sopenharmony_ci case PD_EVENT_ACC_RW_FAIL: 1298c2ecf20Sopenharmony_ci len += append_str(buf, len, "RW signature check failed"); 1308c2ecf20Sopenharmony_ci break; 1318c2ecf20Sopenharmony_ci case PD_EVENT_PS_FAULT: 1328c2ecf20Sopenharmony_ci fault = r->data < ARRAY_SIZE(fault_names) ? fault_names[r->data] 1338c2ecf20Sopenharmony_ci : "???"; 1348c2ecf20Sopenharmony_ci len += append_str(buf, len, "Power supply fault: %s", fault); 1358c2ecf20Sopenharmony_ci break; 1368c2ecf20Sopenharmony_ci case PD_EVENT_VIDEO_DP_MODE: 1378c2ecf20Sopenharmony_ci len += append_str(buf, len, "DP mode %sabled", r->data == 1 ? 1388c2ecf20Sopenharmony_ci "en" : "dis"); 1398c2ecf20Sopenharmony_ci break; 1408c2ecf20Sopenharmony_ci case PD_EVENT_VIDEO_CODEC: 1418c2ecf20Sopenharmony_ci minfo = (struct mcdp_info *)r->payload; 1428c2ecf20Sopenharmony_ci len += append_str(buf, len, "HDMI info: family:%04x chipid:%04x ", 1438c2ecf20Sopenharmony_ci MCDP_FAMILY(minfo->family), 1448c2ecf20Sopenharmony_ci MCDP_CHIPID(minfo->chipid)); 1458c2ecf20Sopenharmony_ci len += append_str(buf, len, "irom:%d.%d.%d fw:%d.%d.%d", 1468c2ecf20Sopenharmony_ci minfo->irom.major, minfo->irom.minor, 1478c2ecf20Sopenharmony_ci minfo->irom.build, minfo->fw.major, 1488c2ecf20Sopenharmony_ci minfo->fw.minor, minfo->fw.build); 1498c2ecf20Sopenharmony_ci break; 1508c2ecf20Sopenharmony_ci default: 1518c2ecf20Sopenharmony_ci len += append_str(buf, len, "Event %02x (%04x) [", r->type, 1528c2ecf20Sopenharmony_ci r->data); 1538c2ecf20Sopenharmony_ci 1548c2ecf20Sopenharmony_ci for (i = 0; i < PD_LOG_SIZE(r->size_port); i++) 1558c2ecf20Sopenharmony_ci len += append_str(buf, len, "%02x ", r->payload[i]); 1568c2ecf20Sopenharmony_ci 1578c2ecf20Sopenharmony_ci len += append_str(buf, len, "]"); 1588c2ecf20Sopenharmony_ci break; 1598c2ecf20Sopenharmony_ci } 1608c2ecf20Sopenharmony_ci 1618c2ecf20Sopenharmony_ci div_s64_rem(ktime_to_ms(tstamp), MSEC_PER_SEC, &rem); 1628c2ecf20Sopenharmony_ci pr_info("PDLOG %d/%02d/%02d %02d:%02d:%02d.%03d P%d %s\n", 1638c2ecf20Sopenharmony_ci rt.tm_year + 1900, rt.tm_mon + 1, rt.tm_mday, 1648c2ecf20Sopenharmony_ci rt.tm_hour, rt.tm_min, rt.tm_sec, rem, 1658c2ecf20Sopenharmony_ci PD_LOG_PORT(r->size_port), buf); 1668c2ecf20Sopenharmony_ci} 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_cistatic void cros_usbpd_log_check(struct work_struct *work) 1698c2ecf20Sopenharmony_ci{ 1708c2ecf20Sopenharmony_ci struct logger_data *logger = container_of(to_delayed_work(work), 1718c2ecf20Sopenharmony_ci struct logger_data, 1728c2ecf20Sopenharmony_ci log_work); 1738c2ecf20Sopenharmony_ci struct device *dev = logger->dev; 1748c2ecf20Sopenharmony_ci struct ec_response_pd_log *r; 1758c2ecf20Sopenharmony_ci int entries = 0; 1768c2ecf20Sopenharmony_ci ktime_t now; 1778c2ecf20Sopenharmony_ci 1788c2ecf20Sopenharmony_ci while (entries++ < CROS_USBPD_MAX_LOG_ENTRIES) { 1798c2ecf20Sopenharmony_ci r = ec_get_log_entry(logger); 1808c2ecf20Sopenharmony_ci now = ktime_get_real(); 1818c2ecf20Sopenharmony_ci if (IS_ERR(r)) { 1828c2ecf20Sopenharmony_ci dev_dbg(dev, "Cannot get PD log %ld\n", PTR_ERR(r)); 1838c2ecf20Sopenharmony_ci break; 1848c2ecf20Sopenharmony_ci } 1858c2ecf20Sopenharmony_ci if (r->type == PD_EVENT_NO_ENTRY) 1868c2ecf20Sopenharmony_ci break; 1878c2ecf20Sopenharmony_ci 1888c2ecf20Sopenharmony_ci cros_usbpd_print_log_entry(r, now); 1898c2ecf20Sopenharmony_ci } 1908c2ecf20Sopenharmony_ci 1918c2ecf20Sopenharmony_ci queue_delayed_work(logger->log_workqueue, &logger->log_work, 1928c2ecf20Sopenharmony_ci CROS_USBPD_LOG_UPDATE_DELAY); 1938c2ecf20Sopenharmony_ci} 1948c2ecf20Sopenharmony_ci 1958c2ecf20Sopenharmony_cistatic int cros_usbpd_logger_probe(struct platform_device *pd) 1968c2ecf20Sopenharmony_ci{ 1978c2ecf20Sopenharmony_ci struct cros_ec_dev *ec_dev = dev_get_drvdata(pd->dev.parent); 1988c2ecf20Sopenharmony_ci struct device *dev = &pd->dev; 1998c2ecf20Sopenharmony_ci struct logger_data *logger; 2008c2ecf20Sopenharmony_ci 2018c2ecf20Sopenharmony_ci logger = devm_kzalloc(dev, sizeof(*logger), GFP_KERNEL); 2028c2ecf20Sopenharmony_ci if (!logger) 2038c2ecf20Sopenharmony_ci return -ENOMEM; 2048c2ecf20Sopenharmony_ci 2058c2ecf20Sopenharmony_ci logger->dev = dev; 2068c2ecf20Sopenharmony_ci logger->ec_dev = ec_dev; 2078c2ecf20Sopenharmony_ci 2088c2ecf20Sopenharmony_ci platform_set_drvdata(pd, logger); 2098c2ecf20Sopenharmony_ci 2108c2ecf20Sopenharmony_ci /* Retrieve PD event logs periodically */ 2118c2ecf20Sopenharmony_ci INIT_DELAYED_WORK(&logger->log_work, cros_usbpd_log_check); 2128c2ecf20Sopenharmony_ci logger->log_workqueue = create_singlethread_workqueue("cros_usbpd_log"); 2138c2ecf20Sopenharmony_ci if (!logger->log_workqueue) 2148c2ecf20Sopenharmony_ci return -ENOMEM; 2158c2ecf20Sopenharmony_ci 2168c2ecf20Sopenharmony_ci queue_delayed_work(logger->log_workqueue, &logger->log_work, 2178c2ecf20Sopenharmony_ci CROS_USBPD_LOG_UPDATE_DELAY); 2188c2ecf20Sopenharmony_ci 2198c2ecf20Sopenharmony_ci return 0; 2208c2ecf20Sopenharmony_ci} 2218c2ecf20Sopenharmony_ci 2228c2ecf20Sopenharmony_cistatic int cros_usbpd_logger_remove(struct platform_device *pd) 2238c2ecf20Sopenharmony_ci{ 2248c2ecf20Sopenharmony_ci struct logger_data *logger = platform_get_drvdata(pd); 2258c2ecf20Sopenharmony_ci 2268c2ecf20Sopenharmony_ci cancel_delayed_work_sync(&logger->log_work); 2278c2ecf20Sopenharmony_ci destroy_workqueue(logger->log_workqueue); 2288c2ecf20Sopenharmony_ci 2298c2ecf20Sopenharmony_ci return 0; 2308c2ecf20Sopenharmony_ci} 2318c2ecf20Sopenharmony_ci 2328c2ecf20Sopenharmony_cistatic int __maybe_unused cros_usbpd_logger_resume(struct device *dev) 2338c2ecf20Sopenharmony_ci{ 2348c2ecf20Sopenharmony_ci struct logger_data *logger = dev_get_drvdata(dev); 2358c2ecf20Sopenharmony_ci 2368c2ecf20Sopenharmony_ci queue_delayed_work(logger->log_workqueue, &logger->log_work, 2378c2ecf20Sopenharmony_ci CROS_USBPD_LOG_UPDATE_DELAY); 2388c2ecf20Sopenharmony_ci 2398c2ecf20Sopenharmony_ci return 0; 2408c2ecf20Sopenharmony_ci} 2418c2ecf20Sopenharmony_ci 2428c2ecf20Sopenharmony_cistatic int __maybe_unused cros_usbpd_logger_suspend(struct device *dev) 2438c2ecf20Sopenharmony_ci{ 2448c2ecf20Sopenharmony_ci struct logger_data *logger = dev_get_drvdata(dev); 2458c2ecf20Sopenharmony_ci 2468c2ecf20Sopenharmony_ci cancel_delayed_work_sync(&logger->log_work); 2478c2ecf20Sopenharmony_ci 2488c2ecf20Sopenharmony_ci return 0; 2498c2ecf20Sopenharmony_ci} 2508c2ecf20Sopenharmony_ci 2518c2ecf20Sopenharmony_cistatic SIMPLE_DEV_PM_OPS(cros_usbpd_logger_pm_ops, cros_usbpd_logger_suspend, 2528c2ecf20Sopenharmony_ci cros_usbpd_logger_resume); 2538c2ecf20Sopenharmony_ci 2548c2ecf20Sopenharmony_cistatic struct platform_driver cros_usbpd_logger_driver = { 2558c2ecf20Sopenharmony_ci .driver = { 2568c2ecf20Sopenharmony_ci .name = DRV_NAME, 2578c2ecf20Sopenharmony_ci .pm = &cros_usbpd_logger_pm_ops, 2588c2ecf20Sopenharmony_ci }, 2598c2ecf20Sopenharmony_ci .probe = cros_usbpd_logger_probe, 2608c2ecf20Sopenharmony_ci .remove = cros_usbpd_logger_remove, 2618c2ecf20Sopenharmony_ci}; 2628c2ecf20Sopenharmony_ci 2638c2ecf20Sopenharmony_cimodule_platform_driver(cros_usbpd_logger_driver); 2648c2ecf20Sopenharmony_ci 2658c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 2668c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Logging driver for ChromeOS EC USBPD Charger."); 2678c2ecf20Sopenharmony_ciMODULE_ALIAS("platform:" DRV_NAME); 268