162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright 2019 Google LLC
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Sysfs properties to view and modify EC-controlled features on Wilco devices.
662306a36Sopenharmony_ci * The entries will appear under /sys/bus/platform/devices/GOOG000C:00/
762306a36Sopenharmony_ci *
862306a36Sopenharmony_ci * See Documentation/ABI/testing/sysfs-platform-wilco-ec for more information.
962306a36Sopenharmony_ci */
1062306a36Sopenharmony_ci
1162306a36Sopenharmony_ci#include <linux/device.h>
1262306a36Sopenharmony_ci#include <linux/kernel.h>
1362306a36Sopenharmony_ci#include <linux/platform_data/wilco-ec.h>
1462306a36Sopenharmony_ci#include <linux/string.h>
1562306a36Sopenharmony_ci#include <linux/sysfs.h>
1662306a36Sopenharmony_ci#include <linux/types.h>
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci#define CMD_KB_CMOS			0x7C
1962306a36Sopenharmony_ci#define SUB_CMD_KB_CMOS_AUTO_ON		0x03
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_cistruct boot_on_ac_request {
2262306a36Sopenharmony_ci	u8 cmd;			/* Always CMD_KB_CMOS */
2362306a36Sopenharmony_ci	u8 reserved1;
2462306a36Sopenharmony_ci	u8 sub_cmd;		/* Always SUB_CMD_KB_CMOS_AUTO_ON */
2562306a36Sopenharmony_ci	u8 reserved3to5[3];
2662306a36Sopenharmony_ci	u8 val;			/* Either 0 or 1 */
2762306a36Sopenharmony_ci	u8 reserved7;
2862306a36Sopenharmony_ci} __packed;
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_ci#define CMD_USB_CHARGE 0x39
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_cienum usb_charge_op {
3362306a36Sopenharmony_ci	USB_CHARGE_GET = 0,
3462306a36Sopenharmony_ci	USB_CHARGE_SET = 1,
3562306a36Sopenharmony_ci};
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_cistruct usb_charge_request {
3862306a36Sopenharmony_ci	u8 cmd;		/* Always CMD_USB_CHARGE */
3962306a36Sopenharmony_ci	u8 reserved;
4062306a36Sopenharmony_ci	u8 op;		/* One of enum usb_charge_op */
4162306a36Sopenharmony_ci	u8 val;		/* When setting, either 0 or 1 */
4262306a36Sopenharmony_ci} __packed;
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_cistruct usb_charge_response {
4562306a36Sopenharmony_ci	u8 reserved;
4662306a36Sopenharmony_ci	u8 status;	/* Set by EC to 0 on success, other value on failure */
4762306a36Sopenharmony_ci	u8 val;		/* When getting, set by EC to either 0 or 1 */
4862306a36Sopenharmony_ci} __packed;
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_ci#define CMD_EC_INFO			0x38
5162306a36Sopenharmony_cienum get_ec_info_op {
5262306a36Sopenharmony_ci	CMD_GET_EC_LABEL	= 0,
5362306a36Sopenharmony_ci	CMD_GET_EC_REV		= 1,
5462306a36Sopenharmony_ci	CMD_GET_EC_MODEL	= 2,
5562306a36Sopenharmony_ci	CMD_GET_EC_BUILD_DATE	= 3,
5662306a36Sopenharmony_ci};
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_cistruct get_ec_info_req {
5962306a36Sopenharmony_ci	u8 cmd;			/* Always CMD_EC_INFO */
6062306a36Sopenharmony_ci	u8 reserved;
6162306a36Sopenharmony_ci	u8 op;			/* One of enum get_ec_info_op */
6262306a36Sopenharmony_ci} __packed;
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_cistruct get_ec_info_resp {
6562306a36Sopenharmony_ci	u8 reserved[2];
6662306a36Sopenharmony_ci	char value[9]; /* __nonstring: might not be null terminated */
6762306a36Sopenharmony_ci} __packed;
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_cistatic ssize_t boot_on_ac_store(struct device *dev,
7062306a36Sopenharmony_ci				struct device_attribute *attr,
7162306a36Sopenharmony_ci				const char *buf, size_t count)
7262306a36Sopenharmony_ci{
7362306a36Sopenharmony_ci	struct wilco_ec_device *ec = dev_get_drvdata(dev);
7462306a36Sopenharmony_ci	struct boot_on_ac_request rq;
7562306a36Sopenharmony_ci	struct wilco_ec_message msg;
7662306a36Sopenharmony_ci	int ret;
7762306a36Sopenharmony_ci	u8 val;
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_ci	ret = kstrtou8(buf, 10, &val);
8062306a36Sopenharmony_ci	if (ret < 0)
8162306a36Sopenharmony_ci		return ret;
8262306a36Sopenharmony_ci	if (val > 1)
8362306a36Sopenharmony_ci		return -EINVAL;
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_ci	memset(&rq, 0, sizeof(rq));
8662306a36Sopenharmony_ci	rq.cmd = CMD_KB_CMOS;
8762306a36Sopenharmony_ci	rq.sub_cmd = SUB_CMD_KB_CMOS_AUTO_ON;
8862306a36Sopenharmony_ci	rq.val = val;
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_ci	memset(&msg, 0, sizeof(msg));
9162306a36Sopenharmony_ci	msg.type = WILCO_EC_MSG_LEGACY;
9262306a36Sopenharmony_ci	msg.request_data = &rq;
9362306a36Sopenharmony_ci	msg.request_size = sizeof(rq);
9462306a36Sopenharmony_ci	ret = wilco_ec_mailbox(ec, &msg);
9562306a36Sopenharmony_ci	if (ret < 0)
9662306a36Sopenharmony_ci		return ret;
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci	return count;
9962306a36Sopenharmony_ci}
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_cistatic DEVICE_ATTR_WO(boot_on_ac);
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_cistatic ssize_t get_info(struct device *dev, char *buf, enum get_ec_info_op op)
10462306a36Sopenharmony_ci{
10562306a36Sopenharmony_ci	struct wilco_ec_device *ec = dev_get_drvdata(dev);
10662306a36Sopenharmony_ci	struct get_ec_info_req req = { .cmd = CMD_EC_INFO, .op = op };
10762306a36Sopenharmony_ci	struct get_ec_info_resp resp;
10862306a36Sopenharmony_ci	int ret;
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ci	struct wilco_ec_message msg = {
11162306a36Sopenharmony_ci		.type = WILCO_EC_MSG_LEGACY,
11262306a36Sopenharmony_ci		.request_data = &req,
11362306a36Sopenharmony_ci		.request_size = sizeof(req),
11462306a36Sopenharmony_ci		.response_data = &resp,
11562306a36Sopenharmony_ci		.response_size = sizeof(resp),
11662306a36Sopenharmony_ci	};
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_ci	ret = wilco_ec_mailbox(ec, &msg);
11962306a36Sopenharmony_ci	if (ret < 0)
12062306a36Sopenharmony_ci		return ret;
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_ci	return sysfs_emit(buf, "%.*s\n", (int)sizeof(resp.value), (char *)&resp.value);
12362306a36Sopenharmony_ci}
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_cistatic ssize_t version_show(struct device *dev, struct device_attribute *attr,
12662306a36Sopenharmony_ci			  char *buf)
12762306a36Sopenharmony_ci{
12862306a36Sopenharmony_ci	return get_info(dev, buf, CMD_GET_EC_LABEL);
12962306a36Sopenharmony_ci}
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_cistatic DEVICE_ATTR_RO(version);
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_cistatic ssize_t build_revision_show(struct device *dev,
13462306a36Sopenharmony_ci				   struct device_attribute *attr, char *buf)
13562306a36Sopenharmony_ci{
13662306a36Sopenharmony_ci	return get_info(dev, buf, CMD_GET_EC_REV);
13762306a36Sopenharmony_ci}
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_cistatic DEVICE_ATTR_RO(build_revision);
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_cistatic ssize_t build_date_show(struct device *dev,
14262306a36Sopenharmony_ci			       struct device_attribute *attr, char *buf)
14362306a36Sopenharmony_ci{
14462306a36Sopenharmony_ci	return get_info(dev, buf, CMD_GET_EC_BUILD_DATE);
14562306a36Sopenharmony_ci}
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_cistatic DEVICE_ATTR_RO(build_date);
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_cistatic ssize_t model_number_show(struct device *dev,
15062306a36Sopenharmony_ci				 struct device_attribute *attr, char *buf)
15162306a36Sopenharmony_ci{
15262306a36Sopenharmony_ci	return get_info(dev, buf, CMD_GET_EC_MODEL);
15362306a36Sopenharmony_ci}
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_cistatic DEVICE_ATTR_RO(model_number);
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_cistatic int send_usb_charge(struct wilco_ec_device *ec,
15862306a36Sopenharmony_ci				struct usb_charge_request *rq,
15962306a36Sopenharmony_ci				struct usb_charge_response *rs)
16062306a36Sopenharmony_ci{
16162306a36Sopenharmony_ci	struct wilco_ec_message msg;
16262306a36Sopenharmony_ci	int ret;
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_ci	memset(&msg, 0, sizeof(msg));
16562306a36Sopenharmony_ci	msg.type = WILCO_EC_MSG_LEGACY;
16662306a36Sopenharmony_ci	msg.request_data = rq;
16762306a36Sopenharmony_ci	msg.request_size = sizeof(*rq);
16862306a36Sopenharmony_ci	msg.response_data = rs;
16962306a36Sopenharmony_ci	msg.response_size = sizeof(*rs);
17062306a36Sopenharmony_ci	ret = wilco_ec_mailbox(ec, &msg);
17162306a36Sopenharmony_ci	if (ret < 0)
17262306a36Sopenharmony_ci		return ret;
17362306a36Sopenharmony_ci	if (rs->status)
17462306a36Sopenharmony_ci		return -EIO;
17562306a36Sopenharmony_ci
17662306a36Sopenharmony_ci	return 0;
17762306a36Sopenharmony_ci}
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_cistatic ssize_t usb_charge_show(struct device *dev,
18062306a36Sopenharmony_ci				    struct device_attribute *attr, char *buf)
18162306a36Sopenharmony_ci{
18262306a36Sopenharmony_ci	struct wilco_ec_device *ec = dev_get_drvdata(dev);
18362306a36Sopenharmony_ci	struct usb_charge_request rq;
18462306a36Sopenharmony_ci	struct usb_charge_response rs;
18562306a36Sopenharmony_ci	int ret;
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_ci	memset(&rq, 0, sizeof(rq));
18862306a36Sopenharmony_ci	rq.cmd = CMD_USB_CHARGE;
18962306a36Sopenharmony_ci	rq.op = USB_CHARGE_GET;
19062306a36Sopenharmony_ci
19162306a36Sopenharmony_ci	ret = send_usb_charge(ec, &rq, &rs);
19262306a36Sopenharmony_ci	if (ret < 0)
19362306a36Sopenharmony_ci		return ret;
19462306a36Sopenharmony_ci
19562306a36Sopenharmony_ci	return sprintf(buf, "%d\n", rs.val);
19662306a36Sopenharmony_ci}
19762306a36Sopenharmony_ci
19862306a36Sopenharmony_cistatic ssize_t usb_charge_store(struct device *dev,
19962306a36Sopenharmony_ci				     struct device_attribute *attr,
20062306a36Sopenharmony_ci				     const char *buf, size_t count)
20162306a36Sopenharmony_ci{
20262306a36Sopenharmony_ci	struct wilco_ec_device *ec = dev_get_drvdata(dev);
20362306a36Sopenharmony_ci	struct usb_charge_request rq;
20462306a36Sopenharmony_ci	struct usb_charge_response rs;
20562306a36Sopenharmony_ci	int ret;
20662306a36Sopenharmony_ci	u8 val;
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_ci	ret = kstrtou8(buf, 10, &val);
20962306a36Sopenharmony_ci	if (ret < 0)
21062306a36Sopenharmony_ci		return ret;
21162306a36Sopenharmony_ci	if (val > 1)
21262306a36Sopenharmony_ci		return -EINVAL;
21362306a36Sopenharmony_ci
21462306a36Sopenharmony_ci	memset(&rq, 0, sizeof(rq));
21562306a36Sopenharmony_ci	rq.cmd = CMD_USB_CHARGE;
21662306a36Sopenharmony_ci	rq.op = USB_CHARGE_SET;
21762306a36Sopenharmony_ci	rq.val = val;
21862306a36Sopenharmony_ci
21962306a36Sopenharmony_ci	ret = send_usb_charge(ec, &rq, &rs);
22062306a36Sopenharmony_ci	if (ret < 0)
22162306a36Sopenharmony_ci		return ret;
22262306a36Sopenharmony_ci
22362306a36Sopenharmony_ci	return count;
22462306a36Sopenharmony_ci}
22562306a36Sopenharmony_ci
22662306a36Sopenharmony_cistatic DEVICE_ATTR_RW(usb_charge);
22762306a36Sopenharmony_ci
22862306a36Sopenharmony_cistatic struct attribute *wilco_dev_attrs[] = {
22962306a36Sopenharmony_ci	&dev_attr_boot_on_ac.attr,
23062306a36Sopenharmony_ci	&dev_attr_build_date.attr,
23162306a36Sopenharmony_ci	&dev_attr_build_revision.attr,
23262306a36Sopenharmony_ci	&dev_attr_model_number.attr,
23362306a36Sopenharmony_ci	&dev_attr_usb_charge.attr,
23462306a36Sopenharmony_ci	&dev_attr_version.attr,
23562306a36Sopenharmony_ci	NULL,
23662306a36Sopenharmony_ci};
23762306a36Sopenharmony_ci
23862306a36Sopenharmony_cistatic const struct attribute_group wilco_dev_attr_group = {
23962306a36Sopenharmony_ci	.attrs = wilco_dev_attrs,
24062306a36Sopenharmony_ci};
24162306a36Sopenharmony_ci
24262306a36Sopenharmony_ciint wilco_ec_add_sysfs(struct wilco_ec_device *ec)
24362306a36Sopenharmony_ci{
24462306a36Sopenharmony_ci	return sysfs_create_group(&ec->dev->kobj, &wilco_dev_attr_group);
24562306a36Sopenharmony_ci}
24662306a36Sopenharmony_ci
24762306a36Sopenharmony_civoid wilco_ec_remove_sysfs(struct wilco_ec_device *ec)
24862306a36Sopenharmony_ci{
24962306a36Sopenharmony_ci	sysfs_remove_group(&ec->dev->kobj, &wilco_dev_attr_group);
25062306a36Sopenharmony_ci}
251