18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Copyright 2019 Google LLC
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Sysfs properties to view and modify EC-controlled features on Wilco devices.
68c2ecf20Sopenharmony_ci * The entries will appear under /sys/bus/platform/devices/GOOG000C:00/
78c2ecf20Sopenharmony_ci *
88c2ecf20Sopenharmony_ci * See Documentation/ABI/testing/sysfs-platform-wilco-ec for more information.
98c2ecf20Sopenharmony_ci */
108c2ecf20Sopenharmony_ci
118c2ecf20Sopenharmony_ci#include <linux/device.h>
128c2ecf20Sopenharmony_ci#include <linux/kernel.h>
138c2ecf20Sopenharmony_ci#include <linux/platform_data/wilco-ec.h>
148c2ecf20Sopenharmony_ci#include <linux/string.h>
158c2ecf20Sopenharmony_ci#include <linux/sysfs.h>
168c2ecf20Sopenharmony_ci#include <linux/types.h>
178c2ecf20Sopenharmony_ci
188c2ecf20Sopenharmony_ci#define CMD_KB_CMOS			0x7C
198c2ecf20Sopenharmony_ci#define SUB_CMD_KB_CMOS_AUTO_ON		0x03
208c2ecf20Sopenharmony_ci
218c2ecf20Sopenharmony_cistruct boot_on_ac_request {
228c2ecf20Sopenharmony_ci	u8 cmd;			/* Always CMD_KB_CMOS */
238c2ecf20Sopenharmony_ci	u8 reserved1;
248c2ecf20Sopenharmony_ci	u8 sub_cmd;		/* Always SUB_CMD_KB_CMOS_AUTO_ON */
258c2ecf20Sopenharmony_ci	u8 reserved3to5[3];
268c2ecf20Sopenharmony_ci	u8 val;			/* Either 0 or 1 */
278c2ecf20Sopenharmony_ci	u8 reserved7;
288c2ecf20Sopenharmony_ci} __packed;
298c2ecf20Sopenharmony_ci
308c2ecf20Sopenharmony_ci#define CMD_USB_CHARGE 0x39
318c2ecf20Sopenharmony_ci
328c2ecf20Sopenharmony_cienum usb_charge_op {
338c2ecf20Sopenharmony_ci	USB_CHARGE_GET = 0,
348c2ecf20Sopenharmony_ci	USB_CHARGE_SET = 1,
358c2ecf20Sopenharmony_ci};
368c2ecf20Sopenharmony_ci
378c2ecf20Sopenharmony_cistruct usb_charge_request {
388c2ecf20Sopenharmony_ci	u8 cmd;		/* Always CMD_USB_CHARGE */
398c2ecf20Sopenharmony_ci	u8 reserved;
408c2ecf20Sopenharmony_ci	u8 op;		/* One of enum usb_charge_op */
418c2ecf20Sopenharmony_ci	u8 val;		/* When setting, either 0 or 1 */
428c2ecf20Sopenharmony_ci} __packed;
438c2ecf20Sopenharmony_ci
448c2ecf20Sopenharmony_cistruct usb_charge_response {
458c2ecf20Sopenharmony_ci	u8 reserved;
468c2ecf20Sopenharmony_ci	u8 status;	/* Set by EC to 0 on success, other value on failure */
478c2ecf20Sopenharmony_ci	u8 val;		/* When getting, set by EC to either 0 or 1 */
488c2ecf20Sopenharmony_ci} __packed;
498c2ecf20Sopenharmony_ci
508c2ecf20Sopenharmony_ci#define CMD_EC_INFO			0x38
518c2ecf20Sopenharmony_cienum get_ec_info_op {
528c2ecf20Sopenharmony_ci	CMD_GET_EC_LABEL	= 0,
538c2ecf20Sopenharmony_ci	CMD_GET_EC_REV		= 1,
548c2ecf20Sopenharmony_ci	CMD_GET_EC_MODEL	= 2,
558c2ecf20Sopenharmony_ci	CMD_GET_EC_BUILD_DATE	= 3,
568c2ecf20Sopenharmony_ci};
578c2ecf20Sopenharmony_ci
588c2ecf20Sopenharmony_cistruct get_ec_info_req {
598c2ecf20Sopenharmony_ci	u8 cmd;			/* Always CMD_EC_INFO */
608c2ecf20Sopenharmony_ci	u8 reserved;
618c2ecf20Sopenharmony_ci	u8 op;			/* One of enum get_ec_info_op */
628c2ecf20Sopenharmony_ci} __packed;
638c2ecf20Sopenharmony_ci
648c2ecf20Sopenharmony_cistruct get_ec_info_resp {
658c2ecf20Sopenharmony_ci	u8 reserved[2];
668c2ecf20Sopenharmony_ci	char value[9]; /* __nonstring: might not be null terminated */
678c2ecf20Sopenharmony_ci} __packed;
688c2ecf20Sopenharmony_ci
698c2ecf20Sopenharmony_cistatic ssize_t boot_on_ac_store(struct device *dev,
708c2ecf20Sopenharmony_ci				struct device_attribute *attr,
718c2ecf20Sopenharmony_ci				const char *buf, size_t count)
728c2ecf20Sopenharmony_ci{
738c2ecf20Sopenharmony_ci	struct wilco_ec_device *ec = dev_get_drvdata(dev);
748c2ecf20Sopenharmony_ci	struct boot_on_ac_request rq;
758c2ecf20Sopenharmony_ci	struct wilco_ec_message msg;
768c2ecf20Sopenharmony_ci	int ret;
778c2ecf20Sopenharmony_ci	u8 val;
788c2ecf20Sopenharmony_ci
798c2ecf20Sopenharmony_ci	ret = kstrtou8(buf, 10, &val);
808c2ecf20Sopenharmony_ci	if (ret < 0)
818c2ecf20Sopenharmony_ci		return ret;
828c2ecf20Sopenharmony_ci	if (val > 1)
838c2ecf20Sopenharmony_ci		return -EINVAL;
848c2ecf20Sopenharmony_ci
858c2ecf20Sopenharmony_ci	memset(&rq, 0, sizeof(rq));
868c2ecf20Sopenharmony_ci	rq.cmd = CMD_KB_CMOS;
878c2ecf20Sopenharmony_ci	rq.sub_cmd = SUB_CMD_KB_CMOS_AUTO_ON;
888c2ecf20Sopenharmony_ci	rq.val = val;
898c2ecf20Sopenharmony_ci
908c2ecf20Sopenharmony_ci	memset(&msg, 0, sizeof(msg));
918c2ecf20Sopenharmony_ci	msg.type = WILCO_EC_MSG_LEGACY;
928c2ecf20Sopenharmony_ci	msg.request_data = &rq;
938c2ecf20Sopenharmony_ci	msg.request_size = sizeof(rq);
948c2ecf20Sopenharmony_ci	ret = wilco_ec_mailbox(ec, &msg);
958c2ecf20Sopenharmony_ci	if (ret < 0)
968c2ecf20Sopenharmony_ci		return ret;
978c2ecf20Sopenharmony_ci
988c2ecf20Sopenharmony_ci	return count;
998c2ecf20Sopenharmony_ci}
1008c2ecf20Sopenharmony_ci
1018c2ecf20Sopenharmony_cistatic DEVICE_ATTR_WO(boot_on_ac);
1028c2ecf20Sopenharmony_ci
1038c2ecf20Sopenharmony_cistatic ssize_t get_info(struct device *dev, char *buf, enum get_ec_info_op op)
1048c2ecf20Sopenharmony_ci{
1058c2ecf20Sopenharmony_ci	struct wilco_ec_device *ec = dev_get_drvdata(dev);
1068c2ecf20Sopenharmony_ci	struct get_ec_info_req req = { .cmd = CMD_EC_INFO, .op = op };
1078c2ecf20Sopenharmony_ci	struct get_ec_info_resp resp;
1088c2ecf20Sopenharmony_ci	int ret;
1098c2ecf20Sopenharmony_ci
1108c2ecf20Sopenharmony_ci	struct wilco_ec_message msg = {
1118c2ecf20Sopenharmony_ci		.type = WILCO_EC_MSG_LEGACY,
1128c2ecf20Sopenharmony_ci		.request_data = &req,
1138c2ecf20Sopenharmony_ci		.request_size = sizeof(req),
1148c2ecf20Sopenharmony_ci		.response_data = &resp,
1158c2ecf20Sopenharmony_ci		.response_size = sizeof(resp),
1168c2ecf20Sopenharmony_ci	};
1178c2ecf20Sopenharmony_ci
1188c2ecf20Sopenharmony_ci	ret = wilco_ec_mailbox(ec, &msg);
1198c2ecf20Sopenharmony_ci	if (ret < 0)
1208c2ecf20Sopenharmony_ci		return ret;
1218c2ecf20Sopenharmony_ci
1228c2ecf20Sopenharmony_ci	return scnprintf(buf, PAGE_SIZE, "%.*s\n", (int)sizeof(resp.value),
1238c2ecf20Sopenharmony_ci			 (char *)&resp.value);
1248c2ecf20Sopenharmony_ci}
1258c2ecf20Sopenharmony_ci
1268c2ecf20Sopenharmony_cistatic ssize_t version_show(struct device *dev, struct device_attribute *attr,
1278c2ecf20Sopenharmony_ci			  char *buf)
1288c2ecf20Sopenharmony_ci{
1298c2ecf20Sopenharmony_ci	return get_info(dev, buf, CMD_GET_EC_LABEL);
1308c2ecf20Sopenharmony_ci}
1318c2ecf20Sopenharmony_ci
1328c2ecf20Sopenharmony_cistatic DEVICE_ATTR_RO(version);
1338c2ecf20Sopenharmony_ci
1348c2ecf20Sopenharmony_cistatic ssize_t build_revision_show(struct device *dev,
1358c2ecf20Sopenharmony_ci				   struct device_attribute *attr, char *buf)
1368c2ecf20Sopenharmony_ci{
1378c2ecf20Sopenharmony_ci	return get_info(dev, buf, CMD_GET_EC_REV);
1388c2ecf20Sopenharmony_ci}
1398c2ecf20Sopenharmony_ci
1408c2ecf20Sopenharmony_cistatic DEVICE_ATTR_RO(build_revision);
1418c2ecf20Sopenharmony_ci
1428c2ecf20Sopenharmony_cistatic ssize_t build_date_show(struct device *dev,
1438c2ecf20Sopenharmony_ci			       struct device_attribute *attr, char *buf)
1448c2ecf20Sopenharmony_ci{
1458c2ecf20Sopenharmony_ci	return get_info(dev, buf, CMD_GET_EC_BUILD_DATE);
1468c2ecf20Sopenharmony_ci}
1478c2ecf20Sopenharmony_ci
1488c2ecf20Sopenharmony_cistatic DEVICE_ATTR_RO(build_date);
1498c2ecf20Sopenharmony_ci
1508c2ecf20Sopenharmony_cistatic ssize_t model_number_show(struct device *dev,
1518c2ecf20Sopenharmony_ci				 struct device_attribute *attr, char *buf)
1528c2ecf20Sopenharmony_ci{
1538c2ecf20Sopenharmony_ci	return get_info(dev, buf, CMD_GET_EC_MODEL);
1548c2ecf20Sopenharmony_ci}
1558c2ecf20Sopenharmony_ci
1568c2ecf20Sopenharmony_cistatic DEVICE_ATTR_RO(model_number);
1578c2ecf20Sopenharmony_ci
1588c2ecf20Sopenharmony_cistatic int send_usb_charge(struct wilco_ec_device *ec,
1598c2ecf20Sopenharmony_ci				struct usb_charge_request *rq,
1608c2ecf20Sopenharmony_ci				struct usb_charge_response *rs)
1618c2ecf20Sopenharmony_ci{
1628c2ecf20Sopenharmony_ci	struct wilco_ec_message msg;
1638c2ecf20Sopenharmony_ci	int ret;
1648c2ecf20Sopenharmony_ci
1658c2ecf20Sopenharmony_ci	memset(&msg, 0, sizeof(msg));
1668c2ecf20Sopenharmony_ci	msg.type = WILCO_EC_MSG_LEGACY;
1678c2ecf20Sopenharmony_ci	msg.request_data = rq;
1688c2ecf20Sopenharmony_ci	msg.request_size = sizeof(*rq);
1698c2ecf20Sopenharmony_ci	msg.response_data = rs;
1708c2ecf20Sopenharmony_ci	msg.response_size = sizeof(*rs);
1718c2ecf20Sopenharmony_ci	ret = wilco_ec_mailbox(ec, &msg);
1728c2ecf20Sopenharmony_ci	if (ret < 0)
1738c2ecf20Sopenharmony_ci		return ret;
1748c2ecf20Sopenharmony_ci	if (rs->status)
1758c2ecf20Sopenharmony_ci		return -EIO;
1768c2ecf20Sopenharmony_ci
1778c2ecf20Sopenharmony_ci	return 0;
1788c2ecf20Sopenharmony_ci}
1798c2ecf20Sopenharmony_ci
1808c2ecf20Sopenharmony_cistatic ssize_t usb_charge_show(struct device *dev,
1818c2ecf20Sopenharmony_ci				    struct device_attribute *attr, char *buf)
1828c2ecf20Sopenharmony_ci{
1838c2ecf20Sopenharmony_ci	struct wilco_ec_device *ec = dev_get_drvdata(dev);
1848c2ecf20Sopenharmony_ci	struct usb_charge_request rq;
1858c2ecf20Sopenharmony_ci	struct usb_charge_response rs;
1868c2ecf20Sopenharmony_ci	int ret;
1878c2ecf20Sopenharmony_ci
1888c2ecf20Sopenharmony_ci	memset(&rq, 0, sizeof(rq));
1898c2ecf20Sopenharmony_ci	rq.cmd = CMD_USB_CHARGE;
1908c2ecf20Sopenharmony_ci	rq.op = USB_CHARGE_GET;
1918c2ecf20Sopenharmony_ci
1928c2ecf20Sopenharmony_ci	ret = send_usb_charge(ec, &rq, &rs);
1938c2ecf20Sopenharmony_ci	if (ret < 0)
1948c2ecf20Sopenharmony_ci		return ret;
1958c2ecf20Sopenharmony_ci
1968c2ecf20Sopenharmony_ci	return sprintf(buf, "%d\n", rs.val);
1978c2ecf20Sopenharmony_ci}
1988c2ecf20Sopenharmony_ci
1998c2ecf20Sopenharmony_cistatic ssize_t usb_charge_store(struct device *dev,
2008c2ecf20Sopenharmony_ci				     struct device_attribute *attr,
2018c2ecf20Sopenharmony_ci				     const char *buf, size_t count)
2028c2ecf20Sopenharmony_ci{
2038c2ecf20Sopenharmony_ci	struct wilco_ec_device *ec = dev_get_drvdata(dev);
2048c2ecf20Sopenharmony_ci	struct usb_charge_request rq;
2058c2ecf20Sopenharmony_ci	struct usb_charge_response rs;
2068c2ecf20Sopenharmony_ci	int ret;
2078c2ecf20Sopenharmony_ci	u8 val;
2088c2ecf20Sopenharmony_ci
2098c2ecf20Sopenharmony_ci	ret = kstrtou8(buf, 10, &val);
2108c2ecf20Sopenharmony_ci	if (ret < 0)
2118c2ecf20Sopenharmony_ci		return ret;
2128c2ecf20Sopenharmony_ci	if (val > 1)
2138c2ecf20Sopenharmony_ci		return -EINVAL;
2148c2ecf20Sopenharmony_ci
2158c2ecf20Sopenharmony_ci	memset(&rq, 0, sizeof(rq));
2168c2ecf20Sopenharmony_ci	rq.cmd = CMD_USB_CHARGE;
2178c2ecf20Sopenharmony_ci	rq.op = USB_CHARGE_SET;
2188c2ecf20Sopenharmony_ci	rq.val = val;
2198c2ecf20Sopenharmony_ci
2208c2ecf20Sopenharmony_ci	ret = send_usb_charge(ec, &rq, &rs);
2218c2ecf20Sopenharmony_ci	if (ret < 0)
2228c2ecf20Sopenharmony_ci		return ret;
2238c2ecf20Sopenharmony_ci
2248c2ecf20Sopenharmony_ci	return count;
2258c2ecf20Sopenharmony_ci}
2268c2ecf20Sopenharmony_ci
2278c2ecf20Sopenharmony_cistatic DEVICE_ATTR_RW(usb_charge);
2288c2ecf20Sopenharmony_ci
2298c2ecf20Sopenharmony_cistatic struct attribute *wilco_dev_attrs[] = {
2308c2ecf20Sopenharmony_ci	&dev_attr_boot_on_ac.attr,
2318c2ecf20Sopenharmony_ci	&dev_attr_build_date.attr,
2328c2ecf20Sopenharmony_ci	&dev_attr_build_revision.attr,
2338c2ecf20Sopenharmony_ci	&dev_attr_model_number.attr,
2348c2ecf20Sopenharmony_ci	&dev_attr_usb_charge.attr,
2358c2ecf20Sopenharmony_ci	&dev_attr_version.attr,
2368c2ecf20Sopenharmony_ci	NULL,
2378c2ecf20Sopenharmony_ci};
2388c2ecf20Sopenharmony_ci
2398c2ecf20Sopenharmony_cistatic struct attribute_group wilco_dev_attr_group = {
2408c2ecf20Sopenharmony_ci	.attrs = wilco_dev_attrs,
2418c2ecf20Sopenharmony_ci};
2428c2ecf20Sopenharmony_ci
2438c2ecf20Sopenharmony_ciint wilco_ec_add_sysfs(struct wilco_ec_device *ec)
2448c2ecf20Sopenharmony_ci{
2458c2ecf20Sopenharmony_ci	return sysfs_create_group(&ec->dev->kobj, &wilco_dev_attr_group);
2468c2ecf20Sopenharmony_ci}
2478c2ecf20Sopenharmony_ci
2488c2ecf20Sopenharmony_civoid wilco_ec_remove_sysfs(struct wilco_ec_device *ec)
2498c2ecf20Sopenharmony_ci{
2508c2ecf20Sopenharmony_ci	sysfs_remove_group(&ec->dev->kobj, &wilco_dev_attr_group);
2518c2ecf20Sopenharmony_ci}
252