CHROMIUM: usb_pd_charger: retrieve and dump USB PD event log entries

Poll regularly the PD MCU for events and dump the entries in the kernel
log.

Signed-off-by: Vincent Palatin <vpalatin@chromium.org>
Signed-off-by: Shawn Nematbakhsh <shawnn@chromium.org>

BUG=chrome-os-partner:33248
TEST=plug and unplug type-C accessories on Samus and check kernel log.
$ dmesg | grep PDLOG
[   60.845033] PDLOG 2015/01/23 18:06:18.529 P1 SRC
[   60.851286] PDLOG 2015/01/23 18:06:19.411 P0 Disconnected
[   60.857519] PDLOG 2015/01/23 18:06:24.927 P0 SNK Charger Type-C 5002mV max 5000mV / 500mA
[   60.863699] PDLOG 2015/01/23 18:06:29.142 P0 Disconnected
[   60.869864] PDLOG 2015/01/23 18:06:29.142 P1 Disconnected
[   60.876052] PDLOG 2015/01/23 18:06:31.967 P0 SNK Charger Type-C 5166mV max 5000mV / 3000mA
[   60.882080] PDLOG 2015/01/23 18:06:31.995 P0 SNK Charger PD 4908mV max 20000mV / 3000mA
[   60.888227] PDLOG 2015/01/23 18:06:32.107 P0 SNK Charger PD 20154mV max 20000mV / 3000mA
[  182.160391] PDLOG 2015/01/31 00:00:30.929 P0 HDMI info: family:000e
chipid:0001 irom:1.0.0 fw:0.0.0
[  182.175460] PDLOG 2015/01/31 00:00:31.335 P0 DP mode enabled
Also verify logging is still functional after suspend + resume.

Change-Id: I7e7a3ed8dcbbd83f751598e6ca39be84b14c7eaa
Reviewed-on: https://chromium-review.googlesource.com/242860
Reviewed-by: Alec Berg <alecaberg@chromium.org>
Tested-by: Shawn N <shawnn@chromium.org>
Commit-Queue: Shawn N <shawnn@chromium.org>
This commit is contained in:
Vincent Palatin
2015-01-22 20:34:20 -08:00
committed by ChromeOS Commit Bot
parent c82b518b78
commit 6d7ab6ca9b

View File

