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