xref: /openbmc/linux/drivers/hid/hid-google-hammer.c (revision 8ebc80a25f9d9bf7a8e368b266d5b740c485c362)
1bc774b8cSWei-Ning Huang // SPDX-License-Identifier: GPL-2.0+
2bc774b8cSWei-Ning Huang /*
3bc774b8cSWei-Ning Huang  *  HID driver for Google Hammer device.
4bc774b8cSWei-Ning Huang  *
5bc774b8cSWei-Ning Huang  *  Copyright (c) 2017 Google Inc.
6bc774b8cSWei-Ning Huang  *  Author: Wei-Ning Huang <wnhuang@google.com>
7bc774b8cSWei-Ning Huang  */
8bc774b8cSWei-Ning Huang 
9bc774b8cSWei-Ning Huang /*
10bc774b8cSWei-Ning Huang  * This program is free software; you can redistribute it and/or modify it
11bc774b8cSWei-Ning Huang  * under the terms of the GNU General Public License as published by the Free
12bc774b8cSWei-Ning Huang  * Software Foundation; either version 2 of the License, or (at your option)
13bc774b8cSWei-Ning Huang  * any later version.
14bc774b8cSWei-Ning Huang  */
15bc774b8cSWei-Ning Huang 
16eb1aac4cSDmitry Torokhov #include <linux/acpi.h>
17bc774b8cSWei-Ning Huang #include <linux/hid.h>
18a9d67299SStephen Boyd #include <linux/input/vivaldi-fmap.h>
19bc774b8cSWei-Ning Huang #include <linux/leds.h>
20bc774b8cSWei-Ning Huang #include <linux/module.h>
218dcaa046SIkjoon Jang #include <linux/of.h>
22840d9f13SEnric Balletbo i Serra #include <linux/platform_data/cros_ec_commands.h>
23840d9f13SEnric Balletbo i Serra #include <linux/platform_data/cros_ec_proto.h>
24eb1aac4cSDmitry Torokhov #include <linux/platform_device.h>
25eb1aac4cSDmitry Torokhov #include <linux/pm_wakeup.h>
26eb1aac4cSDmitry Torokhov #include <asm/unaligned.h>
27bc774b8cSWei-Ning Huang 
28bc774b8cSWei-Ning Huang #include "hid-ids.h"
29a9d67299SStephen Boyd #include "hid-vivaldi-common.h"
30bc774b8cSWei-Ning Huang 
31eb1aac4cSDmitry Torokhov /*
32eb1aac4cSDmitry Torokhov  * C(hrome)B(ase)A(ttached)S(witch) - switch exported by Chrome EC and reporting
33eb1aac4cSDmitry Torokhov  * state of the "Whiskers" base - attached or detached. Whiskers USB device also
34eb1aac4cSDmitry Torokhov  * reports position of the keyboard - folded or not. Combining base state and
35eb1aac4cSDmitry Torokhov  * position allows us to generate proper "Tablet mode" events.
36eb1aac4cSDmitry Torokhov  */
37eb1aac4cSDmitry Torokhov struct cbas_ec {
38eb1aac4cSDmitry Torokhov 	struct device *dev;	/* The platform device (EC) */
39eb1aac4cSDmitry Torokhov 	struct input_dev *input;
40eb1aac4cSDmitry Torokhov 	bool base_present;
4138e57f06SDmitry Torokhov 	bool base_folded;
42eb1aac4cSDmitry Torokhov 	struct notifier_block notifier;
43eb1aac4cSDmitry Torokhov };
44bc774b8cSWei-Ning Huang 
45eb1aac4cSDmitry Torokhov static struct cbas_ec cbas_ec;
46eb1aac4cSDmitry Torokhov static DEFINE_SPINLOCK(cbas_ec_lock);
47eb1aac4cSDmitry Torokhov static DEFINE_MUTEX(cbas_ec_reglock);
48eb1aac4cSDmitry Torokhov 
cbas_parse_base_state(const void * data)49eb1aac4cSDmitry Torokhov static bool cbas_parse_base_state(const void *data)
50eb1aac4cSDmitry Torokhov {
51eb1aac4cSDmitry Torokhov 	u32 switches = get_unaligned_le32(data);
52eb1aac4cSDmitry Torokhov 
53eb1aac4cSDmitry Torokhov 	return !!(switches & BIT(EC_MKBP_BASE_ATTACHED));
54eb1aac4cSDmitry Torokhov }
55eb1aac4cSDmitry Torokhov 
cbas_ec_query_base(struct cros_ec_device * ec_dev,bool get_state,bool * state)56eb1aac4cSDmitry Torokhov static int cbas_ec_query_base(struct cros_ec_device *ec_dev, bool get_state,
57eb1aac4cSDmitry Torokhov 				  bool *state)
58eb1aac4cSDmitry Torokhov {
59eb1aac4cSDmitry Torokhov 	struct ec_params_mkbp_info *params;
60eb1aac4cSDmitry Torokhov 	struct cros_ec_command *msg;
61eb1aac4cSDmitry Torokhov 	int ret;
62eb1aac4cSDmitry Torokhov 
6301f1269fSGustavo A. R. Silva 	msg = kzalloc(struct_size(msg, data, max(sizeof(u32), sizeof(*params))),
64eb1aac4cSDmitry Torokhov 		      GFP_KERNEL);
65eb1aac4cSDmitry Torokhov 	if (!msg)
66eb1aac4cSDmitry Torokhov 		return -ENOMEM;
67eb1aac4cSDmitry Torokhov 
68eb1aac4cSDmitry Torokhov 	msg->command = EC_CMD_MKBP_INFO;
69eb1aac4cSDmitry Torokhov 	msg->version = 1;
70eb1aac4cSDmitry Torokhov 	msg->outsize = sizeof(*params);
71eb1aac4cSDmitry Torokhov 	msg->insize = sizeof(u32);
72eb1aac4cSDmitry Torokhov 	params = (struct ec_params_mkbp_info *)msg->data;
73eb1aac4cSDmitry Torokhov 	params->info_type = get_state ?
74eb1aac4cSDmitry Torokhov 		EC_MKBP_INFO_CURRENT : EC_MKBP_INFO_SUPPORTED;
75eb1aac4cSDmitry Torokhov 	params->event_type = EC_MKBP_EVENT_SWITCH;
76eb1aac4cSDmitry Torokhov 
77eb1aac4cSDmitry Torokhov 	ret = cros_ec_cmd_xfer_status(ec_dev, msg);
78eb1aac4cSDmitry Torokhov 	if (ret >= 0) {
79eb1aac4cSDmitry Torokhov 		if (ret != sizeof(u32)) {
80eb1aac4cSDmitry Torokhov 			dev_warn(ec_dev->dev, "wrong result size: %d != %zu\n",
81eb1aac4cSDmitry Torokhov 				 ret, sizeof(u32));
82eb1aac4cSDmitry Torokhov 			ret = -EPROTO;
83eb1aac4cSDmitry Torokhov 		} else {
84eb1aac4cSDmitry Torokhov 			*state = cbas_parse_base_state(msg->data);
85eb1aac4cSDmitry Torokhov 			ret = 0;
86eb1aac4cSDmitry Torokhov 		}
87eb1aac4cSDmitry Torokhov 	}
88eb1aac4cSDmitry Torokhov 
89eb1aac4cSDmitry Torokhov 	kfree(msg);
90eb1aac4cSDmitry Torokhov 
91eb1aac4cSDmitry Torokhov 	return ret;
92eb1aac4cSDmitry Torokhov }
93eb1aac4cSDmitry Torokhov 
cbas_ec_notify(struct notifier_block * nb,unsigned long queued_during_suspend,void * _notify)94eb1aac4cSDmitry Torokhov static int cbas_ec_notify(struct notifier_block *nb,
95eb1aac4cSDmitry Torokhov 			      unsigned long queued_during_suspend,
96eb1aac4cSDmitry Torokhov 			      void *_notify)
97eb1aac4cSDmitry Torokhov {
98eb1aac4cSDmitry Torokhov 	struct cros_ec_device *ec = _notify;
99eb1aac4cSDmitry Torokhov 	unsigned long flags;
100eb1aac4cSDmitry Torokhov 	bool base_present;
101eb1aac4cSDmitry Torokhov 
102eb1aac4cSDmitry Torokhov 	if (ec->event_data.event_type == EC_MKBP_EVENT_SWITCH) {
103eb1aac4cSDmitry Torokhov 		base_present = cbas_parse_base_state(
104eb1aac4cSDmitry Torokhov 					&ec->event_data.data.switches);
105eb1aac4cSDmitry Torokhov 		dev_dbg(cbas_ec.dev,
106eb1aac4cSDmitry Torokhov 			"%s: base: %d\n", __func__, base_present);
107eb1aac4cSDmitry Torokhov 
108eb1aac4cSDmitry Torokhov 		if (device_may_wakeup(cbas_ec.dev) ||
109eb1aac4cSDmitry Torokhov 		    !queued_during_suspend) {
110eb1aac4cSDmitry Torokhov 
111eb1aac4cSDmitry Torokhov 			pm_wakeup_event(cbas_ec.dev, 0);
112eb1aac4cSDmitry Torokhov 
113eb1aac4cSDmitry Torokhov 			spin_lock_irqsave(&cbas_ec_lock, flags);
114eb1aac4cSDmitry Torokhov 
115eb1aac4cSDmitry Torokhov 			/*
116eb1aac4cSDmitry Torokhov 			 * While input layer dedupes the events, we do not want
117eb1aac4cSDmitry Torokhov 			 * to disrupt the state reported by the base by
118eb1aac4cSDmitry Torokhov 			 * overriding it with state reported by the LID. Only
119eb1aac4cSDmitry Torokhov 			 * report changes, as we assume that on attach the base
120eb1aac4cSDmitry Torokhov 			 * is not folded.
121eb1aac4cSDmitry Torokhov 			 */
122eb1aac4cSDmitry Torokhov 			if (base_present != cbas_ec.base_present) {
123eb1aac4cSDmitry Torokhov 				input_report_switch(cbas_ec.input,
124eb1aac4cSDmitry Torokhov 						    SW_TABLET_MODE,
125eb1aac4cSDmitry Torokhov 						    !base_present);
126eb1aac4cSDmitry Torokhov 				input_sync(cbas_ec.input);
127eb1aac4cSDmitry Torokhov 				cbas_ec.base_present = base_present;
128eb1aac4cSDmitry Torokhov 			}
129eb1aac4cSDmitry Torokhov 
130eb1aac4cSDmitry Torokhov 			spin_unlock_irqrestore(&cbas_ec_lock, flags);
131eb1aac4cSDmitry Torokhov 		}
132eb1aac4cSDmitry Torokhov 	}
133eb1aac4cSDmitry Torokhov 
134eb1aac4cSDmitry Torokhov 	return NOTIFY_OK;
135eb1aac4cSDmitry Torokhov }
136eb1aac4cSDmitry Torokhov 
cbas_ec_resume(struct device * dev)137eb1aac4cSDmitry Torokhov static __maybe_unused int cbas_ec_resume(struct device *dev)
138eb1aac4cSDmitry Torokhov {
139eb1aac4cSDmitry Torokhov 	struct cros_ec_device *ec = dev_get_drvdata(dev->parent);
140eb1aac4cSDmitry Torokhov 	bool base_present;
141eb1aac4cSDmitry Torokhov 	int error;
142eb1aac4cSDmitry Torokhov 
143eb1aac4cSDmitry Torokhov 	error = cbas_ec_query_base(ec, true, &base_present);
144eb1aac4cSDmitry Torokhov 	if (error) {
145eb1aac4cSDmitry Torokhov 		dev_warn(dev, "failed to fetch base state on resume: %d\n",
146eb1aac4cSDmitry Torokhov 			 error);
147eb1aac4cSDmitry Torokhov 	} else {
148eb1aac4cSDmitry Torokhov 		spin_lock_irq(&cbas_ec_lock);
149eb1aac4cSDmitry Torokhov 
150eb1aac4cSDmitry Torokhov 		cbas_ec.base_present = base_present;
151eb1aac4cSDmitry Torokhov 
152eb1aac4cSDmitry Torokhov 		/*
153eb1aac4cSDmitry Torokhov 		 * Only report if base is disconnected. If base is connected,
154eb1aac4cSDmitry Torokhov 		 * it will resend its state on resume, and we'll update it
155eb1aac4cSDmitry Torokhov 		 * in hammer_event().
156eb1aac4cSDmitry Torokhov 		 */
157eb1aac4cSDmitry Torokhov 		if (!cbas_ec.base_present) {
158eb1aac4cSDmitry Torokhov 			input_report_switch(cbas_ec.input, SW_TABLET_MODE, 1);
159eb1aac4cSDmitry Torokhov 			input_sync(cbas_ec.input);
160eb1aac4cSDmitry Torokhov 		}
161eb1aac4cSDmitry Torokhov 
162eb1aac4cSDmitry Torokhov 		spin_unlock_irq(&cbas_ec_lock);
163eb1aac4cSDmitry Torokhov 	}
164eb1aac4cSDmitry Torokhov 
165eb1aac4cSDmitry Torokhov 	return 0;
166eb1aac4cSDmitry Torokhov }
167eb1aac4cSDmitry Torokhov 
1688f35260eSJiri Kosina static SIMPLE_DEV_PM_OPS(cbas_ec_pm_ops, NULL, cbas_ec_resume);
169eb1aac4cSDmitry Torokhov 
cbas_ec_set_input(struct input_dev * input)170eb1aac4cSDmitry Torokhov static void cbas_ec_set_input(struct input_dev *input)
171eb1aac4cSDmitry Torokhov {
172eb1aac4cSDmitry Torokhov 	/* Take the lock so hammer_event() does not race with us here */
173eb1aac4cSDmitry Torokhov 	spin_lock_irq(&cbas_ec_lock);
174eb1aac4cSDmitry Torokhov 	cbas_ec.input = input;
175eb1aac4cSDmitry Torokhov 	spin_unlock_irq(&cbas_ec_lock);
176eb1aac4cSDmitry Torokhov }
177eb1aac4cSDmitry Torokhov 
__cbas_ec_probe(struct platform_device * pdev)178eb1aac4cSDmitry Torokhov static int __cbas_ec_probe(struct platform_device *pdev)
179eb1aac4cSDmitry Torokhov {
180eb1aac4cSDmitry Torokhov 	struct cros_ec_device *ec = dev_get_drvdata(pdev->dev.parent);
181eb1aac4cSDmitry Torokhov 	struct input_dev *input;
182eb1aac4cSDmitry Torokhov 	bool base_supported;
183eb1aac4cSDmitry Torokhov 	int error;
184eb1aac4cSDmitry Torokhov 
185eb1aac4cSDmitry Torokhov 	error = cbas_ec_query_base(ec, false, &base_supported);
186eb1aac4cSDmitry Torokhov 	if (error)
187eb1aac4cSDmitry Torokhov 		return error;
188eb1aac4cSDmitry Torokhov 
189eb1aac4cSDmitry Torokhov 	if (!base_supported)
190eb1aac4cSDmitry Torokhov 		return -ENXIO;
191eb1aac4cSDmitry Torokhov 
192eb1aac4cSDmitry Torokhov 	input = devm_input_allocate_device(&pdev->dev);
193eb1aac4cSDmitry Torokhov 	if (!input)
194eb1aac4cSDmitry Torokhov 		return -ENOMEM;
195eb1aac4cSDmitry Torokhov 
196eb1aac4cSDmitry Torokhov 	input->name = "Whiskers Tablet Mode Switch";
197eb1aac4cSDmitry Torokhov 	input->id.bustype = BUS_HOST;
198eb1aac4cSDmitry Torokhov 
199eb1aac4cSDmitry Torokhov 	input_set_capability(input, EV_SW, SW_TABLET_MODE);
200eb1aac4cSDmitry Torokhov 
201eb1aac4cSDmitry Torokhov 	error = input_register_device(input);
202eb1aac4cSDmitry Torokhov 	if (error) {
203eb1aac4cSDmitry Torokhov 		dev_err(&pdev->dev, "cannot register input device: %d\n",
204eb1aac4cSDmitry Torokhov 			error);
205eb1aac4cSDmitry Torokhov 		return error;
206eb1aac4cSDmitry Torokhov 	}
207eb1aac4cSDmitry Torokhov 
208eb1aac4cSDmitry Torokhov 	/* Seed the state */
209eb1aac4cSDmitry Torokhov 	error = cbas_ec_query_base(ec, true, &cbas_ec.base_present);
210eb1aac4cSDmitry Torokhov 	if (error) {
211eb1aac4cSDmitry Torokhov 		dev_err(&pdev->dev, "cannot query base state: %d\n", error);
212eb1aac4cSDmitry Torokhov 		return error;
213eb1aac4cSDmitry Torokhov 	}
214eb1aac4cSDmitry Torokhov 
21538e57f06SDmitry Torokhov 	if (!cbas_ec.base_present)
21638e57f06SDmitry Torokhov 		cbas_ec.base_folded = false;
21738e57f06SDmitry Torokhov 
21838e57f06SDmitry Torokhov 	dev_dbg(&pdev->dev, "%s: base: %d, folded: %d\n", __func__,
21938e57f06SDmitry Torokhov 		cbas_ec.base_present, cbas_ec.base_folded);
22038e57f06SDmitry Torokhov 
22138e57f06SDmitry Torokhov 	input_report_switch(input, SW_TABLET_MODE,
22238e57f06SDmitry Torokhov 			    !cbas_ec.base_present || cbas_ec.base_folded);
223eb1aac4cSDmitry Torokhov 
224eb1aac4cSDmitry Torokhov 	cbas_ec_set_input(input);
225eb1aac4cSDmitry Torokhov 
226eb1aac4cSDmitry Torokhov 	cbas_ec.dev = &pdev->dev;
227eb1aac4cSDmitry Torokhov 	cbas_ec.notifier.notifier_call = cbas_ec_notify;
228eb1aac4cSDmitry Torokhov 	error = blocking_notifier_chain_register(&ec->event_notifier,
229eb1aac4cSDmitry Torokhov 						 &cbas_ec.notifier);
230eb1aac4cSDmitry Torokhov 	if (error) {
231eb1aac4cSDmitry Torokhov 		dev_err(&pdev->dev, "cannot register notifier: %d\n", error);
232eb1aac4cSDmitry Torokhov 		cbas_ec_set_input(NULL);
233eb1aac4cSDmitry Torokhov 		return error;
234eb1aac4cSDmitry Torokhov 	}
235eb1aac4cSDmitry Torokhov 
236eb1aac4cSDmitry Torokhov 	device_init_wakeup(&pdev->dev, true);
237eb1aac4cSDmitry Torokhov 	return 0;
238eb1aac4cSDmitry Torokhov }
239eb1aac4cSDmitry Torokhov 
cbas_ec_probe(struct platform_device * pdev)240eb1aac4cSDmitry Torokhov static int cbas_ec_probe(struct platform_device *pdev)
241eb1aac4cSDmitry Torokhov {
242eb1aac4cSDmitry Torokhov 	int retval;
243eb1aac4cSDmitry Torokhov 
244eb1aac4cSDmitry Torokhov 	mutex_lock(&cbas_ec_reglock);
245eb1aac4cSDmitry Torokhov 
246eb1aac4cSDmitry Torokhov 	if (cbas_ec.input) {
247eb1aac4cSDmitry Torokhov 		retval = -EBUSY;
248eb1aac4cSDmitry Torokhov 		goto out;
249eb1aac4cSDmitry Torokhov 	}
250eb1aac4cSDmitry Torokhov 
251eb1aac4cSDmitry Torokhov 	retval = __cbas_ec_probe(pdev);
252eb1aac4cSDmitry Torokhov 
253eb1aac4cSDmitry Torokhov out:
254eb1aac4cSDmitry Torokhov 	mutex_unlock(&cbas_ec_reglock);
255eb1aac4cSDmitry Torokhov 	return retval;
256eb1aac4cSDmitry Torokhov }
257eb1aac4cSDmitry Torokhov 
cbas_ec_remove(struct platform_device * pdev)258eb1aac4cSDmitry Torokhov static int cbas_ec_remove(struct platform_device *pdev)
259eb1aac4cSDmitry Torokhov {
260eb1aac4cSDmitry Torokhov 	struct cros_ec_device *ec = dev_get_drvdata(pdev->dev.parent);
261eb1aac4cSDmitry Torokhov 
262eb1aac4cSDmitry Torokhov 	mutex_lock(&cbas_ec_reglock);
263eb1aac4cSDmitry Torokhov 
264eb1aac4cSDmitry Torokhov 	blocking_notifier_chain_unregister(&ec->event_notifier,
265eb1aac4cSDmitry Torokhov 					   &cbas_ec.notifier);
266eb1aac4cSDmitry Torokhov 	cbas_ec_set_input(NULL);
267eb1aac4cSDmitry Torokhov 
268eb1aac4cSDmitry Torokhov 	mutex_unlock(&cbas_ec_reglock);
269eb1aac4cSDmitry Torokhov 	return 0;
270eb1aac4cSDmitry Torokhov }
271eb1aac4cSDmitry Torokhov 
272*d0ef5f19SYu-Chun Lin #ifdef CONFIG_ACPI
273eb1aac4cSDmitry Torokhov static const struct acpi_device_id cbas_ec_acpi_ids[] = {
274eb1aac4cSDmitry Torokhov 	{ "GOOG000B", 0 },
275eb1aac4cSDmitry Torokhov 	{ }
276eb1aac4cSDmitry Torokhov };
277eb1aac4cSDmitry Torokhov MODULE_DEVICE_TABLE(acpi, cbas_ec_acpi_ids);
278*d0ef5f19SYu-Chun Lin #endif
279eb1aac4cSDmitry Torokhov 
2808dcaa046SIkjoon Jang #ifdef CONFIG_OF
2818dcaa046SIkjoon Jang static const struct of_device_id cbas_ec_of_match[] = {
2828dcaa046SIkjoon Jang 	{ .compatible = "google,cros-cbas" },
2838dcaa046SIkjoon Jang 	{ },
2848dcaa046SIkjoon Jang };
2858dcaa046SIkjoon Jang MODULE_DEVICE_TABLE(of, cbas_ec_of_match);
2868dcaa046SIkjoon Jang #endif
2878dcaa046SIkjoon Jang 
288eb1aac4cSDmitry Torokhov static struct platform_driver cbas_ec_driver = {
289eb1aac4cSDmitry Torokhov 	.probe = cbas_ec_probe,
290eb1aac4cSDmitry Torokhov 	.remove = cbas_ec_remove,
291eb1aac4cSDmitry Torokhov 	.driver = {
292eb1aac4cSDmitry Torokhov 		.name = "cbas_ec",
293eb1aac4cSDmitry Torokhov 		.acpi_match_table = ACPI_PTR(cbas_ec_acpi_ids),
2948dcaa046SIkjoon Jang 		.of_match_table = of_match_ptr(cbas_ec_of_match),
295eb1aac4cSDmitry Torokhov 		.pm = &cbas_ec_pm_ops,
296eb1aac4cSDmitry Torokhov 	},
297eb1aac4cSDmitry Torokhov };
298eb1aac4cSDmitry Torokhov 
299eb1aac4cSDmitry Torokhov #define MAX_BRIGHTNESS 100
300bc774b8cSWei-Ning Huang 
301bc774b8cSWei-Ning Huang struct hammer_kbd_leds {
302bc774b8cSWei-Ning Huang 	struct led_classdev cdev;
303bc774b8cSWei-Ning Huang 	struct hid_device *hdev;
304bc774b8cSWei-Ning Huang 	u8 buf[2] ____cacheline_aligned;
305bc774b8cSWei-Ning Huang };
306bc774b8cSWei-Ning Huang 
hammer_kbd_brightness_set_blocking(struct led_classdev * cdev,enum led_brightness br)307bc774b8cSWei-Ning Huang static int hammer_kbd_brightness_set_blocking(struct led_classdev *cdev,
308bc774b8cSWei-Ning Huang 		enum led_brightness br)
309bc774b8cSWei-Ning Huang {
310bc774b8cSWei-Ning Huang 	struct hammer_kbd_leds *led = container_of(cdev,
311bc774b8cSWei-Ning Huang 						   struct hammer_kbd_leds,
312bc774b8cSWei-Ning Huang 						   cdev);
313bc774b8cSWei-Ning Huang 	int ret;
314bc774b8cSWei-Ning Huang 
315bc774b8cSWei-Ning Huang 	led->buf[0] = 0;
316bc774b8cSWei-Ning Huang 	led->buf[1] = br;
317bc774b8cSWei-Ning Huang 
3187d3d8840SHaridhar Kalvala 	/*
3197d3d8840SHaridhar Kalvala 	 * Request USB HID device to be in Full On mode, so that sending
3207d3d8840SHaridhar Kalvala 	 * hardware output report and hardware raw request won't fail.
3217d3d8840SHaridhar Kalvala 	 */
3227d3d8840SHaridhar Kalvala 	ret = hid_hw_power(led->hdev, PM_HINT_FULLON);
3237d3d8840SHaridhar Kalvala 	if (ret < 0) {
3247d3d8840SHaridhar Kalvala 		hid_err(led->hdev, "failed: device not resumed %d\n", ret);
3257d3d8840SHaridhar Kalvala 		return ret;
3267d3d8840SHaridhar Kalvala 	}
3277d3d8840SHaridhar Kalvala 
328bc774b8cSWei-Ning Huang 	ret = hid_hw_output_report(led->hdev, led->buf, sizeof(led->buf));
329bc774b8cSWei-Ning Huang 	if (ret == -ENOSYS)
330bc774b8cSWei-Ning Huang 		ret = hid_hw_raw_request(led->hdev, 0, led->buf,
331bc774b8cSWei-Ning Huang 					 sizeof(led->buf),
332bc774b8cSWei-Ning Huang 					 HID_OUTPUT_REPORT,
333bc774b8cSWei-Ning Huang 					 HID_REQ_SET_REPORT);
334bc774b8cSWei-Ning Huang 	if (ret < 0)
335bc774b8cSWei-Ning Huang 		hid_err(led->hdev, "failed to set keyboard backlight: %d\n",
336bc774b8cSWei-Ning Huang 			ret);
3377d3d8840SHaridhar Kalvala 
3387d3d8840SHaridhar Kalvala 	/* Request USB HID device back to Normal Mode. */
3397d3d8840SHaridhar Kalvala 	hid_hw_power(led->hdev, PM_HINT_NORMAL);
3407d3d8840SHaridhar Kalvala 
341bc774b8cSWei-Ning Huang 	return ret;
342bc774b8cSWei-Ning Huang }
343bc774b8cSWei-Ning Huang 
hammer_register_leds(struct hid_device * hdev)344bc774b8cSWei-Ning Huang static int hammer_register_leds(struct hid_device *hdev)
345bc774b8cSWei-Ning Huang {
346bc774b8cSWei-Ning Huang 	struct hammer_kbd_leds *kbd_backlight;
347bc774b8cSWei-Ning Huang 
348d950db3fSDmitry Torokhov 	kbd_backlight = devm_kzalloc(&hdev->dev, sizeof(*kbd_backlight),
349d950db3fSDmitry Torokhov 				     GFP_KERNEL);
350bc774b8cSWei-Ning Huang 	if (!kbd_backlight)
351bc774b8cSWei-Ning Huang 		return -ENOMEM;
352bc774b8cSWei-Ning Huang 
353bc774b8cSWei-Ning Huang 	kbd_backlight->hdev = hdev;
354bc774b8cSWei-Ning Huang 	kbd_backlight->cdev.name = "hammer::kbd_backlight";
355bc774b8cSWei-Ning Huang 	kbd_backlight->cdev.max_brightness = MAX_BRIGHTNESS;
356bc774b8cSWei-Ning Huang 	kbd_backlight->cdev.brightness_set_blocking =
357bc774b8cSWei-Ning Huang 		hammer_kbd_brightness_set_blocking;
358bc774b8cSWei-Ning Huang 	kbd_backlight->cdev.flags = LED_HW_PLUGGABLE;
359bc774b8cSWei-Ning Huang 
360bc774b8cSWei-Ning Huang 	/* Set backlight to 0% initially. */
361bc774b8cSWei-Ning Huang 	hammer_kbd_brightness_set_blocking(&kbd_backlight->cdev, 0);
362bc774b8cSWei-Ning Huang 
363d950db3fSDmitry Torokhov 	return devm_led_classdev_register(&hdev->dev, &kbd_backlight->cdev);
364bc774b8cSWei-Ning Huang }
365bc774b8cSWei-Ning Huang 
366eb1aac4cSDmitry Torokhov #define HID_UP_GOOGLEVENDOR	0xffd10000
367eb1aac4cSDmitry Torokhov #define HID_VD_KBD_FOLDED	0x00000019
36820c55f25SNicolas Boichat #define HID_USAGE_KBD_FOLDED	(HID_UP_GOOGLEVENDOR | HID_VD_KBD_FOLDED)
369eb1aac4cSDmitry Torokhov 
370eb1aac4cSDmitry Torokhov /* HID usage for keyboard backlight (Alphanumeric display brightness) */
371eb1aac4cSDmitry Torokhov #define HID_AD_BRIGHTNESS	0x00140046
372eb1aac4cSDmitry Torokhov 
hammer_input_mapping(struct hid_device * hdev,struct hid_input * hi,struct hid_field * field,struct hid_usage * usage,unsigned long ** bit,int * max)373eb1aac4cSDmitry Torokhov static int hammer_input_mapping(struct hid_device *hdev, struct hid_input *hi,
374eb1aac4cSDmitry Torokhov 				struct hid_field *field,
375eb1aac4cSDmitry Torokhov 				struct hid_usage *usage,
376eb1aac4cSDmitry Torokhov 				unsigned long **bit, int *max)
377bc774b8cSWei-Ning Huang {
37820c55f25SNicolas Boichat 	if (usage->hid == HID_USAGE_KBD_FOLDED) {
379eb1aac4cSDmitry Torokhov 		/*
380eb1aac4cSDmitry Torokhov 		 * We do not want to have this usage mapped as it will get
381eb1aac4cSDmitry Torokhov 		 * mixed in with "base attached" signal and delivered over
382eb1aac4cSDmitry Torokhov 		 * separate input device for tablet switch mode.
383eb1aac4cSDmitry Torokhov 		 */
384eb1aac4cSDmitry Torokhov 		return -1;
385eb1aac4cSDmitry Torokhov 	}
386eb1aac4cSDmitry Torokhov 
387eb1aac4cSDmitry Torokhov 	return 0;
388eb1aac4cSDmitry Torokhov }
389eb1aac4cSDmitry Torokhov 
hammer_folded_event(struct hid_device * hdev,bool folded)390df7b6229SNicolas Boichat static void hammer_folded_event(struct hid_device *hdev, bool folded)
391eb1aac4cSDmitry Torokhov {
392eb1aac4cSDmitry Torokhov 	unsigned long flags;
393eb1aac4cSDmitry Torokhov 
394eb1aac4cSDmitry Torokhov 	spin_lock_irqsave(&cbas_ec_lock, flags);
395eb1aac4cSDmitry Torokhov 
396eb1aac4cSDmitry Torokhov 	/*
397b543db46SDmitry Torokhov 	 * If we are getting events from Whiskers that means that it
398b543db46SDmitry Torokhov 	 * is attached to the lid.
399eb1aac4cSDmitry Torokhov 	 */
400b543db46SDmitry Torokhov 	cbas_ec.base_present = true;
401df7b6229SNicolas Boichat 	cbas_ec.base_folded = folded;
402df7b6229SNicolas Boichat 	hid_dbg(hdev, "%s: base: %d, folded: %d\n", __func__,
40338e57f06SDmitry Torokhov 		cbas_ec.base_present, cbas_ec.base_folded);
404eb1aac4cSDmitry Torokhov 
405b543db46SDmitry Torokhov 	if (cbas_ec.input) {
406df7b6229SNicolas Boichat 		input_report_switch(cbas_ec.input, SW_TABLET_MODE, folded);
407eb1aac4cSDmitry Torokhov 		input_sync(cbas_ec.input);
408eb1aac4cSDmitry Torokhov 	}
409eb1aac4cSDmitry Torokhov 
410eb1aac4cSDmitry Torokhov 	spin_unlock_irqrestore(&cbas_ec_lock, flags);
411df7b6229SNicolas Boichat }
412df7b6229SNicolas Boichat 
hammer_event(struct hid_device * hid,struct hid_field * field,struct hid_usage * usage,__s32 value)413df7b6229SNicolas Boichat static int hammer_event(struct hid_device *hid, struct hid_field *field,
414df7b6229SNicolas Boichat 			struct hid_usage *usage, __s32 value)
415df7b6229SNicolas Boichat {
416df7b6229SNicolas Boichat 	if (usage->hid == HID_USAGE_KBD_FOLDED) {
417df7b6229SNicolas Boichat 		hammer_folded_event(hid, value);
418eb1aac4cSDmitry Torokhov 		return 1; /* We handled this event */
419eb1aac4cSDmitry Torokhov 	}
420eb1aac4cSDmitry Torokhov 
421eb1aac4cSDmitry Torokhov 	return 0;
422eb1aac4cSDmitry Torokhov }
423eb1aac4cSDmitry Torokhov 
hammer_has_usage(struct hid_device * hdev,unsigned int report_type,unsigned application,unsigned usage)42420c55f25SNicolas Boichat static bool hammer_has_usage(struct hid_device *hdev, unsigned int report_type,
42520c55f25SNicolas Boichat 			unsigned application, unsigned usage)
426eb1aac4cSDmitry Torokhov {
42720c55f25SNicolas Boichat 	struct hid_report_enum *re = &hdev->report_enum[report_type];
428eb1aac4cSDmitry Torokhov 	struct hid_report *report;
429eb1aac4cSDmitry Torokhov 	int i, j;
430bc774b8cSWei-Ning Huang 
431eb1aac4cSDmitry Torokhov 	list_for_each_entry(report, &re->report_list, list) {
43220c55f25SNicolas Boichat 		if (report->application != application)
433eb1aac4cSDmitry Torokhov 			continue;
434eb1aac4cSDmitry Torokhov 
435eb1aac4cSDmitry Torokhov 		for (i = 0; i < report->maxfield; i++) {
436eb1aac4cSDmitry Torokhov 			struct hid_field *field = report->field[i];
437eb1aac4cSDmitry Torokhov 
438eb1aac4cSDmitry Torokhov 			for (j = 0; j < field->maxusage; j++)
43920c55f25SNicolas Boichat 				if (field->usage[j].hid == usage)
440eb1aac4cSDmitry Torokhov 					return true;
441eb1aac4cSDmitry Torokhov 		}
442eb1aac4cSDmitry Torokhov 	}
443eb1aac4cSDmitry Torokhov 
444eb1aac4cSDmitry Torokhov 	return false;
445eb1aac4cSDmitry Torokhov }
446eb1aac4cSDmitry Torokhov 
hammer_has_folded_event(struct hid_device * hdev)44720c55f25SNicolas Boichat static bool hammer_has_folded_event(struct hid_device *hdev)
44820c55f25SNicolas Boichat {
44920c55f25SNicolas Boichat 	return hammer_has_usage(hdev, HID_INPUT_REPORT,
45020c55f25SNicolas Boichat 				HID_GD_KEYBOARD, HID_USAGE_KBD_FOLDED);
45120c55f25SNicolas Boichat }
45220c55f25SNicolas Boichat 
hammer_has_backlight_control(struct hid_device * hdev)45320c55f25SNicolas Boichat static bool hammer_has_backlight_control(struct hid_device *hdev)
45420c55f25SNicolas Boichat {
45520c55f25SNicolas Boichat 	return hammer_has_usage(hdev, HID_OUTPUT_REPORT,
45620c55f25SNicolas Boichat 				HID_GD_KEYBOARD, HID_AD_BRIGHTNESS);
45720c55f25SNicolas Boichat }
45820c55f25SNicolas Boichat 
hammer_get_folded_state(struct hid_device * hdev)459df7b6229SNicolas Boichat static void hammer_get_folded_state(struct hid_device *hdev)
460df7b6229SNicolas Boichat {
461df7b6229SNicolas Boichat 	struct hid_report *report;
462df7b6229SNicolas Boichat 	char *buf;
463df7b6229SNicolas Boichat 	int len, rlen;
464df7b6229SNicolas Boichat 	int a;
465df7b6229SNicolas Boichat 
466df7b6229SNicolas Boichat 	report = hdev->report_enum[HID_INPUT_REPORT].report_id_hash[0x0];
467df7b6229SNicolas Boichat 
468df7b6229SNicolas Boichat 	if (!report || report->maxfield < 1)
469df7b6229SNicolas Boichat 		return;
470df7b6229SNicolas Boichat 
471df7b6229SNicolas Boichat 	len = hid_report_len(report) + 1;
472df7b6229SNicolas Boichat 
473df7b6229SNicolas Boichat 	buf = kmalloc(len, GFP_KERNEL);
474df7b6229SNicolas Boichat 	if (!buf)
475df7b6229SNicolas Boichat 		return;
476df7b6229SNicolas Boichat 
477df7b6229SNicolas Boichat 	rlen = hid_hw_raw_request(hdev, report->id, buf, len, report->type, HID_REQ_GET_REPORT);
478df7b6229SNicolas Boichat 
479df7b6229SNicolas Boichat 	if (rlen != len) {
480df7b6229SNicolas Boichat 		hid_warn(hdev, "Unable to read base folded state: %d (expected %d)\n", rlen, len);
481df7b6229SNicolas Boichat 		goto out;
482df7b6229SNicolas Boichat 	}
483df7b6229SNicolas Boichat 
484df7b6229SNicolas Boichat 	for (a = 0; a < report->maxfield; a++) {
485df7b6229SNicolas Boichat 		struct hid_field *field = report->field[a];
486df7b6229SNicolas Boichat 
487df7b6229SNicolas Boichat 		if (field->usage->hid == HID_USAGE_KBD_FOLDED) {
488df7b6229SNicolas Boichat 			u32 value = hid_field_extract(hdev, buf+1,
489df7b6229SNicolas Boichat 					field->report_offset, field->report_size);
490df7b6229SNicolas Boichat 
491df7b6229SNicolas Boichat 			hammer_folded_event(hdev, value);
492df7b6229SNicolas Boichat 			break;
493df7b6229SNicolas Boichat 		}
494df7b6229SNicolas Boichat 	}
495df7b6229SNicolas Boichat 
496df7b6229SNicolas Boichat out:
497df7b6229SNicolas Boichat 	kfree(buf);
498df7b6229SNicolas Boichat }
499df7b6229SNicolas Boichat 
hammer_stop(void * hdev)500d950db3fSDmitry Torokhov static void hammer_stop(void *hdev)
501d950db3fSDmitry Torokhov {
502d950db3fSDmitry Torokhov 	hid_hw_stop(hdev);
503d950db3fSDmitry Torokhov }
504d950db3fSDmitry Torokhov 
hammer_probe(struct hid_device * hdev,const struct hid_device_id * id)505eb1aac4cSDmitry Torokhov static int hammer_probe(struct hid_device *hdev,
506eb1aac4cSDmitry Torokhov 			const struct hid_device_id *id)
507eb1aac4cSDmitry Torokhov {
508a9d67299SStephen Boyd 	struct vivaldi_data *vdata;
509eb1aac4cSDmitry Torokhov 	int error;
510eb1aac4cSDmitry Torokhov 
511a9d67299SStephen Boyd 	vdata = devm_kzalloc(&hdev->dev, sizeof(*vdata), GFP_KERNEL);
512a9d67299SStephen Boyd 	if (!vdata)
513a9d67299SStephen Boyd 		return -ENOMEM;
514a9d67299SStephen Boyd 
515a9d67299SStephen Boyd 	hid_set_drvdata(hdev, vdata);
516a9d67299SStephen Boyd 
517eb1aac4cSDmitry Torokhov 	error = hid_parse(hdev);
518eb1aac4cSDmitry Torokhov 	if (error)
519eb1aac4cSDmitry Torokhov 		return error;
520eb1aac4cSDmitry Torokhov 
521eb1aac4cSDmitry Torokhov 	error = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
522eb1aac4cSDmitry Torokhov 	if (error)
523eb1aac4cSDmitry Torokhov 		return error;
524eb1aac4cSDmitry Torokhov 
525d950db3fSDmitry Torokhov 	error = devm_add_action(&hdev->dev, hammer_stop, hdev);
526d950db3fSDmitry Torokhov 	if (error)
527d950db3fSDmitry Torokhov 		return error;
528d950db3fSDmitry Torokhov 
52938e57f06SDmitry Torokhov 	/*
53038e57f06SDmitry Torokhov 	 * We always want to poll for, and handle tablet mode events from
53120c55f25SNicolas Boichat 	 * devices that have folded usage, even when nobody has opened the input
53220c55f25SNicolas Boichat 	 * device. This also prevents the hid core from dropping early tablet
53320c55f25SNicolas Boichat 	 * mode events from the device.
53438e57f06SDmitry Torokhov 	 */
53520c55f25SNicolas Boichat 	if (hammer_has_folded_event(hdev)) {
53638e57f06SDmitry Torokhov 		hdev->quirks |= HID_QUIRK_ALWAYS_POLL;
53738e57f06SDmitry Torokhov 		error = hid_hw_open(hdev);
53838e57f06SDmitry Torokhov 		if (error)
53938e57f06SDmitry Torokhov 			return error;
540df7b6229SNicolas Boichat 
541df7b6229SNicolas Boichat 		hammer_get_folded_state(hdev);
54238e57f06SDmitry Torokhov 	}
54338e57f06SDmitry Torokhov 
544eb1aac4cSDmitry Torokhov 	if (hammer_has_backlight_control(hdev)) {
545eb1aac4cSDmitry Torokhov 		error = hammer_register_leds(hdev);
546eb1aac4cSDmitry Torokhov 		if (error)
547bc774b8cSWei-Ning Huang 			hid_warn(hdev,
548bc774b8cSWei-Ning Huang 				"Failed to register keyboard backlight: %d\n",
549eb1aac4cSDmitry Torokhov 				error);
550bc774b8cSWei-Ning Huang 	}
551bc774b8cSWei-Ning Huang 
552bc774b8cSWei-Ning Huang 	return 0;
553bc774b8cSWei-Ning Huang }
554bc774b8cSWei-Ning Huang 
hammer_remove(struct hid_device * hdev)55538e57f06SDmitry Torokhov static void hammer_remove(struct hid_device *hdev)
55638e57f06SDmitry Torokhov {
55779085c7dSDmitry Torokhov 	unsigned long flags;
55879085c7dSDmitry Torokhov 
55920c55f25SNicolas Boichat 	if (hammer_has_folded_event(hdev)) {
56038e57f06SDmitry Torokhov 		hid_hw_close(hdev);
56138e57f06SDmitry Torokhov 
56279085c7dSDmitry Torokhov 		/*
56379085c7dSDmitry Torokhov 		 * If we are disconnecting then most likely Whiskers is
56479085c7dSDmitry Torokhov 		 * being removed. Even if it is not removed, without proper
56579085c7dSDmitry Torokhov 		 * keyboard we should not stay in clamshell mode.
56679085c7dSDmitry Torokhov 		 *
56779085c7dSDmitry Torokhov 		 * The reason for doing it here and not waiting for signal
56879085c7dSDmitry Torokhov 		 * from EC, is that on some devices there are high leakage
56979085c7dSDmitry Torokhov 		 * on Whiskers pins and we do not detect disconnect reliably,
57079085c7dSDmitry Torokhov 		 * resulting in devices being stuck in clamshell mode.
57179085c7dSDmitry Torokhov 		 */
57279085c7dSDmitry Torokhov 		spin_lock_irqsave(&cbas_ec_lock, flags);
57379085c7dSDmitry Torokhov 		if (cbas_ec.input && cbas_ec.base_present) {
57479085c7dSDmitry Torokhov 			input_report_switch(cbas_ec.input, SW_TABLET_MODE, 1);
57579085c7dSDmitry Torokhov 			input_sync(cbas_ec.input);
57679085c7dSDmitry Torokhov 		}
57779085c7dSDmitry Torokhov 		cbas_ec.base_present = false;
57879085c7dSDmitry Torokhov 		spin_unlock_irqrestore(&cbas_ec_lock, flags);
57979085c7dSDmitry Torokhov 	}
58079085c7dSDmitry Torokhov 
581d950db3fSDmitry Torokhov 	/* Unregistering LEDs and stopping the hardware is done via devm */
58238e57f06SDmitry Torokhov }
583eb1aac4cSDmitry Torokhov 
584bc774b8cSWei-Ning Huang static const struct hid_device_id hammer_devices[] = {
585bc774b8cSWei-Ning Huang 	{ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
58636b87cf3SShou-Chieh Hsu 		     USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_DON) },
5878a3e634dSZhengqiao Xia 	{ HID_DEVICE(BUS_USB, HID_GROUP_VIVALDI,
588caff0090Sxiazhengqiao 		     USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_EEL) },
589caff0090Sxiazhengqiao 	{ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
590bc774b8cSWei-Ning Huang 		     USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_HAMMER) },
591bc774b8cSWei-Ning Huang 	{ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
592ed84c451SSung-Chi Li 		     USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_JEWEL) },
593ed84c451SSung-Chi Li 	{ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
5949e4dbc46SNicolas Boichat 		     USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_MAGNEMITE) },
5959e4dbc46SNicolas Boichat 	{ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
5969e4dbc46SNicolas Boichat 		     USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_MASTERBALL) },
5979e4dbc46SNicolas Boichat 	{ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
59858322a15SChen-Tsung Hsieh 		     USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_MOONBALL) },
59958322a15SChen-Tsung Hsieh 	{ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
600bc774b8cSWei-Ning Huang 		     USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_STAFF) },
601bc774b8cSWei-Ning Huang 	{ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
602bc774b8cSWei-Ning Huang 		     USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_WAND) },
6033e84c765SNicolas Boichat 	{ HID_DEVICE(BUS_USB, HID_GROUP_GENERIC,
6043e84c765SNicolas Boichat 		     USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_WHISKERS) },
605bc774b8cSWei-Ning Huang 	{ }
606bc774b8cSWei-Ning Huang };
607bc774b8cSWei-Ning Huang MODULE_DEVICE_TABLE(hid, hammer_devices);
608bc774b8cSWei-Ning Huang 
609bc774b8cSWei-Ning Huang static struct hid_driver hammer_driver = {
610bc774b8cSWei-Ning Huang 	.name = "hammer",
611bc774b8cSWei-Ning Huang 	.id_table = hammer_devices,
612eb1aac4cSDmitry Torokhov 	.probe = hammer_probe,
61338e57f06SDmitry Torokhov 	.remove = hammer_remove,
614a9d67299SStephen Boyd 	.feature_mapping = vivaldi_feature_mapping,
615eb1aac4cSDmitry Torokhov 	.input_mapping = hammer_input_mapping,
616eb1aac4cSDmitry Torokhov 	.event = hammer_event,
6179f4441fcSGreg Kroah-Hartman 	.driver = {
6189f4441fcSGreg Kroah-Hartman 		.dev_groups = vivaldi_attribute_groups,
6199f4441fcSGreg Kroah-Hartman 	},
620bc774b8cSWei-Ning Huang };
621eb1aac4cSDmitry Torokhov 
hammer_init(void)622eb1aac4cSDmitry Torokhov static int __init hammer_init(void)
623eb1aac4cSDmitry Torokhov {
624eb1aac4cSDmitry Torokhov 	int error;
625eb1aac4cSDmitry Torokhov 
626eb1aac4cSDmitry Torokhov 	error = platform_driver_register(&cbas_ec_driver);
627eb1aac4cSDmitry Torokhov 	if (error)
628eb1aac4cSDmitry Torokhov 		return error;
629eb1aac4cSDmitry Torokhov 
630eb1aac4cSDmitry Torokhov 	error = hid_register_driver(&hammer_driver);
631eb1aac4cSDmitry Torokhov 	if (error) {
632eb1aac4cSDmitry Torokhov 		platform_driver_unregister(&cbas_ec_driver);
633eb1aac4cSDmitry Torokhov 		return error;
634eb1aac4cSDmitry Torokhov 	}
635eb1aac4cSDmitry Torokhov 
636eb1aac4cSDmitry Torokhov 	return 0;
637eb1aac4cSDmitry Torokhov }
638eb1aac4cSDmitry Torokhov module_init(hammer_init);
639eb1aac4cSDmitry Torokhov 
hammer_exit(void)640eb1aac4cSDmitry Torokhov static void __exit hammer_exit(void)
641eb1aac4cSDmitry Torokhov {
642eb1aac4cSDmitry Torokhov 	hid_unregister_driver(&hammer_driver);
643eb1aac4cSDmitry Torokhov 	platform_driver_unregister(&cbas_ec_driver);
644eb1aac4cSDmitry Torokhov }
645eb1aac4cSDmitry Torokhov module_exit(hammer_exit);
646bc774b8cSWei-Ning Huang 
647bc774b8cSWei-Ning Huang MODULE_LICENSE("GPL");
648