• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1  // SPDX-License-Identifier: GPL-2.0-only
2  /*
3   *  Driver for the LID cover switch of the Surface 3
4   *
5   *  Copyright (c) 2016 Red Hat Inc.
6   */
7  
8  
9  #include <linux/kernel.h>
10  #include <linux/module.h>
11  #include <linux/slab.h>
12  
13  #include <linux/acpi.h>
14  #include <linux/dmi.h>
15  #include <linux/input.h>
16  #include <linux/mutex.h>
17  #include <linux/platform_device.h>
18  #include <linux/spi/spi.h>
19  
20  MODULE_AUTHOR("Benjamin Tissoires <benjamin.tissoires@redhat.com>");
21  MODULE_DESCRIPTION("Surface 3 platform driver");
22  MODULE_LICENSE("GPL");
23  
24  #define ACPI_BUTTON_HID_LID		"PNP0C0D"
25  #define SPI_CTL_OBJ_NAME		"SPI"
26  #define SPI_TS_OBJ_NAME			"NTRG"
27  
28  #define SURFACE3_LID_GUID "F7CC25EC-D20B-404C-8903-0ED4359C18AE"
29  
30  MODULE_ALIAS("wmi:" SURFACE3_LID_GUID);
31  
32  static const struct dmi_system_id surface3_dmi_table[] = {
33  #if defined(CONFIG_X86)
34  	{
35  		.matches = {
36  			DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
37  			DMI_MATCH(DMI_PRODUCT_NAME, "Surface 3"),
38  		},
39  	},
40  #endif
41  	{ }
42  };
43  
44  struct surface3_wmi {
45  	struct acpi_device *touchscreen_adev;
46  	struct acpi_device *pnp0c0d_adev;
47  	struct acpi_hotplug_context hp;
48  	struct input_dev *input;
49  };
50  
51  static struct platform_device *s3_wmi_pdev;
52  
53  static struct surface3_wmi s3_wmi;
54  
55  static DEFINE_MUTEX(s3_wmi_lock);
56  
s3_wmi_query_block(const char * guid,int instance,int * ret)57  static int s3_wmi_query_block(const char *guid, int instance, int *ret)
58  {
59  	struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
60  	acpi_status status;
61  	union acpi_object *obj;
62  	int error = 0;
63  
64  	mutex_lock(&s3_wmi_lock);
65  	status = wmi_query_block(guid, instance, &output);
66  
67  	obj = output.pointer;
68  
69  	if (!obj || obj->type != ACPI_TYPE_INTEGER) {
70  		if (obj) {
71  			pr_err("query block returned object type: %d - buffer length:%d\n",
72  			       obj->type,
73  			       obj->type == ACPI_TYPE_BUFFER ?
74  						obj->buffer.length : 0);
75  		}
76  		error = -EINVAL;
77  		goto out_free_unlock;
78  	}
79  	*ret = obj->integer.value;
80   out_free_unlock:
81  	kfree(obj);
82  	mutex_unlock(&s3_wmi_lock);
83  	return error;
84  }
85  
s3_wmi_query_lid(int * ret)86  static inline int s3_wmi_query_lid(int *ret)
87  {
88  	return s3_wmi_query_block(SURFACE3_LID_GUID, 0, ret);
89  }
90  
s3_wmi_send_lid_state(void)91  static int s3_wmi_send_lid_state(void)
92  {
93  	int ret, lid_sw;
94  
95  	ret = s3_wmi_query_lid(&lid_sw);
96  	if (ret)
97  		return ret;
98  
99  	input_report_switch(s3_wmi.input, SW_LID, lid_sw);
100  	input_sync(s3_wmi.input);
101  
102  	return 0;
103  }
104  
s3_wmi_hp_notify(struct acpi_device * adev,u32 value)105  static int s3_wmi_hp_notify(struct acpi_device *adev, u32 value)
106  {
107  	return s3_wmi_send_lid_state();
108  }
109  
s3_wmi_attach_spi_device(acpi_handle handle,u32 level,void * data,void ** return_value)110  static acpi_status s3_wmi_attach_spi_device(acpi_handle handle,
111  					    u32 level,
112  					    void *data,
113  					    void **return_value)
114  {
115  	struct acpi_device *adev, **ts_adev;
116  
117  	if (acpi_bus_get_device(handle, &adev))
118  		return AE_OK;
119  
120  	ts_adev = data;
121  
122  	if (strncmp(acpi_device_bid(adev), SPI_TS_OBJ_NAME,
123  	    strlen(SPI_TS_OBJ_NAME)))
124  		return AE_OK;
125  
126  	if (*ts_adev) {
127  		pr_err("duplicate entry %s\n", SPI_TS_OBJ_NAME);
128  		return AE_OK;
129  	}
130  
131  	*ts_adev = adev;
132  
133  	return AE_OK;
134  }
135  
s3_wmi_check_platform_device(struct device * dev,void * data)136  static int s3_wmi_check_platform_device(struct device *dev, void *data)
137  {
138  	struct acpi_device *adev, *ts_adev = NULL;
139  	acpi_handle handle;
140  	acpi_status status;
141  
142  	/* ignore non ACPI devices */
143  	handle = ACPI_HANDLE(dev);
144  	if (!handle || acpi_bus_get_device(handle, &adev))
145  		return 0;
146  
147  	/* check for LID ACPI switch */
148  	if (!strcmp(ACPI_BUTTON_HID_LID, acpi_device_hid(adev))) {
149  		s3_wmi.pnp0c0d_adev = adev;
150  		return 0;
151  	}
152  
153  	/* ignore non SPI controllers */
154  	if (strncmp(acpi_device_bid(adev), SPI_CTL_OBJ_NAME,
155  	    strlen(SPI_CTL_OBJ_NAME)))
156  		return 0;
157  
158  	status = acpi_walk_namespace(ACPI_TYPE_DEVICE, handle, 1,
159  				     s3_wmi_attach_spi_device, NULL,
160  				     &ts_adev, NULL);
161  	if (ACPI_FAILURE(status))
162  		dev_warn(dev, "failed to enumerate SPI slaves\n");
163  
164  	if (!ts_adev)
165  		return 0;
166  
167  	s3_wmi.touchscreen_adev = ts_adev;
168  
169  	return 0;
170  }
171  
s3_wmi_create_and_register_input(struct platform_device * pdev)172  static int s3_wmi_create_and_register_input(struct platform_device *pdev)
173  {
174  	struct input_dev *input;
175  	int error;
176  
177  	input = devm_input_allocate_device(&pdev->dev);
178  	if (!input)
179  		return -ENOMEM;
180  
181  	input->name = "Lid Switch";
182  	input->phys = "button/input0";
183  	input->id.bustype = BUS_HOST;
184  	input->id.product = 0x0005;
185  
186  	input_set_capability(input, EV_SW, SW_LID);
187  
188  	error = input_register_device(input);
189  	if (error)
190  		goto out_err;
191  
192  	s3_wmi.input = input;
193  
194  	return 0;
195   out_err:
196  	input_free_device(s3_wmi.input);
197  	return error;
198  }
199  
s3_wmi_probe(struct platform_device * pdev)200  static int __init s3_wmi_probe(struct platform_device *pdev)
201  {
202  	int error;
203  
204  	if (!dmi_check_system(surface3_dmi_table))
205  		return -ENODEV;
206  
207  	memset(&s3_wmi, 0, sizeof(s3_wmi));
208  
209  	bus_for_each_dev(&platform_bus_type, NULL, NULL,
210  			 s3_wmi_check_platform_device);
211  
212  	if (!s3_wmi.touchscreen_adev)
213  		return -ENODEV;
214  
215  	acpi_bus_trim(s3_wmi.pnp0c0d_adev);
216  
217  	error = s3_wmi_create_and_register_input(pdev);
218  	if (error)
219  		goto restore_acpi_lid;
220  
221  	acpi_initialize_hp_context(s3_wmi.touchscreen_adev, &s3_wmi.hp,
222  				   s3_wmi_hp_notify, NULL);
223  
224  	s3_wmi_send_lid_state();
225  
226  	return 0;
227  
228   restore_acpi_lid:
229  	acpi_bus_scan(s3_wmi.pnp0c0d_adev->handle);
230  	return error;
231  }
232  
s3_wmi_remove(struct platform_device * device)233  static int s3_wmi_remove(struct platform_device *device)
234  {
235  	/* remove the hotplug context from the acpi device */
236  	s3_wmi.touchscreen_adev->hp = NULL;
237  
238  	/* reinstall the actual PNPC0C0D LID default handle */
239  	acpi_bus_scan(s3_wmi.pnp0c0d_adev->handle);
240  	return 0;
241  }
242  
s3_wmi_resume(struct device * dev)243  static int __maybe_unused s3_wmi_resume(struct device *dev)
244  {
245  	s3_wmi_send_lid_state();
246  	return 0;
247  }
248  static SIMPLE_DEV_PM_OPS(s3_wmi_pm, NULL, s3_wmi_resume);
249  
250  static struct platform_driver s3_wmi_driver = {
251  	.driver = {
252  		.name = "surface3-wmi",
253  		.pm = &s3_wmi_pm,
254  	},
255  	.remove = s3_wmi_remove,
256  };
257  
s3_wmi_init(void)258  static int __init s3_wmi_init(void)
259  {
260  	int error;
261  
262  	s3_wmi_pdev = platform_device_alloc("surface3-wmi", -1);
263  	if (!s3_wmi_pdev)
264  		return -ENOMEM;
265  
266  	error = platform_device_add(s3_wmi_pdev);
267  	if (error)
268  		goto err_device_put;
269  
270  	error = platform_driver_probe(&s3_wmi_driver, s3_wmi_probe);
271  	if (error)
272  		goto err_device_del;
273  
274  	pr_info("Surface 3 WMI Extras loaded\n");
275  	return 0;
276  
277   err_device_del:
278  	platform_device_del(s3_wmi_pdev);
279   err_device_put:
280  	platform_device_put(s3_wmi_pdev);
281  	return error;
282  }
283  
s3_wmi_exit(void)284  static void __exit s3_wmi_exit(void)
285  {
286  	platform_device_unregister(s3_wmi_pdev);
287  	platform_driver_unregister(&s3_wmi_driver);
288  }
289  
290  module_init(s3_wmi_init);
291  module_exit(s3_wmi_exit);
292