162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*-*-linux-c-*-*/
362306a36Sopenharmony_ci
462306a36Sopenharmony_ci/*
562306a36Sopenharmony_ci  Copyright (C) 2007,2008 Jonathan Woithe <jwoithe@just42.net>
662306a36Sopenharmony_ci  Copyright (C) 2008 Peter Gruber <nokos@gmx.net>
762306a36Sopenharmony_ci  Copyright (C) 2008 Tony Vroon <tony@linx.net>
862306a36Sopenharmony_ci  Based on earlier work:
962306a36Sopenharmony_ci    Copyright (C) 2003 Shane Spencer <shane@bogomip.com>
1062306a36Sopenharmony_ci    Adrian Yee <brewt-fujitsu@brewt.org>
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_ci  Templated from msi-laptop.c and thinkpad_acpi.c which is copyright
1362306a36Sopenharmony_ci  by its respective authors.
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_ci */
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_ci/*
1862306a36Sopenharmony_ci * fujitsu-laptop.c - Fujitsu laptop support, providing access to additional
1962306a36Sopenharmony_ci * features made available on a range of Fujitsu laptops including the
2062306a36Sopenharmony_ci * P2xxx/P5xxx/S6xxx/S7xxx series.
2162306a36Sopenharmony_ci *
2262306a36Sopenharmony_ci * This driver implements a vendor-specific backlight control interface for
2362306a36Sopenharmony_ci * Fujitsu laptops and provides support for hotkeys present on certain Fujitsu
2462306a36Sopenharmony_ci * laptops.
2562306a36Sopenharmony_ci *
2662306a36Sopenharmony_ci * This driver has been tested on a Fujitsu Lifebook S6410, S7020 and
2762306a36Sopenharmony_ci * P8010.  It should work on most P-series and S-series Lifebooks, but
2862306a36Sopenharmony_ci * YMMV.
2962306a36Sopenharmony_ci *
3062306a36Sopenharmony_ci * The module parameter use_alt_lcd_levels switches between different ACPI
3162306a36Sopenharmony_ci * brightness controls which are used by different Fujitsu laptops.  In most
3262306a36Sopenharmony_ci * cases the correct method is automatically detected. "use_alt_lcd_levels=1"
3362306a36Sopenharmony_ci * is applicable for a Fujitsu Lifebook S6410 if autodetection fails.
3462306a36Sopenharmony_ci *
3562306a36Sopenharmony_ci */
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_ci#include <linux/module.h>
4062306a36Sopenharmony_ci#include <linux/kernel.h>
4162306a36Sopenharmony_ci#include <linux/init.h>
4262306a36Sopenharmony_ci#include <linux/acpi.h>
4362306a36Sopenharmony_ci#include <linux/bitops.h>
4462306a36Sopenharmony_ci#include <linux/dmi.h>
4562306a36Sopenharmony_ci#include <linux/backlight.h>
4662306a36Sopenharmony_ci#include <linux/fb.h>
4762306a36Sopenharmony_ci#include <linux/input.h>
4862306a36Sopenharmony_ci#include <linux/input/sparse-keymap.h>
4962306a36Sopenharmony_ci#include <linux/kfifo.h>
5062306a36Sopenharmony_ci#include <linux/leds.h>
5162306a36Sopenharmony_ci#include <linux/platform_device.h>
5262306a36Sopenharmony_ci#include <acpi/video.h>
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_ci#define FUJITSU_DRIVER_VERSION		"0.6.0"
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_ci#define FUJITSU_LCD_N_LEVELS		8
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_ci#define ACPI_FUJITSU_CLASS		"fujitsu"
5962306a36Sopenharmony_ci#define ACPI_FUJITSU_BL_HID		"FUJ02B1"
6062306a36Sopenharmony_ci#define ACPI_FUJITSU_BL_DRIVER_NAME	"Fujitsu laptop FUJ02B1 ACPI brightness driver"
6162306a36Sopenharmony_ci#define ACPI_FUJITSU_BL_DEVICE_NAME	"Fujitsu FUJ02B1"
6262306a36Sopenharmony_ci#define ACPI_FUJITSU_LAPTOP_HID		"FUJ02E3"
6362306a36Sopenharmony_ci#define ACPI_FUJITSU_LAPTOP_DRIVER_NAME	"Fujitsu laptop FUJ02E3 ACPI hotkeys driver"
6462306a36Sopenharmony_ci#define ACPI_FUJITSU_LAPTOP_DEVICE_NAME	"Fujitsu FUJ02E3"
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_ci#define ACPI_FUJITSU_NOTIFY_CODE	0x80
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_ci/* FUNC interface - command values */
6962306a36Sopenharmony_ci#define FUNC_FLAGS			BIT(12)
7062306a36Sopenharmony_ci#define FUNC_LEDS			(BIT(12) | BIT(0))
7162306a36Sopenharmony_ci#define FUNC_BUTTONS			(BIT(12) | BIT(1))
7262306a36Sopenharmony_ci#define FUNC_BACKLIGHT			(BIT(12) | BIT(2))
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_ci/* FUNC interface - responses */
7562306a36Sopenharmony_ci#define UNSUPPORTED_CMD			0x80000000
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_ci/* FUNC interface - status flags */
7862306a36Sopenharmony_ci#define FLAG_RFKILL			BIT(5)
7962306a36Sopenharmony_ci#define FLAG_LID			BIT(8)
8062306a36Sopenharmony_ci#define FLAG_DOCK			BIT(9)
8162306a36Sopenharmony_ci#define FLAG_TOUCHPAD_TOGGLE		BIT(26)
8262306a36Sopenharmony_ci#define FLAG_MICMUTE			BIT(29)
8362306a36Sopenharmony_ci#define FLAG_SOFTKEYS			(FLAG_RFKILL | FLAG_TOUCHPAD_TOGGLE | FLAG_MICMUTE)
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_ci/* FUNC interface - LED control */
8662306a36Sopenharmony_ci#define FUNC_LED_OFF			BIT(0)
8762306a36Sopenharmony_ci#define FUNC_LED_ON			(BIT(0) | BIT(16) | BIT(17))
8862306a36Sopenharmony_ci#define LOGOLAMP_POWERON		BIT(13)
8962306a36Sopenharmony_ci#define LOGOLAMP_ALWAYS			BIT(14)
9062306a36Sopenharmony_ci#define KEYBOARD_LAMPS			BIT(8)
9162306a36Sopenharmony_ci#define RADIO_LED_ON			BIT(5)
9262306a36Sopenharmony_ci#define ECO_LED				BIT(16)
9362306a36Sopenharmony_ci#define ECO_LED_ON			BIT(19)
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ci/* FUNC interface - backlight power control */
9662306a36Sopenharmony_ci#define BACKLIGHT_PARAM_POWER		BIT(2)
9762306a36Sopenharmony_ci#define BACKLIGHT_OFF			(BIT(0) | BIT(1))
9862306a36Sopenharmony_ci#define BACKLIGHT_ON			0
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_ci/* Scancodes read from the GIRB register */
10162306a36Sopenharmony_ci#define KEY1_CODE			0x410
10262306a36Sopenharmony_ci#define KEY2_CODE			0x411
10362306a36Sopenharmony_ci#define KEY3_CODE			0x412
10462306a36Sopenharmony_ci#define KEY4_CODE			0x413
10562306a36Sopenharmony_ci#define KEY5_CODE			0x420
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ci/* Hotkey ringbuffer limits */
10862306a36Sopenharmony_ci#define MAX_HOTKEY_RINGBUFFER_SIZE	100
10962306a36Sopenharmony_ci#define RINGBUFFERSIZE			40
11062306a36Sopenharmony_ci
11162306a36Sopenharmony_ci/* Module parameters */
11262306a36Sopenharmony_cistatic int use_alt_lcd_levels = -1;
11362306a36Sopenharmony_cistatic bool disable_brightness_adjust;
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_ci/* Device controlling the backlight and associated keys */
11662306a36Sopenharmony_cistruct fujitsu_bl {
11762306a36Sopenharmony_ci	struct input_dev *input;
11862306a36Sopenharmony_ci	char phys[32];
11962306a36Sopenharmony_ci	struct backlight_device *bl_device;
12062306a36Sopenharmony_ci	unsigned int max_brightness;
12162306a36Sopenharmony_ci	unsigned int brightness_level;
12262306a36Sopenharmony_ci};
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_cistatic struct fujitsu_bl *fujitsu_bl;
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_ci/* Device used to access hotkeys and other features on the laptop */
12762306a36Sopenharmony_cistruct fujitsu_laptop {
12862306a36Sopenharmony_ci	struct input_dev *input;
12962306a36Sopenharmony_ci	char phys[32];
13062306a36Sopenharmony_ci	struct platform_device *pf_device;
13162306a36Sopenharmony_ci	struct kfifo fifo;
13262306a36Sopenharmony_ci	spinlock_t fifo_lock;
13362306a36Sopenharmony_ci	int flags_supported;
13462306a36Sopenharmony_ci	int flags_state;
13562306a36Sopenharmony_ci};
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_cistatic struct acpi_device *fext;
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_ci/* Fujitsu ACPI interface function */
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_cistatic int call_fext_func(struct acpi_device *device,
14262306a36Sopenharmony_ci			  int func, int op, int feature, int state)
14362306a36Sopenharmony_ci{
14462306a36Sopenharmony_ci	union acpi_object params[4] = {
14562306a36Sopenharmony_ci		{ .integer.type = ACPI_TYPE_INTEGER, .integer.value = func },
14662306a36Sopenharmony_ci		{ .integer.type = ACPI_TYPE_INTEGER, .integer.value = op },
14762306a36Sopenharmony_ci		{ .integer.type = ACPI_TYPE_INTEGER, .integer.value = feature },
14862306a36Sopenharmony_ci		{ .integer.type = ACPI_TYPE_INTEGER, .integer.value = state }
14962306a36Sopenharmony_ci	};
15062306a36Sopenharmony_ci	struct acpi_object_list arg_list = { 4, params };
15162306a36Sopenharmony_ci	unsigned long long value;
15262306a36Sopenharmony_ci	acpi_status status;
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_ci	status = acpi_evaluate_integer(device->handle, "FUNC", &arg_list,
15562306a36Sopenharmony_ci				       &value);
15662306a36Sopenharmony_ci	if (ACPI_FAILURE(status)) {
15762306a36Sopenharmony_ci		acpi_handle_err(device->handle, "Failed to evaluate FUNC\n");
15862306a36Sopenharmony_ci		return -ENODEV;
15962306a36Sopenharmony_ci	}
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci	acpi_handle_debug(device->handle,
16262306a36Sopenharmony_ci			  "FUNC 0x%x (args 0x%x, 0x%x, 0x%x) returned 0x%x\n",
16362306a36Sopenharmony_ci			  func, op, feature, state, (int)value);
16462306a36Sopenharmony_ci	return value;
16562306a36Sopenharmony_ci}
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ci/* Hardware access for LCD brightness control */
16862306a36Sopenharmony_ci
16962306a36Sopenharmony_cistatic int set_lcd_level(struct acpi_device *device, int level)
17062306a36Sopenharmony_ci{
17162306a36Sopenharmony_ci	struct fujitsu_bl *priv = acpi_driver_data(device);
17262306a36Sopenharmony_ci	acpi_status status;
17362306a36Sopenharmony_ci	char *method;
17462306a36Sopenharmony_ci
17562306a36Sopenharmony_ci	switch (use_alt_lcd_levels) {
17662306a36Sopenharmony_ci	case -1:
17762306a36Sopenharmony_ci		if (acpi_has_method(device->handle, "SBL2"))
17862306a36Sopenharmony_ci			method = "SBL2";
17962306a36Sopenharmony_ci		else
18062306a36Sopenharmony_ci			method = "SBLL";
18162306a36Sopenharmony_ci		break;
18262306a36Sopenharmony_ci	case 1:
18362306a36Sopenharmony_ci		method = "SBL2";
18462306a36Sopenharmony_ci		break;
18562306a36Sopenharmony_ci	default:
18662306a36Sopenharmony_ci		method = "SBLL";
18762306a36Sopenharmony_ci		break;
18862306a36Sopenharmony_ci	}
18962306a36Sopenharmony_ci
19062306a36Sopenharmony_ci	acpi_handle_debug(device->handle, "set lcd level via %s [%d]\n", method,
19162306a36Sopenharmony_ci			  level);
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_ci	if (level < 0 || level >= priv->max_brightness)
19462306a36Sopenharmony_ci		return -EINVAL;
19562306a36Sopenharmony_ci
19662306a36Sopenharmony_ci	status = acpi_execute_simple_method(device->handle, method, level);
19762306a36Sopenharmony_ci	if (ACPI_FAILURE(status)) {
19862306a36Sopenharmony_ci		acpi_handle_err(device->handle, "Failed to evaluate %s\n",
19962306a36Sopenharmony_ci				method);
20062306a36Sopenharmony_ci		return -ENODEV;
20162306a36Sopenharmony_ci	}
20262306a36Sopenharmony_ci
20362306a36Sopenharmony_ci	priv->brightness_level = level;
20462306a36Sopenharmony_ci
20562306a36Sopenharmony_ci	return 0;
20662306a36Sopenharmony_ci}
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_cistatic int get_lcd_level(struct acpi_device *device)
20962306a36Sopenharmony_ci{
21062306a36Sopenharmony_ci	struct fujitsu_bl *priv = acpi_driver_data(device);
21162306a36Sopenharmony_ci	unsigned long long state = 0;
21262306a36Sopenharmony_ci	acpi_status status = AE_OK;
21362306a36Sopenharmony_ci
21462306a36Sopenharmony_ci	acpi_handle_debug(device->handle, "get lcd level via GBLL\n");
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_ci	status = acpi_evaluate_integer(device->handle, "GBLL", NULL, &state);
21762306a36Sopenharmony_ci	if (ACPI_FAILURE(status))
21862306a36Sopenharmony_ci		return 0;
21962306a36Sopenharmony_ci
22062306a36Sopenharmony_ci	priv->brightness_level = state & 0x0fffffff;
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_ci	return priv->brightness_level;
22362306a36Sopenharmony_ci}
22462306a36Sopenharmony_ci
22562306a36Sopenharmony_cistatic int get_max_brightness(struct acpi_device *device)
22662306a36Sopenharmony_ci{
22762306a36Sopenharmony_ci	struct fujitsu_bl *priv = acpi_driver_data(device);
22862306a36Sopenharmony_ci	unsigned long long state = 0;
22962306a36Sopenharmony_ci	acpi_status status = AE_OK;
23062306a36Sopenharmony_ci
23162306a36Sopenharmony_ci	acpi_handle_debug(device->handle, "get max lcd level via RBLL\n");
23262306a36Sopenharmony_ci
23362306a36Sopenharmony_ci	status = acpi_evaluate_integer(device->handle, "RBLL", NULL, &state);
23462306a36Sopenharmony_ci	if (ACPI_FAILURE(status))
23562306a36Sopenharmony_ci		return -1;
23662306a36Sopenharmony_ci
23762306a36Sopenharmony_ci	priv->max_brightness = state;
23862306a36Sopenharmony_ci
23962306a36Sopenharmony_ci	return priv->max_brightness;
24062306a36Sopenharmony_ci}
24162306a36Sopenharmony_ci
24262306a36Sopenharmony_ci/* Backlight device stuff */
24362306a36Sopenharmony_ci
24462306a36Sopenharmony_cistatic int bl_get_brightness(struct backlight_device *b)
24562306a36Sopenharmony_ci{
24662306a36Sopenharmony_ci	struct acpi_device *device = bl_get_data(b);
24762306a36Sopenharmony_ci
24862306a36Sopenharmony_ci	return b->props.power == FB_BLANK_POWERDOWN ? 0 : get_lcd_level(device);
24962306a36Sopenharmony_ci}
25062306a36Sopenharmony_ci
25162306a36Sopenharmony_cistatic int bl_update_status(struct backlight_device *b)
25262306a36Sopenharmony_ci{
25362306a36Sopenharmony_ci	struct acpi_device *device = bl_get_data(b);
25462306a36Sopenharmony_ci
25562306a36Sopenharmony_ci	if (fext) {
25662306a36Sopenharmony_ci		if (b->props.power == FB_BLANK_POWERDOWN)
25762306a36Sopenharmony_ci			call_fext_func(fext, FUNC_BACKLIGHT, 0x1,
25862306a36Sopenharmony_ci				       BACKLIGHT_PARAM_POWER, BACKLIGHT_OFF);
25962306a36Sopenharmony_ci		else
26062306a36Sopenharmony_ci			call_fext_func(fext, FUNC_BACKLIGHT, 0x1,
26162306a36Sopenharmony_ci				       BACKLIGHT_PARAM_POWER, BACKLIGHT_ON);
26262306a36Sopenharmony_ci	}
26362306a36Sopenharmony_ci
26462306a36Sopenharmony_ci	return set_lcd_level(device, b->props.brightness);
26562306a36Sopenharmony_ci}
26662306a36Sopenharmony_ci
26762306a36Sopenharmony_cistatic const struct backlight_ops fujitsu_bl_ops = {
26862306a36Sopenharmony_ci	.get_brightness = bl_get_brightness,
26962306a36Sopenharmony_ci	.update_status = bl_update_status,
27062306a36Sopenharmony_ci};
27162306a36Sopenharmony_ci
27262306a36Sopenharmony_cistatic ssize_t lid_show(struct device *dev, struct device_attribute *attr,
27362306a36Sopenharmony_ci			char *buf)
27462306a36Sopenharmony_ci{
27562306a36Sopenharmony_ci	struct fujitsu_laptop *priv = dev_get_drvdata(dev);
27662306a36Sopenharmony_ci
27762306a36Sopenharmony_ci	if (!(priv->flags_supported & FLAG_LID))
27862306a36Sopenharmony_ci		return sprintf(buf, "unknown\n");
27962306a36Sopenharmony_ci	if (priv->flags_state & FLAG_LID)
28062306a36Sopenharmony_ci		return sprintf(buf, "open\n");
28162306a36Sopenharmony_ci	else
28262306a36Sopenharmony_ci		return sprintf(buf, "closed\n");
28362306a36Sopenharmony_ci}
28462306a36Sopenharmony_ci
28562306a36Sopenharmony_cistatic ssize_t dock_show(struct device *dev, struct device_attribute *attr,
28662306a36Sopenharmony_ci			 char *buf)
28762306a36Sopenharmony_ci{
28862306a36Sopenharmony_ci	struct fujitsu_laptop *priv = dev_get_drvdata(dev);
28962306a36Sopenharmony_ci
29062306a36Sopenharmony_ci	if (!(priv->flags_supported & FLAG_DOCK))
29162306a36Sopenharmony_ci		return sprintf(buf, "unknown\n");
29262306a36Sopenharmony_ci	if (priv->flags_state & FLAG_DOCK)
29362306a36Sopenharmony_ci		return sprintf(buf, "docked\n");
29462306a36Sopenharmony_ci	else
29562306a36Sopenharmony_ci		return sprintf(buf, "undocked\n");
29662306a36Sopenharmony_ci}
29762306a36Sopenharmony_ci
29862306a36Sopenharmony_cistatic ssize_t radios_show(struct device *dev, struct device_attribute *attr,
29962306a36Sopenharmony_ci			   char *buf)
30062306a36Sopenharmony_ci{
30162306a36Sopenharmony_ci	struct fujitsu_laptop *priv = dev_get_drvdata(dev);
30262306a36Sopenharmony_ci
30362306a36Sopenharmony_ci	if (!(priv->flags_supported & FLAG_RFKILL))
30462306a36Sopenharmony_ci		return sprintf(buf, "unknown\n");
30562306a36Sopenharmony_ci	if (priv->flags_state & FLAG_RFKILL)
30662306a36Sopenharmony_ci		return sprintf(buf, "on\n");
30762306a36Sopenharmony_ci	else
30862306a36Sopenharmony_ci		return sprintf(buf, "killed\n");
30962306a36Sopenharmony_ci}
31062306a36Sopenharmony_ci
31162306a36Sopenharmony_cistatic DEVICE_ATTR_RO(lid);
31262306a36Sopenharmony_cistatic DEVICE_ATTR_RO(dock);
31362306a36Sopenharmony_cistatic DEVICE_ATTR_RO(radios);
31462306a36Sopenharmony_ci
31562306a36Sopenharmony_cistatic struct attribute *fujitsu_pf_attributes[] = {
31662306a36Sopenharmony_ci	&dev_attr_lid.attr,
31762306a36Sopenharmony_ci	&dev_attr_dock.attr,
31862306a36Sopenharmony_ci	&dev_attr_radios.attr,
31962306a36Sopenharmony_ci	NULL
32062306a36Sopenharmony_ci};
32162306a36Sopenharmony_ci
32262306a36Sopenharmony_cistatic const struct attribute_group fujitsu_pf_attribute_group = {
32362306a36Sopenharmony_ci	.attrs = fujitsu_pf_attributes
32462306a36Sopenharmony_ci};
32562306a36Sopenharmony_ci
32662306a36Sopenharmony_cistatic struct platform_driver fujitsu_pf_driver = {
32762306a36Sopenharmony_ci	.driver = {
32862306a36Sopenharmony_ci		   .name = "fujitsu-laptop",
32962306a36Sopenharmony_ci		   }
33062306a36Sopenharmony_ci};
33162306a36Sopenharmony_ci
33262306a36Sopenharmony_ci/* ACPI device for LCD brightness control */
33362306a36Sopenharmony_ci
33462306a36Sopenharmony_cistatic const struct key_entry keymap_backlight[] = {
33562306a36Sopenharmony_ci	{ KE_KEY, true, { KEY_BRIGHTNESSUP } },
33662306a36Sopenharmony_ci	{ KE_KEY, false, { KEY_BRIGHTNESSDOWN } },
33762306a36Sopenharmony_ci	{ KE_END, 0 }
33862306a36Sopenharmony_ci};
33962306a36Sopenharmony_ci
34062306a36Sopenharmony_cistatic int acpi_fujitsu_bl_input_setup(struct acpi_device *device)
34162306a36Sopenharmony_ci{
34262306a36Sopenharmony_ci	struct fujitsu_bl *priv = acpi_driver_data(device);
34362306a36Sopenharmony_ci	int ret;
34462306a36Sopenharmony_ci
34562306a36Sopenharmony_ci	priv->input = devm_input_allocate_device(&device->dev);
34662306a36Sopenharmony_ci	if (!priv->input)
34762306a36Sopenharmony_ci		return -ENOMEM;
34862306a36Sopenharmony_ci
34962306a36Sopenharmony_ci	snprintf(priv->phys, sizeof(priv->phys), "%s/video/input0",
35062306a36Sopenharmony_ci		 acpi_device_hid(device));
35162306a36Sopenharmony_ci
35262306a36Sopenharmony_ci	priv->input->name = acpi_device_name(device);
35362306a36Sopenharmony_ci	priv->input->phys = priv->phys;
35462306a36Sopenharmony_ci	priv->input->id.bustype = BUS_HOST;
35562306a36Sopenharmony_ci	priv->input->id.product = 0x06;
35662306a36Sopenharmony_ci
35762306a36Sopenharmony_ci	ret = sparse_keymap_setup(priv->input, keymap_backlight, NULL);
35862306a36Sopenharmony_ci	if (ret)
35962306a36Sopenharmony_ci		return ret;
36062306a36Sopenharmony_ci
36162306a36Sopenharmony_ci	return input_register_device(priv->input);
36262306a36Sopenharmony_ci}
36362306a36Sopenharmony_ci
36462306a36Sopenharmony_cistatic int fujitsu_backlight_register(struct acpi_device *device)
36562306a36Sopenharmony_ci{
36662306a36Sopenharmony_ci	struct fujitsu_bl *priv = acpi_driver_data(device);
36762306a36Sopenharmony_ci	const struct backlight_properties props = {
36862306a36Sopenharmony_ci		.brightness = priv->brightness_level,
36962306a36Sopenharmony_ci		.max_brightness = priv->max_brightness - 1,
37062306a36Sopenharmony_ci		.type = BACKLIGHT_PLATFORM
37162306a36Sopenharmony_ci	};
37262306a36Sopenharmony_ci	struct backlight_device *bd;
37362306a36Sopenharmony_ci
37462306a36Sopenharmony_ci	bd = devm_backlight_device_register(&device->dev, "fujitsu-laptop",
37562306a36Sopenharmony_ci					    &device->dev, device,
37662306a36Sopenharmony_ci					    &fujitsu_bl_ops, &props);
37762306a36Sopenharmony_ci	if (IS_ERR(bd))
37862306a36Sopenharmony_ci		return PTR_ERR(bd);
37962306a36Sopenharmony_ci
38062306a36Sopenharmony_ci	priv->bl_device = bd;
38162306a36Sopenharmony_ci
38262306a36Sopenharmony_ci	return 0;
38362306a36Sopenharmony_ci}
38462306a36Sopenharmony_ci
38562306a36Sopenharmony_cistatic int acpi_fujitsu_bl_add(struct acpi_device *device)
38662306a36Sopenharmony_ci{
38762306a36Sopenharmony_ci	struct fujitsu_bl *priv;
38862306a36Sopenharmony_ci	int ret;
38962306a36Sopenharmony_ci
39062306a36Sopenharmony_ci	if (acpi_video_get_backlight_type() != acpi_backlight_vendor)
39162306a36Sopenharmony_ci		return -ENODEV;
39262306a36Sopenharmony_ci
39362306a36Sopenharmony_ci	priv = devm_kzalloc(&device->dev, sizeof(*priv), GFP_KERNEL);
39462306a36Sopenharmony_ci	if (!priv)
39562306a36Sopenharmony_ci		return -ENOMEM;
39662306a36Sopenharmony_ci
39762306a36Sopenharmony_ci	fujitsu_bl = priv;
39862306a36Sopenharmony_ci	strcpy(acpi_device_name(device), ACPI_FUJITSU_BL_DEVICE_NAME);
39962306a36Sopenharmony_ci	strcpy(acpi_device_class(device), ACPI_FUJITSU_CLASS);
40062306a36Sopenharmony_ci	device->driver_data = priv;
40162306a36Sopenharmony_ci
40262306a36Sopenharmony_ci	pr_info("ACPI: %s [%s]\n",
40362306a36Sopenharmony_ci		acpi_device_name(device), acpi_device_bid(device));
40462306a36Sopenharmony_ci
40562306a36Sopenharmony_ci	if (get_max_brightness(device) <= 0)
40662306a36Sopenharmony_ci		priv->max_brightness = FUJITSU_LCD_N_LEVELS;
40762306a36Sopenharmony_ci	get_lcd_level(device);
40862306a36Sopenharmony_ci
40962306a36Sopenharmony_ci	ret = acpi_fujitsu_bl_input_setup(device);
41062306a36Sopenharmony_ci	if (ret)
41162306a36Sopenharmony_ci		return ret;
41262306a36Sopenharmony_ci
41362306a36Sopenharmony_ci	return fujitsu_backlight_register(device);
41462306a36Sopenharmony_ci}
41562306a36Sopenharmony_ci
41662306a36Sopenharmony_ci/* Brightness notify */
41762306a36Sopenharmony_ci
41862306a36Sopenharmony_cistatic void acpi_fujitsu_bl_notify(struct acpi_device *device, u32 event)
41962306a36Sopenharmony_ci{
42062306a36Sopenharmony_ci	struct fujitsu_bl *priv = acpi_driver_data(device);
42162306a36Sopenharmony_ci	int oldb, newb;
42262306a36Sopenharmony_ci
42362306a36Sopenharmony_ci	if (event != ACPI_FUJITSU_NOTIFY_CODE) {
42462306a36Sopenharmony_ci		acpi_handle_info(device->handle, "unsupported event [0x%x]\n",
42562306a36Sopenharmony_ci				 event);
42662306a36Sopenharmony_ci		sparse_keymap_report_event(priv->input, -1, 1, true);
42762306a36Sopenharmony_ci		return;
42862306a36Sopenharmony_ci	}
42962306a36Sopenharmony_ci
43062306a36Sopenharmony_ci	oldb = priv->brightness_level;
43162306a36Sopenharmony_ci	get_lcd_level(device);
43262306a36Sopenharmony_ci	newb = priv->brightness_level;
43362306a36Sopenharmony_ci
43462306a36Sopenharmony_ci	acpi_handle_debug(device->handle,
43562306a36Sopenharmony_ci			  "brightness button event [%i -> %i]\n", oldb, newb);
43662306a36Sopenharmony_ci
43762306a36Sopenharmony_ci	if (oldb == newb)
43862306a36Sopenharmony_ci		return;
43962306a36Sopenharmony_ci
44062306a36Sopenharmony_ci	if (!disable_brightness_adjust)
44162306a36Sopenharmony_ci		set_lcd_level(device, newb);
44262306a36Sopenharmony_ci
44362306a36Sopenharmony_ci	sparse_keymap_report_event(priv->input, oldb < newb, 1, true);
44462306a36Sopenharmony_ci}
44562306a36Sopenharmony_ci
44662306a36Sopenharmony_ci/* ACPI device for hotkey handling */
44762306a36Sopenharmony_ci
44862306a36Sopenharmony_cistatic const struct key_entry keymap_default[] = {
44962306a36Sopenharmony_ci	{ KE_KEY, KEY1_CODE,            { KEY_PROG1 } },
45062306a36Sopenharmony_ci	{ KE_KEY, KEY2_CODE,            { KEY_PROG2 } },
45162306a36Sopenharmony_ci	{ KE_KEY, KEY3_CODE,            { KEY_PROG3 } },
45262306a36Sopenharmony_ci	{ KE_KEY, KEY4_CODE,            { KEY_PROG4 } },
45362306a36Sopenharmony_ci	{ KE_KEY, KEY5_CODE,            { KEY_RFKILL } },
45462306a36Sopenharmony_ci	/* Soft keys read from status flags */
45562306a36Sopenharmony_ci	{ KE_KEY, FLAG_RFKILL,          { KEY_RFKILL } },
45662306a36Sopenharmony_ci	{ KE_KEY, FLAG_TOUCHPAD_TOGGLE, { KEY_TOUCHPAD_TOGGLE } },
45762306a36Sopenharmony_ci	{ KE_KEY, FLAG_MICMUTE,         { KEY_MICMUTE } },
45862306a36Sopenharmony_ci	{ KE_END, 0 }
45962306a36Sopenharmony_ci};
46062306a36Sopenharmony_ci
46162306a36Sopenharmony_cistatic const struct key_entry keymap_s64x0[] = {
46262306a36Sopenharmony_ci	{ KE_KEY, KEY1_CODE, { KEY_SCREENLOCK } },	/* "Lock" */
46362306a36Sopenharmony_ci	{ KE_KEY, KEY2_CODE, { KEY_HELP } },		/* "Mobility Center */
46462306a36Sopenharmony_ci	{ KE_KEY, KEY3_CODE, { KEY_PROG3 } },
46562306a36Sopenharmony_ci	{ KE_KEY, KEY4_CODE, { KEY_PROG4 } },
46662306a36Sopenharmony_ci	{ KE_END, 0 }
46762306a36Sopenharmony_ci};
46862306a36Sopenharmony_ci
46962306a36Sopenharmony_cistatic const struct key_entry keymap_p8010[] = {
47062306a36Sopenharmony_ci	{ KE_KEY, KEY1_CODE, { KEY_HELP } },		/* "Support" */
47162306a36Sopenharmony_ci	{ KE_KEY, KEY2_CODE, { KEY_PROG2 } },
47262306a36Sopenharmony_ci	{ KE_KEY, KEY3_CODE, { KEY_SWITCHVIDEOMODE } },	/* "Presentation" */
47362306a36Sopenharmony_ci	{ KE_KEY, KEY4_CODE, { KEY_WWW } },		/* "WWW" */
47462306a36Sopenharmony_ci	{ KE_END, 0 }
47562306a36Sopenharmony_ci};
47662306a36Sopenharmony_ci
47762306a36Sopenharmony_cistatic const struct key_entry *keymap = keymap_default;
47862306a36Sopenharmony_ci
47962306a36Sopenharmony_cistatic int fujitsu_laptop_dmi_keymap_override(const struct dmi_system_id *id)
48062306a36Sopenharmony_ci{
48162306a36Sopenharmony_ci	pr_info("Identified laptop model '%s'\n", id->ident);
48262306a36Sopenharmony_ci	keymap = id->driver_data;
48362306a36Sopenharmony_ci	return 1;
48462306a36Sopenharmony_ci}
48562306a36Sopenharmony_ci
48662306a36Sopenharmony_cistatic const struct dmi_system_id fujitsu_laptop_dmi_table[] = {
48762306a36Sopenharmony_ci	{
48862306a36Sopenharmony_ci		.callback = fujitsu_laptop_dmi_keymap_override,
48962306a36Sopenharmony_ci		.ident = "Fujitsu Siemens S6410",
49062306a36Sopenharmony_ci		.matches = {
49162306a36Sopenharmony_ci			DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"),
49262306a36Sopenharmony_ci			DMI_MATCH(DMI_PRODUCT_NAME, "LIFEBOOK S6410"),
49362306a36Sopenharmony_ci		},
49462306a36Sopenharmony_ci		.driver_data = (void *)keymap_s64x0
49562306a36Sopenharmony_ci	},
49662306a36Sopenharmony_ci	{
49762306a36Sopenharmony_ci		.callback = fujitsu_laptop_dmi_keymap_override,
49862306a36Sopenharmony_ci		.ident = "Fujitsu Siemens S6420",
49962306a36Sopenharmony_ci		.matches = {
50062306a36Sopenharmony_ci			DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"),
50162306a36Sopenharmony_ci			DMI_MATCH(DMI_PRODUCT_NAME, "LIFEBOOK S6420"),
50262306a36Sopenharmony_ci		},
50362306a36Sopenharmony_ci		.driver_data = (void *)keymap_s64x0
50462306a36Sopenharmony_ci	},
50562306a36Sopenharmony_ci	{
50662306a36Sopenharmony_ci		.callback = fujitsu_laptop_dmi_keymap_override,
50762306a36Sopenharmony_ci		.ident = "Fujitsu LifeBook P8010",
50862306a36Sopenharmony_ci		.matches = {
50962306a36Sopenharmony_ci			DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"),
51062306a36Sopenharmony_ci			DMI_MATCH(DMI_PRODUCT_NAME, "LifeBook P8010"),
51162306a36Sopenharmony_ci		},
51262306a36Sopenharmony_ci		.driver_data = (void *)keymap_p8010
51362306a36Sopenharmony_ci	},
51462306a36Sopenharmony_ci	{}
51562306a36Sopenharmony_ci};
51662306a36Sopenharmony_ci
51762306a36Sopenharmony_cistatic int acpi_fujitsu_laptop_input_setup(struct acpi_device *device)
51862306a36Sopenharmony_ci{
51962306a36Sopenharmony_ci	struct fujitsu_laptop *priv = acpi_driver_data(device);
52062306a36Sopenharmony_ci	int ret;
52162306a36Sopenharmony_ci
52262306a36Sopenharmony_ci	priv->input = devm_input_allocate_device(&device->dev);
52362306a36Sopenharmony_ci	if (!priv->input)
52462306a36Sopenharmony_ci		return -ENOMEM;
52562306a36Sopenharmony_ci
52662306a36Sopenharmony_ci	snprintf(priv->phys, sizeof(priv->phys), "%s/input0",
52762306a36Sopenharmony_ci		 acpi_device_hid(device));
52862306a36Sopenharmony_ci
52962306a36Sopenharmony_ci	priv->input->name = acpi_device_name(device);
53062306a36Sopenharmony_ci	priv->input->phys = priv->phys;
53162306a36Sopenharmony_ci	priv->input->id.bustype = BUS_HOST;
53262306a36Sopenharmony_ci
53362306a36Sopenharmony_ci	dmi_check_system(fujitsu_laptop_dmi_table);
53462306a36Sopenharmony_ci	ret = sparse_keymap_setup(priv->input, keymap, NULL);
53562306a36Sopenharmony_ci	if (ret)
53662306a36Sopenharmony_ci		return ret;
53762306a36Sopenharmony_ci
53862306a36Sopenharmony_ci	return input_register_device(priv->input);
53962306a36Sopenharmony_ci}
54062306a36Sopenharmony_ci
54162306a36Sopenharmony_cistatic int fujitsu_laptop_platform_add(struct acpi_device *device)
54262306a36Sopenharmony_ci{
54362306a36Sopenharmony_ci	struct fujitsu_laptop *priv = acpi_driver_data(device);
54462306a36Sopenharmony_ci	int ret;
54562306a36Sopenharmony_ci
54662306a36Sopenharmony_ci	priv->pf_device = platform_device_alloc("fujitsu-laptop", PLATFORM_DEVID_NONE);
54762306a36Sopenharmony_ci	if (!priv->pf_device)
54862306a36Sopenharmony_ci		return -ENOMEM;
54962306a36Sopenharmony_ci
55062306a36Sopenharmony_ci	platform_set_drvdata(priv->pf_device, priv);
55162306a36Sopenharmony_ci
55262306a36Sopenharmony_ci	ret = platform_device_add(priv->pf_device);
55362306a36Sopenharmony_ci	if (ret)
55462306a36Sopenharmony_ci		goto err_put_platform_device;
55562306a36Sopenharmony_ci
55662306a36Sopenharmony_ci	ret = sysfs_create_group(&priv->pf_device->dev.kobj,
55762306a36Sopenharmony_ci				 &fujitsu_pf_attribute_group);
55862306a36Sopenharmony_ci	if (ret)
55962306a36Sopenharmony_ci		goto err_del_platform_device;
56062306a36Sopenharmony_ci
56162306a36Sopenharmony_ci	return 0;
56262306a36Sopenharmony_ci
56362306a36Sopenharmony_cierr_del_platform_device:
56462306a36Sopenharmony_ci	platform_device_del(priv->pf_device);
56562306a36Sopenharmony_cierr_put_platform_device:
56662306a36Sopenharmony_ci	platform_device_put(priv->pf_device);
56762306a36Sopenharmony_ci
56862306a36Sopenharmony_ci	return ret;
56962306a36Sopenharmony_ci}
57062306a36Sopenharmony_ci
57162306a36Sopenharmony_cistatic void fujitsu_laptop_platform_remove(struct acpi_device *device)
57262306a36Sopenharmony_ci{
57362306a36Sopenharmony_ci	struct fujitsu_laptop *priv = acpi_driver_data(device);
57462306a36Sopenharmony_ci
57562306a36Sopenharmony_ci	sysfs_remove_group(&priv->pf_device->dev.kobj,
57662306a36Sopenharmony_ci			   &fujitsu_pf_attribute_group);
57762306a36Sopenharmony_ci	platform_device_unregister(priv->pf_device);
57862306a36Sopenharmony_ci}
57962306a36Sopenharmony_ci
58062306a36Sopenharmony_cistatic int logolamp_set(struct led_classdev *cdev,
58162306a36Sopenharmony_ci			enum led_brightness brightness)
58262306a36Sopenharmony_ci{
58362306a36Sopenharmony_ci	struct acpi_device *device = to_acpi_device(cdev->dev->parent);
58462306a36Sopenharmony_ci	int poweron = FUNC_LED_ON, always = FUNC_LED_ON;
58562306a36Sopenharmony_ci	int ret;
58662306a36Sopenharmony_ci
58762306a36Sopenharmony_ci	if (brightness < LED_HALF)
58862306a36Sopenharmony_ci		poweron = FUNC_LED_OFF;
58962306a36Sopenharmony_ci
59062306a36Sopenharmony_ci	if (brightness < LED_FULL)
59162306a36Sopenharmony_ci		always = FUNC_LED_OFF;
59262306a36Sopenharmony_ci
59362306a36Sopenharmony_ci	ret = call_fext_func(device, FUNC_LEDS, 0x1, LOGOLAMP_POWERON, poweron);
59462306a36Sopenharmony_ci	if (ret < 0)
59562306a36Sopenharmony_ci		return ret;
59662306a36Sopenharmony_ci
59762306a36Sopenharmony_ci	return call_fext_func(device, FUNC_LEDS, 0x1, LOGOLAMP_ALWAYS, always);
59862306a36Sopenharmony_ci}
59962306a36Sopenharmony_ci
60062306a36Sopenharmony_cistatic enum led_brightness logolamp_get(struct led_classdev *cdev)
60162306a36Sopenharmony_ci{
60262306a36Sopenharmony_ci	struct acpi_device *device = to_acpi_device(cdev->dev->parent);
60362306a36Sopenharmony_ci	int ret;
60462306a36Sopenharmony_ci
60562306a36Sopenharmony_ci	ret = call_fext_func(device, FUNC_LEDS, 0x2, LOGOLAMP_ALWAYS, 0x0);
60662306a36Sopenharmony_ci	if (ret == FUNC_LED_ON)
60762306a36Sopenharmony_ci		return LED_FULL;
60862306a36Sopenharmony_ci
60962306a36Sopenharmony_ci	ret = call_fext_func(device, FUNC_LEDS, 0x2, LOGOLAMP_POWERON, 0x0);
61062306a36Sopenharmony_ci	if (ret == FUNC_LED_ON)
61162306a36Sopenharmony_ci		return LED_HALF;
61262306a36Sopenharmony_ci
61362306a36Sopenharmony_ci	return LED_OFF;
61462306a36Sopenharmony_ci}
61562306a36Sopenharmony_ci
61662306a36Sopenharmony_cistatic int kblamps_set(struct led_classdev *cdev,
61762306a36Sopenharmony_ci		       enum led_brightness brightness)
61862306a36Sopenharmony_ci{
61962306a36Sopenharmony_ci	struct acpi_device *device = to_acpi_device(cdev->dev->parent);
62062306a36Sopenharmony_ci
62162306a36Sopenharmony_ci	if (brightness >= LED_FULL)
62262306a36Sopenharmony_ci		return call_fext_func(device, FUNC_LEDS, 0x1, KEYBOARD_LAMPS,
62362306a36Sopenharmony_ci				      FUNC_LED_ON);
62462306a36Sopenharmony_ci	else
62562306a36Sopenharmony_ci		return call_fext_func(device, FUNC_LEDS, 0x1, KEYBOARD_LAMPS,
62662306a36Sopenharmony_ci				      FUNC_LED_OFF);
62762306a36Sopenharmony_ci}
62862306a36Sopenharmony_ci
62962306a36Sopenharmony_cistatic enum led_brightness kblamps_get(struct led_classdev *cdev)
63062306a36Sopenharmony_ci{
63162306a36Sopenharmony_ci	struct acpi_device *device = to_acpi_device(cdev->dev->parent);
63262306a36Sopenharmony_ci	enum led_brightness brightness = LED_OFF;
63362306a36Sopenharmony_ci
63462306a36Sopenharmony_ci	if (call_fext_func(device,
63562306a36Sopenharmony_ci			   FUNC_LEDS, 0x2, KEYBOARD_LAMPS, 0x0) == FUNC_LED_ON)
63662306a36Sopenharmony_ci		brightness = LED_FULL;
63762306a36Sopenharmony_ci
63862306a36Sopenharmony_ci	return brightness;
63962306a36Sopenharmony_ci}
64062306a36Sopenharmony_ci
64162306a36Sopenharmony_cistatic int radio_led_set(struct led_classdev *cdev,
64262306a36Sopenharmony_ci			 enum led_brightness brightness)
64362306a36Sopenharmony_ci{
64462306a36Sopenharmony_ci	struct acpi_device *device = to_acpi_device(cdev->dev->parent);
64562306a36Sopenharmony_ci
64662306a36Sopenharmony_ci	if (brightness >= LED_FULL)
64762306a36Sopenharmony_ci		return call_fext_func(device, FUNC_FLAGS, 0x5, RADIO_LED_ON,
64862306a36Sopenharmony_ci				      RADIO_LED_ON);
64962306a36Sopenharmony_ci	else
65062306a36Sopenharmony_ci		return call_fext_func(device, FUNC_FLAGS, 0x5, RADIO_LED_ON,
65162306a36Sopenharmony_ci				      0x0);
65262306a36Sopenharmony_ci}
65362306a36Sopenharmony_ci
65462306a36Sopenharmony_cistatic enum led_brightness radio_led_get(struct led_classdev *cdev)
65562306a36Sopenharmony_ci{
65662306a36Sopenharmony_ci	struct acpi_device *device = to_acpi_device(cdev->dev->parent);
65762306a36Sopenharmony_ci	enum led_brightness brightness = LED_OFF;
65862306a36Sopenharmony_ci
65962306a36Sopenharmony_ci	if (call_fext_func(device, FUNC_FLAGS, 0x4, 0x0, 0x0) & RADIO_LED_ON)
66062306a36Sopenharmony_ci		brightness = LED_FULL;
66162306a36Sopenharmony_ci
66262306a36Sopenharmony_ci	return brightness;
66362306a36Sopenharmony_ci}
66462306a36Sopenharmony_ci
66562306a36Sopenharmony_cistatic int eco_led_set(struct led_classdev *cdev,
66662306a36Sopenharmony_ci		       enum led_brightness brightness)
66762306a36Sopenharmony_ci{
66862306a36Sopenharmony_ci	struct acpi_device *device = to_acpi_device(cdev->dev->parent);
66962306a36Sopenharmony_ci	int curr;
67062306a36Sopenharmony_ci
67162306a36Sopenharmony_ci	curr = call_fext_func(device, FUNC_LEDS, 0x2, ECO_LED, 0x0);
67262306a36Sopenharmony_ci	if (brightness >= LED_FULL)
67362306a36Sopenharmony_ci		return call_fext_func(device, FUNC_LEDS, 0x1, ECO_LED,
67462306a36Sopenharmony_ci				      curr | ECO_LED_ON);
67562306a36Sopenharmony_ci	else
67662306a36Sopenharmony_ci		return call_fext_func(device, FUNC_LEDS, 0x1, ECO_LED,
67762306a36Sopenharmony_ci				      curr & ~ECO_LED_ON);
67862306a36Sopenharmony_ci}
67962306a36Sopenharmony_ci
68062306a36Sopenharmony_cistatic enum led_brightness eco_led_get(struct led_classdev *cdev)
68162306a36Sopenharmony_ci{
68262306a36Sopenharmony_ci	struct acpi_device *device = to_acpi_device(cdev->dev->parent);
68362306a36Sopenharmony_ci	enum led_brightness brightness = LED_OFF;
68462306a36Sopenharmony_ci
68562306a36Sopenharmony_ci	if (call_fext_func(device, FUNC_LEDS, 0x2, ECO_LED, 0x0) & ECO_LED_ON)
68662306a36Sopenharmony_ci		brightness = LED_FULL;
68762306a36Sopenharmony_ci
68862306a36Sopenharmony_ci	return brightness;
68962306a36Sopenharmony_ci}
69062306a36Sopenharmony_ci
69162306a36Sopenharmony_cistatic int acpi_fujitsu_laptop_leds_register(struct acpi_device *device)
69262306a36Sopenharmony_ci{
69362306a36Sopenharmony_ci	struct fujitsu_laptop *priv = acpi_driver_data(device);
69462306a36Sopenharmony_ci	struct led_classdev *led;
69562306a36Sopenharmony_ci	int ret;
69662306a36Sopenharmony_ci
69762306a36Sopenharmony_ci	if (call_fext_func(device,
69862306a36Sopenharmony_ci			   FUNC_LEDS, 0x0, 0x0, 0x0) & LOGOLAMP_POWERON) {
69962306a36Sopenharmony_ci		led = devm_kzalloc(&device->dev, sizeof(*led), GFP_KERNEL);
70062306a36Sopenharmony_ci		if (!led)
70162306a36Sopenharmony_ci			return -ENOMEM;
70262306a36Sopenharmony_ci
70362306a36Sopenharmony_ci		led->name = "fujitsu::logolamp";
70462306a36Sopenharmony_ci		led->brightness_set_blocking = logolamp_set;
70562306a36Sopenharmony_ci		led->brightness_get = logolamp_get;
70662306a36Sopenharmony_ci		ret = devm_led_classdev_register(&device->dev, led);
70762306a36Sopenharmony_ci		if (ret)
70862306a36Sopenharmony_ci			return ret;
70962306a36Sopenharmony_ci	}
71062306a36Sopenharmony_ci
71162306a36Sopenharmony_ci	if ((call_fext_func(device,
71262306a36Sopenharmony_ci			    FUNC_LEDS, 0x0, 0x0, 0x0) & KEYBOARD_LAMPS) &&
71362306a36Sopenharmony_ci	    (call_fext_func(device, FUNC_BUTTONS, 0x0, 0x0, 0x0) == 0x0)) {
71462306a36Sopenharmony_ci		led = devm_kzalloc(&device->dev, sizeof(*led), GFP_KERNEL);
71562306a36Sopenharmony_ci		if (!led)
71662306a36Sopenharmony_ci			return -ENOMEM;
71762306a36Sopenharmony_ci
71862306a36Sopenharmony_ci		led->name = "fujitsu::kblamps";
71962306a36Sopenharmony_ci		led->brightness_set_blocking = kblamps_set;
72062306a36Sopenharmony_ci		led->brightness_get = kblamps_get;
72162306a36Sopenharmony_ci		ret = devm_led_classdev_register(&device->dev, led);
72262306a36Sopenharmony_ci		if (ret)
72362306a36Sopenharmony_ci			return ret;
72462306a36Sopenharmony_ci	}
72562306a36Sopenharmony_ci
72662306a36Sopenharmony_ci	/*
72762306a36Sopenharmony_ci	 * Some Fujitsu laptops have a radio toggle button in place of a slide
72862306a36Sopenharmony_ci	 * switch and all such machines appear to also have an RF LED.  Based on
72962306a36Sopenharmony_ci	 * comparing DSDT tables of four Fujitsu Lifebook models (E744, E751,
73062306a36Sopenharmony_ci	 * S7110, S8420; the first one has a radio toggle button, the other
73162306a36Sopenharmony_ci	 * three have slide switches), bit 17 of flags_supported (the value
73262306a36Sopenharmony_ci	 * returned by method S000 of ACPI device FUJ02E3) seems to indicate
73362306a36Sopenharmony_ci	 * whether given model has a radio toggle button.
73462306a36Sopenharmony_ci	 */
73562306a36Sopenharmony_ci	if (priv->flags_supported & BIT(17)) {
73662306a36Sopenharmony_ci		led = devm_kzalloc(&device->dev, sizeof(*led), GFP_KERNEL);
73762306a36Sopenharmony_ci		if (!led)
73862306a36Sopenharmony_ci			return -ENOMEM;
73962306a36Sopenharmony_ci
74062306a36Sopenharmony_ci		led->name = "fujitsu::radio_led";
74162306a36Sopenharmony_ci		led->brightness_set_blocking = radio_led_set;
74262306a36Sopenharmony_ci		led->brightness_get = radio_led_get;
74362306a36Sopenharmony_ci		led->default_trigger = "rfkill-any";
74462306a36Sopenharmony_ci		ret = devm_led_classdev_register(&device->dev, led);
74562306a36Sopenharmony_ci		if (ret)
74662306a36Sopenharmony_ci			return ret;
74762306a36Sopenharmony_ci	}
74862306a36Sopenharmony_ci
74962306a36Sopenharmony_ci	/* Support for eco led is not always signaled in bit corresponding
75062306a36Sopenharmony_ci	 * to the bit used to control the led. According to the DSDT table,
75162306a36Sopenharmony_ci	 * bit 14 seems to indicate presence of said led as well.
75262306a36Sopenharmony_ci	 * Confirm by testing the status.
75362306a36Sopenharmony_ci	 */
75462306a36Sopenharmony_ci	if ((call_fext_func(device, FUNC_LEDS, 0x0, 0x0, 0x0) & BIT(14)) &&
75562306a36Sopenharmony_ci	    (call_fext_func(device,
75662306a36Sopenharmony_ci			    FUNC_LEDS, 0x2, ECO_LED, 0x0) != UNSUPPORTED_CMD)) {
75762306a36Sopenharmony_ci		led = devm_kzalloc(&device->dev, sizeof(*led), GFP_KERNEL);
75862306a36Sopenharmony_ci		if (!led)
75962306a36Sopenharmony_ci			return -ENOMEM;
76062306a36Sopenharmony_ci
76162306a36Sopenharmony_ci		led->name = "fujitsu::eco_led";
76262306a36Sopenharmony_ci		led->brightness_set_blocking = eco_led_set;
76362306a36Sopenharmony_ci		led->brightness_get = eco_led_get;
76462306a36Sopenharmony_ci		ret = devm_led_classdev_register(&device->dev, led);
76562306a36Sopenharmony_ci		if (ret)
76662306a36Sopenharmony_ci			return ret;
76762306a36Sopenharmony_ci	}
76862306a36Sopenharmony_ci
76962306a36Sopenharmony_ci	return 0;
77062306a36Sopenharmony_ci}
77162306a36Sopenharmony_ci
77262306a36Sopenharmony_cistatic int acpi_fujitsu_laptop_add(struct acpi_device *device)
77362306a36Sopenharmony_ci{
77462306a36Sopenharmony_ci	struct fujitsu_laptop *priv;
77562306a36Sopenharmony_ci	int ret, i = 0;
77662306a36Sopenharmony_ci
77762306a36Sopenharmony_ci	priv = devm_kzalloc(&device->dev, sizeof(*priv), GFP_KERNEL);
77862306a36Sopenharmony_ci	if (!priv)
77962306a36Sopenharmony_ci		return -ENOMEM;
78062306a36Sopenharmony_ci
78162306a36Sopenharmony_ci	WARN_ONCE(fext, "More than one FUJ02E3 ACPI device was found.  Driver may not work as intended.");
78262306a36Sopenharmony_ci	fext = device;
78362306a36Sopenharmony_ci
78462306a36Sopenharmony_ci	strcpy(acpi_device_name(device), ACPI_FUJITSU_LAPTOP_DEVICE_NAME);
78562306a36Sopenharmony_ci	strcpy(acpi_device_class(device), ACPI_FUJITSU_CLASS);
78662306a36Sopenharmony_ci	device->driver_data = priv;
78762306a36Sopenharmony_ci
78862306a36Sopenharmony_ci	/* kfifo */
78962306a36Sopenharmony_ci	spin_lock_init(&priv->fifo_lock);
79062306a36Sopenharmony_ci	ret = kfifo_alloc(&priv->fifo, RINGBUFFERSIZE * sizeof(int),
79162306a36Sopenharmony_ci			  GFP_KERNEL);
79262306a36Sopenharmony_ci	if (ret)
79362306a36Sopenharmony_ci		return ret;
79462306a36Sopenharmony_ci
79562306a36Sopenharmony_ci	pr_info("ACPI: %s [%s]\n",
79662306a36Sopenharmony_ci		acpi_device_name(device), acpi_device_bid(device));
79762306a36Sopenharmony_ci
79862306a36Sopenharmony_ci	while (call_fext_func(device, FUNC_BUTTONS, 0x1, 0x0, 0x0) != 0 &&
79962306a36Sopenharmony_ci	       i++ < MAX_HOTKEY_RINGBUFFER_SIZE)
80062306a36Sopenharmony_ci		; /* No action, result is discarded */
80162306a36Sopenharmony_ci	acpi_handle_debug(device->handle, "Discarded %i ringbuffer entries\n",
80262306a36Sopenharmony_ci			  i);
80362306a36Sopenharmony_ci
80462306a36Sopenharmony_ci	priv->flags_supported = call_fext_func(device, FUNC_FLAGS, 0x0, 0x0,
80562306a36Sopenharmony_ci					       0x0);
80662306a36Sopenharmony_ci
80762306a36Sopenharmony_ci	/* Make sure our bitmask of supported functions is cleared if the
80862306a36Sopenharmony_ci	   RFKILL function block is not implemented, like on the S7020. */
80962306a36Sopenharmony_ci	if (priv->flags_supported == UNSUPPORTED_CMD)
81062306a36Sopenharmony_ci		priv->flags_supported = 0;
81162306a36Sopenharmony_ci
81262306a36Sopenharmony_ci	if (priv->flags_supported)
81362306a36Sopenharmony_ci		priv->flags_state = call_fext_func(device, FUNC_FLAGS, 0x4, 0x0,
81462306a36Sopenharmony_ci						   0x0);
81562306a36Sopenharmony_ci
81662306a36Sopenharmony_ci	/* Suspect this is a keymap of the application panel, print it */
81762306a36Sopenharmony_ci	acpi_handle_info(device->handle, "BTNI: [0x%x]\n",
81862306a36Sopenharmony_ci			 call_fext_func(device, FUNC_BUTTONS, 0x0, 0x0, 0x0));
81962306a36Sopenharmony_ci
82062306a36Sopenharmony_ci	/* Sync backlight power status */
82162306a36Sopenharmony_ci	if (fujitsu_bl && fujitsu_bl->bl_device &&
82262306a36Sopenharmony_ci	    acpi_video_get_backlight_type() == acpi_backlight_vendor) {
82362306a36Sopenharmony_ci		if (call_fext_func(fext, FUNC_BACKLIGHT, 0x2,
82462306a36Sopenharmony_ci				   BACKLIGHT_PARAM_POWER, 0x0) == BACKLIGHT_OFF)
82562306a36Sopenharmony_ci			fujitsu_bl->bl_device->props.power = FB_BLANK_POWERDOWN;
82662306a36Sopenharmony_ci		else
82762306a36Sopenharmony_ci			fujitsu_bl->bl_device->props.power = FB_BLANK_UNBLANK;
82862306a36Sopenharmony_ci	}
82962306a36Sopenharmony_ci
83062306a36Sopenharmony_ci	ret = acpi_fujitsu_laptop_input_setup(device);
83162306a36Sopenharmony_ci	if (ret)
83262306a36Sopenharmony_ci		goto err_free_fifo;
83362306a36Sopenharmony_ci
83462306a36Sopenharmony_ci	ret = acpi_fujitsu_laptop_leds_register(device);
83562306a36Sopenharmony_ci	if (ret)
83662306a36Sopenharmony_ci		goto err_free_fifo;
83762306a36Sopenharmony_ci
83862306a36Sopenharmony_ci	ret = fujitsu_laptop_platform_add(device);
83962306a36Sopenharmony_ci	if (ret)
84062306a36Sopenharmony_ci		goto err_free_fifo;
84162306a36Sopenharmony_ci
84262306a36Sopenharmony_ci	return 0;
84362306a36Sopenharmony_ci
84462306a36Sopenharmony_cierr_free_fifo:
84562306a36Sopenharmony_ci	kfifo_free(&priv->fifo);
84662306a36Sopenharmony_ci
84762306a36Sopenharmony_ci	return ret;
84862306a36Sopenharmony_ci}
84962306a36Sopenharmony_ci
85062306a36Sopenharmony_cistatic void acpi_fujitsu_laptop_remove(struct acpi_device *device)
85162306a36Sopenharmony_ci{
85262306a36Sopenharmony_ci	struct fujitsu_laptop *priv = acpi_driver_data(device);
85362306a36Sopenharmony_ci
85462306a36Sopenharmony_ci	fujitsu_laptop_platform_remove(device);
85562306a36Sopenharmony_ci
85662306a36Sopenharmony_ci	kfifo_free(&priv->fifo);
85762306a36Sopenharmony_ci}
85862306a36Sopenharmony_ci
85962306a36Sopenharmony_cistatic void acpi_fujitsu_laptop_press(struct acpi_device *device, int scancode)
86062306a36Sopenharmony_ci{
86162306a36Sopenharmony_ci	struct fujitsu_laptop *priv = acpi_driver_data(device);
86262306a36Sopenharmony_ci	int ret;
86362306a36Sopenharmony_ci
86462306a36Sopenharmony_ci	ret = kfifo_in_locked(&priv->fifo, (unsigned char *)&scancode,
86562306a36Sopenharmony_ci			      sizeof(scancode), &priv->fifo_lock);
86662306a36Sopenharmony_ci	if (ret != sizeof(scancode)) {
86762306a36Sopenharmony_ci		dev_info(&priv->input->dev, "Could not push scancode [0x%x]\n",
86862306a36Sopenharmony_ci			 scancode);
86962306a36Sopenharmony_ci		return;
87062306a36Sopenharmony_ci	}
87162306a36Sopenharmony_ci	sparse_keymap_report_event(priv->input, scancode, 1, false);
87262306a36Sopenharmony_ci	dev_dbg(&priv->input->dev, "Push scancode into ringbuffer [0x%x]\n",
87362306a36Sopenharmony_ci		scancode);
87462306a36Sopenharmony_ci}
87562306a36Sopenharmony_ci
87662306a36Sopenharmony_cistatic void acpi_fujitsu_laptop_release(struct acpi_device *device)
87762306a36Sopenharmony_ci{
87862306a36Sopenharmony_ci	struct fujitsu_laptop *priv = acpi_driver_data(device);
87962306a36Sopenharmony_ci	int scancode, ret;
88062306a36Sopenharmony_ci
88162306a36Sopenharmony_ci	while (true) {
88262306a36Sopenharmony_ci		ret = kfifo_out_locked(&priv->fifo, (unsigned char *)&scancode,
88362306a36Sopenharmony_ci				       sizeof(scancode), &priv->fifo_lock);
88462306a36Sopenharmony_ci		if (ret != sizeof(scancode))
88562306a36Sopenharmony_ci			return;
88662306a36Sopenharmony_ci		sparse_keymap_report_event(priv->input, scancode, 0, false);
88762306a36Sopenharmony_ci		dev_dbg(&priv->input->dev,
88862306a36Sopenharmony_ci			"Pop scancode from ringbuffer [0x%x]\n", scancode);
88962306a36Sopenharmony_ci	}
89062306a36Sopenharmony_ci}
89162306a36Sopenharmony_ci
89262306a36Sopenharmony_cistatic void acpi_fujitsu_laptop_notify(struct acpi_device *device, u32 event)
89362306a36Sopenharmony_ci{
89462306a36Sopenharmony_ci	struct fujitsu_laptop *priv = acpi_driver_data(device);
89562306a36Sopenharmony_ci	unsigned long flags;
89662306a36Sopenharmony_ci	int scancode, i = 0;
89762306a36Sopenharmony_ci	unsigned int irb;
89862306a36Sopenharmony_ci
89962306a36Sopenharmony_ci	if (event != ACPI_FUJITSU_NOTIFY_CODE) {
90062306a36Sopenharmony_ci		acpi_handle_info(device->handle, "Unsupported event [0x%x]\n",
90162306a36Sopenharmony_ci				 event);
90262306a36Sopenharmony_ci		sparse_keymap_report_event(priv->input, -1, 1, true);
90362306a36Sopenharmony_ci		return;
90462306a36Sopenharmony_ci	}
90562306a36Sopenharmony_ci
90662306a36Sopenharmony_ci	if (priv->flags_supported)
90762306a36Sopenharmony_ci		priv->flags_state = call_fext_func(device, FUNC_FLAGS, 0x4, 0x0,
90862306a36Sopenharmony_ci						   0x0);
90962306a36Sopenharmony_ci
91062306a36Sopenharmony_ci	while ((irb = call_fext_func(device,
91162306a36Sopenharmony_ci				     FUNC_BUTTONS, 0x1, 0x0, 0x0)) != 0 &&
91262306a36Sopenharmony_ci	       i++ < MAX_HOTKEY_RINGBUFFER_SIZE) {
91362306a36Sopenharmony_ci		scancode = irb & 0x4ff;
91462306a36Sopenharmony_ci		if (sparse_keymap_entry_from_scancode(priv->input, scancode))
91562306a36Sopenharmony_ci			acpi_fujitsu_laptop_press(device, scancode);
91662306a36Sopenharmony_ci		else if (scancode == 0)
91762306a36Sopenharmony_ci			acpi_fujitsu_laptop_release(device);
91862306a36Sopenharmony_ci		else
91962306a36Sopenharmony_ci			acpi_handle_info(device->handle,
92062306a36Sopenharmony_ci					 "Unknown GIRB result [%x]\n", irb);
92162306a36Sopenharmony_ci	}
92262306a36Sopenharmony_ci
92362306a36Sopenharmony_ci	/*
92462306a36Sopenharmony_ci	 * First seen on the Skylake-based Lifebook E736/E746/E756), the
92562306a36Sopenharmony_ci	 * touchpad toggle hotkey (Fn+F4) is handled in software. Other models
92662306a36Sopenharmony_ci	 * have since added additional "soft keys". These are reported in the
92762306a36Sopenharmony_ci	 * status flags queried using FUNC_FLAGS.
92862306a36Sopenharmony_ci	 */
92962306a36Sopenharmony_ci	if (priv->flags_supported & (FLAG_SOFTKEYS)) {
93062306a36Sopenharmony_ci		flags = call_fext_func(device, FUNC_FLAGS, 0x1, 0x0, 0x0);
93162306a36Sopenharmony_ci		flags &= (FLAG_SOFTKEYS);
93262306a36Sopenharmony_ci		for_each_set_bit(i, &flags, BITS_PER_LONG)
93362306a36Sopenharmony_ci			sparse_keymap_report_event(priv->input, BIT(i), 1, true);
93462306a36Sopenharmony_ci	}
93562306a36Sopenharmony_ci}
93662306a36Sopenharmony_ci
93762306a36Sopenharmony_ci/* Initialization */
93862306a36Sopenharmony_ci
93962306a36Sopenharmony_cistatic const struct acpi_device_id fujitsu_bl_device_ids[] = {
94062306a36Sopenharmony_ci	{ACPI_FUJITSU_BL_HID, 0},
94162306a36Sopenharmony_ci	{"", 0},
94262306a36Sopenharmony_ci};
94362306a36Sopenharmony_ci
94462306a36Sopenharmony_cistatic struct acpi_driver acpi_fujitsu_bl_driver = {
94562306a36Sopenharmony_ci	.name = ACPI_FUJITSU_BL_DRIVER_NAME,
94662306a36Sopenharmony_ci	.class = ACPI_FUJITSU_CLASS,
94762306a36Sopenharmony_ci	.ids = fujitsu_bl_device_ids,
94862306a36Sopenharmony_ci	.ops = {
94962306a36Sopenharmony_ci		.add = acpi_fujitsu_bl_add,
95062306a36Sopenharmony_ci		.notify = acpi_fujitsu_bl_notify,
95162306a36Sopenharmony_ci		},
95262306a36Sopenharmony_ci};
95362306a36Sopenharmony_ci
95462306a36Sopenharmony_cistatic const struct acpi_device_id fujitsu_laptop_device_ids[] = {
95562306a36Sopenharmony_ci	{ACPI_FUJITSU_LAPTOP_HID, 0},
95662306a36Sopenharmony_ci	{"", 0},
95762306a36Sopenharmony_ci};
95862306a36Sopenharmony_ci
95962306a36Sopenharmony_cistatic struct acpi_driver acpi_fujitsu_laptop_driver = {
96062306a36Sopenharmony_ci	.name = ACPI_FUJITSU_LAPTOP_DRIVER_NAME,
96162306a36Sopenharmony_ci	.class = ACPI_FUJITSU_CLASS,
96262306a36Sopenharmony_ci	.ids = fujitsu_laptop_device_ids,
96362306a36Sopenharmony_ci	.ops = {
96462306a36Sopenharmony_ci		.add = acpi_fujitsu_laptop_add,
96562306a36Sopenharmony_ci		.remove = acpi_fujitsu_laptop_remove,
96662306a36Sopenharmony_ci		.notify = acpi_fujitsu_laptop_notify,
96762306a36Sopenharmony_ci		},
96862306a36Sopenharmony_ci};
96962306a36Sopenharmony_ci
97062306a36Sopenharmony_cistatic const struct acpi_device_id fujitsu_ids[] __used = {
97162306a36Sopenharmony_ci	{ACPI_FUJITSU_BL_HID, 0},
97262306a36Sopenharmony_ci	{ACPI_FUJITSU_LAPTOP_HID, 0},
97362306a36Sopenharmony_ci	{"", 0}
97462306a36Sopenharmony_ci};
97562306a36Sopenharmony_ciMODULE_DEVICE_TABLE(acpi, fujitsu_ids);
97662306a36Sopenharmony_ci
97762306a36Sopenharmony_cistatic int __init fujitsu_init(void)
97862306a36Sopenharmony_ci{
97962306a36Sopenharmony_ci	int ret;
98062306a36Sopenharmony_ci
98162306a36Sopenharmony_ci	ret = acpi_bus_register_driver(&acpi_fujitsu_bl_driver);
98262306a36Sopenharmony_ci	if (ret)
98362306a36Sopenharmony_ci		return ret;
98462306a36Sopenharmony_ci
98562306a36Sopenharmony_ci	/* Register platform stuff */
98662306a36Sopenharmony_ci
98762306a36Sopenharmony_ci	ret = platform_driver_register(&fujitsu_pf_driver);
98862306a36Sopenharmony_ci	if (ret)
98962306a36Sopenharmony_ci		goto err_unregister_acpi;
99062306a36Sopenharmony_ci
99162306a36Sopenharmony_ci	/* Register laptop driver */
99262306a36Sopenharmony_ci
99362306a36Sopenharmony_ci	ret = acpi_bus_register_driver(&acpi_fujitsu_laptop_driver);
99462306a36Sopenharmony_ci	if (ret)
99562306a36Sopenharmony_ci		goto err_unregister_platform_driver;
99662306a36Sopenharmony_ci
99762306a36Sopenharmony_ci	pr_info("driver " FUJITSU_DRIVER_VERSION " successfully loaded\n");
99862306a36Sopenharmony_ci
99962306a36Sopenharmony_ci	return 0;
100062306a36Sopenharmony_ci
100162306a36Sopenharmony_cierr_unregister_platform_driver:
100262306a36Sopenharmony_ci	platform_driver_unregister(&fujitsu_pf_driver);
100362306a36Sopenharmony_cierr_unregister_acpi:
100462306a36Sopenharmony_ci	acpi_bus_unregister_driver(&acpi_fujitsu_bl_driver);
100562306a36Sopenharmony_ci
100662306a36Sopenharmony_ci	return ret;
100762306a36Sopenharmony_ci}
100862306a36Sopenharmony_ci
100962306a36Sopenharmony_cistatic void __exit fujitsu_cleanup(void)
101062306a36Sopenharmony_ci{
101162306a36Sopenharmony_ci	acpi_bus_unregister_driver(&acpi_fujitsu_laptop_driver);
101262306a36Sopenharmony_ci
101362306a36Sopenharmony_ci	platform_driver_unregister(&fujitsu_pf_driver);
101462306a36Sopenharmony_ci
101562306a36Sopenharmony_ci	acpi_bus_unregister_driver(&acpi_fujitsu_bl_driver);
101662306a36Sopenharmony_ci
101762306a36Sopenharmony_ci	pr_info("driver unloaded\n");
101862306a36Sopenharmony_ci}
101962306a36Sopenharmony_ci
102062306a36Sopenharmony_cimodule_init(fujitsu_init);
102162306a36Sopenharmony_cimodule_exit(fujitsu_cleanup);
102262306a36Sopenharmony_ci
102362306a36Sopenharmony_cimodule_param(use_alt_lcd_levels, int, 0644);
102462306a36Sopenharmony_ciMODULE_PARM_DESC(use_alt_lcd_levels, "Interface used for setting LCD brightness level (-1 = auto, 0 = force SBLL, 1 = force SBL2)");
102562306a36Sopenharmony_cimodule_param(disable_brightness_adjust, bool, 0644);
102662306a36Sopenharmony_ciMODULE_PARM_DESC(disable_brightness_adjust, "Disable LCD brightness adjustment");
102762306a36Sopenharmony_ci
102862306a36Sopenharmony_ciMODULE_AUTHOR("Jonathan Woithe, Peter Gruber, Tony Vroon");
102962306a36Sopenharmony_ciMODULE_DESCRIPTION("Fujitsu laptop extras support");
103062306a36Sopenharmony_ciMODULE_VERSION(FUJITSU_DRIVER_VERSION);
103162306a36Sopenharmony_ciMODULE_LICENSE("GPL");
1032