1*4882a593Smuzhiyun // SPDX-License-Identifier: GPL-2.0-or-later
2*4882a593Smuzhiyun /*-*-linux-c-*-*/
3*4882a593Smuzhiyun
4*4882a593Smuzhiyun /*
5*4882a593Smuzhiyun Copyright (C) 2007,2008 Jonathan Woithe <jwoithe@just42.net>
6*4882a593Smuzhiyun Copyright (C) 2008 Peter Gruber <nokos@gmx.net>
7*4882a593Smuzhiyun Copyright (C) 2008 Tony Vroon <tony@linx.net>
8*4882a593Smuzhiyun Based on earlier work:
9*4882a593Smuzhiyun Copyright (C) 2003 Shane Spencer <shane@bogomip.com>
10*4882a593Smuzhiyun Adrian Yee <brewt-fujitsu@brewt.org>
11*4882a593Smuzhiyun
12*4882a593Smuzhiyun Templated from msi-laptop.c and thinkpad_acpi.c which is copyright
13*4882a593Smuzhiyun by its respective authors.
14*4882a593Smuzhiyun
15*4882a593Smuzhiyun */
16*4882a593Smuzhiyun
17*4882a593Smuzhiyun /*
18*4882a593Smuzhiyun * fujitsu-laptop.c - Fujitsu laptop support, providing access to additional
19*4882a593Smuzhiyun * features made available on a range of Fujitsu laptops including the
20*4882a593Smuzhiyun * P2xxx/P5xxx/S6xxx/S7xxx series.
21*4882a593Smuzhiyun *
22*4882a593Smuzhiyun * This driver implements a vendor-specific backlight control interface for
23*4882a593Smuzhiyun * Fujitsu laptops and provides support for hotkeys present on certain Fujitsu
24*4882a593Smuzhiyun * laptops.
25*4882a593Smuzhiyun *
26*4882a593Smuzhiyun * This driver has been tested on a Fujitsu Lifebook S6410, S7020 and
27*4882a593Smuzhiyun * P8010. It should work on most P-series and S-series Lifebooks, but
28*4882a593Smuzhiyun * YMMV.
29*4882a593Smuzhiyun *
30*4882a593Smuzhiyun * The module parameter use_alt_lcd_levels switches between different ACPI
31*4882a593Smuzhiyun * brightness controls which are used by different Fujitsu laptops. In most
32*4882a593Smuzhiyun * cases the correct method is automatically detected. "use_alt_lcd_levels=1"
33*4882a593Smuzhiyun * is applicable for a Fujitsu Lifebook S6410 if autodetection fails.
34*4882a593Smuzhiyun *
35*4882a593Smuzhiyun */
36*4882a593Smuzhiyun
37*4882a593Smuzhiyun #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
38*4882a593Smuzhiyun
39*4882a593Smuzhiyun #include <linux/module.h>
40*4882a593Smuzhiyun #include <linux/kernel.h>
41*4882a593Smuzhiyun #include <linux/init.h>
42*4882a593Smuzhiyun #include <linux/acpi.h>
43*4882a593Smuzhiyun #include <linux/bitops.h>
44*4882a593Smuzhiyun #include <linux/dmi.h>
45*4882a593Smuzhiyun #include <linux/backlight.h>
46*4882a593Smuzhiyun #include <linux/fb.h>
47*4882a593Smuzhiyun #include <linux/input.h>
48*4882a593Smuzhiyun #include <linux/input/sparse-keymap.h>
49*4882a593Smuzhiyun #include <linux/kfifo.h>
50*4882a593Smuzhiyun #include <linux/leds.h>
51*4882a593Smuzhiyun #include <linux/platform_device.h>
52*4882a593Smuzhiyun #include <acpi/video.h>
53*4882a593Smuzhiyun
54*4882a593Smuzhiyun #define FUJITSU_DRIVER_VERSION "0.6.0"
55*4882a593Smuzhiyun
56*4882a593Smuzhiyun #define FUJITSU_LCD_N_LEVELS 8
57*4882a593Smuzhiyun
58*4882a593Smuzhiyun #define ACPI_FUJITSU_CLASS "fujitsu"
59*4882a593Smuzhiyun #define ACPI_FUJITSU_BL_HID "FUJ02B1"
60*4882a593Smuzhiyun #define ACPI_FUJITSU_BL_DRIVER_NAME "Fujitsu laptop FUJ02B1 ACPI brightness driver"
61*4882a593Smuzhiyun #define ACPI_FUJITSU_BL_DEVICE_NAME "Fujitsu FUJ02B1"
62*4882a593Smuzhiyun #define ACPI_FUJITSU_LAPTOP_HID "FUJ02E3"
63*4882a593Smuzhiyun #define ACPI_FUJITSU_LAPTOP_DRIVER_NAME "Fujitsu laptop FUJ02E3 ACPI hotkeys driver"
64*4882a593Smuzhiyun #define ACPI_FUJITSU_LAPTOP_DEVICE_NAME "Fujitsu FUJ02E3"
65*4882a593Smuzhiyun
66*4882a593Smuzhiyun #define ACPI_FUJITSU_NOTIFY_CODE 0x80
67*4882a593Smuzhiyun
68*4882a593Smuzhiyun /* FUNC interface - command values */
69*4882a593Smuzhiyun #define FUNC_FLAGS BIT(12)
70*4882a593Smuzhiyun #define FUNC_LEDS (BIT(12) | BIT(0))
71*4882a593Smuzhiyun #define FUNC_BUTTONS (BIT(12) | BIT(1))
72*4882a593Smuzhiyun #define FUNC_BACKLIGHT (BIT(12) | BIT(2))
73*4882a593Smuzhiyun
74*4882a593Smuzhiyun /* FUNC interface - responses */
75*4882a593Smuzhiyun #define UNSUPPORTED_CMD 0x80000000
76*4882a593Smuzhiyun
77*4882a593Smuzhiyun /* FUNC interface - status flags */
78*4882a593Smuzhiyun #define FLAG_RFKILL BIT(5)
79*4882a593Smuzhiyun #define FLAG_LID BIT(8)
80*4882a593Smuzhiyun #define FLAG_DOCK BIT(9)
81*4882a593Smuzhiyun #define FLAG_TOUCHPAD_TOGGLE BIT(26)
82*4882a593Smuzhiyun #define FLAG_MICMUTE BIT(29)
83*4882a593Smuzhiyun #define FLAG_SOFTKEYS (FLAG_RFKILL | FLAG_TOUCHPAD_TOGGLE | FLAG_MICMUTE)
84*4882a593Smuzhiyun
85*4882a593Smuzhiyun /* FUNC interface - LED control */
86*4882a593Smuzhiyun #define FUNC_LED_OFF BIT(0)
87*4882a593Smuzhiyun #define FUNC_LED_ON (BIT(0) | BIT(16) | BIT(17))
88*4882a593Smuzhiyun #define LOGOLAMP_POWERON BIT(13)
89*4882a593Smuzhiyun #define LOGOLAMP_ALWAYS BIT(14)
90*4882a593Smuzhiyun #define KEYBOARD_LAMPS BIT(8)
91*4882a593Smuzhiyun #define RADIO_LED_ON BIT(5)
92*4882a593Smuzhiyun #define ECO_LED BIT(16)
93*4882a593Smuzhiyun #define ECO_LED_ON BIT(19)
94*4882a593Smuzhiyun
95*4882a593Smuzhiyun /* FUNC interface - backlight power control */
96*4882a593Smuzhiyun #define BACKLIGHT_PARAM_POWER BIT(2)
97*4882a593Smuzhiyun #define BACKLIGHT_OFF (BIT(0) | BIT(1))
98*4882a593Smuzhiyun #define BACKLIGHT_ON 0
99*4882a593Smuzhiyun
100*4882a593Smuzhiyun /* Scancodes read from the GIRB register */
101*4882a593Smuzhiyun #define KEY1_CODE 0x410
102*4882a593Smuzhiyun #define KEY2_CODE 0x411
103*4882a593Smuzhiyun #define KEY3_CODE 0x412
104*4882a593Smuzhiyun #define KEY4_CODE 0x413
105*4882a593Smuzhiyun #define KEY5_CODE 0x420
106*4882a593Smuzhiyun
107*4882a593Smuzhiyun /* Hotkey ringbuffer limits */
108*4882a593Smuzhiyun #define MAX_HOTKEY_RINGBUFFER_SIZE 100
109*4882a593Smuzhiyun #define RINGBUFFERSIZE 40
110*4882a593Smuzhiyun
111*4882a593Smuzhiyun /* Module parameters */
112*4882a593Smuzhiyun static int use_alt_lcd_levels = -1;
113*4882a593Smuzhiyun static bool disable_brightness_adjust;
114*4882a593Smuzhiyun
115*4882a593Smuzhiyun /* Device controlling the backlight and associated keys */
116*4882a593Smuzhiyun struct fujitsu_bl {
117*4882a593Smuzhiyun struct input_dev *input;
118*4882a593Smuzhiyun char phys[32];
119*4882a593Smuzhiyun struct backlight_device *bl_device;
120*4882a593Smuzhiyun unsigned int max_brightness;
121*4882a593Smuzhiyun unsigned int brightness_level;
122*4882a593Smuzhiyun };
123*4882a593Smuzhiyun
124*4882a593Smuzhiyun static struct fujitsu_bl *fujitsu_bl;
125*4882a593Smuzhiyun
126*4882a593Smuzhiyun /* Device used to access hotkeys and other features on the laptop */
127*4882a593Smuzhiyun struct fujitsu_laptop {
128*4882a593Smuzhiyun struct input_dev *input;
129*4882a593Smuzhiyun char phys[32];
130*4882a593Smuzhiyun struct platform_device *pf_device;
131*4882a593Smuzhiyun struct kfifo fifo;
132*4882a593Smuzhiyun spinlock_t fifo_lock;
133*4882a593Smuzhiyun int flags_supported;
134*4882a593Smuzhiyun int flags_state;
135*4882a593Smuzhiyun };
136*4882a593Smuzhiyun
137*4882a593Smuzhiyun static struct acpi_device *fext;
138*4882a593Smuzhiyun
139*4882a593Smuzhiyun /* Fujitsu ACPI interface function */
140*4882a593Smuzhiyun
call_fext_func(struct acpi_device * device,int func,int op,int feature,int state)141*4882a593Smuzhiyun static int call_fext_func(struct acpi_device *device,
142*4882a593Smuzhiyun int func, int op, int feature, int state)
143*4882a593Smuzhiyun {
144*4882a593Smuzhiyun union acpi_object params[4] = {
145*4882a593Smuzhiyun { .integer.type = ACPI_TYPE_INTEGER, .integer.value = func },
146*4882a593Smuzhiyun { .integer.type = ACPI_TYPE_INTEGER, .integer.value = op },
147*4882a593Smuzhiyun { .integer.type = ACPI_TYPE_INTEGER, .integer.value = feature },
148*4882a593Smuzhiyun { .integer.type = ACPI_TYPE_INTEGER, .integer.value = state }
149*4882a593Smuzhiyun };
150*4882a593Smuzhiyun struct acpi_object_list arg_list = { 4, params };
151*4882a593Smuzhiyun unsigned long long value;
152*4882a593Smuzhiyun acpi_status status;
153*4882a593Smuzhiyun
154*4882a593Smuzhiyun status = acpi_evaluate_integer(device->handle, "FUNC", &arg_list,
155*4882a593Smuzhiyun &value);
156*4882a593Smuzhiyun if (ACPI_FAILURE(status)) {
157*4882a593Smuzhiyun acpi_handle_err(device->handle, "Failed to evaluate FUNC\n");
158*4882a593Smuzhiyun return -ENODEV;
159*4882a593Smuzhiyun }
160*4882a593Smuzhiyun
161*4882a593Smuzhiyun acpi_handle_debug(device->handle,
162*4882a593Smuzhiyun "FUNC 0x%x (args 0x%x, 0x%x, 0x%x) returned 0x%x\n",
163*4882a593Smuzhiyun func, op, feature, state, (int)value);
164*4882a593Smuzhiyun return value;
165*4882a593Smuzhiyun }
166*4882a593Smuzhiyun
167*4882a593Smuzhiyun /* Hardware access for LCD brightness control */
168*4882a593Smuzhiyun
set_lcd_level(struct acpi_device * device,int level)169*4882a593Smuzhiyun static int set_lcd_level(struct acpi_device *device, int level)
170*4882a593Smuzhiyun {
171*4882a593Smuzhiyun struct fujitsu_bl *priv = acpi_driver_data(device);
172*4882a593Smuzhiyun acpi_status status;
173*4882a593Smuzhiyun char *method;
174*4882a593Smuzhiyun
175*4882a593Smuzhiyun switch (use_alt_lcd_levels) {
176*4882a593Smuzhiyun case -1:
177*4882a593Smuzhiyun if (acpi_has_method(device->handle, "SBL2"))
178*4882a593Smuzhiyun method = "SBL2";
179*4882a593Smuzhiyun else
180*4882a593Smuzhiyun method = "SBLL";
181*4882a593Smuzhiyun break;
182*4882a593Smuzhiyun case 1:
183*4882a593Smuzhiyun method = "SBL2";
184*4882a593Smuzhiyun break;
185*4882a593Smuzhiyun default:
186*4882a593Smuzhiyun method = "SBLL";
187*4882a593Smuzhiyun break;
188*4882a593Smuzhiyun }
189*4882a593Smuzhiyun
190*4882a593Smuzhiyun acpi_handle_debug(device->handle, "set lcd level via %s [%d]\n", method,
191*4882a593Smuzhiyun level);
192*4882a593Smuzhiyun
193*4882a593Smuzhiyun if (level < 0 || level >= priv->max_brightness)
194*4882a593Smuzhiyun return -EINVAL;
195*4882a593Smuzhiyun
196*4882a593Smuzhiyun status = acpi_execute_simple_method(device->handle, method, level);
197*4882a593Smuzhiyun if (ACPI_FAILURE(status)) {
198*4882a593Smuzhiyun acpi_handle_err(device->handle, "Failed to evaluate %s\n",
199*4882a593Smuzhiyun method);
200*4882a593Smuzhiyun return -ENODEV;
201*4882a593Smuzhiyun }
202*4882a593Smuzhiyun
203*4882a593Smuzhiyun priv->brightness_level = level;
204*4882a593Smuzhiyun
205*4882a593Smuzhiyun return 0;
206*4882a593Smuzhiyun }
207*4882a593Smuzhiyun
get_lcd_level(struct acpi_device * device)208*4882a593Smuzhiyun static int get_lcd_level(struct acpi_device *device)
209*4882a593Smuzhiyun {
210*4882a593Smuzhiyun struct fujitsu_bl *priv = acpi_driver_data(device);
211*4882a593Smuzhiyun unsigned long long state = 0;
212*4882a593Smuzhiyun acpi_status status = AE_OK;
213*4882a593Smuzhiyun
214*4882a593Smuzhiyun acpi_handle_debug(device->handle, "get lcd level via GBLL\n");
215*4882a593Smuzhiyun
216*4882a593Smuzhiyun status = acpi_evaluate_integer(device->handle, "GBLL", NULL, &state);
217*4882a593Smuzhiyun if (ACPI_FAILURE(status))
218*4882a593Smuzhiyun return 0;
219*4882a593Smuzhiyun
220*4882a593Smuzhiyun priv->brightness_level = state & 0x0fffffff;
221*4882a593Smuzhiyun
222*4882a593Smuzhiyun return priv->brightness_level;
223*4882a593Smuzhiyun }
224*4882a593Smuzhiyun
get_max_brightness(struct acpi_device * device)225*4882a593Smuzhiyun static int get_max_brightness(struct acpi_device *device)
226*4882a593Smuzhiyun {
227*4882a593Smuzhiyun struct fujitsu_bl *priv = acpi_driver_data(device);
228*4882a593Smuzhiyun unsigned long long state = 0;
229*4882a593Smuzhiyun acpi_status status = AE_OK;
230*4882a593Smuzhiyun
231*4882a593Smuzhiyun acpi_handle_debug(device->handle, "get max lcd level via RBLL\n");
232*4882a593Smuzhiyun
233*4882a593Smuzhiyun status = acpi_evaluate_integer(device->handle, "RBLL", NULL, &state);
234*4882a593Smuzhiyun if (ACPI_FAILURE(status))
235*4882a593Smuzhiyun return -1;
236*4882a593Smuzhiyun
237*4882a593Smuzhiyun priv->max_brightness = state;
238*4882a593Smuzhiyun
239*4882a593Smuzhiyun return priv->max_brightness;
240*4882a593Smuzhiyun }
241*4882a593Smuzhiyun
242*4882a593Smuzhiyun /* Backlight device stuff */
243*4882a593Smuzhiyun
bl_get_brightness(struct backlight_device * b)244*4882a593Smuzhiyun static int bl_get_brightness(struct backlight_device *b)
245*4882a593Smuzhiyun {
246*4882a593Smuzhiyun struct acpi_device *device = bl_get_data(b);
247*4882a593Smuzhiyun
248*4882a593Smuzhiyun return b->props.power == FB_BLANK_POWERDOWN ? 0 : get_lcd_level(device);
249*4882a593Smuzhiyun }
250*4882a593Smuzhiyun
bl_update_status(struct backlight_device * b)251*4882a593Smuzhiyun static int bl_update_status(struct backlight_device *b)
252*4882a593Smuzhiyun {
253*4882a593Smuzhiyun struct acpi_device *device = bl_get_data(b);
254*4882a593Smuzhiyun
255*4882a593Smuzhiyun if (fext) {
256*4882a593Smuzhiyun if (b->props.power == FB_BLANK_POWERDOWN)
257*4882a593Smuzhiyun call_fext_func(fext, FUNC_BACKLIGHT, 0x1,
258*4882a593Smuzhiyun BACKLIGHT_PARAM_POWER, BACKLIGHT_OFF);
259*4882a593Smuzhiyun else
260*4882a593Smuzhiyun call_fext_func(fext, FUNC_BACKLIGHT, 0x1,
261*4882a593Smuzhiyun BACKLIGHT_PARAM_POWER, BACKLIGHT_ON);
262*4882a593Smuzhiyun }
263*4882a593Smuzhiyun
264*4882a593Smuzhiyun return set_lcd_level(device, b->props.brightness);
265*4882a593Smuzhiyun }
266*4882a593Smuzhiyun
267*4882a593Smuzhiyun static const struct backlight_ops fujitsu_bl_ops = {
268*4882a593Smuzhiyun .get_brightness = bl_get_brightness,
269*4882a593Smuzhiyun .update_status = bl_update_status,
270*4882a593Smuzhiyun };
271*4882a593Smuzhiyun
lid_show(struct device * dev,struct device_attribute * attr,char * buf)272*4882a593Smuzhiyun static ssize_t lid_show(struct device *dev, struct device_attribute *attr,
273*4882a593Smuzhiyun char *buf)
274*4882a593Smuzhiyun {
275*4882a593Smuzhiyun struct fujitsu_laptop *priv = dev_get_drvdata(dev);
276*4882a593Smuzhiyun
277*4882a593Smuzhiyun if (!(priv->flags_supported & FLAG_LID))
278*4882a593Smuzhiyun return sprintf(buf, "unknown\n");
279*4882a593Smuzhiyun if (priv->flags_state & FLAG_LID)
280*4882a593Smuzhiyun return sprintf(buf, "open\n");
281*4882a593Smuzhiyun else
282*4882a593Smuzhiyun return sprintf(buf, "closed\n");
283*4882a593Smuzhiyun }
284*4882a593Smuzhiyun
dock_show(struct device * dev,struct device_attribute * attr,char * buf)285*4882a593Smuzhiyun static ssize_t dock_show(struct device *dev, struct device_attribute *attr,
286*4882a593Smuzhiyun char *buf)
287*4882a593Smuzhiyun {
288*4882a593Smuzhiyun struct fujitsu_laptop *priv = dev_get_drvdata(dev);
289*4882a593Smuzhiyun
290*4882a593Smuzhiyun if (!(priv->flags_supported & FLAG_DOCK))
291*4882a593Smuzhiyun return sprintf(buf, "unknown\n");
292*4882a593Smuzhiyun if (priv->flags_state & FLAG_DOCK)
293*4882a593Smuzhiyun return sprintf(buf, "docked\n");
294*4882a593Smuzhiyun else
295*4882a593Smuzhiyun return sprintf(buf, "undocked\n");
296*4882a593Smuzhiyun }
297*4882a593Smuzhiyun
radios_show(struct device * dev,struct device_attribute * attr,char * buf)298*4882a593Smuzhiyun static ssize_t radios_show(struct device *dev, struct device_attribute *attr,
299*4882a593Smuzhiyun char *buf)
300*4882a593Smuzhiyun {
301*4882a593Smuzhiyun struct fujitsu_laptop *priv = dev_get_drvdata(dev);
302*4882a593Smuzhiyun
303*4882a593Smuzhiyun if (!(priv->flags_supported & FLAG_RFKILL))
304*4882a593Smuzhiyun return sprintf(buf, "unknown\n");
305*4882a593Smuzhiyun if (priv->flags_state & FLAG_RFKILL)
306*4882a593Smuzhiyun return sprintf(buf, "on\n");
307*4882a593Smuzhiyun else
308*4882a593Smuzhiyun return sprintf(buf, "killed\n");
309*4882a593Smuzhiyun }
310*4882a593Smuzhiyun
311*4882a593Smuzhiyun static DEVICE_ATTR_RO(lid);
312*4882a593Smuzhiyun static DEVICE_ATTR_RO(dock);
313*4882a593Smuzhiyun static DEVICE_ATTR_RO(radios);
314*4882a593Smuzhiyun
315*4882a593Smuzhiyun static struct attribute *fujitsu_pf_attributes[] = {
316*4882a593Smuzhiyun &dev_attr_lid.attr,
317*4882a593Smuzhiyun &dev_attr_dock.attr,
318*4882a593Smuzhiyun &dev_attr_radios.attr,
319*4882a593Smuzhiyun NULL
320*4882a593Smuzhiyun };
321*4882a593Smuzhiyun
322*4882a593Smuzhiyun static const struct attribute_group fujitsu_pf_attribute_group = {
323*4882a593Smuzhiyun .attrs = fujitsu_pf_attributes
324*4882a593Smuzhiyun };
325*4882a593Smuzhiyun
326*4882a593Smuzhiyun static struct platform_driver fujitsu_pf_driver = {
327*4882a593Smuzhiyun .driver = {
328*4882a593Smuzhiyun .name = "fujitsu-laptop",
329*4882a593Smuzhiyun }
330*4882a593Smuzhiyun };
331*4882a593Smuzhiyun
332*4882a593Smuzhiyun /* ACPI device for LCD brightness control */
333*4882a593Smuzhiyun
334*4882a593Smuzhiyun static const struct key_entry keymap_backlight[] = {
335*4882a593Smuzhiyun { KE_KEY, true, { KEY_BRIGHTNESSUP } },
336*4882a593Smuzhiyun { KE_KEY, false, { KEY_BRIGHTNESSDOWN } },
337*4882a593Smuzhiyun { KE_END, 0 }
338*4882a593Smuzhiyun };
339*4882a593Smuzhiyun
acpi_fujitsu_bl_input_setup(struct acpi_device * device)340*4882a593Smuzhiyun static int acpi_fujitsu_bl_input_setup(struct acpi_device *device)
341*4882a593Smuzhiyun {
342*4882a593Smuzhiyun struct fujitsu_bl *priv = acpi_driver_data(device);
343*4882a593Smuzhiyun int ret;
344*4882a593Smuzhiyun
345*4882a593Smuzhiyun priv->input = devm_input_allocate_device(&device->dev);
346*4882a593Smuzhiyun if (!priv->input)
347*4882a593Smuzhiyun return -ENOMEM;
348*4882a593Smuzhiyun
349*4882a593Smuzhiyun snprintf(priv->phys, sizeof(priv->phys), "%s/video/input0",
350*4882a593Smuzhiyun acpi_device_hid(device));
351*4882a593Smuzhiyun
352*4882a593Smuzhiyun priv->input->name = acpi_device_name(device);
353*4882a593Smuzhiyun priv->input->phys = priv->phys;
354*4882a593Smuzhiyun priv->input->id.bustype = BUS_HOST;
355*4882a593Smuzhiyun priv->input->id.product = 0x06;
356*4882a593Smuzhiyun
357*4882a593Smuzhiyun ret = sparse_keymap_setup(priv->input, keymap_backlight, NULL);
358*4882a593Smuzhiyun if (ret)
359*4882a593Smuzhiyun return ret;
360*4882a593Smuzhiyun
361*4882a593Smuzhiyun return input_register_device(priv->input);
362*4882a593Smuzhiyun }
363*4882a593Smuzhiyun
fujitsu_backlight_register(struct acpi_device * device)364*4882a593Smuzhiyun static int fujitsu_backlight_register(struct acpi_device *device)
365*4882a593Smuzhiyun {
366*4882a593Smuzhiyun struct fujitsu_bl *priv = acpi_driver_data(device);
367*4882a593Smuzhiyun const struct backlight_properties props = {
368*4882a593Smuzhiyun .brightness = priv->brightness_level,
369*4882a593Smuzhiyun .max_brightness = priv->max_brightness - 1,
370*4882a593Smuzhiyun .type = BACKLIGHT_PLATFORM
371*4882a593Smuzhiyun };
372*4882a593Smuzhiyun struct backlight_device *bd;
373*4882a593Smuzhiyun
374*4882a593Smuzhiyun bd = devm_backlight_device_register(&device->dev, "fujitsu-laptop",
375*4882a593Smuzhiyun &device->dev, device,
376*4882a593Smuzhiyun &fujitsu_bl_ops, &props);
377*4882a593Smuzhiyun if (IS_ERR(bd))
378*4882a593Smuzhiyun return PTR_ERR(bd);
379*4882a593Smuzhiyun
380*4882a593Smuzhiyun priv->bl_device = bd;
381*4882a593Smuzhiyun
382*4882a593Smuzhiyun return 0;
383*4882a593Smuzhiyun }
384*4882a593Smuzhiyun
acpi_fujitsu_bl_add(struct acpi_device * device)385*4882a593Smuzhiyun static int acpi_fujitsu_bl_add(struct acpi_device *device)
386*4882a593Smuzhiyun {
387*4882a593Smuzhiyun struct fujitsu_bl *priv;
388*4882a593Smuzhiyun int ret;
389*4882a593Smuzhiyun
390*4882a593Smuzhiyun if (acpi_video_get_backlight_type() != acpi_backlight_vendor)
391*4882a593Smuzhiyun return -ENODEV;
392*4882a593Smuzhiyun
393*4882a593Smuzhiyun priv = devm_kzalloc(&device->dev, sizeof(*priv), GFP_KERNEL);
394*4882a593Smuzhiyun if (!priv)
395*4882a593Smuzhiyun return -ENOMEM;
396*4882a593Smuzhiyun
397*4882a593Smuzhiyun fujitsu_bl = priv;
398*4882a593Smuzhiyun strcpy(acpi_device_name(device), ACPI_FUJITSU_BL_DEVICE_NAME);
399*4882a593Smuzhiyun strcpy(acpi_device_class(device), ACPI_FUJITSU_CLASS);
400*4882a593Smuzhiyun device->driver_data = priv;
401*4882a593Smuzhiyun
402*4882a593Smuzhiyun pr_info("ACPI: %s [%s]\n",
403*4882a593Smuzhiyun acpi_device_name(device), acpi_device_bid(device));
404*4882a593Smuzhiyun
405*4882a593Smuzhiyun if (get_max_brightness(device) <= 0)
406*4882a593Smuzhiyun priv->max_brightness = FUJITSU_LCD_N_LEVELS;
407*4882a593Smuzhiyun get_lcd_level(device);
408*4882a593Smuzhiyun
409*4882a593Smuzhiyun ret = acpi_fujitsu_bl_input_setup(device);
410*4882a593Smuzhiyun if (ret)
411*4882a593Smuzhiyun return ret;
412*4882a593Smuzhiyun
413*4882a593Smuzhiyun return fujitsu_backlight_register(device);
414*4882a593Smuzhiyun }
415*4882a593Smuzhiyun
416*4882a593Smuzhiyun /* Brightness notify */
417*4882a593Smuzhiyun
acpi_fujitsu_bl_notify(struct acpi_device * device,u32 event)418*4882a593Smuzhiyun static void acpi_fujitsu_bl_notify(struct acpi_device *device, u32 event)
419*4882a593Smuzhiyun {
420*4882a593Smuzhiyun struct fujitsu_bl *priv = acpi_driver_data(device);
421*4882a593Smuzhiyun int oldb, newb;
422*4882a593Smuzhiyun
423*4882a593Smuzhiyun if (event != ACPI_FUJITSU_NOTIFY_CODE) {
424*4882a593Smuzhiyun acpi_handle_info(device->handle, "unsupported event [0x%x]\n",
425*4882a593Smuzhiyun event);
426*4882a593Smuzhiyun sparse_keymap_report_event(priv->input, -1, 1, true);
427*4882a593Smuzhiyun return;
428*4882a593Smuzhiyun }
429*4882a593Smuzhiyun
430*4882a593Smuzhiyun oldb = priv->brightness_level;
431*4882a593Smuzhiyun get_lcd_level(device);
432*4882a593Smuzhiyun newb = priv->brightness_level;
433*4882a593Smuzhiyun
434*4882a593Smuzhiyun acpi_handle_debug(device->handle,
435*4882a593Smuzhiyun "brightness button event [%i -> %i]\n", oldb, newb);
436*4882a593Smuzhiyun
437*4882a593Smuzhiyun if (oldb == newb)
438*4882a593Smuzhiyun return;
439*4882a593Smuzhiyun
440*4882a593Smuzhiyun if (!disable_brightness_adjust)
441*4882a593Smuzhiyun set_lcd_level(device, newb);
442*4882a593Smuzhiyun
443*4882a593Smuzhiyun sparse_keymap_report_event(priv->input, oldb < newb, 1, true);
444*4882a593Smuzhiyun }
445*4882a593Smuzhiyun
446*4882a593Smuzhiyun /* ACPI device for hotkey handling */
447*4882a593Smuzhiyun
448*4882a593Smuzhiyun static const struct key_entry keymap_default[] = {
449*4882a593Smuzhiyun { KE_KEY, KEY1_CODE, { KEY_PROG1 } },
450*4882a593Smuzhiyun { KE_KEY, KEY2_CODE, { KEY_PROG2 } },
451*4882a593Smuzhiyun { KE_KEY, KEY3_CODE, { KEY_PROG3 } },
452*4882a593Smuzhiyun { KE_KEY, KEY4_CODE, { KEY_PROG4 } },
453*4882a593Smuzhiyun { KE_KEY, KEY5_CODE, { KEY_RFKILL } },
454*4882a593Smuzhiyun /* Soft keys read from status flags */
455*4882a593Smuzhiyun { KE_KEY, FLAG_RFKILL, { KEY_RFKILL } },
456*4882a593Smuzhiyun { KE_KEY, FLAG_TOUCHPAD_TOGGLE, { KEY_TOUCHPAD_TOGGLE } },
457*4882a593Smuzhiyun { KE_KEY, FLAG_MICMUTE, { KEY_MICMUTE } },
458*4882a593Smuzhiyun { KE_END, 0 }
459*4882a593Smuzhiyun };
460*4882a593Smuzhiyun
461*4882a593Smuzhiyun static const struct key_entry keymap_s64x0[] = {
462*4882a593Smuzhiyun { KE_KEY, KEY1_CODE, { KEY_SCREENLOCK } }, /* "Lock" */
463*4882a593Smuzhiyun { KE_KEY, KEY2_CODE, { KEY_HELP } }, /* "Mobility Center */
464*4882a593Smuzhiyun { KE_KEY, KEY3_CODE, { KEY_PROG3 } },
465*4882a593Smuzhiyun { KE_KEY, KEY4_CODE, { KEY_PROG4 } },
466*4882a593Smuzhiyun { KE_END, 0 }
467*4882a593Smuzhiyun };
468*4882a593Smuzhiyun
469*4882a593Smuzhiyun static const struct key_entry keymap_p8010[] = {
470*4882a593Smuzhiyun { KE_KEY, KEY1_CODE, { KEY_HELP } }, /* "Support" */
471*4882a593Smuzhiyun { KE_KEY, KEY2_CODE, { KEY_PROG2 } },
472*4882a593Smuzhiyun { KE_KEY, KEY3_CODE, { KEY_SWITCHVIDEOMODE } }, /* "Presentation" */
473*4882a593Smuzhiyun { KE_KEY, KEY4_CODE, { KEY_WWW } }, /* "WWW" */
474*4882a593Smuzhiyun { KE_END, 0 }
475*4882a593Smuzhiyun };
476*4882a593Smuzhiyun
477*4882a593Smuzhiyun static const struct key_entry *keymap = keymap_default;
478*4882a593Smuzhiyun
fujitsu_laptop_dmi_keymap_override(const struct dmi_system_id * id)479*4882a593Smuzhiyun static int fujitsu_laptop_dmi_keymap_override(const struct dmi_system_id *id)
480*4882a593Smuzhiyun {
481*4882a593Smuzhiyun pr_info("Identified laptop model '%s'\n", id->ident);
482*4882a593Smuzhiyun keymap = id->driver_data;
483*4882a593Smuzhiyun return 1;
484*4882a593Smuzhiyun }
485*4882a593Smuzhiyun
486*4882a593Smuzhiyun static const struct dmi_system_id fujitsu_laptop_dmi_table[] = {
487*4882a593Smuzhiyun {
488*4882a593Smuzhiyun .callback = fujitsu_laptop_dmi_keymap_override,
489*4882a593Smuzhiyun .ident = "Fujitsu Siemens S6410",
490*4882a593Smuzhiyun .matches = {
491*4882a593Smuzhiyun DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"),
492*4882a593Smuzhiyun DMI_MATCH(DMI_PRODUCT_NAME, "LIFEBOOK S6410"),
493*4882a593Smuzhiyun },
494*4882a593Smuzhiyun .driver_data = (void *)keymap_s64x0
495*4882a593Smuzhiyun },
496*4882a593Smuzhiyun {
497*4882a593Smuzhiyun .callback = fujitsu_laptop_dmi_keymap_override,
498*4882a593Smuzhiyun .ident = "Fujitsu Siemens S6420",
499*4882a593Smuzhiyun .matches = {
500*4882a593Smuzhiyun DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"),
501*4882a593Smuzhiyun DMI_MATCH(DMI_PRODUCT_NAME, "LIFEBOOK S6420"),
502*4882a593Smuzhiyun },
503*4882a593Smuzhiyun .driver_data = (void *)keymap_s64x0
504*4882a593Smuzhiyun },
505*4882a593Smuzhiyun {
506*4882a593Smuzhiyun .callback = fujitsu_laptop_dmi_keymap_override,
507*4882a593Smuzhiyun .ident = "Fujitsu LifeBook P8010",
508*4882a593Smuzhiyun .matches = {
509*4882a593Smuzhiyun DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"),
510*4882a593Smuzhiyun DMI_MATCH(DMI_PRODUCT_NAME, "LifeBook P8010"),
511*4882a593Smuzhiyun },
512*4882a593Smuzhiyun .driver_data = (void *)keymap_p8010
513*4882a593Smuzhiyun },
514*4882a593Smuzhiyun {}
515*4882a593Smuzhiyun };
516*4882a593Smuzhiyun
acpi_fujitsu_laptop_input_setup(struct acpi_device * device)517*4882a593Smuzhiyun static int acpi_fujitsu_laptop_input_setup(struct acpi_device *device)
518*4882a593Smuzhiyun {
519*4882a593Smuzhiyun struct fujitsu_laptop *priv = acpi_driver_data(device);
520*4882a593Smuzhiyun int ret;
521*4882a593Smuzhiyun
522*4882a593Smuzhiyun priv->input = devm_input_allocate_device(&device->dev);
523*4882a593Smuzhiyun if (!priv->input)
524*4882a593Smuzhiyun return -ENOMEM;
525*4882a593Smuzhiyun
526*4882a593Smuzhiyun snprintf(priv->phys, sizeof(priv->phys), "%s/input0",
527*4882a593Smuzhiyun acpi_device_hid(device));
528*4882a593Smuzhiyun
529*4882a593Smuzhiyun priv->input->name = acpi_device_name(device);
530*4882a593Smuzhiyun priv->input->phys = priv->phys;
531*4882a593Smuzhiyun priv->input->id.bustype = BUS_HOST;
532*4882a593Smuzhiyun
533*4882a593Smuzhiyun dmi_check_system(fujitsu_laptop_dmi_table);
534*4882a593Smuzhiyun ret = sparse_keymap_setup(priv->input, keymap, NULL);
535*4882a593Smuzhiyun if (ret)
536*4882a593Smuzhiyun return ret;
537*4882a593Smuzhiyun
538*4882a593Smuzhiyun return input_register_device(priv->input);
539*4882a593Smuzhiyun }
540*4882a593Smuzhiyun
fujitsu_laptop_platform_add(struct acpi_device * device)541*4882a593Smuzhiyun static int fujitsu_laptop_platform_add(struct acpi_device *device)
542*4882a593Smuzhiyun {
543*4882a593Smuzhiyun struct fujitsu_laptop *priv = acpi_driver_data(device);
544*4882a593Smuzhiyun int ret;
545*4882a593Smuzhiyun
546*4882a593Smuzhiyun priv->pf_device = platform_device_alloc("fujitsu-laptop", -1);
547*4882a593Smuzhiyun if (!priv->pf_device)
548*4882a593Smuzhiyun return -ENOMEM;
549*4882a593Smuzhiyun
550*4882a593Smuzhiyun platform_set_drvdata(priv->pf_device, priv);
551*4882a593Smuzhiyun
552*4882a593Smuzhiyun ret = platform_device_add(priv->pf_device);
553*4882a593Smuzhiyun if (ret)
554*4882a593Smuzhiyun goto err_put_platform_device;
555*4882a593Smuzhiyun
556*4882a593Smuzhiyun ret = sysfs_create_group(&priv->pf_device->dev.kobj,
557*4882a593Smuzhiyun &fujitsu_pf_attribute_group);
558*4882a593Smuzhiyun if (ret)
559*4882a593Smuzhiyun goto err_del_platform_device;
560*4882a593Smuzhiyun
561*4882a593Smuzhiyun return 0;
562*4882a593Smuzhiyun
563*4882a593Smuzhiyun err_del_platform_device:
564*4882a593Smuzhiyun platform_device_del(priv->pf_device);
565*4882a593Smuzhiyun err_put_platform_device:
566*4882a593Smuzhiyun platform_device_put(priv->pf_device);
567*4882a593Smuzhiyun
568*4882a593Smuzhiyun return ret;
569*4882a593Smuzhiyun }
570*4882a593Smuzhiyun
fujitsu_laptop_platform_remove(struct acpi_device * device)571*4882a593Smuzhiyun static void fujitsu_laptop_platform_remove(struct acpi_device *device)
572*4882a593Smuzhiyun {
573*4882a593Smuzhiyun struct fujitsu_laptop *priv = acpi_driver_data(device);
574*4882a593Smuzhiyun
575*4882a593Smuzhiyun sysfs_remove_group(&priv->pf_device->dev.kobj,
576*4882a593Smuzhiyun &fujitsu_pf_attribute_group);
577*4882a593Smuzhiyun platform_device_unregister(priv->pf_device);
578*4882a593Smuzhiyun }
579*4882a593Smuzhiyun
logolamp_set(struct led_classdev * cdev,enum led_brightness brightness)580*4882a593Smuzhiyun static int logolamp_set(struct led_classdev *cdev,
581*4882a593Smuzhiyun enum led_brightness brightness)
582*4882a593Smuzhiyun {
583*4882a593Smuzhiyun struct acpi_device *device = to_acpi_device(cdev->dev->parent);
584*4882a593Smuzhiyun int poweron = FUNC_LED_ON, always = FUNC_LED_ON;
585*4882a593Smuzhiyun int ret;
586*4882a593Smuzhiyun
587*4882a593Smuzhiyun if (brightness < LED_HALF)
588*4882a593Smuzhiyun poweron = FUNC_LED_OFF;
589*4882a593Smuzhiyun
590*4882a593Smuzhiyun if (brightness < LED_FULL)
591*4882a593Smuzhiyun always = FUNC_LED_OFF;
592*4882a593Smuzhiyun
593*4882a593Smuzhiyun ret = call_fext_func(device, FUNC_LEDS, 0x1, LOGOLAMP_POWERON, poweron);
594*4882a593Smuzhiyun if (ret < 0)
595*4882a593Smuzhiyun return ret;
596*4882a593Smuzhiyun
597*4882a593Smuzhiyun return call_fext_func(device, FUNC_LEDS, 0x1, LOGOLAMP_ALWAYS, always);
598*4882a593Smuzhiyun }
599*4882a593Smuzhiyun
logolamp_get(struct led_classdev * cdev)600*4882a593Smuzhiyun static enum led_brightness logolamp_get(struct led_classdev *cdev)
601*4882a593Smuzhiyun {
602*4882a593Smuzhiyun struct acpi_device *device = to_acpi_device(cdev->dev->parent);
603*4882a593Smuzhiyun int ret;
604*4882a593Smuzhiyun
605*4882a593Smuzhiyun ret = call_fext_func(device, FUNC_LEDS, 0x2, LOGOLAMP_ALWAYS, 0x0);
606*4882a593Smuzhiyun if (ret == FUNC_LED_ON)
607*4882a593Smuzhiyun return LED_FULL;
608*4882a593Smuzhiyun
609*4882a593Smuzhiyun ret = call_fext_func(device, FUNC_LEDS, 0x2, LOGOLAMP_POWERON, 0x0);
610*4882a593Smuzhiyun if (ret == FUNC_LED_ON)
611*4882a593Smuzhiyun return LED_HALF;
612*4882a593Smuzhiyun
613*4882a593Smuzhiyun return LED_OFF;
614*4882a593Smuzhiyun }
615*4882a593Smuzhiyun
kblamps_set(struct led_classdev * cdev,enum led_brightness brightness)616*4882a593Smuzhiyun static int kblamps_set(struct led_classdev *cdev,
617*4882a593Smuzhiyun enum led_brightness brightness)
618*4882a593Smuzhiyun {
619*4882a593Smuzhiyun struct acpi_device *device = to_acpi_device(cdev->dev->parent);
620*4882a593Smuzhiyun
621*4882a593Smuzhiyun if (brightness >= LED_FULL)
622*4882a593Smuzhiyun return call_fext_func(device, FUNC_LEDS, 0x1, KEYBOARD_LAMPS,
623*4882a593Smuzhiyun FUNC_LED_ON);
624*4882a593Smuzhiyun else
625*4882a593Smuzhiyun return call_fext_func(device, FUNC_LEDS, 0x1, KEYBOARD_LAMPS,
626*4882a593Smuzhiyun FUNC_LED_OFF);
627*4882a593Smuzhiyun }
628*4882a593Smuzhiyun
kblamps_get(struct led_classdev * cdev)629*4882a593Smuzhiyun static enum led_brightness kblamps_get(struct led_classdev *cdev)
630*4882a593Smuzhiyun {
631*4882a593Smuzhiyun struct acpi_device *device = to_acpi_device(cdev->dev->parent);
632*4882a593Smuzhiyun enum led_brightness brightness = LED_OFF;
633*4882a593Smuzhiyun
634*4882a593Smuzhiyun if (call_fext_func(device,
635*4882a593Smuzhiyun FUNC_LEDS, 0x2, KEYBOARD_LAMPS, 0x0) == FUNC_LED_ON)
636*4882a593Smuzhiyun brightness = LED_FULL;
637*4882a593Smuzhiyun
638*4882a593Smuzhiyun return brightness;
639*4882a593Smuzhiyun }
640*4882a593Smuzhiyun
radio_led_set(struct led_classdev * cdev,enum led_brightness brightness)641*4882a593Smuzhiyun static int radio_led_set(struct led_classdev *cdev,
642*4882a593Smuzhiyun enum led_brightness brightness)
643*4882a593Smuzhiyun {
644*4882a593Smuzhiyun struct acpi_device *device = to_acpi_device(cdev->dev->parent);
645*4882a593Smuzhiyun
646*4882a593Smuzhiyun if (brightness >= LED_FULL)
647*4882a593Smuzhiyun return call_fext_func(device, FUNC_FLAGS, 0x5, RADIO_LED_ON,
648*4882a593Smuzhiyun RADIO_LED_ON);
649*4882a593Smuzhiyun else
650*4882a593Smuzhiyun return call_fext_func(device, FUNC_FLAGS, 0x5, RADIO_LED_ON,
651*4882a593Smuzhiyun 0x0);
652*4882a593Smuzhiyun }
653*4882a593Smuzhiyun
radio_led_get(struct led_classdev * cdev)654*4882a593Smuzhiyun static enum led_brightness radio_led_get(struct led_classdev *cdev)
655*4882a593Smuzhiyun {
656*4882a593Smuzhiyun struct acpi_device *device = to_acpi_device(cdev->dev->parent);
657*4882a593Smuzhiyun enum led_brightness brightness = LED_OFF;
658*4882a593Smuzhiyun
659*4882a593Smuzhiyun if (call_fext_func(device, FUNC_FLAGS, 0x4, 0x0, 0x0) & RADIO_LED_ON)
660*4882a593Smuzhiyun brightness = LED_FULL;
661*4882a593Smuzhiyun
662*4882a593Smuzhiyun return brightness;
663*4882a593Smuzhiyun }
664*4882a593Smuzhiyun
eco_led_set(struct led_classdev * cdev,enum led_brightness brightness)665*4882a593Smuzhiyun static int eco_led_set(struct led_classdev *cdev,
666*4882a593Smuzhiyun enum led_brightness brightness)
667*4882a593Smuzhiyun {
668*4882a593Smuzhiyun struct acpi_device *device = to_acpi_device(cdev->dev->parent);
669*4882a593Smuzhiyun int curr;
670*4882a593Smuzhiyun
671*4882a593Smuzhiyun curr = call_fext_func(device, FUNC_LEDS, 0x2, ECO_LED, 0x0);
672*4882a593Smuzhiyun if (brightness >= LED_FULL)
673*4882a593Smuzhiyun return call_fext_func(device, FUNC_LEDS, 0x1, ECO_LED,
674*4882a593Smuzhiyun curr | ECO_LED_ON);
675*4882a593Smuzhiyun else
676*4882a593Smuzhiyun return call_fext_func(device, FUNC_LEDS, 0x1, ECO_LED,
677*4882a593Smuzhiyun curr & ~ECO_LED_ON);
678*4882a593Smuzhiyun }
679*4882a593Smuzhiyun
eco_led_get(struct led_classdev * cdev)680*4882a593Smuzhiyun static enum led_brightness eco_led_get(struct led_classdev *cdev)
681*4882a593Smuzhiyun {
682*4882a593Smuzhiyun struct acpi_device *device = to_acpi_device(cdev->dev->parent);
683*4882a593Smuzhiyun enum led_brightness brightness = LED_OFF;
684*4882a593Smuzhiyun
685*4882a593Smuzhiyun if (call_fext_func(device, FUNC_LEDS, 0x2, ECO_LED, 0x0) & ECO_LED_ON)
686*4882a593Smuzhiyun brightness = LED_FULL;
687*4882a593Smuzhiyun
688*4882a593Smuzhiyun return brightness;
689*4882a593Smuzhiyun }
690*4882a593Smuzhiyun
acpi_fujitsu_laptop_leds_register(struct acpi_device * device)691*4882a593Smuzhiyun static int acpi_fujitsu_laptop_leds_register(struct acpi_device *device)
692*4882a593Smuzhiyun {
693*4882a593Smuzhiyun struct fujitsu_laptop *priv = acpi_driver_data(device);
694*4882a593Smuzhiyun struct led_classdev *led;
695*4882a593Smuzhiyun int ret;
696*4882a593Smuzhiyun
697*4882a593Smuzhiyun if (call_fext_func(device,
698*4882a593Smuzhiyun FUNC_LEDS, 0x0, 0x0, 0x0) & LOGOLAMP_POWERON) {
699*4882a593Smuzhiyun led = devm_kzalloc(&device->dev, sizeof(*led), GFP_KERNEL);
700*4882a593Smuzhiyun if (!led)
701*4882a593Smuzhiyun return -ENOMEM;
702*4882a593Smuzhiyun
703*4882a593Smuzhiyun led->name = "fujitsu::logolamp";
704*4882a593Smuzhiyun led->brightness_set_blocking = logolamp_set;
705*4882a593Smuzhiyun led->brightness_get = logolamp_get;
706*4882a593Smuzhiyun ret = devm_led_classdev_register(&device->dev, led);
707*4882a593Smuzhiyun if (ret)
708*4882a593Smuzhiyun return ret;
709*4882a593Smuzhiyun }
710*4882a593Smuzhiyun
711*4882a593Smuzhiyun if ((call_fext_func(device,
712*4882a593Smuzhiyun FUNC_LEDS, 0x0, 0x0, 0x0) & KEYBOARD_LAMPS) &&
713*4882a593Smuzhiyun (call_fext_func(device, FUNC_BUTTONS, 0x0, 0x0, 0x0) == 0x0)) {
714*4882a593Smuzhiyun led = devm_kzalloc(&device->dev, sizeof(*led), GFP_KERNEL);
715*4882a593Smuzhiyun if (!led)
716*4882a593Smuzhiyun return -ENOMEM;
717*4882a593Smuzhiyun
718*4882a593Smuzhiyun led->name = "fujitsu::kblamps";
719*4882a593Smuzhiyun led->brightness_set_blocking = kblamps_set;
720*4882a593Smuzhiyun led->brightness_get = kblamps_get;
721*4882a593Smuzhiyun ret = devm_led_classdev_register(&device->dev, led);
722*4882a593Smuzhiyun if (ret)
723*4882a593Smuzhiyun return ret;
724*4882a593Smuzhiyun }
725*4882a593Smuzhiyun
726*4882a593Smuzhiyun /*
727*4882a593Smuzhiyun * Some Fujitsu laptops have a radio toggle button in place of a slide
728*4882a593Smuzhiyun * switch and all such machines appear to also have an RF LED. Based on
729*4882a593Smuzhiyun * comparing DSDT tables of four Fujitsu Lifebook models (E744, E751,
730*4882a593Smuzhiyun * S7110, S8420; the first one has a radio toggle button, the other
731*4882a593Smuzhiyun * three have slide switches), bit 17 of flags_supported (the value
732*4882a593Smuzhiyun * returned by method S000 of ACPI device FUJ02E3) seems to indicate
733*4882a593Smuzhiyun * whether given model has a radio toggle button.
734*4882a593Smuzhiyun */
735*4882a593Smuzhiyun if (priv->flags_supported & BIT(17)) {
736*4882a593Smuzhiyun led = devm_kzalloc(&device->dev, sizeof(*led), GFP_KERNEL);
737*4882a593Smuzhiyun if (!led)
738*4882a593Smuzhiyun return -ENOMEM;
739*4882a593Smuzhiyun
740*4882a593Smuzhiyun led->name = "fujitsu::radio_led";
741*4882a593Smuzhiyun led->brightness_set_blocking = radio_led_set;
742*4882a593Smuzhiyun led->brightness_get = radio_led_get;
743*4882a593Smuzhiyun led->default_trigger = "rfkill-any";
744*4882a593Smuzhiyun ret = devm_led_classdev_register(&device->dev, led);
745*4882a593Smuzhiyun if (ret)
746*4882a593Smuzhiyun return ret;
747*4882a593Smuzhiyun }
748*4882a593Smuzhiyun
749*4882a593Smuzhiyun /* Support for eco led is not always signaled in bit corresponding
750*4882a593Smuzhiyun * to the bit used to control the led. According to the DSDT table,
751*4882a593Smuzhiyun * bit 14 seems to indicate presence of said led as well.
752*4882a593Smuzhiyun * Confirm by testing the status.
753*4882a593Smuzhiyun */
754*4882a593Smuzhiyun if ((call_fext_func(device, FUNC_LEDS, 0x0, 0x0, 0x0) & BIT(14)) &&
755*4882a593Smuzhiyun (call_fext_func(device,
756*4882a593Smuzhiyun FUNC_LEDS, 0x2, ECO_LED, 0x0) != UNSUPPORTED_CMD)) {
757*4882a593Smuzhiyun led = devm_kzalloc(&device->dev, sizeof(*led), GFP_KERNEL);
758*4882a593Smuzhiyun if (!led)
759*4882a593Smuzhiyun return -ENOMEM;
760*4882a593Smuzhiyun
761*4882a593Smuzhiyun led->name = "fujitsu::eco_led";
762*4882a593Smuzhiyun led->brightness_set_blocking = eco_led_set;
763*4882a593Smuzhiyun led->brightness_get = eco_led_get;
764*4882a593Smuzhiyun ret = devm_led_classdev_register(&device->dev, led);
765*4882a593Smuzhiyun if (ret)
766*4882a593Smuzhiyun return ret;
767*4882a593Smuzhiyun }
768*4882a593Smuzhiyun
769*4882a593Smuzhiyun return 0;
770*4882a593Smuzhiyun }
771*4882a593Smuzhiyun
acpi_fujitsu_laptop_add(struct acpi_device * device)772*4882a593Smuzhiyun static int acpi_fujitsu_laptop_add(struct acpi_device *device)
773*4882a593Smuzhiyun {
774*4882a593Smuzhiyun struct fujitsu_laptop *priv;
775*4882a593Smuzhiyun int ret, i = 0;
776*4882a593Smuzhiyun
777*4882a593Smuzhiyun priv = devm_kzalloc(&device->dev, sizeof(*priv), GFP_KERNEL);
778*4882a593Smuzhiyun if (!priv)
779*4882a593Smuzhiyun return -ENOMEM;
780*4882a593Smuzhiyun
781*4882a593Smuzhiyun WARN_ONCE(fext, "More than one FUJ02E3 ACPI device was found. Driver may not work as intended.");
782*4882a593Smuzhiyun fext = device;
783*4882a593Smuzhiyun
784*4882a593Smuzhiyun strcpy(acpi_device_name(device), ACPI_FUJITSU_LAPTOP_DEVICE_NAME);
785*4882a593Smuzhiyun strcpy(acpi_device_class(device), ACPI_FUJITSU_CLASS);
786*4882a593Smuzhiyun device->driver_data = priv;
787*4882a593Smuzhiyun
788*4882a593Smuzhiyun /* kfifo */
789*4882a593Smuzhiyun spin_lock_init(&priv->fifo_lock);
790*4882a593Smuzhiyun ret = kfifo_alloc(&priv->fifo, RINGBUFFERSIZE * sizeof(int),
791*4882a593Smuzhiyun GFP_KERNEL);
792*4882a593Smuzhiyun if (ret)
793*4882a593Smuzhiyun return ret;
794*4882a593Smuzhiyun
795*4882a593Smuzhiyun pr_info("ACPI: %s [%s]\n",
796*4882a593Smuzhiyun acpi_device_name(device), acpi_device_bid(device));
797*4882a593Smuzhiyun
798*4882a593Smuzhiyun while (call_fext_func(device, FUNC_BUTTONS, 0x1, 0x0, 0x0) != 0 &&
799*4882a593Smuzhiyun i++ < MAX_HOTKEY_RINGBUFFER_SIZE)
800*4882a593Smuzhiyun ; /* No action, result is discarded */
801*4882a593Smuzhiyun acpi_handle_debug(device->handle, "Discarded %i ringbuffer entries\n",
802*4882a593Smuzhiyun i);
803*4882a593Smuzhiyun
804*4882a593Smuzhiyun priv->flags_supported = call_fext_func(device, FUNC_FLAGS, 0x0, 0x0,
805*4882a593Smuzhiyun 0x0);
806*4882a593Smuzhiyun
807*4882a593Smuzhiyun /* Make sure our bitmask of supported functions is cleared if the
808*4882a593Smuzhiyun RFKILL function block is not implemented, like on the S7020. */
809*4882a593Smuzhiyun if (priv->flags_supported == UNSUPPORTED_CMD)
810*4882a593Smuzhiyun priv->flags_supported = 0;
811*4882a593Smuzhiyun
812*4882a593Smuzhiyun if (priv->flags_supported)
813*4882a593Smuzhiyun priv->flags_state = call_fext_func(device, FUNC_FLAGS, 0x4, 0x0,
814*4882a593Smuzhiyun 0x0);
815*4882a593Smuzhiyun
816*4882a593Smuzhiyun /* Suspect this is a keymap of the application panel, print it */
817*4882a593Smuzhiyun acpi_handle_info(device->handle, "BTNI: [0x%x]\n",
818*4882a593Smuzhiyun call_fext_func(device, FUNC_BUTTONS, 0x0, 0x0, 0x0));
819*4882a593Smuzhiyun
820*4882a593Smuzhiyun /* Sync backlight power status */
821*4882a593Smuzhiyun if (fujitsu_bl && fujitsu_bl->bl_device &&
822*4882a593Smuzhiyun acpi_video_get_backlight_type() == acpi_backlight_vendor) {
823*4882a593Smuzhiyun if (call_fext_func(fext, FUNC_BACKLIGHT, 0x2,
824*4882a593Smuzhiyun BACKLIGHT_PARAM_POWER, 0x0) == BACKLIGHT_OFF)
825*4882a593Smuzhiyun fujitsu_bl->bl_device->props.power = FB_BLANK_POWERDOWN;
826*4882a593Smuzhiyun else
827*4882a593Smuzhiyun fujitsu_bl->bl_device->props.power = FB_BLANK_UNBLANK;
828*4882a593Smuzhiyun }
829*4882a593Smuzhiyun
830*4882a593Smuzhiyun ret = acpi_fujitsu_laptop_input_setup(device);
831*4882a593Smuzhiyun if (ret)
832*4882a593Smuzhiyun goto err_free_fifo;
833*4882a593Smuzhiyun
834*4882a593Smuzhiyun ret = acpi_fujitsu_laptop_leds_register(device);
835*4882a593Smuzhiyun if (ret)
836*4882a593Smuzhiyun goto err_free_fifo;
837*4882a593Smuzhiyun
838*4882a593Smuzhiyun ret = fujitsu_laptop_platform_add(device);
839*4882a593Smuzhiyun if (ret)
840*4882a593Smuzhiyun goto err_free_fifo;
841*4882a593Smuzhiyun
842*4882a593Smuzhiyun return 0;
843*4882a593Smuzhiyun
844*4882a593Smuzhiyun err_free_fifo:
845*4882a593Smuzhiyun kfifo_free(&priv->fifo);
846*4882a593Smuzhiyun
847*4882a593Smuzhiyun return ret;
848*4882a593Smuzhiyun }
849*4882a593Smuzhiyun
acpi_fujitsu_laptop_remove(struct acpi_device * device)850*4882a593Smuzhiyun static int acpi_fujitsu_laptop_remove(struct acpi_device *device)
851*4882a593Smuzhiyun {
852*4882a593Smuzhiyun struct fujitsu_laptop *priv = acpi_driver_data(device);
853*4882a593Smuzhiyun
854*4882a593Smuzhiyun fujitsu_laptop_platform_remove(device);
855*4882a593Smuzhiyun
856*4882a593Smuzhiyun kfifo_free(&priv->fifo);
857*4882a593Smuzhiyun
858*4882a593Smuzhiyun return 0;
859*4882a593Smuzhiyun }
860*4882a593Smuzhiyun
acpi_fujitsu_laptop_press(struct acpi_device * device,int scancode)861*4882a593Smuzhiyun static void acpi_fujitsu_laptop_press(struct acpi_device *device, int scancode)
862*4882a593Smuzhiyun {
863*4882a593Smuzhiyun struct fujitsu_laptop *priv = acpi_driver_data(device);
864*4882a593Smuzhiyun int ret;
865*4882a593Smuzhiyun
866*4882a593Smuzhiyun ret = kfifo_in_locked(&priv->fifo, (unsigned char *)&scancode,
867*4882a593Smuzhiyun sizeof(scancode), &priv->fifo_lock);
868*4882a593Smuzhiyun if (ret != sizeof(scancode)) {
869*4882a593Smuzhiyun dev_info(&priv->input->dev, "Could not push scancode [0x%x]\n",
870*4882a593Smuzhiyun scancode);
871*4882a593Smuzhiyun return;
872*4882a593Smuzhiyun }
873*4882a593Smuzhiyun sparse_keymap_report_event(priv->input, scancode, 1, false);
874*4882a593Smuzhiyun dev_dbg(&priv->input->dev, "Push scancode into ringbuffer [0x%x]\n",
875*4882a593Smuzhiyun scancode);
876*4882a593Smuzhiyun }
877*4882a593Smuzhiyun
acpi_fujitsu_laptop_release(struct acpi_device * device)878*4882a593Smuzhiyun static void acpi_fujitsu_laptop_release(struct acpi_device *device)
879*4882a593Smuzhiyun {
880*4882a593Smuzhiyun struct fujitsu_laptop *priv = acpi_driver_data(device);
881*4882a593Smuzhiyun int scancode, ret;
882*4882a593Smuzhiyun
883*4882a593Smuzhiyun while (true) {
884*4882a593Smuzhiyun ret = kfifo_out_locked(&priv->fifo, (unsigned char *)&scancode,
885*4882a593Smuzhiyun sizeof(scancode), &priv->fifo_lock);
886*4882a593Smuzhiyun if (ret != sizeof(scancode))
887*4882a593Smuzhiyun return;
888*4882a593Smuzhiyun sparse_keymap_report_event(priv->input, scancode, 0, false);
889*4882a593Smuzhiyun dev_dbg(&priv->input->dev,
890*4882a593Smuzhiyun "Pop scancode from ringbuffer [0x%x]\n", scancode);
891*4882a593Smuzhiyun }
892*4882a593Smuzhiyun }
893*4882a593Smuzhiyun
acpi_fujitsu_laptop_notify(struct acpi_device * device,u32 event)894*4882a593Smuzhiyun static void acpi_fujitsu_laptop_notify(struct acpi_device *device, u32 event)
895*4882a593Smuzhiyun {
896*4882a593Smuzhiyun struct fujitsu_laptop *priv = acpi_driver_data(device);
897*4882a593Smuzhiyun unsigned long flags;
898*4882a593Smuzhiyun int scancode, i = 0;
899*4882a593Smuzhiyun unsigned int irb;
900*4882a593Smuzhiyun
901*4882a593Smuzhiyun if (event != ACPI_FUJITSU_NOTIFY_CODE) {
902*4882a593Smuzhiyun acpi_handle_info(device->handle, "Unsupported event [0x%x]\n",
903*4882a593Smuzhiyun event);
904*4882a593Smuzhiyun sparse_keymap_report_event(priv->input, -1, 1, true);
905*4882a593Smuzhiyun return;
906*4882a593Smuzhiyun }
907*4882a593Smuzhiyun
908*4882a593Smuzhiyun if (priv->flags_supported)
909*4882a593Smuzhiyun priv->flags_state = call_fext_func(device, FUNC_FLAGS, 0x4, 0x0,
910*4882a593Smuzhiyun 0x0);
911*4882a593Smuzhiyun
912*4882a593Smuzhiyun while ((irb = call_fext_func(device,
913*4882a593Smuzhiyun FUNC_BUTTONS, 0x1, 0x0, 0x0)) != 0 &&
914*4882a593Smuzhiyun i++ < MAX_HOTKEY_RINGBUFFER_SIZE) {
915*4882a593Smuzhiyun scancode = irb & 0x4ff;
916*4882a593Smuzhiyun if (sparse_keymap_entry_from_scancode(priv->input, scancode))
917*4882a593Smuzhiyun acpi_fujitsu_laptop_press(device, scancode);
918*4882a593Smuzhiyun else if (scancode == 0)
919*4882a593Smuzhiyun acpi_fujitsu_laptop_release(device);
920*4882a593Smuzhiyun else
921*4882a593Smuzhiyun acpi_handle_info(device->handle,
922*4882a593Smuzhiyun "Unknown GIRB result [%x]\n", irb);
923*4882a593Smuzhiyun }
924*4882a593Smuzhiyun
925*4882a593Smuzhiyun /*
926*4882a593Smuzhiyun * First seen on the Skylake-based Lifebook E736/E746/E756), the
927*4882a593Smuzhiyun * touchpad toggle hotkey (Fn+F4) is handled in software. Other models
928*4882a593Smuzhiyun * have since added additional "soft keys". These are reported in the
929*4882a593Smuzhiyun * status flags queried using FUNC_FLAGS.
930*4882a593Smuzhiyun */
931*4882a593Smuzhiyun if (priv->flags_supported & (FLAG_SOFTKEYS)) {
932*4882a593Smuzhiyun flags = call_fext_func(device, FUNC_FLAGS, 0x1, 0x0, 0x0);
933*4882a593Smuzhiyun flags &= (FLAG_SOFTKEYS);
934*4882a593Smuzhiyun for_each_set_bit(i, &flags, BITS_PER_LONG)
935*4882a593Smuzhiyun sparse_keymap_report_event(priv->input, BIT(i), 1, true);
936*4882a593Smuzhiyun }
937*4882a593Smuzhiyun }
938*4882a593Smuzhiyun
939*4882a593Smuzhiyun /* Initialization */
940*4882a593Smuzhiyun
941*4882a593Smuzhiyun static const struct acpi_device_id fujitsu_bl_device_ids[] = {
942*4882a593Smuzhiyun {ACPI_FUJITSU_BL_HID, 0},
943*4882a593Smuzhiyun {"", 0},
944*4882a593Smuzhiyun };
945*4882a593Smuzhiyun
946*4882a593Smuzhiyun static struct acpi_driver acpi_fujitsu_bl_driver = {
947*4882a593Smuzhiyun .name = ACPI_FUJITSU_BL_DRIVER_NAME,
948*4882a593Smuzhiyun .class = ACPI_FUJITSU_CLASS,
949*4882a593Smuzhiyun .ids = fujitsu_bl_device_ids,
950*4882a593Smuzhiyun .ops = {
951*4882a593Smuzhiyun .add = acpi_fujitsu_bl_add,
952*4882a593Smuzhiyun .notify = acpi_fujitsu_bl_notify,
953*4882a593Smuzhiyun },
954*4882a593Smuzhiyun };
955*4882a593Smuzhiyun
956*4882a593Smuzhiyun static const struct acpi_device_id fujitsu_laptop_device_ids[] = {
957*4882a593Smuzhiyun {ACPI_FUJITSU_LAPTOP_HID, 0},
958*4882a593Smuzhiyun {"", 0},
959*4882a593Smuzhiyun };
960*4882a593Smuzhiyun
961*4882a593Smuzhiyun static struct acpi_driver acpi_fujitsu_laptop_driver = {
962*4882a593Smuzhiyun .name = ACPI_FUJITSU_LAPTOP_DRIVER_NAME,
963*4882a593Smuzhiyun .class = ACPI_FUJITSU_CLASS,
964*4882a593Smuzhiyun .ids = fujitsu_laptop_device_ids,
965*4882a593Smuzhiyun .ops = {
966*4882a593Smuzhiyun .add = acpi_fujitsu_laptop_add,
967*4882a593Smuzhiyun .remove = acpi_fujitsu_laptop_remove,
968*4882a593Smuzhiyun .notify = acpi_fujitsu_laptop_notify,
969*4882a593Smuzhiyun },
970*4882a593Smuzhiyun };
971*4882a593Smuzhiyun
972*4882a593Smuzhiyun static const struct acpi_device_id fujitsu_ids[] __used = {
973*4882a593Smuzhiyun {ACPI_FUJITSU_BL_HID, 0},
974*4882a593Smuzhiyun {ACPI_FUJITSU_LAPTOP_HID, 0},
975*4882a593Smuzhiyun {"", 0}
976*4882a593Smuzhiyun };
977*4882a593Smuzhiyun MODULE_DEVICE_TABLE(acpi, fujitsu_ids);
978*4882a593Smuzhiyun
fujitsu_init(void)979*4882a593Smuzhiyun static int __init fujitsu_init(void)
980*4882a593Smuzhiyun {
981*4882a593Smuzhiyun int ret;
982*4882a593Smuzhiyun
983*4882a593Smuzhiyun ret = acpi_bus_register_driver(&acpi_fujitsu_bl_driver);
984*4882a593Smuzhiyun if (ret)
985*4882a593Smuzhiyun return ret;
986*4882a593Smuzhiyun
987*4882a593Smuzhiyun /* Register platform stuff */
988*4882a593Smuzhiyun
989*4882a593Smuzhiyun ret = platform_driver_register(&fujitsu_pf_driver);
990*4882a593Smuzhiyun if (ret)
991*4882a593Smuzhiyun goto err_unregister_acpi;
992*4882a593Smuzhiyun
993*4882a593Smuzhiyun /* Register laptop driver */
994*4882a593Smuzhiyun
995*4882a593Smuzhiyun ret = acpi_bus_register_driver(&acpi_fujitsu_laptop_driver);
996*4882a593Smuzhiyun if (ret)
997*4882a593Smuzhiyun goto err_unregister_platform_driver;
998*4882a593Smuzhiyun
999*4882a593Smuzhiyun pr_info("driver " FUJITSU_DRIVER_VERSION " successfully loaded\n");
1000*4882a593Smuzhiyun
1001*4882a593Smuzhiyun return 0;
1002*4882a593Smuzhiyun
1003*4882a593Smuzhiyun err_unregister_platform_driver:
1004*4882a593Smuzhiyun platform_driver_unregister(&fujitsu_pf_driver);
1005*4882a593Smuzhiyun err_unregister_acpi:
1006*4882a593Smuzhiyun acpi_bus_unregister_driver(&acpi_fujitsu_bl_driver);
1007*4882a593Smuzhiyun
1008*4882a593Smuzhiyun return ret;
1009*4882a593Smuzhiyun }
1010*4882a593Smuzhiyun
fujitsu_cleanup(void)1011*4882a593Smuzhiyun static void __exit fujitsu_cleanup(void)
1012*4882a593Smuzhiyun {
1013*4882a593Smuzhiyun acpi_bus_unregister_driver(&acpi_fujitsu_laptop_driver);
1014*4882a593Smuzhiyun
1015*4882a593Smuzhiyun platform_driver_unregister(&fujitsu_pf_driver);
1016*4882a593Smuzhiyun
1017*4882a593Smuzhiyun acpi_bus_unregister_driver(&acpi_fujitsu_bl_driver);
1018*4882a593Smuzhiyun
1019*4882a593Smuzhiyun pr_info("driver unloaded\n");
1020*4882a593Smuzhiyun }
1021*4882a593Smuzhiyun
1022*4882a593Smuzhiyun module_init(fujitsu_init);
1023*4882a593Smuzhiyun module_exit(fujitsu_cleanup);
1024*4882a593Smuzhiyun
1025*4882a593Smuzhiyun module_param(use_alt_lcd_levels, int, 0644);
1026*4882a593Smuzhiyun MODULE_PARM_DESC(use_alt_lcd_levels, "Interface used for setting LCD brightness level (-1 = auto, 0 = force SBLL, 1 = force SBL2)");
1027*4882a593Smuzhiyun module_param(disable_brightness_adjust, bool, 0644);
1028*4882a593Smuzhiyun MODULE_PARM_DESC(disable_brightness_adjust, "Disable LCD brightness adjustment");
1029*4882a593Smuzhiyun
1030*4882a593Smuzhiyun MODULE_AUTHOR("Jonathan Woithe, Peter Gruber, Tony Vroon");
1031*4882a593Smuzhiyun MODULE_DESCRIPTION("Fujitsu laptop extras support");
1032*4882a593Smuzhiyun MODULE_VERSION(FUJITSU_DRIVER_VERSION);
1033*4882a593Smuzhiyun MODULE_LICENSE("GPL");
1034