| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * Copyright 2019 Google LLC | |
| * | |
| * Sysfs properties to view and modify EC-controlled features on Wilco devices. | |
| * The entries will appear under /sys/bus/platform/devices/GOOG000C:00/ | |
| * | |
| * See Documentation/ABI/testing/sysfs-platform-wilco-ec for more information. | |
| */ | |
| #include <linux/device.h> | |
| #include <linux/kernel.h> | |
| #include <linux/platform_data/wilco-ec.h> | |
| #include <linux/string.h> | |
| #include <linux/sysfs.h> | |
| #include <linux/types.h> | |
| #define CMD_KB_CMOS 0x7C | |
| #define SUB_CMD_KB_CMOS_AUTO_ON 0x03 | |
| struct boot_on_ac_request { | |
| u8 cmd; /* Always CMD_KB_CMOS */ | |
| u8 reserved1; | |
| u8 sub_cmd; /* Always SUB_CMD_KB_CMOS_AUTO_ON */ | |
| u8 reserved3to5[3]; | |
| u8 val; /* Either 0 or 1 */ | |
| u8 reserved7; | |
| } __packed; | |
| #define CMD_USB_CHARGE 0x39 | |
| enum usb_charge_op { | |
| USB_CHARGE_GET = 0, | |
| USB_CHARGE_SET = 1, | |
| }; | |
| struct usb_charge_request { | |
| u8 cmd; /* Always CMD_USB_CHARGE */ | |
| u8 reserved; | |
| u8 op; /* One of enum usb_charge_op */ | |
| u8 val; /* When setting, either 0 or 1 */ | |
| } __packed; | |
| struct usb_charge_response { | |
| u8 reserved; | |
| u8 status; /* Set by EC to 0 on success, other value on failure */ | |
| u8 val; /* When getting, set by EC to either 0 or 1 */ | |
| } __packed; | |
| #define CMD_EC_INFO 0x38 | |
| enum get_ec_info_op { | |
| CMD_GET_EC_LABEL = 0, | |
| CMD_GET_EC_REV = 1, | |
| CMD_GET_EC_MODEL = 2, | |
| CMD_GET_EC_BUILD_DATE = 3, | |
| }; | |
| struct get_ec_info_req { | |
| u8 cmd; /* Always CMD_EC_INFO */ | |
| u8 reserved; | |
| u8 op; /* One of enum get_ec_info_op */ | |
| } __packed; | |
| struct get_ec_info_resp { | |
| u8 reserved[2]; | |
| char value[9]; /* __nonstring: might not be null terminated */ | |
| } __packed; | |
| static ssize_t boot_on_ac_store(struct device *dev, | |
| struct device_attribute *attr, | |
| const char *buf, size_t count) | |
| { | |
| struct wilco_ec_device *ec = dev_get_drvdata(dev); | |
| struct boot_on_ac_request rq; | |
| struct wilco_ec_message msg; | |
| int ret; | |
| u8 val; | |
| ret = kstrtou8(buf, 10, &val); | |
| if (ret < 0) | |
| return ret; | |
| if (val > 1) | |
| return -EINVAL; | |
| memset(&rq, 0, sizeof(rq)); | |
| rq.cmd = CMD_KB_CMOS; | |
| rq.sub_cmd = SUB_CMD_KB_CMOS_AUTO_ON; | |
| rq.val = val; | |
| memset(&msg, 0, sizeof(msg)); | |
| msg.type = WILCO_EC_MSG_LEGACY; | |
| msg.request_data = &rq; | |
| msg.request_size = sizeof(rq); | |
| ret = wilco_ec_mailbox(ec, &msg); | |
| if (ret < 0) | |
| return ret; | |
| return count; | |
| } | |
| static DEVICE_ATTR_WO(boot_on_ac); | |
| static ssize_t get_info(struct device *dev, char *buf, enum get_ec_info_op op) | |
| { | |
| struct wilco_ec_device *ec = dev_get_drvdata(dev); | |
| struct get_ec_info_req req = { .cmd = CMD_EC_INFO, .op = op }; | |
| struct get_ec_info_resp resp; | |
| int ret; | |
| struct wilco_ec_message msg = { | |
| .type = WILCO_EC_MSG_LEGACY, | |
| .request_data = &req, | |
| .request_size = sizeof(req), | |
| .response_data = &resp, | |
| .response_size = sizeof(resp), | |
| }; | |
| ret = wilco_ec_mailbox(ec, &msg); | |
| if (ret < 0) | |
| return ret; | |
| return scnprintf(buf, PAGE_SIZE, "%.*s\n", (int)sizeof(resp.value), | |
| (char *)&resp.value); | |
| } | |
| static ssize_t version_show(struct device *dev, struct device_attribute *attr, | |
| char *buf) | |
| { | |
| return get_info(dev, buf, CMD_GET_EC_LABEL); | |
| } | |
| static DEVICE_ATTR_RO(version); | |
| static ssize_t build_revision_show(struct device *dev, | |
| struct device_attribute *attr, char *buf) | |
| { | |
| return get_info(dev, buf, CMD_GET_EC_REV); | |
| } | |
| static DEVICE_ATTR_RO(build_revision); | |
| static ssize_t build_date_show(struct device *dev, | |
| struct device_attribute *attr, char *buf) | |
| { | |
| return get_info(dev, buf, CMD_GET_EC_BUILD_DATE); | |
| } | |
| static DEVICE_ATTR_RO(build_date); | |
| static ssize_t model_number_show(struct device *dev, | |
| struct device_attribute *attr, char *buf) | |
| { | |
| return get_info(dev, buf, CMD_GET_EC_MODEL); | |
| } | |
| static DEVICE_ATTR_RO(model_number); | |
| static int send_usb_charge(struct wilco_ec_device *ec, | |
| struct usb_charge_request *rq, | |
| struct usb_charge_response *rs) | |
| { | |
| struct wilco_ec_message msg; | |
| int ret; | |
| memset(&msg, 0, sizeof(msg)); | |
| msg.type = WILCO_EC_MSG_LEGACY; | |
| msg.request_data = rq; | |
| msg.request_size = sizeof(*rq); | |
| msg.response_data = rs; | |
| msg.response_size = sizeof(*rs); | |
| ret = wilco_ec_mailbox(ec, &msg); | |
| if (ret < 0) | |
| return ret; | |
| if (rs->status) | |
| return -EIO; | |
| return 0; | |
| } | |
| static ssize_t usb_charge_show(struct device *dev, | |
| struct device_attribute *attr, char *buf) | |
| { | |
| struct wilco_ec_device *ec = dev_get_drvdata(dev); | |
| struct usb_charge_request rq; | |
| struct usb_charge_response rs; | |
| int ret; | |
| memset(&rq, 0, sizeof(rq)); | |
| rq.cmd = CMD_USB_CHARGE; | |
| rq.op = USB_CHARGE_GET; | |
| ret = send_usb_charge(ec, &rq, &rs); | |
| if (ret < 0) | |
| return ret; | |
| return sprintf(buf, "%d\n", rs.val); | |
| } | |
| static ssize_t usb_charge_store(struct device *dev, | |
| struct device_attribute *attr, | |
| const char *buf, size_t count) | |
| { | |
| struct wilco_ec_device *ec = dev_get_drvdata(dev); | |
| struct usb_charge_request rq; | |
| struct usb_charge_response rs; | |
| int ret; | |
| u8 val; | |
| ret = kstrtou8(buf, 10, &val); | |
| if (ret < 0) | |
| return ret; | |
| if (val > 1) | |
| return -EINVAL; | |
| memset(&rq, 0, sizeof(rq)); | |
| rq.cmd = CMD_USB_CHARGE; | |
| rq.op = USB_CHARGE_SET; | |
| rq.val = val; | |
| ret = send_usb_charge(ec, &rq, &rs); | |
| if (ret < 0) | |
| return ret; | |
| return count; | |
| } | |
| static DEVICE_ATTR_RW(usb_charge); | |
| static struct attribute *wilco_dev_attrs[] = { | |
| &dev_attr_boot_on_ac.attr, | |
| &dev_attr_build_date.attr, | |
| &dev_attr_build_revision.attr, | |
| &dev_attr_model_number.attr, | |
| &dev_attr_usb_charge.attr, | |
| &dev_attr_version.attr, | |
| NULL, | |
| }; | |
| static const struct attribute_group wilco_dev_attr_group = { | |
| .attrs = wilco_dev_attrs, | |
| }; | |
| int wilco_ec_add_sysfs(struct wilco_ec_device *ec) | |
| { | |
| return sysfs_create_group(&ec->dev->kobj, &wilco_dev_attr_group); | |
| } | |
| void wilco_ec_remove_sysfs(struct wilco_ec_device *ec) | |
| { | |
| sysfs_remove_group(&ec->dev->kobj, &wilco_dev_attr_group); | |
| } |