blob: d44c435596213f694ab52c59d24563a307c125a8 [file] [log] [blame]
// 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 sysfs_emit(buf, "%.*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 sysfs_emit(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);
}