@@ -18,11 +18,13 @@
* Power supply driver for ChromeOS EC based USB PD Charger.
*/
#include <linux/ktime.h>
#include <linux/module.h>
#include <linux/mfd/cros_ec.h>
#include <linux/mfd/cros_ec_commands.h>
#include <linux/platform_device.h>
#include <linux/power_supply.h>
#include <linux/rtc.h>
/*
* TODO: hack alert! Move cros_ec_dev out of here to
@@ -31,6 +33,14 @@
#include "../mfd/cros_ec_dev.h"
#define CROS_USB_PD_MAX_PORTS 8
#define CROS_USB_PD_MAX_LOG_ENTRIES 30
#define CROS_USB_PD_LOG_UPDATE_DELAY msecs_to_jiffies(60000)
/* Buffer + macro for building PDLOG string */
#define BUF_SIZE 80
#define APPEND_STRING(buf, len, str, ...) ((len) += \
snprintf((buf) + (len), max(BUF_SIZE - (len), 0), (str), ##__VA_ARGS__))
#define CHARGER_DIR_NAME "CROS_USB_PD_CHARGER%d"
#define CHARGER_DIR_NAME_LENGTH sizeof(CHARGER_DIR_NAME)
@@ -60,6 +70,8 @@ struct charger_data {
int num_charger_ports;
int num_registered_psy;
struct port_data *ports[CROS_USB_PD_MAX_PORTS];
struct delayed_work log_work;
struct workqueue_struct *log_workqueue;
};
#define EC_MAX_IN_SIZE EC_PROTO2_MAX_REQUEST_SIZE
@@ -362,6 +374,131 @@ static int cros_usb_pd_charger_is_writeable(struct power_supply *psy,
return ret;
}
static void cros_usb_pd_print_log_entry(struct ec_response_pd_log *r,
ktime_t tstamp)
{
static const char * const fault_names[] = {
"---", "OCP", "fast OCP", "OVP", "Discharge"
};
static const char * const role_names[] = {
"Disconnected", "SRC", "SNK", "SNK (not charging)"
};
static const char * const chg_type_names[] = {
"None", "PD", "Type-C", "Proprietary",
"DCP", "CDP", "SDP", "Other"
};
int i;
int role_idx, type_idx;
const char *fault, *role, *chg_type;
struct usb_chg_measures *meas;
struct mcdp_info *minfo;
struct rtc_time rt;
int len = 0;
char buf[BUF_SIZE + 1];
/* the timestamp is the number of 1024th of seconds in the past */
tstamp = ktime_sub_us(tstamp,
(uint64_t)r->timestamp << PD_LOG_TIMESTAMP_SHIFT);
rt = rtc_ktime_to_tm(tstamp);
switch (r->type) {
case PD_EVENT_MCU_CHARGE:
if (r->data & CHARGE_FLAGS_OVERRIDE)
APPEND_STRING(buf, len, "override ");
if (r->data & CHARGE_FLAGS_DELAYED_OVERRIDE)
APPEND_STRING(buf, len, "pending_override ");
role_idx = r->data & CHARGE_FLAGS_ROLE_MASK;
role = role_idx < ARRAY_SIZE(role_names) ?
role_names[role_idx] : "Unknown";
type_idx = (r->data & CHARGE_FLAGS_TYPE_MASK)
>> CHARGE_FLAGS_TYPE_SHIFT;
chg_type = type_idx < ARRAY_SIZE(chg_type_names) ?
chg_type_names[type_idx] : "???";
if ((role_idx == USB_PD_PORT_POWER_DISCONNECTED) ||
(role_idx == USB_PD_PORT_POWER_SOURCE)) {
APPEND_STRING(buf, len, "%s", role);
break;
}
meas = (struct usb_chg_measures *)r->payload;
APPEND_STRING(buf, len, "%s %s %s %dmV max %dmV / %dmA", role,
r->data & CHARGE_FLAGS_DUAL_ROLE ? "DRP" : "Charger",
chg_type,
meas->voltage_now,
meas->voltage_max,
meas->current_max);
break;
case PD_EVENT_ACC_RW_FAIL:
APPEND_STRING(buf, len, "RW signature check failed");
break;
case PD_EVENT_PS_FAULT:
fault = r->data < ARRAY_SIZE(fault_names) ? fault_names[r->data]
: "???";
APPEND_STRING(buf, len, "Power supply fault: %s", fault);
break;
case PD_EVENT_VIDEO_DP_MODE:
APPEND_STRING(buf, len, "DP mode %sabled",
(r->data == 1) ? "en" : "dis");
break;
case PD_EVENT_VIDEO_CODEC:
minfo = (struct mcdp_info *)r->payload;
APPEND_STRING(buf, len,
"HDMI info: family:%04x chipid:%04x "
"irom:%d.%d.%d fw:%d.%d.%d",
MCDP_FAMILY(minfo->family),
MCDP_CHIPID(minfo->chipid),
minfo->irom.major, minfo->irom.minor,
minfo->irom.build, minfo->fw.major,
minfo->fw.minor, minfo->fw.build);
break;
default:
APPEND_STRING(buf, len,
"Event %02x (%04x) [", r->type, r->data);
for (i = 0; i < PD_LOG_SIZE(r->size_port); i++)
APPEND_STRING(buf, len, "%02x ", r->payload[i]);
APPEND_STRING(buf, len, "]");
break;
}
pr_info("PDLOG %d/%02d/%02d %02d:%02d:%02d.%03d P%d %s\n",
rt.tm_year + 1900, rt.tm_mon + 1, rt.tm_mday,
rt.tm_hour, rt.tm_min, rt.tm_sec,
(int)(ktime_to_ms(tstamp) % MSEC_PER_SEC),
PD_LOG_PORT(r->size_port), buf);
}
static void cros_usb_pd_log_check(struct work_struct *work)
{
struct charger_data *charger = container_of(to_delayed_work(work),
struct charger_data, log_work);
struct device *dev = charger->dev;
union {
struct ec_response_pd_log r;
uint32_t words[8]; /* space for the payload */
} u;
int ret;
int entries = 0;
ktime_t now;
while (entries++ < CROS_USB_PD_MAX_LOG_ENTRIES) {
ret = ec_command(charger, EC_CMD_PD_GET_LOG_ENTRY,
NULL, 0, (uint8_t *)&u, sizeof(u));
now = ktime_get_real();
if (ret < 0) {
dev_dbg(dev, "Cannot get PD log %d\n", ret);
break;
}
if (u.r.type == PD_EVENT_NO_ENTRY)
break;
cros_usb_pd_print_log_entry(&u.r, now);
}
queue_delayed_work(charger->log_workqueue, &charger->log_work,
CROS_USB_PD_LOG_UPDATE_DELAY);
}
static char *charger_supplied_to[] = {"cros-usb_pd-charger"};
static int cros_usb_pd_charger_probe(struct platform_device *pd)
@@ -451,6 +588,13 @@ static int cros_usb_pd_charger_probe(struct platform_device *pd)
goto fail;
}
/* Retrieve PD event logs periodically */
INIT_DELAYED_WORK(&charger->log_work, cros_usb_pd_log_check);
charger->log_workqueue =
create_singlethread_workqueue("cros_usb_pd_log");
queue_delayed_work(charger->log_workqueue, &charger->log_work,
CROS_USB_PD_LOG_UPDATE_DELAY);
return 0;
fail:
@@ -487,6 +631,7 @@ static int cros_usb_pd_charger_remove(struct platform_device *pd)
power_supply_unregister(&port->psy);
devm_kfree(dev, port);
}
flush_delayed_work(&charger->log_work);
platform_set_drvdata(pd, NULL);
devm_kfree(dev, charger);
}
@@ -499,16 +644,30 @@ static int cros_usb_pd_charger_resume(struct device *dev)
struct charger_data *charger = dev_get_drvdata(dev);
int i;
if (!charger)
return 0;
dev_dbg(dev, "cros_usb_pd_charger_resume: updating power supplies\n");
for (i = 0; i < charger->num_registered_psy; i++)
power_supply_changed(&charger->ports[i]->psy);
queue_delayed_work(charger->log_workqueue, &charger->log_work,
CROS_USB_PD_LOG_UPDATE_DELAY);
return 0;
}
static int cros_usb_pd_charger_suspend(struct device *dev)
{
struct charger_data *charger = dev_get_drvdata(dev);
if (charger)
flush_delayed_work(&charger->log_work);
return 0;
}
#endif
static SIMPLE_DEV_PM_OPS(cros_usb_pd_charger_pm_ops, NULL,
cros_usb_pd_charger_resume);
static SIMPLE_DEV_PM_OPS(cros_usb_pd_charger_pm_ops,
cros_usb_pd_charger_suspend, cros_usb_pd_charger_resume);
static struct platform_driver cros_usb_pd_charger_driver = {
.driver = {