// SPDX-License-Identifier: GPL-2.0-or-later
/*-*-linux-c-*-*/
/*
Copyright (C) 2007,2008 Jonathan Woithe <jwoithe@just42.net>
Copyright (C) 2008 Peter Gruber <nokos@gmx.net>
Copyright (C) 2008 Tony Vroon <tony@linx.net>
Based on earlier work:
Copyright (C) 2003 Shane Spencer <shane@bogomip.com>
Adrian Yee <brewt-fujitsu@brewt.org>
Templated from msi-laptop.c and thinkpad_acpi.c which is copyright
by its respective authors.
*/
/*
* fujitsu-laptop.c - Fujitsu laptop support, providing access to additional
* features made available on a range of Fujitsu laptops including the
* P2xxx/P5xxx/S6xxx/S7xxx series.
*
* This driver implements a vendor-specific backlight control interface for
* Fujitsu laptops and provides support for hotkeys present on certain Fujitsu
* laptops.
*
* This driver has been tested on a Fujitsu Lifebook S6410, S7020 and
* P8010. It should work on most P-series and S-series Lifebooks, but
* YMMV.
*
* The module parameter use_alt_lcd_levels switches between different ACPI
* brightness controls which are used by different Fujitsu laptops. In most
* cases the correct method is automatically detected. "use_alt_lcd_levels=1"
* is applicable for a Fujitsu Lifebook S6410 if autodetection fails.
*
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/acpi.h>
#include <linux/bitops.h>
#include <linux/dmi.h>
#include <linux/backlight.h>
#include <linux/input.h>
#include <linux/input/sparse-keymap.h>
#include <linux/kfifo.h>
#include <linux/leds.h>
#include <linux/platform_device.h>
#include <linux/power_supply.h>
#include <acpi/battery.h>
#include <acpi/video.h>
#define FUJITSU_DRIVER_VERSION "0.6.0"
#define FUJITSU_LCD_N_LEVELS 8
#define ACPI_FUJITSU_CLASS "fujitsu"
#define ACPI_FUJITSU_BL_HID "FUJ02B1"
#define ACPI_FUJITSU_BL_DRIVER_NAME "Fujitsu laptop FUJ02B1 ACPI brightness driver"
#define ACPI_FUJITSU_BL_DEVICE_NAME "Fujitsu FUJ02B1"
#define ACPI_FUJITSU_LAPTOP_HID "FUJ02E3"
#define ACPI_FUJITSU_LAPTOP_DRIVER_NAME "Fujitsu laptop FUJ02E3 ACPI hotkeys driver"
#define ACPI_FUJITSU_LAPTOP_DEVICE_NAME "Fujitsu FUJ02E3"
#define ACPI_FUJITSU_NOTIFY_CODE 0x80
/* FUNC interface - command values */
#define FUNC_FLAGS BIT(12)
#define FUNC_LEDS (BIT(12) | BIT(0))
#define FUNC_BUTTONS (BIT(12) | BIT(1))
#define FUNC_BACKLIGHT (BIT(12) | BIT(2))
/* FUNC interface - responses */
#define UNSUPPORTED_CMD 0x80000000
/* FUNC interface - status flags */
#define FLAG_RFKILL BIT(5)
#define FLAG_LID BIT(8)
#define FLAG_DOCK BIT(9)
#define FLAG_TOUCHPAD_TOGGLE BIT(26)
#define FLAG_MICMUTE BIT(29)
#define FLAG_SOFTKEYS (FLAG_RFKILL | FLAG_TOUCHPAD_TOGGLE | FLAG_MICMUTE)
/* FUNC interface - LED control */
#define FUNC_LED_OFF BIT(0)
#define FUNC_LED_ON (BIT(0) | BIT(16) | BIT(17))
#define LOGOLAMP_POWERON BIT(13)
#define LOGOLAMP_ALWAYS BIT(14)
#define KEYBOARD_LAMPS BIT(8)
#define RADIO_LED_ON BIT(5)
#define ECO_LED BIT(16)
#define ECO_LED_ON BIT(19)
/* FUNC interface - backlight power control */
#define BACKLIGHT_PARAM_POWER BIT(2)
#define BACKLIGHT_OFF (BIT(0) | BIT(1))
#define BACKLIGHT_ON 0
/* FUNC interface - battery control interface */
#define FUNC_S006_METHOD 0x1006
#define CHARGE_CONTROL_RW 0x21
/* Scancodes read from the GIRB register */
#define KEY1_CODE 0x410
#define KEY2_CODE 0x411
#define KEY3_CODE 0x412
#define KEY4_CODE 0x413
#define KEY5_CODE 0x420
/* Hotkey ringbuffer limits */
#define MAX_HOTKEY_RINGBUFFER_SIZE 100
#define RINGBUFFERSIZE 40
/* Module parameters */
static int use_alt_lcd_levels = -1;
static bool disable_brightness_adjust;
/* Device controlling the backlight and associated keys */
struct fujitsu_bl {
struct input_dev *input;
char phys[32];
struct backlight_device *bl_device;
unsigned int max_brightness;
unsigned int brightness_level;
};
static struct fujitsu_bl *fujitsu_bl;
/* Device used to access hotkeys and other features on the laptop */
struct fujitsu_laptop {
struct input_dev *input;
char phys[32];
struct platform_device *pf_device;
struct kfifo fifo;
spinlock_t fifo_lock;
int flags_supported;
int flags_state;
bool charge_control_supported;
};
static struct acpi_device *fext;
/* Fujitsu ACPI interface function */
static int call_fext_func(struct acpi_device *device,
int func, int op, int feature, int state)
{
union acpi_object params[4] = {
{ .integer.type = ACPI_TYPE_INTEGER, .integer.value = func },
{ .integer.type = ACPI_TYPE_INTEGER, .integer.value = op },
{ .integer.type = ACPI_TYPE_INTEGER, .integer.value = feature },
{ .integer.type = ACPI_TYPE_INTEGER, .integer.value = state }
};
struct acpi_object_list arg_list = { 4, params };
unsigned long long value;
acpi_status status;
status = acpi_evaluate_integer(device->handle, "FUNC", &arg_list,
&value);
if (ACPI_FAILURE(status)) {
acpi_handle_err(device->handle, "Failed to evaluate FUNC\n");
return -ENODEV;
}
acpi_handle_debug(device->handle,
"FUNC 0x%x (args 0x%x, 0x%x, 0x%x) returned 0x%x\n",
func, op, feature, state, (int)value);
return value;
}
/* Battery charge control code */
static ssize_t charge_control_end_threshold_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
int cc_end_value, s006_cc_return;
int value, ret;
ret = kstrtouint(buf, 10, &value);
if (ret)
return ret;
if (value < 50 || value > 100)
return -EINVAL;
cc_end_value = value * 0x100 + 0x20;
s006_cc_return = call_fext_func(fext, FUNC_S006_METHOD,
CHARGE_CONTROL_RW, cc_end_value, 0x0);
if (s006_cc_return < 0)
return s006_cc_return;
/*
* The S006 0x21 method returns 0x00 in case the provided value
* is invalid.
*/
if (s006_cc_return == 0x00)
return -EINVAL