Merge tag 'block-6.1-2022-11-11' of git://git.kernel.dk/linux
[linux-block.git] / drivers / platform / loongarch / loongson-laptop.c
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  *  Generic Loongson processor based LAPTOP/ALL-IN-ONE driver
4  *
5  *  Jianmin Lv <lvjianmin@loongson.cn>
6  *  Huacai Chen <chenhuacai@loongson.cn>
7  *
8  * Copyright (C) 2022 Loongson Technology Corporation Limited
9  */
10
11 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
12
13 #include <linux/init.h>
14 #include <linux/kernel.h>
15 #include <linux/module.h>
16 #include <linux/acpi.h>
17 #include <linux/backlight.h>
18 #include <linux/device.h>
19 #include <linux/input.h>
20 #include <linux/input/sparse-keymap.h>
21 #include <linux/platform_device.h>
22 #include <linux/string.h>
23 #include <linux/types.h>
24 #include <acpi/video.h>
25
26 /* 1. Driver-wide structs and misc. variables */
27
28 /* ACPI HIDs */
29 #define LOONGSON_ACPI_EC_HID    "PNP0C09"
30 #define LOONGSON_ACPI_HKEY_HID  "LOON0000"
31
32 #define ACPI_LAPTOP_NAME "loongson-laptop"
33 #define ACPI_LAPTOP_ACPI_EVENT_PREFIX "loongson"
34
35 #define MAX_ACPI_ARGS                   3
36 #define GENERIC_HOTKEY_MAP_MAX          64
37
38 #define GENERIC_EVENT_TYPE_OFF          12
39 #define GENERIC_EVENT_TYPE_MASK         0xF000
40 #define GENERIC_EVENT_CODE_MASK         0x0FFF
41
42 struct generic_sub_driver {
43         u32 type;
44         char *name;
45         acpi_handle *handle;
46         struct acpi_device *device;
47         struct platform_driver *driver;
48         int (*init)(struct generic_sub_driver *sub_driver);
49         void (*notify)(struct generic_sub_driver *sub_driver, u32 event);
50         u8 acpi_notify_installed;
51 };
52
53 static u32 input_device_registered;
54 static struct input_dev *generic_inputdev;
55
56 static acpi_handle hotkey_handle;
57 static struct key_entry hotkey_keycode_map[GENERIC_HOTKEY_MAP_MAX];
58
59 int loongson_laptop_turn_on_backlight(void);
60 int loongson_laptop_turn_off_backlight(void);
61 static int loongson_laptop_backlight_update(struct backlight_device *bd);
62
63 /* 2. ACPI Helpers and device model */
64
65 static int acpi_evalf(acpi_handle handle, int *res, char *method, char *fmt, ...)
66 {
67         char res_type;
68         char *fmt0 = fmt;
69         va_list ap;
70         int success, quiet;
71         acpi_status status;
72         struct acpi_object_list params;
73         struct acpi_buffer result, *resultp;
74         union acpi_object in_objs[MAX_ACPI_ARGS], out_obj;
75
76         if (!*fmt) {
77                 pr_err("acpi_evalf() called with empty format\n");
78                 return 0;
79         }
80
81         if (*fmt == 'q') {
82                 quiet = 1;
83                 fmt++;
84         } else
85                 quiet = 0;
86
87         res_type = *(fmt++);
88
89         params.count = 0;
90         params.pointer = &in_objs[0];
91
92         va_start(ap, fmt);
93         while (*fmt) {
94                 char c = *(fmt++);
95                 switch (c) {
96                 case 'd':       /* int */
97                         in_objs[params.count].integer.value = va_arg(ap, int);
98                         in_objs[params.count++].type = ACPI_TYPE_INTEGER;
99                         break;
100                         /* add more types as needed */
101                 default:
102                         pr_err("acpi_evalf() called with invalid format character '%c'\n", c);
103                         va_end(ap);
104                         return 0;
105                 }
106         }
107         va_end(ap);
108
109         if (res_type != 'v') {
110                 result.length = sizeof(out_obj);
111                 result.pointer = &out_obj;
112                 resultp = &result;
113         } else
114                 resultp = NULL;
115
116         status = acpi_evaluate_object(handle, method, &params, resultp);
117
118         switch (res_type) {
119         case 'd':               /* int */
120                 success = (status == AE_OK && out_obj.type == ACPI_TYPE_INTEGER);
121                 if (success && res)
122                         *res = out_obj.integer.value;
123                 break;
124         case 'v':               /* void */
125                 success = status == AE_OK;
126                 break;
127                 /* add more types as needed */
128         default:
129                 pr_err("acpi_evalf() called with invalid format character '%c'\n", res_type);
130                 return 0;
131         }
132
133         if (!success && !quiet)
134                 pr_err("acpi_evalf(%s, %s, ...) failed: %s\n",
135                        method, fmt0, acpi_format_exception(status));
136
137         return success;
138 }
139
140 static int hotkey_status_get(int *status)
141 {
142         if (!acpi_evalf(hotkey_handle, status, "GSWS", "d"))
143                 return -EIO;
144
145         return 0;
146 }
147
148 static void dispatch_acpi_notify(acpi_handle handle, u32 event, void *data)
149 {
150         struct generic_sub_driver *sub_driver = data;
151
152         if (!sub_driver || !sub_driver->notify)
153                 return;
154         sub_driver->notify(sub_driver, event);
155 }
156
157 static int __init setup_acpi_notify(struct generic_sub_driver *sub_driver)
158 {
159         acpi_status status;
160
161         if (!*sub_driver->handle)
162                 return 0;
163
164         sub_driver->device = acpi_fetch_acpi_dev(*sub_driver->handle);
165         if (!sub_driver->device) {
166                 pr_err("acpi_fetch_acpi_dev(%s) failed\n", sub_driver->name);
167                 return -ENODEV;
168         }
169
170         sub_driver->device->driver_data = sub_driver;
171         sprintf(acpi_device_class(sub_driver->device), "%s/%s",
172                 ACPI_LAPTOP_ACPI_EVENT_PREFIX, sub_driver->name);
173
174         status = acpi_install_notify_handler(*sub_driver->handle,
175                         sub_driver->type, dispatch_acpi_notify, sub_driver);
176         if (ACPI_FAILURE(status)) {
177                 if (status == AE_ALREADY_EXISTS) {
178                         pr_notice("Another device driver is already "
179                                   "handling %s events\n", sub_driver->name);
180                 } else {
181                         pr_err("acpi_install_notify_handler(%s) failed: %s\n",
182                                sub_driver->name, acpi_format_exception(status));
183                 }
184                 return -ENODEV;
185         }
186         sub_driver->acpi_notify_installed = 1;
187
188         return 0;
189 }
190
191 static int loongson_hotkey_suspend(struct device *dev)
192 {
193         return 0;
194 }
195
196 static int loongson_hotkey_resume(struct device *dev)
197 {
198         int status = 0;
199         struct key_entry ke;
200         struct backlight_device *bd;
201
202         bd = backlight_device_get_by_type(BACKLIGHT_PLATFORM);
203         if (bd) {
204                 loongson_laptop_backlight_update(bd) ?
205                 pr_warn("Loongson_backlight: resume brightness failed") :
206                 pr_info("Loongson_backlight: resume brightness %d\n", bd->props.brightness);
207         }
208
209         /*
210          * Only if the firmware supports SW_LID event model, we can handle the
211          * event. This is for the consideration of development board without EC.
212          */
213         if (test_bit(SW_LID, generic_inputdev->swbit)) {
214                 if (hotkey_status_get(&status) < 0)
215                         return -EIO;
216                 /*
217                  * The input device sw element records the last lid status.
218                  * When the system is awakened by other wake-up sources,
219                  * the lid event will also be reported. The judgment of
220                  * adding SW_LID bit which in sw element can avoid this
221                  * case.
222                  *
223                  * Input system will drop lid event when current lid event
224                  * value and last lid status in the same. So laptop driver
225                  * doesn't report repeated events.
226                  *
227                  * Lid status is generally 0, but hardware exception is
228                  * considered. So add lid status confirmation.
229                  */
230                 if (test_bit(SW_LID, generic_inputdev->sw) && !(status & (1 << SW_LID))) {
231                         ke.type = KE_SW;
232                         ke.sw.value = (u8)status;
233                         ke.sw.code = SW_LID;
234                         sparse_keymap_report_entry(generic_inputdev, &ke, 1, true);
235                 }
236         }
237
238         return 0;
239 }
240
241 static DEFINE_SIMPLE_DEV_PM_OPS(loongson_hotkey_pm,
242                 loongson_hotkey_suspend, loongson_hotkey_resume);
243
244 static int loongson_hotkey_probe(struct platform_device *pdev)
245 {
246         hotkey_handle = ACPI_HANDLE(&pdev->dev);
247
248         if (!hotkey_handle)
249                 return -ENODEV;
250
251         return 0;
252 }
253
254 static const struct acpi_device_id loongson_device_ids[] = {
255         {LOONGSON_ACPI_HKEY_HID, 0},
256         {"", 0},
257 };
258 MODULE_DEVICE_TABLE(acpi, loongson_device_ids);
259
260 static struct platform_driver loongson_hotkey_driver = {
261         .probe          = loongson_hotkey_probe,
262         .driver         = {
263                 .name   = "loongson-hotkey",
264                 .owner  = THIS_MODULE,
265                 .pm     = pm_ptr(&loongson_hotkey_pm),
266                 .acpi_match_table = loongson_device_ids,
267         },
268 };
269
270 static int hotkey_map(void)
271 {
272         u32 index;
273         acpi_status status;
274         struct acpi_buffer buf;
275         union acpi_object *pack;
276
277         buf.length = ACPI_ALLOCATE_BUFFER;
278         status = acpi_evaluate_object_typed(hotkey_handle, "KMAP", NULL, &buf, ACPI_TYPE_PACKAGE);
279         if (status != AE_OK) {
280                 pr_err("ACPI exception: %s\n", acpi_format_exception(status));
281                 return -1;
282         }
283         pack = buf.pointer;
284         for (index = 0; index < pack->package.count; index++) {
285                 union acpi_object *element, *sub_pack;
286
287                 sub_pack = &pack->package.elements[index];
288
289                 element = &sub_pack->package.elements[0];
290                 hotkey_keycode_map[index].type = element->integer.value;
291                 element = &sub_pack->package.elements[1];
292                 hotkey_keycode_map[index].code = element->integer.value;
293                 element = &sub_pack->package.elements[2];
294                 hotkey_keycode_map[index].keycode = element->integer.value;
295         }
296
297         return 0;
298 }
299
300 static int hotkey_backlight_set(bool enable)
301 {
302         if (!acpi_evalf(hotkey_handle, NULL, "VCBL", "vd", enable ? 1 : 0))
303                 return -EIO;
304
305         return 0;
306 }
307
308 static int ec_get_brightness(void)
309 {
310         int status = 0;
311
312         if (!hotkey_handle)
313                 return -ENXIO;
314
315         if (!acpi_evalf(hotkey_handle, &status, "ECBG", "d"))
316                 return -EIO;
317
318         return status;
319 }
320
321 static int ec_set_brightness(int level)
322 {
323
324         int ret = 0;
325
326         if (!hotkey_handle)
327                 return -ENXIO;
328
329         if (!acpi_evalf(hotkey_handle, NULL, "ECBS", "vd", level))
330                 ret = -EIO;
331
332         return ret;
333 }
334
335 static int ec_backlight_level(u8 level)
336 {
337         int status = 0;
338
339         if (!hotkey_handle)
340                 return -ENXIO;
341
342         if (!acpi_evalf(hotkey_handle, &status, "ECLL", "d"))
343                 return -EIO;
344
345         if ((status < 0) || (level > status))
346                 return status;
347
348         if (!acpi_evalf(hotkey_handle, &status, "ECSL", "d"))
349                 return -EIO;
350
351         if ((status < 0) || (level < status))
352                 return status;
353
354         return level;
355 }
356
357 static int loongson_laptop_backlight_update(struct backlight_device *bd)
358 {
359         int lvl = ec_backlight_level(bd->props.brightness);
360
361         if (lvl < 0)
362                 return -EIO;
363         if (ec_set_brightness(lvl))
364                 return -EIO;
365
366         return 0;
367 }
368
369 static int loongson_laptop_get_brightness(struct backlight_device *bd)
370 {
371         int level;
372
373         level = ec_get_brightness();
374         if (level < 0)
375                 return -EIO;
376
377         return level;
378 }
379
380 static const struct backlight_ops backlight_laptop_ops = {
381         .update_status = loongson_laptop_backlight_update,
382         .get_brightness = loongson_laptop_get_brightness,
383 };
384
385 static int laptop_backlight_register(void)
386 {
387         int status = 0;
388         struct backlight_properties props;
389
390         memset(&props, 0, sizeof(props));
391
392         if (!acpi_evalf(hotkey_handle, &status, "ECLL", "d"))
393                 return -EIO;
394
395         props.brightness = 1;
396         props.max_brightness = status;
397         props.type = BACKLIGHT_PLATFORM;
398
399         backlight_device_register("loongson_laptop",
400                                 NULL, NULL, &backlight_laptop_ops, &props);
401
402         return 0;
403 }
404
405 int loongson_laptop_turn_on_backlight(void)
406 {
407         int status;
408         union acpi_object arg0 = { ACPI_TYPE_INTEGER };
409         struct acpi_object_list args = { 1, &arg0 };
410
411         arg0.integer.value = 1;
412         status = acpi_evaluate_object(NULL, "\\BLSW", &args, NULL);
413         if (ACPI_FAILURE(status)) {
414                 pr_info("Loongson lvds error: 0x%x\n", status);
415                 return -ENODEV;
416         }
417
418         return 0;
419 }
420
421 int loongson_laptop_turn_off_backlight(void)
422 {
423         int status;
424         union acpi_object arg0 = { ACPI_TYPE_INTEGER };
425         struct acpi_object_list args = { 1, &arg0 };
426
427         arg0.integer.value = 0;
428         status = acpi_evaluate_object(NULL, "\\BLSW", &args, NULL);
429         if (ACPI_FAILURE(status)) {
430                 pr_info("Loongson lvds error: 0x%x\n", status);
431                 return -ENODEV;
432         }
433
434         return 0;
435 }
436
437 static int __init event_init(struct generic_sub_driver *sub_driver)
438 {
439         int ret;
440
441         ret = hotkey_map();
442         if (ret < 0) {
443                 pr_err("Failed to parse keymap from DSDT\n");
444                 return ret;
445         }
446
447         ret = sparse_keymap_setup(generic_inputdev, hotkey_keycode_map, NULL);
448         if (ret < 0) {
449                 pr_err("Failed to setup input device keymap\n");
450                 input_free_device(generic_inputdev);
451                 generic_inputdev = NULL;
452
453                 return ret;
454         }
455
456         /*
457          * This hotkey driver handle backlight event when
458          * acpi_video_get_backlight_type() gets acpi_backlight_vendor
459          */
460         if (acpi_video_get_backlight_type() == acpi_backlight_vendor)
461                 hotkey_backlight_set(true);
462         else
463                 hotkey_backlight_set(false);
464
465         pr_info("ACPI: enabling firmware HKEY event interface...\n");
466
467         return ret;
468 }
469
470 static void event_notify(struct generic_sub_driver *sub_driver, u32 event)
471 {
472         int type, scan_code;
473         struct key_entry *ke = NULL;
474
475         scan_code = event & GENERIC_EVENT_CODE_MASK;
476         type = (event & GENERIC_EVENT_TYPE_MASK) >> GENERIC_EVENT_TYPE_OFF;
477         ke = sparse_keymap_entry_from_scancode(generic_inputdev, scan_code);
478         if (ke) {
479                 if (type == KE_SW) {
480                         int status = 0;
481
482                         if (hotkey_status_get(&status) < 0)
483                                 return;
484
485                         ke->sw.value = !!(status & (1 << ke->sw.code));
486                 }
487                 sparse_keymap_report_entry(generic_inputdev, ke, 1, true);
488         }
489 }
490
491 /* 3. Infrastructure */
492
493 static void generic_subdriver_exit(struct generic_sub_driver *sub_driver);
494
495 static int __init generic_subdriver_init(struct generic_sub_driver *sub_driver)
496 {
497         int ret;
498
499         if (!sub_driver || !sub_driver->driver)
500                 return -EINVAL;
501
502         ret = platform_driver_register(sub_driver->driver);
503         if (ret)
504                 return -EINVAL;
505
506         if (sub_driver->init) {
507                 ret = sub_driver->init(sub_driver);
508                 if (ret)
509                         goto err_out;
510         }
511
512         if (sub_driver->notify) {
513                 ret = setup_acpi_notify(sub_driver);
514                 if (ret == -ENODEV) {
515                         ret = 0;
516                         goto err_out;
517                 }
518                 if (ret < 0)
519                         goto err_out;
520         }
521
522         return 0;
523
524 err_out:
525         generic_subdriver_exit(sub_driver);
526         return ret;
527 }
528
529 static void generic_subdriver_exit(struct generic_sub_driver *sub_driver)
530 {
531
532         if (sub_driver->acpi_notify_installed) {
533                 acpi_remove_notify_handler(*sub_driver->handle,
534                                            sub_driver->type, dispatch_acpi_notify);
535                 sub_driver->acpi_notify_installed = 0;
536         }
537         platform_driver_unregister(sub_driver->driver);
538 }
539
540 static struct generic_sub_driver generic_sub_drivers[] __refdata = {
541         {
542                 .name = "hotkey",
543                 .init = event_init,
544                 .notify = event_notify,
545                 .handle = &hotkey_handle,
546                 .type = ACPI_DEVICE_NOTIFY,
547                 .driver = &loongson_hotkey_driver,
548         },
549 };
550
551 static int __init generic_acpi_laptop_init(void)
552 {
553         bool ec_found;
554         int i, ret, status;
555
556         if (acpi_disabled)
557                 return -ENODEV;
558
559         /* The EC device is required */
560         ec_found = acpi_dev_found(LOONGSON_ACPI_EC_HID);
561         if (!ec_found)
562                 return -ENODEV;
563
564         /* Enable SCI for EC */
565         acpi_write_bit_register(ACPI_BITREG_SCI_ENABLE, 1);
566
567         generic_inputdev = input_allocate_device();
568         if (!generic_inputdev) {
569                 pr_err("Unable to allocate input device\n");
570                 return -ENOMEM;
571         }
572
573         /* Prepare input device, but don't register */
574         generic_inputdev->name =
575                 "Loongson Generic Laptop/All-in-One Extra Buttons";
576         generic_inputdev->phys = ACPI_LAPTOP_NAME "/input0";
577         generic_inputdev->id.bustype = BUS_HOST;
578         generic_inputdev->dev.parent = NULL;
579
580         /* Init subdrivers */
581         for (i = 0; i < ARRAY_SIZE(generic_sub_drivers); i++) {
582                 ret = generic_subdriver_init(&generic_sub_drivers[i]);
583                 if (ret < 0) {
584                         input_free_device(generic_inputdev);
585                         while (--i >= 0)
586                                 generic_subdriver_exit(&generic_sub_drivers[i]);
587                         return ret;
588                 }
589         }
590
591         ret = input_register_device(generic_inputdev);
592         if (ret < 0) {
593                 input_free_device(generic_inputdev);
594                 while (--i >= 0)
595                         generic_subdriver_exit(&generic_sub_drivers[i]);
596                 pr_err("Unable to register input device\n");
597                 return ret;
598         }
599
600         input_device_registered = 1;
601
602         if (acpi_evalf(hotkey_handle, &status, "ECBG", "d")) {
603                 pr_info("Loongson Laptop used, init brightness is 0x%x\n", status);
604                 ret = laptop_backlight_register();
605                 if (ret < 0)
606                         pr_err("Loongson Laptop: laptop-backlight device register failed\n");
607         }
608
609         return 0;
610 }
611
612 static void __exit generic_acpi_laptop_exit(void)
613 {
614         if (generic_inputdev) {
615                 if (input_device_registered)
616                         input_unregister_device(generic_inputdev);
617                 else
618                         input_free_device(generic_inputdev);
619         }
620 }
621
622 module_init(generic_acpi_laptop_init);
623 module_exit(generic_acpi_laptop_exit);
624
625 MODULE_AUTHOR("Jianmin Lv <lvjianmin@loongson.cn>");
626 MODULE_AUTHOR("Huacai Chen <chenhuacai@loongson.cn>");
627 MODULE_DESCRIPTION("Loongson Laptop/All-in-One ACPI Driver");
628 MODULE_LICENSE("GPL");