1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Copyright 2019 Google LLC |
4 | * |
5 | * Sysfs properties to view and modify EC-controlled features on Wilco devices. |
6 | * The entries will appear under /sys/bus/platform/devices/GOOG000C:00/ |
7 | * |
8 | * See Documentation/ABI/testing/sysfs-platform-wilco-ec for more information. |
9 | */ |
10 | |
11 | #include <linux/device.h> |
12 | #include <linux/kernel.h> |
13 | #include <linux/platform_data/wilco-ec.h> |
14 | #include <linux/string.h> |
15 | #include <linux/sysfs.h> |
16 | #include <linux/types.h> |
17 | |
18 | #define CMD_KB_CMOS 0x7C |
19 | #define SUB_CMD_KB_CMOS_AUTO_ON 0x03 |
20 | |
21 | struct boot_on_ac_request { |
22 | u8 cmd; /* Always CMD_KB_CMOS */ |
23 | u8 reserved1; |
24 | u8 sub_cmd; /* Always SUB_CMD_KB_CMOS_AUTO_ON */ |
25 | u8 reserved3to5[3]; |
26 | u8 val; /* Either 0 or 1 */ |
27 | u8 reserved7; |
28 | } __packed; |
29 | |
30 | #define CMD_USB_CHARGE 0x39 |
31 | |
32 | enum usb_charge_op { |
33 | USB_CHARGE_GET = 0, |
34 | USB_CHARGE_SET = 1, |
35 | }; |
36 | |
37 | struct usb_charge_request { |
38 | u8 cmd; /* Always CMD_USB_CHARGE */ |
39 | u8 reserved; |
40 | u8 op; /* One of enum usb_charge_op */ |
41 | u8 val; /* When setting, either 0 or 1 */ |
42 | } __packed; |
43 | |
44 | struct usb_charge_response { |
45 | u8 reserved; |
46 | u8 status; /* Set by EC to 0 on success, other value on failure */ |
47 | u8 val; /* When getting, set by EC to either 0 or 1 */ |
48 | } __packed; |
49 | |
50 | #define CMD_EC_INFO 0x38 |
51 | enum get_ec_info_op { |
52 | CMD_GET_EC_LABEL = 0, |
53 | CMD_GET_EC_REV = 1, |
54 | CMD_GET_EC_MODEL = 2, |
55 | CMD_GET_EC_BUILD_DATE = 3, |
56 | }; |
57 | |
58 | struct get_ec_info_req { |
59 | u8 cmd; /* Always CMD_EC_INFO */ |
60 | u8 reserved; |
61 | u8 op; /* One of enum get_ec_info_op */ |
62 | } __packed; |
63 | |
64 | struct get_ec_info_resp { |
65 | u8 reserved[2]; |
66 | char value[9]; /* __nonstring: might not be null terminated */ |
67 | } __packed; |
68 | |
69 | static ssize_t boot_on_ac_store(struct device *dev, |
70 | struct device_attribute *attr, |
71 | const char *buf, size_t count) |
72 | { |
73 | struct wilco_ec_device *ec = dev_get_drvdata(dev); |
74 | struct boot_on_ac_request rq; |
75 | struct wilco_ec_message msg; |
76 | int ret; |
77 | u8 val; |
78 | |
79 | ret = kstrtou8(s: buf, base: 10, res: &val); |
80 | if (ret < 0) |
81 | return ret; |
82 | if (val > 1) |
83 | return -EINVAL; |
84 | |
85 | memset(&rq, 0, sizeof(rq)); |
86 | rq.cmd = CMD_KB_CMOS; |
87 | rq.sub_cmd = SUB_CMD_KB_CMOS_AUTO_ON; |
88 | rq.val = val; |
89 | |
90 | memset(&msg, 0, sizeof(msg)); |
91 | msg.type = WILCO_EC_MSG_LEGACY; |
92 | msg.request_data = &rq; |
93 | msg.request_size = sizeof(rq); |
94 | ret = wilco_ec_mailbox(ec, msg: &msg); |
95 | if (ret < 0) |
96 | return ret; |
97 | |
98 | return count; |
99 | } |
100 | |
101 | static DEVICE_ATTR_WO(boot_on_ac); |
102 | |
103 | static ssize_t get_info(struct device *dev, char *buf, enum get_ec_info_op op) |
104 | { |
105 | struct wilco_ec_device *ec = dev_get_drvdata(dev); |
106 | struct get_ec_info_req req = { .cmd = CMD_EC_INFO, .op = op }; |
107 | struct get_ec_info_resp resp; |
108 | int ret; |
109 | |
110 | struct wilco_ec_message msg = { |
111 | .type = WILCO_EC_MSG_LEGACY, |
112 | .request_data = &req, |
113 | .request_size = sizeof(req), |
114 | .response_data = &resp, |
115 | .response_size = sizeof(resp), |
116 | }; |
117 | |
118 | ret = wilco_ec_mailbox(ec, msg: &msg); |
119 | if (ret < 0) |
120 | return ret; |
121 | |
122 | return sysfs_emit(buf, fmt: "%.*s\n" , (int)sizeof(resp.value), (char *)&resp.value); |
123 | } |
124 | |
125 | static ssize_t version_show(struct device *dev, struct device_attribute *attr, |
126 | char *buf) |
127 | { |
128 | return get_info(dev, buf, op: CMD_GET_EC_LABEL); |
129 | } |
130 | |
131 | static DEVICE_ATTR_RO(version); |
132 | |
133 | static ssize_t build_revision_show(struct device *dev, |
134 | struct device_attribute *attr, char *buf) |
135 | { |
136 | return get_info(dev, buf, op: CMD_GET_EC_REV); |
137 | } |
138 | |
139 | static DEVICE_ATTR_RO(build_revision); |
140 | |
141 | static ssize_t build_date_show(struct device *dev, |
142 | struct device_attribute *attr, char *buf) |
143 | { |
144 | return get_info(dev, buf, op: CMD_GET_EC_BUILD_DATE); |
145 | } |
146 | |
147 | static DEVICE_ATTR_RO(build_date); |
148 | |
149 | static ssize_t model_number_show(struct device *dev, |
150 | struct device_attribute *attr, char *buf) |
151 | { |
152 | return get_info(dev, buf, op: CMD_GET_EC_MODEL); |
153 | } |
154 | |
155 | static DEVICE_ATTR_RO(model_number); |
156 | |
157 | static int send_usb_charge(struct wilco_ec_device *ec, |
158 | struct usb_charge_request *rq, |
159 | struct usb_charge_response *rs) |
160 | { |
161 | struct wilco_ec_message msg; |
162 | int ret; |
163 | |
164 | memset(&msg, 0, sizeof(msg)); |
165 | msg.type = WILCO_EC_MSG_LEGACY; |
166 | msg.request_data = rq; |
167 | msg.request_size = sizeof(*rq); |
168 | msg.response_data = rs; |
169 | msg.response_size = sizeof(*rs); |
170 | ret = wilco_ec_mailbox(ec, msg: &msg); |
171 | if (ret < 0) |
172 | return ret; |
173 | if (rs->status) |
174 | return -EIO; |
175 | |
176 | return 0; |
177 | } |
178 | |
179 | static ssize_t usb_charge_show(struct device *dev, |
180 | struct device_attribute *attr, char *buf) |
181 | { |
182 | struct wilco_ec_device *ec = dev_get_drvdata(dev); |
183 | struct usb_charge_request rq; |
184 | struct usb_charge_response rs; |
185 | int ret; |
186 | |
187 | memset(&rq, 0, sizeof(rq)); |
188 | rq.cmd = CMD_USB_CHARGE; |
189 | rq.op = USB_CHARGE_GET; |
190 | |
191 | ret = send_usb_charge(ec, rq: &rq, rs: &rs); |
192 | if (ret < 0) |
193 | return ret; |
194 | |
195 | return sprintf(buf, fmt: "%d\n" , rs.val); |
196 | } |
197 | |
198 | static ssize_t usb_charge_store(struct device *dev, |
199 | struct device_attribute *attr, |
200 | const char *buf, size_t count) |
201 | { |
202 | struct wilco_ec_device *ec = dev_get_drvdata(dev); |
203 | struct usb_charge_request rq; |
204 | struct usb_charge_response rs; |
205 | int ret; |
206 | u8 val; |
207 | |
208 | ret = kstrtou8(s: buf, base: 10, res: &val); |
209 | if (ret < 0) |
210 | return ret; |
211 | if (val > 1) |
212 | return -EINVAL; |
213 | |
214 | memset(&rq, 0, sizeof(rq)); |
215 | rq.cmd = CMD_USB_CHARGE; |
216 | rq.op = USB_CHARGE_SET; |
217 | rq.val = val; |
218 | |
219 | ret = send_usb_charge(ec, rq: &rq, rs: &rs); |
220 | if (ret < 0) |
221 | return ret; |
222 | |
223 | return count; |
224 | } |
225 | |
226 | static DEVICE_ATTR_RW(usb_charge); |
227 | |
228 | static struct attribute *wilco_dev_attrs[] = { |
229 | &dev_attr_boot_on_ac.attr, |
230 | &dev_attr_build_date.attr, |
231 | &dev_attr_build_revision.attr, |
232 | &dev_attr_model_number.attr, |
233 | &dev_attr_usb_charge.attr, |
234 | &dev_attr_version.attr, |
235 | NULL, |
236 | }; |
237 | |
238 | static const struct attribute_group wilco_dev_attr_group = { |
239 | .attrs = wilco_dev_attrs, |
240 | }; |
241 | |
242 | int wilco_ec_add_sysfs(struct wilco_ec_device *ec) |
243 | { |
244 | return sysfs_create_group(kobj: &ec->dev->kobj, grp: &wilco_dev_attr_group); |
245 | } |
246 | |
247 | void wilco_ec_remove_sysfs(struct wilco_ec_device *ec) |
248 | { |
249 | sysfs_remove_group(kobj: &ec->dev->kobj, grp: &wilco_dev_attr_group); |
250 | } |
251 | |