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