1*4882a593Smuzhiyun // SPDX-License-Identifier: GPL-2.0+
2*4882a593Smuzhiyun // Expose the ChromeOS EC through sysfs
3*4882a593Smuzhiyun //
4*4882a593Smuzhiyun // Copyright (C) 2014 Google, Inc.
5*4882a593Smuzhiyun
6*4882a593Smuzhiyun #include <linux/ctype.h>
7*4882a593Smuzhiyun #include <linux/delay.h>
8*4882a593Smuzhiyun #include <linux/device.h>
9*4882a593Smuzhiyun #include <linux/fs.h>
10*4882a593Smuzhiyun #include <linux/kobject.h>
11*4882a593Smuzhiyun #include <linux/module.h>
12*4882a593Smuzhiyun #include <linux/platform_data/cros_ec_commands.h>
13*4882a593Smuzhiyun #include <linux/platform_data/cros_ec_proto.h>
14*4882a593Smuzhiyun #include <linux/platform_device.h>
15*4882a593Smuzhiyun #include <linux/printk.h>
16*4882a593Smuzhiyun #include <linux/slab.h>
17*4882a593Smuzhiyun #include <linux/stat.h>
18*4882a593Smuzhiyun #include <linux/types.h>
19*4882a593Smuzhiyun #include <linux/uaccess.h>
20*4882a593Smuzhiyun
21*4882a593Smuzhiyun #define DRV_NAME "cros-ec-sysfs"
22*4882a593Smuzhiyun
23*4882a593Smuzhiyun /* Accessor functions */
24*4882a593Smuzhiyun
reboot_show(struct device * dev,struct device_attribute * attr,char * buf)25*4882a593Smuzhiyun static ssize_t reboot_show(struct device *dev,
26*4882a593Smuzhiyun struct device_attribute *attr, char *buf)
27*4882a593Smuzhiyun {
28*4882a593Smuzhiyun int count = 0;
29*4882a593Smuzhiyun
30*4882a593Smuzhiyun count += scnprintf(buf + count, PAGE_SIZE - count,
31*4882a593Smuzhiyun "ro|rw|cancel|cold|disable-jump|hibernate");
32*4882a593Smuzhiyun count += scnprintf(buf + count, PAGE_SIZE - count,
33*4882a593Smuzhiyun " [at-shutdown]\n");
34*4882a593Smuzhiyun return count;
35*4882a593Smuzhiyun }
36*4882a593Smuzhiyun
reboot_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)37*4882a593Smuzhiyun static ssize_t reboot_store(struct device *dev,
38*4882a593Smuzhiyun struct device_attribute *attr,
39*4882a593Smuzhiyun const char *buf, size_t count)
40*4882a593Smuzhiyun {
41*4882a593Smuzhiyun static const struct {
42*4882a593Smuzhiyun const char * const str;
43*4882a593Smuzhiyun uint8_t cmd;
44*4882a593Smuzhiyun uint8_t flags;
45*4882a593Smuzhiyun } words[] = {
46*4882a593Smuzhiyun {"cancel", EC_REBOOT_CANCEL, 0},
47*4882a593Smuzhiyun {"ro", EC_REBOOT_JUMP_RO, 0},
48*4882a593Smuzhiyun {"rw", EC_REBOOT_JUMP_RW, 0},
49*4882a593Smuzhiyun {"cold", EC_REBOOT_COLD, 0},
50*4882a593Smuzhiyun {"disable-jump", EC_REBOOT_DISABLE_JUMP, 0},
51*4882a593Smuzhiyun {"hibernate", EC_REBOOT_HIBERNATE, 0},
52*4882a593Smuzhiyun {"at-shutdown", -1, EC_REBOOT_FLAG_ON_AP_SHUTDOWN},
53*4882a593Smuzhiyun };
54*4882a593Smuzhiyun struct cros_ec_command *msg;
55*4882a593Smuzhiyun struct ec_params_reboot_ec *param;
56*4882a593Smuzhiyun int got_cmd = 0, offset = 0;
57*4882a593Smuzhiyun int i;
58*4882a593Smuzhiyun int ret;
59*4882a593Smuzhiyun struct cros_ec_dev *ec = to_cros_ec_dev(dev);
60*4882a593Smuzhiyun
61*4882a593Smuzhiyun msg = kmalloc(sizeof(*msg) + sizeof(*param), GFP_KERNEL);
62*4882a593Smuzhiyun if (!msg)
63*4882a593Smuzhiyun return -ENOMEM;
64*4882a593Smuzhiyun
65*4882a593Smuzhiyun param = (struct ec_params_reboot_ec *)msg->data;
66*4882a593Smuzhiyun
67*4882a593Smuzhiyun param->flags = 0;
68*4882a593Smuzhiyun while (1) {
69*4882a593Smuzhiyun /* Find word to start scanning */
70*4882a593Smuzhiyun while (buf[offset] && isspace(buf[offset]))
71*4882a593Smuzhiyun offset++;
72*4882a593Smuzhiyun if (!buf[offset])
73*4882a593Smuzhiyun break;
74*4882a593Smuzhiyun
75*4882a593Smuzhiyun for (i = 0; i < ARRAY_SIZE(words); i++) {
76*4882a593Smuzhiyun if (!strncasecmp(words[i].str, buf+offset,
77*4882a593Smuzhiyun strlen(words[i].str))) {
78*4882a593Smuzhiyun if (words[i].flags) {
79*4882a593Smuzhiyun param->flags |= words[i].flags;
80*4882a593Smuzhiyun } else {
81*4882a593Smuzhiyun param->cmd = words[i].cmd;
82*4882a593Smuzhiyun got_cmd = 1;
83*4882a593Smuzhiyun }
84*4882a593Smuzhiyun break;
85*4882a593Smuzhiyun }
86*4882a593Smuzhiyun }
87*4882a593Smuzhiyun
88*4882a593Smuzhiyun /* On to the next word, if any */
89*4882a593Smuzhiyun while (buf[offset] && !isspace(buf[offset]))
90*4882a593Smuzhiyun offset++;
91*4882a593Smuzhiyun }
92*4882a593Smuzhiyun
93*4882a593Smuzhiyun if (!got_cmd) {
94*4882a593Smuzhiyun count = -EINVAL;
95*4882a593Smuzhiyun goto exit;
96*4882a593Smuzhiyun }
97*4882a593Smuzhiyun
98*4882a593Smuzhiyun msg->version = 0;
99*4882a593Smuzhiyun msg->command = EC_CMD_REBOOT_EC + ec->cmd_offset;
100*4882a593Smuzhiyun msg->outsize = sizeof(*param);
101*4882a593Smuzhiyun msg->insize = 0;
102*4882a593Smuzhiyun ret = cros_ec_cmd_xfer_status(ec->ec_dev, msg);
103*4882a593Smuzhiyun if (ret < 0)
104*4882a593Smuzhiyun count = ret;
105*4882a593Smuzhiyun exit:
106*4882a593Smuzhiyun kfree(msg);
107*4882a593Smuzhiyun return count;
108*4882a593Smuzhiyun }
109*4882a593Smuzhiyun
version_show(struct device * dev,struct device_attribute * attr,char * buf)110*4882a593Smuzhiyun static ssize_t version_show(struct device *dev,
111*4882a593Smuzhiyun struct device_attribute *attr, char *buf)
112*4882a593Smuzhiyun {
113*4882a593Smuzhiyun static const char * const image_names[] = {"unknown", "RO", "RW"};
114*4882a593Smuzhiyun struct ec_response_get_version *r_ver;
115*4882a593Smuzhiyun struct ec_response_get_chip_info *r_chip;
116*4882a593Smuzhiyun struct ec_response_board_version *r_board;
117*4882a593Smuzhiyun struct cros_ec_command *msg;
118*4882a593Smuzhiyun int ret;
119*4882a593Smuzhiyun int count = 0;
120*4882a593Smuzhiyun struct cros_ec_dev *ec = to_cros_ec_dev(dev);
121*4882a593Smuzhiyun
122*4882a593Smuzhiyun msg = kmalloc(sizeof(*msg) + EC_HOST_PARAM_SIZE, GFP_KERNEL);
123*4882a593Smuzhiyun if (!msg)
124*4882a593Smuzhiyun return -ENOMEM;
125*4882a593Smuzhiyun
126*4882a593Smuzhiyun /* Get versions. RW may change. */
127*4882a593Smuzhiyun msg->version = 0;
128*4882a593Smuzhiyun msg->command = EC_CMD_GET_VERSION + ec->cmd_offset;
129*4882a593Smuzhiyun msg->insize = sizeof(*r_ver);
130*4882a593Smuzhiyun msg->outsize = 0;
131*4882a593Smuzhiyun ret = cros_ec_cmd_xfer_status(ec->ec_dev, msg);
132*4882a593Smuzhiyun if (ret < 0) {
133*4882a593Smuzhiyun count = ret;
134*4882a593Smuzhiyun goto exit;
135*4882a593Smuzhiyun }
136*4882a593Smuzhiyun r_ver = (struct ec_response_get_version *)msg->data;
137*4882a593Smuzhiyun /* Strings should be null-terminated, but let's be sure. */
138*4882a593Smuzhiyun r_ver->version_string_ro[sizeof(r_ver->version_string_ro) - 1] = '\0';
139*4882a593Smuzhiyun r_ver->version_string_rw[sizeof(r_ver->version_string_rw) - 1] = '\0';
140*4882a593Smuzhiyun count += scnprintf(buf + count, PAGE_SIZE - count,
141*4882a593Smuzhiyun "RO version: %s\n", r_ver->version_string_ro);
142*4882a593Smuzhiyun count += scnprintf(buf + count, PAGE_SIZE - count,
143*4882a593Smuzhiyun "RW version: %s\n", r_ver->version_string_rw);
144*4882a593Smuzhiyun count += scnprintf(buf + count, PAGE_SIZE - count,
145*4882a593Smuzhiyun "Firmware copy: %s\n",
146*4882a593Smuzhiyun (r_ver->current_image < ARRAY_SIZE(image_names) ?
147*4882a593Smuzhiyun image_names[r_ver->current_image] : "?"));
148*4882a593Smuzhiyun
149*4882a593Smuzhiyun /* Get build info. */
150*4882a593Smuzhiyun msg->command = EC_CMD_GET_BUILD_INFO + ec->cmd_offset;
151*4882a593Smuzhiyun msg->insize = EC_HOST_PARAM_SIZE;
152*4882a593Smuzhiyun ret = cros_ec_cmd_xfer_status(ec->ec_dev, msg);
153*4882a593Smuzhiyun if (ret < 0) {
154*4882a593Smuzhiyun count += scnprintf(buf + count, PAGE_SIZE - count,
155*4882a593Smuzhiyun "Build info: XFER / EC ERROR %d / %d\n",
156*4882a593Smuzhiyun ret, msg->result);
157*4882a593Smuzhiyun } else {
158*4882a593Smuzhiyun msg->data[EC_HOST_PARAM_SIZE - 1] = '\0';
159*4882a593Smuzhiyun count += scnprintf(buf + count, PAGE_SIZE - count,
160*4882a593Smuzhiyun "Build info: %s\n", msg->data);
161*4882a593Smuzhiyun }
162*4882a593Smuzhiyun
163*4882a593Smuzhiyun /* Get chip info. */
164*4882a593Smuzhiyun msg->command = EC_CMD_GET_CHIP_INFO + ec->cmd_offset;
165*4882a593Smuzhiyun msg->insize = sizeof(*r_chip);
166*4882a593Smuzhiyun ret = cros_ec_cmd_xfer_status(ec->ec_dev, msg);
167*4882a593Smuzhiyun if (ret < 0) {
168*4882a593Smuzhiyun count += scnprintf(buf + count, PAGE_SIZE - count,
169*4882a593Smuzhiyun "Chip info: XFER / EC ERROR %d / %d\n",
170*4882a593Smuzhiyun ret, msg->result);
171*4882a593Smuzhiyun } else {
172*4882a593Smuzhiyun r_chip = (struct ec_response_get_chip_info *)msg->data;
173*4882a593Smuzhiyun
174*4882a593Smuzhiyun r_chip->vendor[sizeof(r_chip->vendor) - 1] = '\0';
175*4882a593Smuzhiyun r_chip->name[sizeof(r_chip->name) - 1] = '\0';
176*4882a593Smuzhiyun r_chip->revision[sizeof(r_chip->revision) - 1] = '\0';
177*4882a593Smuzhiyun count += scnprintf(buf + count, PAGE_SIZE - count,
178*4882a593Smuzhiyun "Chip vendor: %s\n", r_chip->vendor);
179*4882a593Smuzhiyun count += scnprintf(buf + count, PAGE_SIZE - count,
180*4882a593Smuzhiyun "Chip name: %s\n", r_chip->name);
181*4882a593Smuzhiyun count += scnprintf(buf + count, PAGE_SIZE - count,
182*4882a593Smuzhiyun "Chip revision: %s\n", r_chip->revision);
183*4882a593Smuzhiyun }
184*4882a593Smuzhiyun
185*4882a593Smuzhiyun /* Get board version */
186*4882a593Smuzhiyun msg->command = EC_CMD_GET_BOARD_VERSION + ec->cmd_offset;
187*4882a593Smuzhiyun msg->insize = sizeof(*r_board);
188*4882a593Smuzhiyun ret = cros_ec_cmd_xfer_status(ec->ec_dev, msg);
189*4882a593Smuzhiyun if (ret < 0) {
190*4882a593Smuzhiyun count += scnprintf(buf + count, PAGE_SIZE - count,
191*4882a593Smuzhiyun "Board version: XFER / EC ERROR %d / %d\n",
192*4882a593Smuzhiyun ret, msg->result);
193*4882a593Smuzhiyun } else {
194*4882a593Smuzhiyun r_board = (struct ec_response_board_version *)msg->data;
195*4882a593Smuzhiyun
196*4882a593Smuzhiyun count += scnprintf(buf + count, PAGE_SIZE - count,
197*4882a593Smuzhiyun "Board version: %d\n",
198*4882a593Smuzhiyun r_board->board_version);
199*4882a593Smuzhiyun }
200*4882a593Smuzhiyun
201*4882a593Smuzhiyun exit:
202*4882a593Smuzhiyun kfree(msg);
203*4882a593Smuzhiyun return count;
204*4882a593Smuzhiyun }
205*4882a593Smuzhiyun
flashinfo_show(struct device * dev,struct device_attribute * attr,char * buf)206*4882a593Smuzhiyun static ssize_t flashinfo_show(struct device *dev,
207*4882a593Smuzhiyun struct device_attribute *attr, char *buf)
208*4882a593Smuzhiyun {
209*4882a593Smuzhiyun struct ec_response_flash_info *resp;
210*4882a593Smuzhiyun struct cros_ec_command *msg;
211*4882a593Smuzhiyun int ret;
212*4882a593Smuzhiyun struct cros_ec_dev *ec = to_cros_ec_dev(dev);
213*4882a593Smuzhiyun
214*4882a593Smuzhiyun msg = kmalloc(sizeof(*msg) + sizeof(*resp), GFP_KERNEL);
215*4882a593Smuzhiyun if (!msg)
216*4882a593Smuzhiyun return -ENOMEM;
217*4882a593Smuzhiyun
218*4882a593Smuzhiyun /* The flash info shouldn't ever change, but ask each time anyway. */
219*4882a593Smuzhiyun msg->version = 0;
220*4882a593Smuzhiyun msg->command = EC_CMD_FLASH_INFO + ec->cmd_offset;
221*4882a593Smuzhiyun msg->insize = sizeof(*resp);
222*4882a593Smuzhiyun msg->outsize = 0;
223*4882a593Smuzhiyun ret = cros_ec_cmd_xfer_status(ec->ec_dev, msg);
224*4882a593Smuzhiyun if (ret < 0)
225*4882a593Smuzhiyun goto exit;
226*4882a593Smuzhiyun
227*4882a593Smuzhiyun resp = (struct ec_response_flash_info *)msg->data;
228*4882a593Smuzhiyun
229*4882a593Smuzhiyun ret = scnprintf(buf, PAGE_SIZE,
230*4882a593Smuzhiyun "FlashSize %d\nWriteSize %d\n"
231*4882a593Smuzhiyun "EraseSize %d\nProtectSize %d\n",
232*4882a593Smuzhiyun resp->flash_size, resp->write_block_size,
233*4882a593Smuzhiyun resp->erase_block_size, resp->protect_block_size);
234*4882a593Smuzhiyun exit:
235*4882a593Smuzhiyun kfree(msg);
236*4882a593Smuzhiyun return ret;
237*4882a593Smuzhiyun }
238*4882a593Smuzhiyun
239*4882a593Smuzhiyun /* Keyboard wake angle control */
kb_wake_angle_show(struct device * dev,struct device_attribute * attr,char * buf)240*4882a593Smuzhiyun static ssize_t kb_wake_angle_show(struct device *dev,
241*4882a593Smuzhiyun struct device_attribute *attr, char *buf)
242*4882a593Smuzhiyun {
243*4882a593Smuzhiyun struct cros_ec_dev *ec = to_cros_ec_dev(dev);
244*4882a593Smuzhiyun struct ec_response_motion_sense *resp;
245*4882a593Smuzhiyun struct ec_params_motion_sense *param;
246*4882a593Smuzhiyun struct cros_ec_command *msg;
247*4882a593Smuzhiyun int ret;
248*4882a593Smuzhiyun
249*4882a593Smuzhiyun msg = kmalloc(sizeof(*msg) + EC_HOST_PARAM_SIZE, GFP_KERNEL);
250*4882a593Smuzhiyun if (!msg)
251*4882a593Smuzhiyun return -ENOMEM;
252*4882a593Smuzhiyun
253*4882a593Smuzhiyun param = (struct ec_params_motion_sense *)msg->data;
254*4882a593Smuzhiyun msg->command = EC_CMD_MOTION_SENSE_CMD + ec->cmd_offset;
255*4882a593Smuzhiyun msg->version = 2;
256*4882a593Smuzhiyun param->cmd = MOTIONSENSE_CMD_KB_WAKE_ANGLE;
257*4882a593Smuzhiyun param->kb_wake_angle.data = EC_MOTION_SENSE_NO_VALUE;
258*4882a593Smuzhiyun msg->outsize = sizeof(*param);
259*4882a593Smuzhiyun msg->insize = sizeof(*resp);
260*4882a593Smuzhiyun
261*4882a593Smuzhiyun ret = cros_ec_cmd_xfer_status(ec->ec_dev, msg);
262*4882a593Smuzhiyun if (ret < 0)
263*4882a593Smuzhiyun goto exit;
264*4882a593Smuzhiyun
265*4882a593Smuzhiyun resp = (struct ec_response_motion_sense *)msg->data;
266*4882a593Smuzhiyun ret = scnprintf(buf, PAGE_SIZE, "%d\n", resp->kb_wake_angle.ret);
267*4882a593Smuzhiyun exit:
268*4882a593Smuzhiyun kfree(msg);
269*4882a593Smuzhiyun return ret;
270*4882a593Smuzhiyun }
271*4882a593Smuzhiyun
kb_wake_angle_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)272*4882a593Smuzhiyun static ssize_t kb_wake_angle_store(struct device *dev,
273*4882a593Smuzhiyun struct device_attribute *attr,
274*4882a593Smuzhiyun const char *buf, size_t count)
275*4882a593Smuzhiyun {
276*4882a593Smuzhiyun struct cros_ec_dev *ec = to_cros_ec_dev(dev);
277*4882a593Smuzhiyun struct ec_params_motion_sense *param;
278*4882a593Smuzhiyun struct cros_ec_command *msg;
279*4882a593Smuzhiyun u16 angle;
280*4882a593Smuzhiyun int ret;
281*4882a593Smuzhiyun
282*4882a593Smuzhiyun ret = kstrtou16(buf, 0, &angle);
283*4882a593Smuzhiyun if (ret)
284*4882a593Smuzhiyun return ret;
285*4882a593Smuzhiyun
286*4882a593Smuzhiyun msg = kmalloc(sizeof(*msg) + EC_HOST_PARAM_SIZE, GFP_KERNEL);
287*4882a593Smuzhiyun if (!msg)
288*4882a593Smuzhiyun return -ENOMEM;
289*4882a593Smuzhiyun
290*4882a593Smuzhiyun param = (struct ec_params_motion_sense *)msg->data;
291*4882a593Smuzhiyun msg->command = EC_CMD_MOTION_SENSE_CMD + ec->cmd_offset;
292*4882a593Smuzhiyun msg->version = 2;
293*4882a593Smuzhiyun param->cmd = MOTIONSENSE_CMD_KB_WAKE_ANGLE;
294*4882a593Smuzhiyun param->kb_wake_angle.data = angle;
295*4882a593Smuzhiyun msg->outsize = sizeof(*param);
296*4882a593Smuzhiyun msg->insize = sizeof(struct ec_response_motion_sense);
297*4882a593Smuzhiyun
298*4882a593Smuzhiyun ret = cros_ec_cmd_xfer_status(ec->ec_dev, msg);
299*4882a593Smuzhiyun kfree(msg);
300*4882a593Smuzhiyun if (ret < 0)
301*4882a593Smuzhiyun return ret;
302*4882a593Smuzhiyun return count;
303*4882a593Smuzhiyun }
304*4882a593Smuzhiyun
305*4882a593Smuzhiyun /* Module initialization */
306*4882a593Smuzhiyun
307*4882a593Smuzhiyun static DEVICE_ATTR_RW(reboot);
308*4882a593Smuzhiyun static DEVICE_ATTR_RO(version);
309*4882a593Smuzhiyun static DEVICE_ATTR_RO(flashinfo);
310*4882a593Smuzhiyun static DEVICE_ATTR_RW(kb_wake_angle);
311*4882a593Smuzhiyun
312*4882a593Smuzhiyun static struct attribute *__ec_attrs[] = {
313*4882a593Smuzhiyun &dev_attr_kb_wake_angle.attr,
314*4882a593Smuzhiyun &dev_attr_reboot.attr,
315*4882a593Smuzhiyun &dev_attr_version.attr,
316*4882a593Smuzhiyun &dev_attr_flashinfo.attr,
317*4882a593Smuzhiyun NULL,
318*4882a593Smuzhiyun };
319*4882a593Smuzhiyun
cros_ec_ctrl_visible(struct kobject * kobj,struct attribute * a,int n)320*4882a593Smuzhiyun static umode_t cros_ec_ctrl_visible(struct kobject *kobj,
321*4882a593Smuzhiyun struct attribute *a, int n)
322*4882a593Smuzhiyun {
323*4882a593Smuzhiyun struct device *dev = kobj_to_dev(kobj);
324*4882a593Smuzhiyun struct cros_ec_dev *ec = to_cros_ec_dev(dev);
325*4882a593Smuzhiyun
326*4882a593Smuzhiyun if (a == &dev_attr_kb_wake_angle.attr && !ec->has_kb_wake_angle)
327*4882a593Smuzhiyun return 0;
328*4882a593Smuzhiyun
329*4882a593Smuzhiyun return a->mode;
330*4882a593Smuzhiyun }
331*4882a593Smuzhiyun
332*4882a593Smuzhiyun static struct attribute_group cros_ec_attr_group = {
333*4882a593Smuzhiyun .attrs = __ec_attrs,
334*4882a593Smuzhiyun .is_visible = cros_ec_ctrl_visible,
335*4882a593Smuzhiyun };
336*4882a593Smuzhiyun
cros_ec_sysfs_probe(struct platform_device * pd)337*4882a593Smuzhiyun static int cros_ec_sysfs_probe(struct platform_device *pd)
338*4882a593Smuzhiyun {
339*4882a593Smuzhiyun struct cros_ec_dev *ec_dev = dev_get_drvdata(pd->dev.parent);
340*4882a593Smuzhiyun struct device *dev = &pd->dev;
341*4882a593Smuzhiyun int ret;
342*4882a593Smuzhiyun
343*4882a593Smuzhiyun ret = sysfs_create_group(&ec_dev->class_dev.kobj, &cros_ec_attr_group);
344*4882a593Smuzhiyun if (ret < 0)
345*4882a593Smuzhiyun dev_err(dev, "failed to create attributes. err=%d\n", ret);
346*4882a593Smuzhiyun
347*4882a593Smuzhiyun return ret;
348*4882a593Smuzhiyun }
349*4882a593Smuzhiyun
cros_ec_sysfs_remove(struct platform_device * pd)350*4882a593Smuzhiyun static int cros_ec_sysfs_remove(struct platform_device *pd)
351*4882a593Smuzhiyun {
352*4882a593Smuzhiyun struct cros_ec_dev *ec_dev = dev_get_drvdata(pd->dev.parent);
353*4882a593Smuzhiyun
354*4882a593Smuzhiyun sysfs_remove_group(&ec_dev->class_dev.kobj, &cros_ec_attr_group);
355*4882a593Smuzhiyun
356*4882a593Smuzhiyun return 0;
357*4882a593Smuzhiyun }
358*4882a593Smuzhiyun
359*4882a593Smuzhiyun static struct platform_driver cros_ec_sysfs_driver = {
360*4882a593Smuzhiyun .driver = {
361*4882a593Smuzhiyun .name = DRV_NAME,
362*4882a593Smuzhiyun },
363*4882a593Smuzhiyun .probe = cros_ec_sysfs_probe,
364*4882a593Smuzhiyun .remove = cros_ec_sysfs_remove,
365*4882a593Smuzhiyun };
366*4882a593Smuzhiyun
367*4882a593Smuzhiyun module_platform_driver(cros_ec_sysfs_driver);
368*4882a593Smuzhiyun
369*4882a593Smuzhiyun MODULE_LICENSE("GPL");
370*4882a593Smuzhiyun MODULE_DESCRIPTION("Expose the ChromeOS EC through sysfs");
371*4882a593Smuzhiyun MODULE_ALIAS("platform:" DRV_NAME);
372