1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (c) 2020, NVIDIA CORPORATION. All rights reserved. |
4 | */ |
5 | |
6 | #include <linux/acpi.h> |
7 | #include <linux/backlight.h> |
8 | #include <linux/mod_devicetable.h> |
9 | #include <linux/module.h> |
10 | #include <linux/platform_data/x86/nvidia-wmi-ec-backlight.h> |
11 | #include <linux/types.h> |
12 | #include <linux/wmi.h> |
13 | #include <acpi/video.h> |
14 | |
15 | static bool force; |
16 | module_param(force, bool, 0444); |
17 | MODULE_PARM_DESC(force, "Force loading (disable acpi_backlight=xxx checks" ); |
18 | |
19 | /** |
20 | * wmi_brightness_notify() - helper function for calling WMI-wrapped ACPI method |
21 | * @w: Pointer to the struct wmi_device identified by %WMI_BRIGHTNESS_GUID |
22 | * @id: The WMI method ID to call (e.g. %WMI_BRIGHTNESS_METHOD_LEVEL or |
23 | * %WMI_BRIGHTNESS_METHOD_SOURCE) |
24 | * @mode: The operation to perform on the method (e.g. %WMI_BRIGHTNESS_MODE_SET |
25 | * or %WMI_BRIGHTNESS_MODE_GET) |
26 | * @val: Pointer to a value passed in by the caller when @mode is |
27 | * %WMI_BRIGHTNESS_MODE_SET, or a value passed out to caller when @mode |
28 | * is %WMI_BRIGHTNESS_MODE_GET or %WMI_BRIGHTNESS_MODE_GET_MAX_LEVEL. |
29 | * |
30 | * Returns 0 on success, or a negative error number on failure. |
31 | */ |
32 | static int wmi_brightness_notify(struct wmi_device *w, enum wmi_brightness_method id, enum wmi_brightness_mode mode, u32 *val) |
33 | { |
34 | struct wmi_brightness_args args = { |
35 | .mode = mode, |
36 | .val = 0, |
37 | .ret = 0, |
38 | }; |
39 | struct acpi_buffer buf = { (acpi_size)sizeof(args), &args }; |
40 | acpi_status status; |
41 | |
42 | if (id < WMI_BRIGHTNESS_METHOD_LEVEL || |
43 | id >= WMI_BRIGHTNESS_METHOD_MAX || |
44 | mode < WMI_BRIGHTNESS_MODE_GET || mode >= WMI_BRIGHTNESS_MODE_MAX) |
45 | return -EINVAL; |
46 | |
47 | if (mode == WMI_BRIGHTNESS_MODE_SET) |
48 | args.val = *val; |
49 | |
50 | status = wmidev_evaluate_method(wdev: w, instance: 0, method_id: id, in: &buf, out: &buf); |
51 | if (ACPI_FAILURE(status)) { |
52 | dev_err(&w->dev, "EC backlight control failed: %s\n" , |
53 | acpi_format_exception(status)); |
54 | return -EIO; |
55 | } |
56 | |
57 | if (mode != WMI_BRIGHTNESS_MODE_SET) |
58 | *val = args.ret; |
59 | |
60 | return 0; |
61 | } |
62 | |
63 | static int nvidia_wmi_ec_backlight_update_status(struct backlight_device *bd) |
64 | { |
65 | struct wmi_device *wdev = bl_get_data(bl_dev: bd); |
66 | |
67 | return wmi_brightness_notify(w: wdev, id: WMI_BRIGHTNESS_METHOD_LEVEL, |
68 | mode: WMI_BRIGHTNESS_MODE_SET, |
69 | val: &bd->props.brightness); |
70 | } |
71 | |
72 | static int nvidia_wmi_ec_backlight_get_brightness(struct backlight_device *bd) |
73 | { |
74 | struct wmi_device *wdev = bl_get_data(bl_dev: bd); |
75 | u32 level; |
76 | int ret; |
77 | |
78 | ret = wmi_brightness_notify(w: wdev, id: WMI_BRIGHTNESS_METHOD_LEVEL, |
79 | mode: WMI_BRIGHTNESS_MODE_GET, val: &level); |
80 | if (ret < 0) |
81 | return ret; |
82 | |
83 | return level; |
84 | } |
85 | |
86 | static const struct backlight_ops nvidia_wmi_ec_backlight_ops = { |
87 | .update_status = nvidia_wmi_ec_backlight_update_status, |
88 | .get_brightness = nvidia_wmi_ec_backlight_get_brightness, |
89 | }; |
90 | |
91 | static int nvidia_wmi_ec_backlight_probe(struct wmi_device *wdev, const void *ctx) |
92 | { |
93 | struct backlight_properties props = {}; |
94 | struct backlight_device *bdev; |
95 | int ret; |
96 | |
97 | /* drivers/acpi/video_detect.c also checks that SOURCE == EC */ |
98 | if (!force && acpi_video_get_backlight_type() != acpi_backlight_nvidia_wmi_ec) |
99 | return -ENODEV; |
100 | |
101 | /* |
102 | * Identify this backlight device as a firmware device so that it can |
103 | * be prioritized over any exposed GPU-driven raw device(s). |
104 | */ |
105 | props.type = BACKLIGHT_FIRMWARE; |
106 | |
107 | ret = wmi_brightness_notify(w: wdev, id: WMI_BRIGHTNESS_METHOD_LEVEL, |
108 | mode: WMI_BRIGHTNESS_MODE_GET_MAX_LEVEL, |
109 | val: &props.max_brightness); |
110 | if (ret) |
111 | return ret; |
112 | |
113 | ret = wmi_brightness_notify(w: wdev, id: WMI_BRIGHTNESS_METHOD_LEVEL, |
114 | mode: WMI_BRIGHTNESS_MODE_GET, val: &props.brightness); |
115 | if (ret) |
116 | return ret; |
117 | |
118 | bdev = devm_backlight_device_register(dev: &wdev->dev, |
119 | name: "nvidia_wmi_ec_backlight" , |
120 | parent: &wdev->dev, devdata: wdev, |
121 | ops: &nvidia_wmi_ec_backlight_ops, |
122 | props: &props); |
123 | return PTR_ERR_OR_ZERO(ptr: bdev); |
124 | } |
125 | |
126 | static const struct wmi_device_id nvidia_wmi_ec_backlight_id_table[] = { |
127 | { .guid_string = WMI_BRIGHTNESS_GUID }, |
128 | { } |
129 | }; |
130 | MODULE_DEVICE_TABLE(wmi, nvidia_wmi_ec_backlight_id_table); |
131 | |
132 | static struct wmi_driver nvidia_wmi_ec_backlight_driver = { |
133 | .driver = { |
134 | .name = "nvidia-wmi-ec-backlight" , |
135 | }, |
136 | .probe = nvidia_wmi_ec_backlight_probe, |
137 | .id_table = nvidia_wmi_ec_backlight_id_table, |
138 | }; |
139 | module_wmi_driver(nvidia_wmi_ec_backlight_driver); |
140 | |
141 | MODULE_AUTHOR("Daniel Dadap <ddadap@nvidia.com>" ); |
142 | MODULE_DESCRIPTION("NVIDIA WMI EC Backlight driver" ); |
143 | MODULE_LICENSE("GPL" ); |
144 | |