2007-08-29 14:28:19 +08:00
|
|
|
/*-*-linux-c-*-*/
|
|
|
|
|
|
|
|
/*
|
2012-03-27 10:31:01 +08:00
|
|
|
Copyright (C) 2007,2008 Jonathan Woithe <jwoithe@just42.net>
|
2008-06-11 08:44:56 +08:00
|
|
|
Copyright (C) 2008 Peter Gruber <nokos@gmx.net>
|
2009-01-01 02:19:59 +08:00
|
|
|
Copyright (C) 2008 Tony Vroon <tony@linx.net>
|
2007-08-29 14:28:19 +08:00
|
|
|
Based on earlier work:
|
|
|
|
Copyright (C) 2003 Shane Spencer <shane@bogomip.com>
|
|
|
|
Adrian Yee <brewt-fujitsu@brewt.org>
|
|
|
|
|
2008-06-11 08:44:56 +08:00
|
|
|
Templated from msi-laptop.c and thinkpad_acpi.c which is copyright
|
|
|
|
by its respective authors.
|
2007-08-29 14:28:19 +08:00
|
|
|
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful, but
|
|
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with this program; if not, write to the Free Software
|
|
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
|
|
|
02110-1301, USA.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*
|
2017-03-14 18:26:27 +08:00
|
|
|
* This driver implements a vendor-specific backlight control interface for
|
|
|
|
* Fujitsu laptops and provides support for hotkeys present on certain Fujitsu
|
|
|
|
* laptops.
|
2008-06-11 08:44:56 +08:00
|
|
|
*
|
2008-10-09 12:14:40 +08:00
|
|
|
* 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.
|
2008-06-11 08:44:56 +08:00
|
|
|
*
|
|
|
|
* 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.
|
|
|
|
*
|
2007-08-29 14:28:19 +08:00
|
|
|
*/
|
|
|
|
|
2011-03-30 06:21:39 +08:00
|
|
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
|
|
|
|
2007-08-29 14:28:19 +08:00
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/kernel.h>
|
|
|
|
#include <linux/init.h>
|
|
|
|
#include <linux/acpi.h>
|
|
|
|
#include <linux/dmi.h>
|
|
|
|
#include <linux/backlight.h>
|
2015-01-19 03:28:46 +08:00
|
|
|
#include <linux/fb.h>
|
2008-06-11 08:44:56 +08:00
|
|
|
#include <linux/input.h>
|
2017-03-20 17:32:19 +08:00
|
|
|
#include <linux/input/sparse-keymap.h>
|
2008-06-11 08:44:56 +08:00
|
|
|
#include <linux/kfifo.h>
|
2017-04-07 21:07:08 +08:00
|
|
|
#include <linux/leds.h>
|
2007-08-29 14:28:19 +08:00
|
|
|
#include <linux/platform_device.h>
|
include cleanup: Update gfp.h and slab.h includes to prepare for breaking implicit slab.h inclusion from percpu.h
percpu.h is included by sched.h and module.h and thus ends up being
included when building most .c files. percpu.h includes slab.h which
in turn includes gfp.h making everything defined by the two files
universally available and complicating inclusion dependencies.
percpu.h -> slab.h dependency is about to be removed. Prepare for
this change by updating users of gfp and slab facilities include those
headers directly instead of assuming availability. As this conversion
needs to touch large number of source files, the following script is
used as the basis of conversion.
http://userweb.kernel.org/~tj/misc/slabh-sweep.py
The script does the followings.
* Scan files for gfp and slab usages and update includes such that
only the necessary includes are there. ie. if only gfp is used,
gfp.h, if slab is used, slab.h.
* When the script inserts a new include, it looks at the include
blocks and try to put the new include such that its order conforms
to its surrounding. It's put in the include block which contains
core kernel includes, in the same order that the rest are ordered -
alphabetical, Christmas tree, rev-Xmas-tree or at the end if there
doesn't seem to be any matching order.
* If the script can't find a place to put a new include (mostly
because the file doesn't have fitting include block), it prints out
an error message indicating which .h file needs to be added to the
file.
The conversion was done in the following steps.
1. The initial automatic conversion of all .c files updated slightly
over 4000 files, deleting around 700 includes and adding ~480 gfp.h
and ~3000 slab.h inclusions. The script emitted errors for ~400
files.
2. Each error was manually checked. Some didn't need the inclusion,
some needed manual addition while adding it to implementation .h or
embedding .c file was more appropriate for others. This step added
inclusions to around 150 files.
3. The script was run again and the output was compared to the edits
from #2 to make sure no file was left behind.
4. Several build tests were done and a couple of problems were fixed.
e.g. lib/decompress_*.c used malloc/free() wrappers around slab
APIs requiring slab.h to be added manually.
5. The script was run on all .h files but without automatically
editing them as sprinkling gfp.h and slab.h inclusions around .h
files could easily lead to inclusion dependency hell. Most gfp.h
inclusion directives were ignored as stuff from gfp.h was usually
wildly available and often used in preprocessor macros. Each
slab.h inclusion directive was examined and added manually as
necessary.
6. percpu.h was updated not to include slab.h.
7. Build test were done on the following configurations and failures
were fixed. CONFIG_GCOV_KERNEL was turned off for all tests (as my
distributed build env didn't work with gcov compiles) and a few
more options had to be turned off depending on archs to make things
build (like ipr on powerpc/64 which failed due to missing writeq).
* x86 and x86_64 UP and SMP allmodconfig and a custom test config.
* powerpc and powerpc64 SMP allmodconfig
* sparc and sparc64 SMP allmodconfig
* ia64 SMP allmodconfig
* s390 SMP allmodconfig
* alpha SMP allmodconfig
* um on x86_64 SMP allmodconfig
8. percpu.h modifications were reverted so that it could be applied as
a separate patch and serve as bisection point.
Given the fact that I had only a couple of failures from tests on step
6, I'm fairly confident about the coverage of this conversion patch.
If there is a breakage, it's likely to be something in one of the arch
headers which should be easily discoverable easily on most builds of
the specific arch.
Signed-off-by: Tejun Heo <tj@kernel.org>
Guess-its-ok-by: Christoph Lameter <cl@linux-foundation.org>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: Lee Schermerhorn <Lee.Schermerhorn@hp.com>
2010-03-24 16:04:11 +08:00
|
|
|
#include <linux/slab.h>
|
2015-06-16 22:28:03 +08:00
|
|
|
#include <acpi/video.h>
|
2007-08-29 14:28:19 +08:00
|
|
|
|
2009-07-31 16:46:59 +08:00
|
|
|
#define FUJITSU_DRIVER_VERSION "0.6.0"
|
2007-08-29 14:28:19 +08:00
|
|
|
|
|
|
|
#define FUJITSU_LCD_N_LEVELS 8
|
|
|
|
|
2017-02-08 21:46:24 +08:00
|
|
|
#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"
|
2017-02-08 21:46:25 +08:00
|
|
|
#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"
|
2008-06-11 08:44:56 +08:00
|
|
|
|
|
|
|
#define ACPI_FUJITSU_NOTIFY_CODE1 0x80
|
|
|
|
|
2009-01-01 02:19:59 +08:00
|
|
|
/* FUNC interface - command values */
|
2017-02-08 21:46:27 +08:00
|
|
|
#define FUNC_FLAGS 0x1000
|
2009-01-01 02:19:59 +08:00
|
|
|
#define FUNC_LEDS 0x1001
|
|
|
|
#define FUNC_BUTTONS 0x1002
|
|
|
|
#define FUNC_BACKLIGHT 0x1004
|
|
|
|
|
|
|
|
/* FUNC interface - responses */
|
|
|
|
#define UNSUPPORTED_CMD 0x80000000
|
|
|
|
|
2017-02-08 21:46:28 +08:00
|
|
|
/* FUNC interface - status flags */
|
|
|
|
#define FLAG_RFKILL 0x020
|
|
|
|
#define FLAG_LID 0x100
|
|
|
|
#define FLAG_DOCK 0x200
|
|
|
|
|
2009-01-01 02:19:59 +08:00
|
|
|
/* FUNC interface - LED control */
|
|
|
|
#define FUNC_LED_OFF 0x1
|
|
|
|
#define FUNC_LED_ON 0x30001
|
|
|
|
#define KEYBOARD_LAMPS 0x100
|
|
|
|
#define LOGOLAMP_POWERON 0x2000
|
|
|
|
#define LOGOLAMP_ALWAYS 0x4000
|
fujitsu-laptop: Support radio LED
Lifebook E734/E744/E754 has a LED which the manual calls "radio
components indicator". It should be lit when any radio transmitter is
enabled. Its state can be read and set using ACPI (FUNC interface,
RFKILL method).
Since the Lifebook E734/E744/E754 only has a button (as compared to a
slider) for enabling/disabling radio transmitters, I believe the LED in
question is meant to indicate whether all radio transmitters are
currently on or off. However, pressing the radio toggle button does not
automatically change the hardware state of the transmitters: it looks
like this machine relies on soft rfkill.
As for detecting whether the LED is present on a given machine, I had to
resort to educated guesswork. I assumed this LED is present on all
devices which have a radio toggle button instead of a slider. My
Lifebook E744 holds 0x01010001 in BTNI. By comparing the bits and
buttons with those of a Lifebook E8420 (BTNI=0x000F0101, has a slider),
I put my money on bit 24 as the indicator of the radio toggle button
being present. Furthermore, bit 24 is also clear on the S7020 which
does not have the toggle button or an RF LED.
Figuring out how the LED is controlled was more deterministic as all it
took was decompiling the DSDT and taking a look at method S000 (the
RFKILL method of the FUNC interface).
The LED control method implemented here is unsuitable for use with
"heavy" LED triggers, like phy0rx. Once blinking frequency achieves a
certain level, the system hangs.
Signed-off-by: Michał Kępień <kernel@kempniu.pl>
[jwoithe: Comment on bit 24 in BTNI, expanded commit msg]
Signed-off-by: Jonathan Woithe <jwoithe@just42.net>
[dvhart: Minor style and commit log adjustments]
Signed-off-by: Darren Hart <dvhart@linux.intel.com>
2016-04-12 20:36:34 +08:00
|
|
|
#define RADIO_LED_ON 0x20
|
2016-06-21 16:09:21 +08:00
|
|
|
#define ECO_LED 0x10000
|
|
|
|
#define ECO_LED_ON 0x80000
|
2009-01-01 02:19:59 +08:00
|
|
|
|
2008-06-11 08:44:56 +08:00
|
|
|
/* Hotkey details */
|
2008-10-09 12:14:40 +08:00
|
|
|
#define KEY1_CODE 0x410 /* codes for the keys in the GIRB register */
|
|
|
|
#define KEY2_CODE 0x411
|
|
|
|
#define KEY3_CODE 0x412
|
|
|
|
#define KEY4_CODE 0x413
|
2016-02-24 21:23:32 +08:00
|
|
|
#define KEY5_CODE 0x420
|
2008-06-11 08:44:56 +08:00
|
|
|
|
|
|
|
#define MAX_HOTKEY_RINGBUFFER_SIZE 100
|
|
|
|
#define RINGBUFFERSIZE 40
|
|
|
|
|
|
|
|
/* Debugging */
|
|
|
|
#define FUJLAPTOP_DBG_ERROR 0x0001
|
|
|
|
#define FUJLAPTOP_DBG_WARN 0x0002
|
|
|
|
#define FUJLAPTOP_DBG_INFO 0x0004
|
|
|
|
#define FUJLAPTOP_DBG_TRACE 0x0008
|
|
|
|
|
2014-06-16 17:55:13 +08:00
|
|
|
#ifdef CONFIG_FUJITSU_LAPTOP_DEBUG
|
|
|
|
#define vdbg_printk(a_dbg_level, format, arg...) \
|
2008-06-11 08:44:56 +08:00
|
|
|
do { if (dbg_level & a_dbg_level) \
|
2016-06-23 18:02:47 +08:00
|
|
|
printk(KERN_DEBUG pr_fmt("%s: " format), __func__, ## arg); \
|
2008-06-11 08:44:56 +08:00
|
|
|
} while (0)
|
|
|
|
#else
|
2014-06-16 17:55:13 +08:00
|
|
|
#define vdbg_printk(a_dbg_level, format, arg...) \
|
|
|
|
do { } while (0)
|
2008-06-11 08:44:56 +08:00
|
|
|
#endif
|
|
|
|
|
|
|
|
/* Device controlling the backlight and associated keys */
|
2017-02-08 21:46:24 +08:00
|
|
|
struct fujitsu_bl {
|
2008-06-11 08:44:56 +08:00
|
|
|
struct input_dev *input;
|
|
|
|
char phys[32];
|
2007-08-29 14:28:19 +08:00
|
|
|
struct backlight_device *bl_device;
|
2008-06-11 08:44:56 +08:00
|
|
|
unsigned int max_brightness;
|
2007-08-29 14:28:19 +08:00
|
|
|
unsigned int brightness_level;
|
|
|
|
};
|
|
|
|
|
2017-02-08 21:46:24 +08:00
|
|
|
static struct fujitsu_bl *fujitsu_bl;
|
2008-06-11 08:44:56 +08:00
|
|
|
static int use_alt_lcd_levels = -1;
|
2017-04-05 14:49:06 +08:00
|
|
|
static bool disable_brightness_adjust;
|
2008-06-11 08:44:56 +08:00
|
|
|
|
2017-02-08 21:46:25 +08:00
|
|
|
/* Device used to access hotkeys and other features on the laptop */
|
|
|
|
struct fujitsu_laptop {
|
2008-06-11 08:44:56 +08:00
|
|
|
struct input_dev *input;
|
|
|
|
char phys[32];
|
|
|
|
struct platform_device *pf_device;
|
kfifo: move struct kfifo in place
This is a new generic kernel FIFO implementation.
The current kernel fifo API is not very widely used, because it has to
many constrains. Only 17 files in the current 2.6.31-rc5 used it.
FIFO's are like list's a very basic thing and a kfifo API which handles
the most use case would save a lot of development time and memory
resources.
I think this are the reasons why kfifo is not in use:
- The API is to simple, important functions are missing
- A fifo can be only allocated dynamically
- There is a requirement of a spinlock whether you need it or not
- There is no support for data records inside a fifo
So I decided to extend the kfifo in a more generic way without blowing up
the API to much. The new API has the following benefits:
- Generic usage: For kernel internal use and/or device driver.
- Provide an API for the most use case.
- Slim API: The whole API provides 25 functions.
- Linux style habit.
- DECLARE_KFIFO, DEFINE_KFIFO and INIT_KFIFO Macros
- Direct copy_to_user from the fifo and copy_from_user into the fifo.
- The kfifo itself is an in place member of the using data structure, this save an
indirection access and does not waste the kernel allocator.
- Lockless access: if only one reader and one writer is active on the fifo,
which is the common use case, no additional locking is necessary.
- Remove spinlock - give the user the freedom of choice what kind of locking to use if
one is required.
- Ability to handle records. Three type of records are supported:
- Variable length records between 0-255 bytes, with a record size
field of 1 bytes.
- Variable length records between 0-65535 bytes, with a record size
field of 2 bytes.
- Fixed size records, which no record size field.
- Preserve memory resource.
- Performance!
- Easy to use!
This patch:
Since most users want to have the kfifo as part of another object,
reorganize the code to allow including struct kfifo in another data
structure. This requires changing the kfifo_alloc and kfifo_init
prototypes so that we pass an existing kfifo pointer into them. This
patch changes the implementation and all existing users.
[akpm@linux-foundation.org: fix warning]
Signed-off-by: Stefani Seibold <stefani@seibold.net>
Acked-by: Greg Kroah-Hartman <gregkh@suse.de>
Acked-by: Mauro Carvalho Chehab <mchehab@redhat.com>
Acked-by: Andi Kleen <ak@linux.intel.com>
Acked-by: Arnd Bergmann <arnd@arndb.de>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2009-12-22 06:37:26 +08:00
|
|
|
struct kfifo fifo;
|
2008-06-11 08:44:56 +08:00
|
|
|
spinlock_t fifo_lock;
|
2017-02-08 21:46:27 +08:00
|
|
|
int flags_supported;
|
|
|
|
int flags_state;
|
2008-06-11 08:44:56 +08:00
|
|
|
};
|
2007-08-29 14:28:19 +08:00
|
|
|
|
2017-02-08 21:46:25 +08:00
|
|
|
static struct fujitsu_laptop *fujitsu_laptop;
|
platform/x86: fujitsu-laptop: track the last instantiated FUJ02E3 ACPI device
fujitsu-laptop registers two ACPI drivers: one for ACPI device FUJ02B1
enabling backlight control and another for ACPI device FUJ02E3 which
handles various other stuff (hotkeys, LEDs, etc.) In a perfect world,
private data used by each of these drivers would be neatly encapsulated
in a structure specific to a given driver instance. Sadly, firmware
present on some Fujitsu laptops makes that impossible by exposing
backlight power control (which is what the FUJ02B1 ACPI device should
take care of) through the FUJ02E3 ACPI device. This means the backlight
driver needs a way to access an ACPI device it is not bound to. When
the backlight driver is extracted into a separate module, it will not be
able to rely on a module-wide variable any more and such access will
happen through an API exposed by fujitsu-laptop.
For all known firmwares out in the wild, it seems that whenever the
FUJ02B1 ACPI device is present, it is always accompanied by a single
instance of the FUJ02E3 ACPI device. We could independently grab an
ACPI handle to the FUJ02E3 ACPI device from the backlight driver, but
that would require using a hardcoded absolute path to that ACPI device,
which is subject to change. It is easier to simply store a module-wide
pointer to the last (most likely only) FUJ02E3 ACPI device found, make
the aforementioned API use it and cover our bases by warning the user if
firmware exposes multiple FUJ02E3 ACPI devices.
Introducing this pointer in advance allows us to get rid of the
acpi_handle field of struct fujitsu_bl and also enables a bit more
step-by-step migration to a device-specific implementation of
call_fext_func().
Signed-off-by: Michał Kępień <kernel@kempniu.pl>
Reviewed-by: Jonathan Woithe <jwoithe@just42.net>
Signed-off-by: Darren Hart (VMware) <dvhart@infradead.org>
2017-05-19 15:44:45 +08:00
|
|
|
static struct acpi_device *fext;
|
2008-06-11 08:44:56 +08:00
|
|
|
|
|
|
|
#ifdef CONFIG_FUJITSU_LAPTOP_DEBUG
|
|
|
|
static u32 dbg_level = 0x03;
|
|
|
|
#endif
|
|
|
|
|
2009-01-01 02:19:59 +08:00
|
|
|
/* Fujitsu ACPI interface function */
|
|
|
|
|
2017-05-19 15:44:46 +08:00
|
|
|
static int call_fext_func(struct acpi_device *device,
|
|
|
|
int func, int op, int feature, int state)
|
2009-01-01 02:19:59 +08:00
|
|
|
{
|
|
|
|
union acpi_object params[4] = {
|
2017-04-03 17:38:59 +08:00
|
|
|
{ .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 }
|
2009-01-01 02:19:59 +08:00
|
|
|
};
|
2017-04-03 17:38:57 +08:00
|
|
|
struct acpi_object_list arg_list = { 4, params };
|
2013-09-03 08:32:12 +08:00
|
|
|
unsigned long long value;
|
2017-04-03 17:38:57 +08:00
|
|
|
acpi_status status;
|
2009-01-01 02:19:59 +08:00
|
|
|
|
2017-05-19 15:44:46 +08:00
|
|
|
status = acpi_evaluate_integer(device->handle, "FUNC", &arg_list,
|
|
|
|
&value);
|
2009-01-01 02:19:59 +08:00
|
|
|
if (ACPI_FAILURE(status)) {
|
2017-04-06 14:46:10 +08:00
|
|
|
vdbg_printk(FUJLAPTOP_DBG_ERROR, "Failed to evaluate FUNC\n");
|
2009-01-01 02:19:59 +08:00
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
|
2017-04-03 17:38:59 +08:00
|
|
|
vdbg_printk(FUJLAPTOP_DBG_TRACE, "FUNC 0x%x (args 0x%x, 0x%x, 0x%x) returned 0x%x\n",
|
|
|
|
func, op, feature, state, (int)value);
|
2013-09-03 08:32:12 +08:00
|
|
|
return value;
|
2009-01-01 02:19:59 +08:00
|
|
|
}
|
|
|
|
|
2008-06-11 08:44:56 +08:00
|
|
|
/* Hardware access for LCD brightness control */
|
2007-08-29 14:28:19 +08:00
|
|
|
|
2017-05-19 15:44:43 +08:00
|
|
|
static int set_lcd_level(struct acpi_device *device, int level)
|
2007-08-29 14:28:19 +08:00
|
|
|
{
|
2017-05-19 15:44:43 +08:00
|
|
|
struct fujitsu_bl *priv = acpi_driver_data(device);
|
2017-04-05 14:49:03 +08:00
|
|
|
acpi_status status;
|
2017-04-05 14:49:02 +08:00
|
|
|
char *method;
|
|
|
|
|
|
|
|
switch (use_alt_lcd_levels) {
|
2017-04-05 14:49:05 +08:00
|
|
|
case -1:
|
2017-05-19 15:44:43 +08:00
|
|
|
if (acpi_has_method(device->handle, "SBL2"))
|
2017-04-05 14:49:05 +08:00
|
|
|
method = "SBL2";
|
|
|
|
else
|
|
|
|
method = "SBLL";
|
|
|
|
break;
|
2017-04-05 14:49:02 +08:00
|
|
|
case 1:
|
|
|
|
method = "SBL2";
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
method = "SBLL";
|
|
|
|
break;
|
2008-06-11 08:44:56 +08:00
|
|
|
}
|
|
|
|
|
2017-04-05 14:49:02 +08:00
|
|
|
vdbg_printk(FUJLAPTOP_DBG_TRACE, "set lcd level via %s [%d]\n",
|
|
|
|
method, level);
|
2008-06-11 08:44:56 +08:00
|
|
|
|
2017-05-19 15:44:43 +08:00
|
|
|
if (level < 0 || level >= priv->max_brightness)
|
2008-06-11 08:44:56 +08:00
|
|
|
return -EINVAL;
|
|
|
|
|
2017-05-19 15:44:43 +08:00
|
|
|
status = acpi_execute_simple_method(device->handle, method, level);
|
2008-06-11 08:44:56 +08:00
|
|
|
if (ACPI_FAILURE(status)) {
|
2017-04-05 14:49:03 +08:00
|
|
|
vdbg_printk(FUJLAPTOP_DBG_ERROR, "Failed to evaluate %s\n",
|
|
|
|
method);
|
2007-08-29 14:28:19 +08:00
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
|
2017-05-19 15:44:43 +08:00
|
|
|
priv->brightness_level = level;
|
2017-04-05 14:49:04 +08:00
|
|
|
|
2007-08-29 14:28:19 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-05-19 15:44:43 +08:00
|
|
|
static int get_lcd_level(struct acpi_device *device)
|
2007-08-29 14:28:19 +08:00
|
|
|
{
|
2017-05-19 15:44:43 +08:00
|
|
|
struct fujitsu_bl *priv = acpi_driver_data(device);
|
2008-10-10 14:22:59 +08:00
|
|
|
unsigned long long state = 0;
|
2007-08-29 14:28:19 +08:00
|
|
|
acpi_status status = AE_OK;
|
|
|
|
|
2008-06-11 08:44:56 +08:00
|
|
|
vdbg_printk(FUJLAPTOP_DBG_TRACE, "get lcd level via GBLL\n");
|
|
|
|
|
2017-05-19 15:44:43 +08:00
|
|
|
status = acpi_evaluate_integer(device->handle, "GBLL", NULL, &state);
|
2009-12-23 06:49:42 +08:00
|
|
|
if (ACPI_FAILURE(status))
|
|
|
|
return 0;
|
2007-08-29 14:28:19 +08:00
|
|
|
|
2017-05-19 15:44:43 +08:00
|
|
|
priv->brightness_level = state & 0x0fffffff;
|
2008-06-11 08:44:56 +08:00
|
|
|
|
2017-05-19 15:44:43 +08:00
|
|
|
return priv->brightness_level;
|
2008-06-11 08:44:56 +08:00
|
|
|
}
|
|
|
|
|
2017-05-19 15:44:43 +08:00
|
|
|
static int get_max_brightness(struct acpi_device *device)
|
2008-06-11 08:44:56 +08:00
|
|
|
{
|
2017-05-19 15:44:43 +08:00
|
|
|
struct fujitsu_bl *priv = acpi_driver_data(device);
|
2008-10-10 14:22:59 +08:00
|
|
|
unsigned long long state = 0;
|
2008-06-11 08:44:56 +08:00
|
|
|
acpi_status status = AE_OK;
|
|
|
|
|
|
|
|
vdbg_printk(FUJLAPTOP_DBG_TRACE, "get max lcd level via RBLL\n");
|
|
|
|
|
2017-05-19 15:44:43 +08:00
|
|
|
status = acpi_evaluate_integer(device->handle, "RBLL", NULL, &state);
|
2009-12-23 06:49:42 +08:00
|
|
|
if (ACPI_FAILURE(status))
|
|
|
|
return -1;
|
2008-06-11 08:44:56 +08:00
|
|
|
|
2017-05-19 15:44:43 +08:00
|
|
|
priv->max_brightness = state;
|
2008-06-11 08:44:56 +08:00
|
|
|
|
2017-05-19 15:44:43 +08:00
|
|
|
return priv->max_brightness;
|
2008-06-11 08:44:56 +08:00
|
|
|
}
|
|
|
|
|
2007-08-29 14:28:19 +08:00
|
|
|
/* Backlight device stuff */
|
|
|
|
|
|
|
|
static int bl_get_brightness(struct backlight_device *b)
|
|
|
|
{
|
2017-05-19 15:44:43 +08:00
|
|
|
struct acpi_device *device = bl_get_data(b);
|
|
|
|
|
|
|
|
return b->props.power == FB_BLANK_POWERDOWN ? 0 : get_lcd_level(device);
|
2007-08-29 14:28:19 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static int bl_update_status(struct backlight_device *b)
|
|
|
|
{
|
2017-05-19 15:44:43 +08:00
|
|
|
struct acpi_device *device = bl_get_data(b);
|
|
|
|
|
2015-01-19 03:28:46 +08:00
|
|
|
if (b->props.power == FB_BLANK_POWERDOWN)
|
2017-05-19 15:44:46 +08:00
|
|
|
call_fext_func(fext, FUNC_BACKLIGHT, 0x1, 0x4, 0x3);
|
2009-01-01 02:19:59 +08:00
|
|
|
else
|
2017-05-19 15:44:46 +08:00
|
|
|
call_fext_func(fext, FUNC_BACKLIGHT, 0x1, 0x4, 0x0);
|
2009-01-01 02:19:59 +08:00
|
|
|
|
2017-05-19 15:44:43 +08:00
|
|
|
return set_lcd_level(device, b->props.brightness);
|
2007-08-29 14:28:19 +08:00
|
|
|
}
|
|
|
|
|
2017-02-08 21:46:24 +08:00
|
|
|
static const struct backlight_ops fujitsu_bl_ops = {
|
2007-08-29 14:28:19 +08:00
|
|
|
.get_brightness = bl_get_brightness,
|
|
|
|
.update_status = bl_update_status,
|
|
|
|
};
|
|
|
|
|
2017-03-14 18:26:28 +08:00
|
|
|
static ssize_t lid_show(struct device *dev, struct device_attribute *attr,
|
|
|
|
char *buf)
|
2009-01-01 02:19:59 +08:00
|
|
|
{
|
2017-02-08 21:46:28 +08:00
|
|
|
if (!(fujitsu_laptop->flags_supported & FLAG_LID))
|
2009-01-01 02:19:59 +08:00
|
|
|
return sprintf(buf, "unknown\n");
|
2017-02-08 21:46:28 +08:00
|
|
|
if (fujitsu_laptop->flags_state & FLAG_LID)
|
2009-01-01 02:19:59 +08:00
|
|
|
return sprintf(buf, "open\n");
|
|
|
|
else
|
|
|
|
return sprintf(buf, "closed\n");
|
|
|
|
}
|
2008-06-11 08:44:56 +08:00
|
|
|
|
2017-03-14 18:26:28 +08:00
|
|
|
static ssize_t dock_show(struct device *dev, struct device_attribute *attr,
|
|
|
|
char *buf)
|
2009-01-01 02:19:59 +08:00
|
|
|
{
|
2017-02-08 21:46:28 +08:00
|
|
|
if (!(fujitsu_laptop->flags_supported & FLAG_DOCK))
|
2009-01-01 02:19:59 +08:00
|
|
|
return sprintf(buf, "unknown\n");
|
2017-02-08 21:46:28 +08:00
|
|
|
if (fujitsu_laptop->flags_state & FLAG_DOCK)
|
2009-01-01 02:19:59 +08:00
|
|
|
return sprintf(buf, "docked\n");
|
|
|
|
else
|
|
|
|
return sprintf(buf, "undocked\n");
|
2008-06-11 08:44:56 +08:00
|
|
|
}
|
|
|
|
|
2017-03-14 18:26:28 +08:00
|
|
|
static ssize_t radios_show(struct device *dev, struct device_attribute *attr,
|
|
|
|
char *buf)
|
2008-06-11 08:44:56 +08:00
|
|
|
{
|
2017-02-08 21:46:28 +08:00
|
|
|
if (!(fujitsu_laptop->flags_supported & FLAG_RFKILL))
|
2009-01-01 02:19:59 +08:00
|
|
|
return sprintf(buf, "unknown\n");
|
2017-02-08 21:46:28 +08:00
|
|
|
if (fujitsu_laptop->flags_state & FLAG_RFKILL)
|
2009-01-01 02:19:59 +08:00
|
|
|
return sprintf(buf, "on\n");
|
|
|
|
else
|
|
|
|
return sprintf(buf, "killed\n");
|
2008-06-11 08:44:56 +08:00
|
|
|
}
|
|
|
|
|
2017-03-14 18:26:28 +08:00
|
|
|
static DEVICE_ATTR_RO(lid);
|
|
|
|
static DEVICE_ATTR_RO(dock);
|
|
|
|
static DEVICE_ATTR_RO(radios);
|
2007-08-29 14:28:19 +08:00
|
|
|
|
2017-02-08 21:46:26 +08:00
|
|
|
static struct attribute *fujitsu_pf_attributes[] = {
|
2009-01-01 02:19:59 +08:00
|
|
|
&dev_attr_lid.attr,
|
|
|
|
&dev_attr_dock.attr,
|
|
|
|
&dev_attr_radios.attr,
|
2007-08-29 14:28:19 +08:00
|
|
|
NULL
|
|
|
|
};
|
|
|
|
|
2017-02-08 21:46:26 +08:00
|
|
|
static struct attribute_group fujitsu_pf_attribute_group = {
|
|
|
|
.attrs = fujitsu_pf_attributes
|
2007-08-29 14:28:19 +08:00
|
|
|
};
|
|
|
|
|
2017-02-08 21:46:26 +08:00
|
|
|
static struct platform_driver fujitsu_pf_driver = {
|
2007-08-29 14:28:19 +08:00
|
|
|
.driver = {
|
|
|
|
.name = "fujitsu-laptop",
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2008-06-11 08:44:56 +08:00
|
|
|
/* ACPI device for LCD brightness control */
|
2007-08-29 14:28:19 +08:00
|
|
|
|
2017-03-20 17:32:19 +08:00
|
|
|
static const struct key_entry keymap_backlight[] = {
|
|
|
|
{ KE_KEY, true, { KEY_BRIGHTNESSUP } },
|
|
|
|
{ KE_KEY, false, { KEY_BRIGHTNESSDOWN } },
|
|
|
|
{ KE_END, 0 }
|
|
|
|
};
|
|
|
|
|
2017-03-20 17:32:17 +08:00
|
|
|
static int acpi_fujitsu_bl_input_setup(struct acpi_device *device)
|
|
|
|
{
|
2017-05-19 15:44:41 +08:00
|
|
|
struct fujitsu_bl *priv = acpi_driver_data(device);
|
2017-03-20 17:32:19 +08:00
|
|
|
int ret;
|
2017-03-20 17:32:17 +08:00
|
|
|
|
2017-05-19 15:44:41 +08:00
|
|
|
priv->input = devm_input_allocate_device(&device->dev);
|
|
|
|
if (!priv->input)
|
2017-03-20 17:32:17 +08:00
|
|
|
return -ENOMEM;
|
|
|
|
|
2017-05-19 15:44:41 +08:00
|
|
|
snprintf(priv->phys, sizeof(priv->phys), "%s/video/input0",
|
|
|
|
acpi_device_hid(device));
|
2017-03-20 17:32:17 +08:00
|
|
|
|
2017-05-19 15:44:41 +08:00
|
|
|
priv->input->name = acpi_device_name(device);
|
|
|
|
priv->input->phys = priv->phys;
|
|
|
|
priv->input->id.bustype = BUS_HOST;
|
|
|
|
priv->input->id.product = 0x06;
|
2017-03-20 17:32:19 +08:00
|
|
|
|
2017-05-19 15:44:41 +08:00
|
|
|
ret = sparse_keymap_setup(priv->input, keymap_backlight, NULL);
|
2017-03-20 17:32:19 +08:00
|
|
|
if (ret)
|
|
|
|
return ret;
|
2017-03-20 17:32:17 +08:00
|
|
|
|
2017-05-19 15:44:41 +08:00
|
|
|
return input_register_device(priv->input);
|
2017-03-20 17:32:17 +08:00
|
|
|
}
|
|
|
|
|
2017-04-05 14:49:01 +08:00
|
|
|
static int fujitsu_backlight_register(struct acpi_device *device)
|
2017-03-10 18:50:33 +08:00
|
|
|
{
|
2017-05-19 15:44:43 +08:00
|
|
|
struct fujitsu_bl *priv = acpi_driver_data(device);
|
2017-04-05 14:49:01 +08:00
|
|
|
const struct backlight_properties props = {
|
2017-05-19 15:44:43 +08:00
|
|
|
.brightness = priv->brightness_level,
|
|
|
|
.max_brightness = priv->max_brightness - 1,
|
2017-03-10 18:50:33 +08:00
|
|
|
.type = BACKLIGHT_PLATFORM
|
|
|
|
};
|
|
|
|
struct backlight_device *bd;
|
|
|
|
|
2017-04-05 14:49:01 +08:00
|
|
|
bd = devm_backlight_device_register(&device->dev, "fujitsu-laptop",
|
2017-05-19 15:44:43 +08:00
|
|
|
&device->dev, device,
|
2017-04-05 14:49:01 +08:00
|
|
|
&fujitsu_bl_ops, &props);
|
2017-03-10 18:50:33 +08:00
|
|
|
if (IS_ERR(bd))
|
|
|
|
return PTR_ERR(bd);
|
|
|
|
|
2017-05-19 15:44:43 +08:00
|
|
|
priv->bl_device = bd;
|
2017-03-10 18:50:33 +08:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-02-08 21:46:24 +08:00
|
|
|
static int acpi_fujitsu_bl_add(struct acpi_device *device)
|
2007-08-29 14:28:19 +08:00
|
|
|
{
|
2017-05-19 15:44:42 +08:00
|
|
|
struct fujitsu_bl *priv;
|
2007-08-29 14:28:19 +08:00
|
|
|
int state = 0;
|
2008-06-11 08:44:56 +08:00
|
|
|
int error;
|
2007-08-29 14:28:19 +08:00
|
|
|
|
2017-04-05 14:49:00 +08:00
|
|
|
if (acpi_video_get_backlight_type() != acpi_backlight_vendor)
|
|
|
|
return -ENODEV;
|
|
|
|
|
2007-08-29 14:28:19 +08:00
|
|
|
if (!device)
|
|
|
|
return -EINVAL;
|
|
|
|
|
2017-05-19 15:44:42 +08:00
|
|
|
priv = devm_kzalloc(&device->dev, sizeof(*priv), GFP_KERNEL);
|
|
|
|
if (!priv)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
fujitsu_bl = priv;
|
2017-02-08 21:46:24 +08:00
|
|
|
sprintf(acpi_device_name(device), "%s", ACPI_FUJITSU_BL_DEVICE_NAME);
|
2007-08-29 14:28:19 +08:00
|
|
|
sprintf(acpi_device_class(device), "%s", ACPI_FUJITSU_CLASS);
|
2017-05-19 15:44:42 +08:00
|
|
|
device->driver_data = priv;
|
2007-08-29 14:28:19 +08:00
|
|
|
|
2017-03-20 17:32:17 +08:00
|
|
|
error = acpi_fujitsu_bl_input_setup(device);
|
2008-06-11 08:44:56 +08:00
|
|
|
if (error)
|
2017-03-20 17:32:18 +08:00
|
|
|
return error;
|
2008-06-11 08:44:56 +08:00
|
|
|
|
2017-05-19 15:44:43 +08:00
|
|
|
error = acpi_bus_update_power(device->handle, &state);
|
2013-12-30 06:47:36 +08:00
|
|
|
if (error) {
|
2011-03-30 06:21:39 +08:00
|
|
|
pr_err("Error reading power state\n");
|
2017-03-20 17:32:18 +08:00
|
|
|
return error;
|
2007-08-29 14:28:19 +08:00
|
|
|
}
|
|
|
|
|
2011-03-30 06:21:39 +08:00
|
|
|
pr_info("ACPI: %s [%s] (%s)\n",
|
2007-08-29 14:28:19 +08:00
|
|
|
acpi_device_name(device), acpi_device_bid(device),
|
|
|
|
!device->power.state ? "on" : "off");
|
|
|
|
|
2013-09-03 08:32:03 +08:00
|
|
|
if (acpi_has_method(device->handle, METHOD_NAME__INI)) {
|
2008-06-11 08:44:56 +08:00
|
|
|
vdbg_printk(FUJLAPTOP_DBG_INFO, "Invoking _INI\n");
|
|
|
|
if (ACPI_FAILURE
|
|
|
|
(acpi_evaluate_object
|
|
|
|
(device->handle, METHOD_NAME__INI, NULL, NULL)))
|
2011-03-30 06:21:39 +08:00
|
|
|
pr_err("_INI Method failed\n");
|
2008-06-11 08:44:56 +08:00
|
|
|
}
|
|
|
|
|
2017-05-19 15:44:43 +08:00
|
|
|
if (get_max_brightness(device) <= 0)
|
|
|
|
priv->max_brightness = FUJITSU_LCD_N_LEVELS;
|
|
|
|
get_lcd_level(device);
|
2008-06-11 08:44:56 +08:00
|
|
|
|
2017-04-05 14:49:01 +08:00
|
|
|
error = fujitsu_backlight_register(device);
|
2017-04-05 14:49:00 +08:00
|
|
|
if (error)
|
|
|
|
return error;
|
2017-03-10 18:50:35 +08:00
|
|
|
|
2013-12-30 06:47:36 +08:00
|
|
|
return 0;
|
2007-08-29 14:28:19 +08:00
|
|
|
}
|
|
|
|
|
2008-06-11 08:44:56 +08:00
|
|
|
/* Brightness notify */
|
|
|
|
|
2017-02-08 21:46:24 +08:00
|
|
|
static void acpi_fujitsu_bl_notify(struct acpi_device *device, u32 event)
|
2008-06-11 08:44:56 +08:00
|
|
|
{
|
2017-05-19 15:44:43 +08:00
|
|
|
struct fujitsu_bl *priv = acpi_driver_data(device);
|
2017-03-20 17:32:19 +08:00
|
|
|
int oldb, newb;
|
2008-06-11 08:44:56 +08:00
|
|
|
|
2017-03-01 14:42:53 +08:00
|
|
|
if (event != ACPI_FUJITSU_NOTIFY_CODE1) {
|
2008-06-11 08:44:56 +08:00
|
|
|
vdbg_printk(FUJLAPTOP_DBG_WARN,
|
|
|
|
"unsupported event [0x%x]\n", event);
|
2017-05-19 15:44:43 +08:00
|
|
|
sparse_keymap_report_event(priv->input, -1, 1, true);
|
2017-03-01 14:42:53 +08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-05-19 15:44:43 +08:00
|
|
|
oldb = priv->brightness_level;
|
|
|
|
get_lcd_level(device);
|
|
|
|
newb = priv->brightness_level;
|
2017-03-01 14:42:53 +08:00
|
|
|
|
2017-04-05 14:49:10 +08:00
|
|
|
vdbg_printk(FUJLAPTOP_DBG_TRACE, "brightness button event [%i -> %i]\n",
|
|
|
|
oldb, newb);
|
2017-03-01 14:42:53 +08:00
|
|
|
|
2017-03-01 14:42:54 +08:00
|
|
|
if (oldb == newb)
|
|
|
|
return;
|
2008-06-11 08:44:56 +08:00
|
|
|
|
2017-04-05 14:49:06 +08:00
|
|
|
if (!disable_brightness_adjust)
|
2017-05-19 15:44:43 +08:00
|
|
|
set_lcd_level(device, newb);
|
2017-03-01 14:42:54 +08:00
|
|
|
|
2017-05-19 15:44:43 +08:00
|
|
|
sparse_keymap_report_event(priv->input, oldb < newb, 1, true);
|
2008-06-11 08:44:56 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/* ACPI device for hotkey handling */
|
|
|
|
|
2017-03-20 17:32:22 +08:00
|
|
|
static const struct key_entry keymap_default[] = {
|
|
|
|
{ KE_KEY, KEY1_CODE, { KEY_PROG1 } },
|
|
|
|
{ KE_KEY, KEY2_CODE, { KEY_PROG2 } },
|
|
|
|
{ KE_KEY, KEY3_CODE, { KEY_PROG3 } },
|
|
|
|
{ KE_KEY, KEY4_CODE, { KEY_PROG4 } },
|
|
|
|
{ KE_KEY, KEY5_CODE, { KEY_RFKILL } },
|
|
|
|
{ KE_KEY, BIT(26), { KEY_TOUCHPAD_TOGGLE } },
|
|
|
|
{ KE_END, 0 }
|
|
|
|
};
|
|
|
|
|
2017-03-20 17:32:23 +08:00
|
|
|
static const struct key_entry keymap_s64x0[] = {
|
|
|
|
{ KE_KEY, KEY1_CODE, { KEY_SCREENLOCK } }, /* "Lock" */
|
|
|
|
{ KE_KEY, KEY2_CODE, { KEY_HELP } }, /* "Mobility Center */
|
|
|
|
{ KE_KEY, KEY3_CODE, { KEY_PROG3 } },
|
|
|
|
{ KE_KEY, KEY4_CODE, { KEY_PROG4 } },
|
|
|
|
{ KE_END, 0 }
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct key_entry keymap_p8010[] = {
|
|
|
|
{ KE_KEY, KEY1_CODE, { KEY_HELP } }, /* "Support" */
|
|
|
|
{ KE_KEY, KEY2_CODE, { KEY_PROG2 } },
|
|
|
|
{ KE_KEY, KEY3_CODE, { KEY_SWITCHVIDEOMODE } }, /* "Presentation" */
|
|
|
|
{ KE_KEY, KEY4_CODE, { KEY_WWW } }, /* "WWW" */
|
|
|
|
{ KE_END, 0 }
|
|
|
|
};
|
|
|
|
|
2017-03-20 17:32:22 +08:00
|
|
|
static const struct key_entry *keymap = keymap_default;
|
|
|
|
|
2017-03-20 17:32:23 +08:00
|
|
|
static int fujitsu_laptop_dmi_keymap_override(const struct dmi_system_id *id)
|
|
|
|
{
|
|
|
|
pr_info("Identified laptop model '%s'\n", id->ident);
|
|
|
|
keymap = id->driver_data;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct dmi_system_id fujitsu_laptop_dmi_table[] = {
|
|
|
|
{
|
|
|
|
.callback = fujitsu_laptop_dmi_keymap_override,
|
|
|
|
.ident = "Fujitsu Siemens S6410",
|
|
|
|
.matches = {
|
|
|
|
DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"),
|
|
|
|
DMI_MATCH(DMI_PRODUCT_NAME, "LIFEBOOK S6410"),
|
|
|
|
},
|
|
|
|
.driver_data = (void *)keymap_s64x0
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.callback = fujitsu_laptop_dmi_keymap_override,
|
|
|
|
.ident = "Fujitsu Siemens S6420",
|
|
|
|
.matches = {
|
|
|
|
DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"),
|
|
|
|
DMI_MATCH(DMI_PRODUCT_NAME, "LIFEBOOK S6420"),
|
|
|
|
},
|
|
|
|
.driver_data = (void *)keymap_s64x0
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.callback = fujitsu_laptop_dmi_keymap_override,
|
|
|
|
.ident = "Fujitsu LifeBook P8010",
|
|
|
|
.matches = {
|
|
|
|
DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"),
|
|
|
|
DMI_MATCH(DMI_PRODUCT_NAME, "LifeBook P8010"),
|
|
|
|
},
|
|
|
|
.driver_data = (void *)keymap_p8010
|
|
|
|
},
|
|
|
|
{}
|
|
|
|
};
|
|
|
|
|
2017-03-20 17:32:20 +08:00
|
|
|
static int acpi_fujitsu_laptop_input_setup(struct acpi_device *device)
|
|
|
|
{
|
2017-05-19 15:44:41 +08:00
|
|
|
struct fujitsu_laptop *priv = acpi_driver_data(device);
|
2017-03-20 17:32:22 +08:00
|
|
|
int ret;
|
2017-03-20 17:32:20 +08:00
|
|
|
|
2017-05-19 15:44:41 +08:00
|
|
|
priv->input = devm_input_allocate_device(&device->dev);
|
|
|
|
if (!priv->input)
|
2017-03-20 17:32:20 +08:00
|
|
|
return -ENOMEM;
|
|
|
|
|
2017-05-19 15:44:41 +08:00
|
|
|
snprintf(priv->phys, sizeof(priv->phys), "%s/video/input0",
|
|
|
|
acpi_device_hid(device));
|
2017-03-20 17:32:20 +08:00
|
|
|
|
2017-05-19 15:44:41 +08:00
|
|
|
priv->input->name = acpi_device_name(device);
|
|
|
|
priv->input->phys = priv->phys;
|
|
|
|
priv->input->id.bustype = BUS_HOST;
|
|
|
|
priv->input->id.product = 0x06;
|
2017-03-20 17:32:21 +08:00
|
|
|
|
2017-03-20 17:32:23 +08:00
|
|
|
dmi_check_system(fujitsu_laptop_dmi_table);
|
2017-05-19 15:44:41 +08:00
|
|
|
ret = sparse_keymap_setup(priv->input, keymap, NULL);
|
2017-03-20 17:32:22 +08:00
|
|
|
if (ret)
|
|
|
|
return ret;
|
2017-03-20 17:32:21 +08:00
|
|
|
|
2017-05-19 15:44:41 +08:00
|
|
|
return input_register_device(priv->input);
|
2017-03-20 17:32:20 +08:00
|
|
|
}
|
|
|
|
|
2017-03-14 18:26:29 +08:00
|
|
|
static int fujitsu_laptop_platform_add(void)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
|
2017-03-14 18:26:31 +08:00
|
|
|
fujitsu_laptop->pf_device = platform_device_alloc("fujitsu-laptop", -1);
|
|
|
|
if (!fujitsu_laptop->pf_device)
|
2017-03-14 18:26:29 +08:00
|
|
|
return -ENOMEM;
|
|
|
|
|
2017-03-14 18:26:31 +08:00
|
|
|
ret = platform_device_add(fujitsu_laptop->pf_device);
|
2017-03-14 18:26:29 +08:00
|
|
|
if (ret)
|
|
|
|
goto err_put_platform_device;
|
|
|
|
|
2017-03-14 18:26:31 +08:00
|
|
|
ret = sysfs_create_group(&fujitsu_laptop->pf_device->dev.kobj,
|
2017-03-14 18:26:29 +08:00
|
|
|
&fujitsu_pf_attribute_group);
|
|
|
|
if (ret)
|
|
|
|
goto err_del_platform_device;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
err_del_platform_device:
|
2017-03-14 18:26:31 +08:00
|
|
|
platform_device_del(fujitsu_laptop->pf_device);
|
2017-03-14 18:26:29 +08:00
|
|
|
err_put_platform_device:
|
2017-03-14 18:26:31 +08:00
|
|
|
platform_device_put(fujitsu_laptop->pf_device);
|
2017-03-14 18:26:29 +08:00
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void fujitsu_laptop_platform_remove(void)
|
|
|
|
{
|
2017-03-14 18:26:31 +08:00
|
|
|
sysfs_remove_group(&fujitsu_laptop->pf_device->dev.kobj,
|
2017-03-14 18:26:29 +08:00
|
|
|
&fujitsu_pf_attribute_group);
|
2017-03-14 18:26:31 +08:00
|
|
|
platform_device_unregister(fujitsu_laptop->pf_device);
|
2017-03-14 18:26:29 +08:00
|
|
|
}
|
|
|
|
|
2017-04-07 21:07:10 +08:00
|
|
|
static int logolamp_set(struct led_classdev *cdev,
|
|
|
|
enum led_brightness brightness)
|
|
|
|
{
|
|
|
|
int poweron = FUNC_LED_ON, always = FUNC_LED_ON;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (brightness < LED_HALF)
|
|
|
|
poweron = FUNC_LED_OFF;
|
|
|
|
|
|
|
|
if (brightness < LED_FULL)
|
|
|
|
always = FUNC_LED_OFF;
|
|
|
|
|
2017-05-19 15:44:46 +08:00
|
|
|
ret = call_fext_func(fext, FUNC_LEDS, 0x1, LOGOLAMP_POWERON, poweron);
|
2017-04-07 21:07:10 +08:00
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
|
2017-05-19 15:44:46 +08:00
|
|
|
return call_fext_func(fext, FUNC_LEDS, 0x1, LOGOLAMP_ALWAYS, always);
|
2017-04-07 21:07:10 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static enum led_brightness logolamp_get(struct led_classdev *cdev)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
|
2017-05-19 15:44:46 +08:00
|
|
|
ret = call_fext_func(fext, FUNC_LEDS, 0x2, LOGOLAMP_ALWAYS, 0x0);
|
2017-04-07 21:07:10 +08:00
|
|
|
if (ret == FUNC_LED_ON)
|
|
|
|
return LED_FULL;
|
|
|
|
|
2017-05-19 15:44:46 +08:00
|
|
|
ret = call_fext_func(fext, FUNC_LEDS, 0x2, LOGOLAMP_POWERON, 0x0);
|
2017-04-07 21:07:10 +08:00
|
|
|
if (ret == FUNC_LED_ON)
|
|
|
|
return LED_HALF;
|
|
|
|
|
|
|
|
return LED_OFF;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct led_classdev logolamp_led = {
|
|
|
|
.name = "fujitsu::logolamp",
|
|
|
|
.brightness_set_blocking = logolamp_set,
|
|
|
|
.brightness_get = logolamp_get
|
|
|
|
};
|
|
|
|
|
|
|
|
static int kblamps_set(struct led_classdev *cdev,
|
|
|
|
enum led_brightness brightness)
|
|
|
|
{
|
|
|
|
if (brightness >= LED_FULL)
|
2017-05-19 15:44:46 +08:00
|
|
|
return call_fext_func(fext, FUNC_LEDS, 0x1, KEYBOARD_LAMPS,
|
2017-04-07 21:07:10 +08:00
|
|
|
FUNC_LED_ON);
|
|
|
|
else
|
2017-05-19 15:44:46 +08:00
|
|
|
return call_fext_func(fext, FUNC_LEDS, 0x1, KEYBOARD_LAMPS,
|
2017-04-07 21:07:10 +08:00
|
|
|
FUNC_LED_OFF);
|
|
|
|
}
|
|
|
|
|
|
|
|
static enum led_brightness kblamps_get(struct led_classdev *cdev)
|
|
|
|
{
|
|
|
|
enum led_brightness brightness = LED_OFF;
|
|
|
|
|
2017-05-19 15:44:46 +08:00
|
|
|
if (call_fext_func(fext,
|
|
|
|
FUNC_LEDS, 0x2, KEYBOARD_LAMPS, 0x0) == FUNC_LED_ON)
|
2017-04-07 21:07:10 +08:00
|
|
|
brightness = LED_FULL;
|
|
|
|
|
|
|
|
return brightness;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct led_classdev kblamps_led = {
|
|
|
|
.name = "fujitsu::kblamps",
|
|
|
|
.brightness_set_blocking = kblamps_set,
|
|
|
|
.brightness_get = kblamps_get
|
|
|
|
};
|
|
|
|
|
|
|
|
static int radio_led_set(struct led_classdev *cdev,
|
|
|
|
enum led_brightness brightness)
|
|
|
|
{
|
|
|
|
if (brightness >= LED_FULL)
|
2017-05-19 15:44:46 +08:00
|
|
|
return call_fext_func(fext, FUNC_FLAGS, 0x5, RADIO_LED_ON,
|
2017-04-07 21:07:10 +08:00
|
|
|
RADIO_LED_ON);
|
|
|
|
else
|
2017-05-19 15:44:46 +08:00
|
|
|
return call_fext_func(fext, FUNC_FLAGS, 0x5, RADIO_LED_ON,
|
|
|
|
0x0);
|
2017-04-07 21:07:10 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static enum led_brightness radio_led_get(struct led_classdev *cdev)
|
|
|
|
{
|
|
|
|
enum led_brightness brightness = LED_OFF;
|
|
|
|
|
2017-05-19 15:44:46 +08:00
|
|
|
if (call_fext_func(fext, FUNC_FLAGS, 0x4, 0x0, 0x0) & RADIO_LED_ON)
|
2017-04-07 21:07:10 +08:00
|
|
|
brightness = LED_FULL;
|
|
|
|
|
|
|
|
return brightness;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct led_classdev radio_led = {
|
|
|
|
.name = "fujitsu::radio_led",
|
|
|
|
.brightness_set_blocking = radio_led_set,
|
|
|
|
.brightness_get = radio_led_get,
|
|
|
|
.default_trigger = "rfkill-any"
|
|
|
|
};
|
|
|
|
|
|
|
|
static int eco_led_set(struct led_classdev *cdev,
|
|
|
|
enum led_brightness brightness)
|
|
|
|
{
|
|
|
|
int curr;
|
|
|
|
|
2017-05-19 15:44:46 +08:00
|
|
|
curr = call_fext_func(fext, FUNC_LEDS, 0x2, ECO_LED, 0x0);
|
2017-04-07 21:07:10 +08:00
|
|
|
if (brightness >= LED_FULL)
|
2017-05-19 15:44:46 +08:00
|
|
|
return call_fext_func(fext, FUNC_LEDS, 0x1, ECO_LED,
|
2017-04-07 21:07:10 +08:00
|
|
|
curr | ECO_LED_ON);
|
|
|
|
else
|
2017-05-19 15:44:46 +08:00
|
|
|
return call_fext_func(fext, FUNC_LEDS, 0x1, ECO_LED,
|
2017-04-07 21:07:10 +08:00
|
|
|
curr & ~ECO_LED_ON);
|
|
|
|
}
|
|
|
|
|
|
|
|
static enum led_brightness eco_led_get(struct led_classdev *cdev)
|
|
|
|
{
|
|
|
|
enum led_brightness brightness = LED_OFF;
|
|
|
|
|
2017-05-19 15:44:46 +08:00
|
|
|
if (call_fext_func(fext, FUNC_LEDS, 0x2, ECO_LED, 0x0) & ECO_LED_ON)
|
2017-04-07 21:07:10 +08:00
|
|
|
brightness = LED_FULL;
|
|
|
|
|
|
|
|
return brightness;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct led_classdev eco_led = {
|
|
|
|
.name = "fujitsu::eco_led",
|
|
|
|
.brightness_set_blocking = eco_led_set,
|
|
|
|
.brightness_get = eco_led_get
|
|
|
|
};
|
|
|
|
|
2017-04-07 21:07:11 +08:00
|
|
|
static int acpi_fujitsu_laptop_leds_register(struct acpi_device *device)
|
2008-06-11 08:44:56 +08:00
|
|
|
{
|
2017-04-07 21:07:12 +08:00
|
|
|
int result;
|
2017-04-07 21:07:09 +08:00
|
|
|
|
2017-05-19 15:44:46 +08:00
|
|
|
if (call_fext_func(fext,
|
|
|
|
FUNC_LEDS, 0x0, 0x0, 0x0) & LOGOLAMP_POWERON) {
|
2017-04-07 21:07:11 +08:00
|
|
|
result = devm_led_classdev_register(&device->dev,
|
|
|
|
&logolamp_led);
|
|
|
|
if (result)
|
2017-04-07 21:07:12 +08:00
|
|
|
return result;
|
2017-04-07 21:07:09 +08:00
|
|
|
}
|
|
|
|
|
2017-05-19 15:44:46 +08:00
|
|
|
if ((call_fext_func(fext,
|
|
|
|
FUNC_LEDS, 0x0, 0x0, 0x0) & KEYBOARD_LAMPS) &&
|
|
|
|
(call_fext_func(fext, FUNC_BUTTONS, 0x0, 0x0, 0x0) == 0x0)) {
|
2017-04-07 21:07:11 +08:00
|
|
|
result = devm_led_classdev_register(&device->dev, &kblamps_led);
|
|
|
|
if (result)
|
2017-04-07 21:07:12 +08:00
|
|
|
return result;
|
2017-04-07 21:07:09 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* BTNI bit 24 seems to indicate the presence of a radio toggle
|
|
|
|
* button in place of a slide switch, and all such machines appear
|
|
|
|
* to also have an RF LED. Therefore use bit 24 as an indicator
|
|
|
|
* that an RF LED is present.
|
|
|
|
*/
|
2017-05-19 15:44:46 +08:00
|
|
|
if (call_fext_func(fext, FUNC_BUTTONS, 0x0, 0x0, 0x0) & BIT(24)) {
|
2017-04-07 21:07:11 +08:00
|
|
|
result = devm_led_classdev_register(&device->dev, &radio_led);
|
|
|
|
if (result)
|
2017-04-07 21:07:12 +08:00
|
|
|
return result;
|
2017-04-07 21:07:09 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Support for eco led is not always signaled in bit corresponding
|
|
|
|
* to the bit used to control the led. According to the DSDT table,
|
|
|
|
* bit 14 seems to indicate presence of said led as well.
|
|
|
|
* Confirm by testing the status.
|
|
|
|
*/
|
2017-05-19 15:44:46 +08:00
|
|
|
if ((call_fext_func(fext, FUNC_LEDS, 0x0, 0x0, 0x0) & BIT(14)) &&
|
|
|
|
(call_fext_func(fext,
|
|
|
|
FUNC_LEDS, 0x2, ECO_LED, 0x0) != UNSUPPORTED_CMD)) {
|
2017-04-07 21:07:11 +08:00
|
|
|
result = devm_led_classdev_register(&device->dev, &eco_led);
|
|
|
|
if (result)
|
2017-04-07 21:07:12 +08:00
|
|
|
return result;
|
2017-04-07 21:07:09 +08:00
|
|
|
}
|
|
|
|
|
2017-04-07 21:07:12 +08:00
|
|
|
return 0;
|
2017-04-07 21:07:09 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static int acpi_fujitsu_laptop_add(struct acpi_device *device)
|
|
|
|
{
|
2017-05-19 15:44:44 +08:00
|
|
|
struct fujitsu_laptop *priv;
|
2008-06-11 08:44:56 +08:00
|
|
|
int state = 0;
|
|
|
|
int error;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
if (!device)
|
|
|
|
return -EINVAL;
|
|
|
|
|
2017-05-19 15:44:44 +08:00
|
|
|
priv = devm_kzalloc(&device->dev, sizeof(*priv), GFP_KERNEL);
|
|
|
|
if (!priv)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
platform/x86: fujitsu-laptop: track the last instantiated FUJ02E3 ACPI device
fujitsu-laptop registers two ACPI drivers: one for ACPI device FUJ02B1
enabling backlight control and another for ACPI device FUJ02E3 which
handles various other stuff (hotkeys, LEDs, etc.) In a perfect world,
private data used by each of these drivers would be neatly encapsulated
in a structure specific to a given driver instance. Sadly, firmware
present on some Fujitsu laptops makes that impossible by exposing
backlight power control (which is what the FUJ02B1 ACPI device should
take care of) through the FUJ02E3 ACPI device. This means the backlight
driver needs a way to access an ACPI device it is not bound to. When
the backlight driver is extracted into a separate module, it will not be
able to rely on a module-wide variable any more and such access will
happen through an API exposed by fujitsu-laptop.
For all known firmwares out in the wild, it seems that whenever the
FUJ02B1 ACPI device is present, it is always accompanied by a single
instance of the FUJ02E3 ACPI device. We could independently grab an
ACPI handle to the FUJ02E3 ACPI device from the backlight driver, but
that would require using a hardcoded absolute path to that ACPI device,
which is subject to change. It is easier to simply store a module-wide
pointer to the last (most likely only) FUJ02E3 ACPI device found, make
the aforementioned API use it and cover our bases by warning the user if
firmware exposes multiple FUJ02E3 ACPI devices.
Introducing this pointer in advance allows us to get rid of the
acpi_handle field of struct fujitsu_bl and also enables a bit more
step-by-step migration to a device-specific implementation of
call_fext_func().
Signed-off-by: Michał Kępień <kernel@kempniu.pl>
Reviewed-by: Jonathan Woithe <jwoithe@just42.net>
Signed-off-by: Darren Hart (VMware) <dvhart@infradead.org>
2017-05-19 15:44:45 +08:00
|
|
|
WARN_ONCE(fext, "More than one FUJ02E3 ACPI device was found. Driver may not work as intended.");
|
|
|
|
fext = device;
|
|
|
|
|
2017-05-19 15:44:44 +08:00
|
|
|
fujitsu_laptop = priv;
|
2008-06-11 08:44:56 +08:00
|
|
|
sprintf(acpi_device_name(device), "%s",
|
2017-02-08 21:46:25 +08:00
|
|
|
ACPI_FUJITSU_LAPTOP_DEVICE_NAME);
|
2008-06-11 08:44:56 +08:00
|
|
|
sprintf(acpi_device_class(device), "%s", ACPI_FUJITSU_CLASS);
|
2017-05-19 15:44:44 +08:00
|
|
|
device->driver_data = priv;
|
2008-06-11 08:44:56 +08:00
|
|
|
|
|
|
|
/* kfifo */
|
2017-02-08 21:46:25 +08:00
|
|
|
spin_lock_init(&fujitsu_laptop->fifo_lock);
|
|
|
|
error = kfifo_alloc(&fujitsu_laptop->fifo, RINGBUFFERSIZE * sizeof(int),
|
2009-12-22 06:37:27 +08:00
|
|
|
GFP_KERNEL);
|
kfifo: move struct kfifo in place
This is a new generic kernel FIFO implementation.
The current kernel fifo API is not very widely used, because it has to
many constrains. Only 17 files in the current 2.6.31-rc5 used it.
FIFO's are like list's a very basic thing and a kfifo API which handles
the most use case would save a lot of development time and memory
resources.
I think this are the reasons why kfifo is not in use:
- The API is to simple, important functions are missing
- A fifo can be only allocated dynamically
- There is a requirement of a spinlock whether you need it or not
- There is no support for data records inside a fifo
So I decided to extend the kfifo in a more generic way without blowing up
the API to much. The new API has the following benefits:
- Generic usage: For kernel internal use and/or device driver.
- Provide an API for the most use case.
- Slim API: The whole API provides 25 functions.
- Linux style habit.
- DECLARE_KFIFO, DEFINE_KFIFO and INIT_KFIFO Macros
- Direct copy_to_user from the fifo and copy_from_user into the fifo.
- The kfifo itself is an in place member of the using data structure, this save an
indirection access and does not waste the kernel allocator.
- Lockless access: if only one reader and one writer is active on the fifo,
which is the common use case, no additional locking is necessary.
- Remove spinlock - give the user the freedom of choice what kind of locking to use if
one is required.
- Ability to handle records. Three type of records are supported:
- Variable length records between 0-255 bytes, with a record size
field of 1 bytes.
- Variable length records between 0-65535 bytes, with a record size
field of 2 bytes.
- Fixed size records, which no record size field.
- Preserve memory resource.
- Performance!
- Easy to use!
This patch:
Since most users want to have the kfifo as part of another object,
reorganize the code to allow including struct kfifo in another data
structure. This requires changing the kfifo_alloc and kfifo_init
prototypes so that we pass an existing kfifo pointer into them. This
patch changes the implementation and all existing users.
[akpm@linux-foundation.org: fix warning]
Signed-off-by: Stefani Seibold <stefani@seibold.net>
Acked-by: Greg Kroah-Hartman <gregkh@suse.de>
Acked-by: Mauro Carvalho Chehab <mchehab@redhat.com>
Acked-by: Andi Kleen <ak@linux.intel.com>
Acked-by: Arnd Bergmann <arnd@arndb.de>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2009-12-22 06:37:26 +08:00
|
|
|
if (error) {
|
2011-03-30 06:21:39 +08:00
|
|
|
pr_err("kfifo_alloc failed\n");
|
2008-06-11 08:44:56 +08:00
|
|
|
goto err_stop;
|
|
|
|
}
|
|
|
|
|
2017-03-20 17:32:20 +08:00
|
|
|
error = acpi_fujitsu_laptop_input_setup(device);
|
2008-06-11 08:44:56 +08:00
|
|
|
if (error)
|
2017-03-20 17:32:20 +08:00
|
|
|
goto err_free_fifo;
|
2008-06-11 08:44:56 +08:00
|
|
|
|
2017-05-19 15:44:46 +08:00
|
|
|
error = acpi_bus_update_power(device->handle, &state);
|
2013-12-30 06:47:36 +08:00
|
|
|
if (error) {
|
2011-03-30 06:21:39 +08:00
|
|
|
pr_err("Error reading power state\n");
|
2017-03-20 17:32:21 +08:00
|
|
|
goto err_free_fifo;
|
2008-06-11 08:44:56 +08:00
|
|
|
}
|
|
|
|
|
2011-03-30 06:21:39 +08:00
|
|
|
pr_info("ACPI: %s [%s] (%s)\n",
|
|
|
|
acpi_device_name(device), acpi_device_bid(device),
|
|
|
|
!device->power.state ? "on" : "off");
|
2008-06-11 08:44:56 +08:00
|
|
|
|
2013-09-03 08:32:03 +08:00
|
|
|
if (acpi_has_method(device->handle, METHOD_NAME__INI)) {
|
2008-06-11 08:44:56 +08:00
|
|
|
vdbg_printk(FUJLAPTOP_DBG_INFO, "Invoking _INI\n");
|
|
|
|
if (ACPI_FAILURE
|
|
|
|
(acpi_evaluate_object
|
|
|
|
(device->handle, METHOD_NAME__INI, NULL, NULL)))
|
2011-03-30 06:21:39 +08:00
|
|
|
pr_err("_INI Method failed\n");
|
2008-06-11 08:44:56 +08:00
|
|
|
}
|
|
|
|
|
2009-01-01 02:19:59 +08:00
|
|
|
i = 0;
|
2017-05-19 15:44:46 +08:00
|
|
|
while (call_fext_func(fext, FUNC_BUTTONS, 0x1, 0x0, 0x0) != 0
|
2009-01-01 02:19:59 +08:00
|
|
|
&& (i++) < MAX_HOTKEY_RINGBUFFER_SIZE)
|
|
|
|
; /* No action, result is discarded */
|
2008-06-11 08:44:56 +08:00
|
|
|
vdbg_printk(FUJLAPTOP_DBG_INFO, "Discarded %i ringbuffer entries\n", i);
|
|
|
|
|
2017-02-08 21:46:27 +08:00
|
|
|
fujitsu_laptop->flags_supported =
|
2017-05-19 15:44:46 +08:00
|
|
|
call_fext_func(fext, FUNC_FLAGS, 0x0, 0x0, 0x0);
|
2009-02-02 19:11:10 +08:00
|
|
|
|
|
|
|
/* Make sure our bitmask of supported functions is cleared if the
|
|
|
|
RFKILL function block is not implemented, like on the S7020. */
|
2017-02-08 21:46:27 +08:00
|
|
|
if (fujitsu_laptop->flags_supported == UNSUPPORTED_CMD)
|
|
|
|
fujitsu_laptop->flags_supported = 0;
|
2009-02-02 19:11:10 +08:00
|
|
|
|
2017-02-08 21:46:27 +08:00
|
|
|
if (fujitsu_laptop->flags_supported)
|
|
|
|
fujitsu_laptop->flags_state =
|
2017-05-19 15:44:46 +08:00
|
|
|
call_fext_func(fext, FUNC_FLAGS, 0x4, 0x0, 0x0);
|
2009-01-01 02:19:59 +08:00
|
|
|
|
|
|
|
/* Suspect this is a keymap of the application panel, print it */
|
2017-05-19 15:44:46 +08:00
|
|
|
pr_info("BTNI: [0x%x]\n", call_fext_func(fext,
|
|
|
|
FUNC_BUTTONS, 0x0, 0x0, 0x0));
|
2009-01-01 02:19:59 +08:00
|
|
|
|
2017-03-10 18:50:34 +08:00
|
|
|
/* Sync backlight power status */
|
2017-05-19 15:44:42 +08:00
|
|
|
if (fujitsu_bl && fujitsu_bl->bl_device &&
|
2017-03-10 18:50:35 +08:00
|
|
|
acpi_video_get_backlight_type() == acpi_backlight_vendor) {
|
2017-05-19 15:44:46 +08:00
|
|
|
if (call_fext_func(fext, FUNC_BACKLIGHT, 0x2, 0x4, 0x0) == 3)
|
2017-03-10 18:50:34 +08:00
|
|
|
fujitsu_bl->bl_device->props.power = FB_BLANK_POWERDOWN;
|
|
|
|
else
|
|
|
|
fujitsu_bl->bl_device->props.power = FB_BLANK_UNBLANK;
|
|
|
|
}
|
|
|
|
|
2017-04-07 21:07:13 +08:00
|
|
|
error = acpi_fujitsu_laptop_leds_register(device);
|
2017-03-14 18:26:30 +08:00
|
|
|
if (error)
|
2017-03-20 17:32:21 +08:00
|
|
|
goto err_free_fifo;
|
2017-03-14 18:26:30 +08:00
|
|
|
|
2017-04-07 21:07:13 +08:00
|
|
|
error = fujitsu_laptop_platform_add();
|
2017-04-07 21:07:09 +08:00
|
|
|
if (error)
|
2017-04-07 21:07:13 +08:00
|
|
|
goto err_free_fifo;
|
2009-01-01 02:19:59 +08:00
|
|
|
|
2017-04-07 21:07:09 +08:00
|
|
|
return 0;
|
2008-06-11 08:44:56 +08:00
|
|
|
|
2009-04-07 23:37:22 +08:00
|
|
|
err_free_fifo:
|
2017-02-08 21:46:25 +08:00
|
|
|
kfifo_free(&fujitsu_laptop->fifo);
|
2008-06-11 08:44:56 +08:00
|
|
|
err_stop:
|
2013-12-30 06:47:36 +08:00
|
|
|
return error;
|
2008-06-11 08:44:56 +08:00
|
|
|
}
|
|
|
|
|
2017-02-08 21:46:25 +08:00
|
|
|
static int acpi_fujitsu_laptop_remove(struct acpi_device *device)
|
2008-06-11 08:44:56 +08:00
|
|
|
{
|
2017-05-19 15:44:41 +08:00
|
|
|
struct fujitsu_laptop *priv = acpi_driver_data(device);
|
2008-06-11 08:44:56 +08:00
|
|
|
|
2017-03-14 18:26:30 +08:00
|
|
|
fujitsu_laptop_platform_remove();
|
|
|
|
|
2017-05-19 15:44:41 +08:00
|
|
|
kfifo_free(&priv->fifo);
|
2008-06-11 08:44:56 +08:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-03-20 17:32:22 +08:00
|
|
|
static void acpi_fujitsu_laptop_press(int scancode)
|
2017-01-11 16:59:31 +08:00
|
|
|
{
|
2017-02-08 21:46:25 +08:00
|
|
|
struct input_dev *input = fujitsu_laptop->input;
|
2017-01-11 16:59:31 +08:00
|
|
|
int status;
|
|
|
|
|
2017-02-08 21:46:25 +08:00
|
|
|
status = kfifo_in_locked(&fujitsu_laptop->fifo,
|
2017-03-20 17:32:22 +08:00
|
|
|
(unsigned char *)&scancode, sizeof(scancode),
|
2017-02-08 21:46:25 +08:00
|
|
|
&fujitsu_laptop->fifo_lock);
|
2017-03-20 17:32:22 +08:00
|
|
|
if (status != sizeof(scancode)) {
|
2017-01-11 16:59:31 +08:00
|
|
|
vdbg_printk(FUJLAPTOP_DBG_WARN,
|
2017-03-20 17:32:22 +08:00
|
|
|
"Could not push scancode [0x%x]\n", scancode);
|
2017-01-11 16:59:33 +08:00
|
|
|
return;
|
2017-01-11 16:59:31 +08:00
|
|
|
}
|
2017-03-20 17:32:22 +08:00
|
|
|
sparse_keymap_report_event(input, scancode, 1, false);
|
2017-01-11 16:59:33 +08:00
|
|
|
vdbg_printk(FUJLAPTOP_DBG_TRACE,
|
2017-03-20 17:32:22 +08:00
|
|
|
"Push scancode into ringbuffer [0x%x]\n", scancode);
|
2017-01-11 16:59:31 +08:00
|
|
|
}
|
|
|
|
|
2017-02-08 21:46:25 +08:00
|
|
|
static void acpi_fujitsu_laptop_release(void)
|
2017-01-11 16:59:31 +08:00
|
|
|
{
|
2017-02-08 21:46:25 +08:00
|
|
|
struct input_dev *input = fujitsu_laptop->input;
|
2017-03-20 17:32:22 +08:00
|
|
|
int scancode, status;
|
2017-01-11 16:59:31 +08:00
|
|
|
|
2017-01-11 16:59:32 +08:00
|
|
|
while (true) {
|
2017-02-08 21:46:25 +08:00
|
|
|
status = kfifo_out_locked(&fujitsu_laptop->fifo,
|
2017-03-20 17:32:22 +08:00
|
|
|
(unsigned char *)&scancode,
|
|
|
|
sizeof(scancode),
|
2017-02-08 21:46:25 +08:00
|
|
|
&fujitsu_laptop->fifo_lock);
|
2017-03-20 17:32:22 +08:00
|
|
|
if (status != sizeof(scancode))
|
2017-01-11 16:59:32 +08:00
|
|
|
return;
|
2017-03-20 17:32:22 +08:00
|
|
|
sparse_keymap_report_event(input, scancode, 0, false);
|
2017-01-11 16:59:31 +08:00
|
|
|
vdbg_printk(FUJLAPTOP_DBG_TRACE,
|
2017-03-20 17:32:22 +08:00
|
|
|
"Pop scancode from ringbuffer [0x%x]\n", scancode);
|
2017-01-11 16:59:31 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-08 21:46:25 +08:00
|
|
|
static void acpi_fujitsu_laptop_notify(struct acpi_device *device, u32 event)
|
2008-06-11 08:44:56 +08:00
|
|
|
{
|
|
|
|
struct input_dev *input;
|
2017-03-20 17:32:22 +08:00
|
|
|
int scancode, i = 0;
|
|
|
|
unsigned int irb;
|
2008-06-11 08:44:56 +08:00
|
|
|
|
2017-02-08 21:46:25 +08:00
|
|
|
input = fujitsu_laptop->input;
|
2008-06-11 08:44:56 +08:00
|
|
|
|
2017-01-11 16:59:30 +08:00
|
|
|
if (event != ACPI_FUJITSU_NOTIFY_CODE1) {
|
|
|
|
vdbg_printk(FUJLAPTOP_DBG_WARN,
|
|
|
|
"Unsupported event [0x%x]\n", event);
|
2017-03-20 17:32:22 +08:00
|
|
|
sparse_keymap_report_event(input, -1, 1, true);
|
2017-01-11 16:59:30 +08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-02-08 21:46:27 +08:00
|
|
|
if (fujitsu_laptop->flags_supported)
|
|
|
|
fujitsu_laptop->flags_state =
|
2017-05-19 15:44:46 +08:00
|
|
|
call_fext_func(fext, FUNC_FLAGS, 0x4, 0x0, 0x0);
|
2008-06-11 08:44:56 +08:00
|
|
|
|
2017-05-19 15:44:46 +08:00
|
|
|
while ((irb = call_fext_func(fext,
|
|
|
|
FUNC_BUTTONS, 0x1, 0x0, 0x0)) != 0 &&
|
2017-03-20 17:32:22 +08:00
|
|
|
i++ < MAX_HOTKEY_RINGBUFFER_SIZE) {
|
|
|
|
scancode = irb & 0x4ff;
|
|
|
|
if (sparse_keymap_entry_from_scancode(input, scancode))
|
|
|
|
acpi_fujitsu_laptop_press(scancode);
|
|
|
|
else if (scancode == 0)
|
|
|
|
acpi_fujitsu_laptop_release();
|
|
|
|
else
|
2017-01-11 16:59:30 +08:00
|
|
|
vdbg_printk(FUJLAPTOP_DBG_WARN,
|
|
|
|
"Unknown GIRB result [%x]\n", irb);
|
|
|
|
}
|
2008-06-11 08:44:56 +08:00
|
|
|
|
2017-01-11 16:59:30 +08:00
|
|
|
/* On some models (first seen on the Skylake-based Lifebook
|
|
|
|
* E736/E746/E756), the touchpad toggle hotkey (Fn+F4) is
|
2017-02-08 21:46:27 +08:00
|
|
|
* handled in software; its state is queried using FUNC_FLAGS
|
2017-01-11 16:59:30 +08:00
|
|
|
*/
|
2017-02-08 21:46:27 +08:00
|
|
|
if ((fujitsu_laptop->flags_supported & BIT(26)) &&
|
2017-05-19 15:44:46 +08:00
|
|
|
(call_fext_func(fext, FUNC_FLAGS, 0x1, 0x0, 0x0) & BIT(26)))
|
2017-03-20 17:32:22 +08:00
|
|
|
sparse_keymap_report_event(input, BIT(26), 1, true);
|
2008-06-11 08:44:56 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Initialization */
|
|
|
|
|
2017-02-08 21:46:24 +08:00
|
|
|
static const struct acpi_device_id fujitsu_bl_device_ids[] = {
|
|
|
|
{ACPI_FUJITSU_BL_HID, 0},
|
2007-08-29 14:28:19 +08:00
|
|
|
{"", 0},
|
|
|
|
};
|
|
|
|
|
2017-02-08 21:46:24 +08:00
|
|
|
static struct acpi_driver acpi_fujitsu_bl_driver = {
|
|
|
|
.name = ACPI_FUJITSU_BL_DRIVER_NAME,
|
2007-08-29 14:28:19 +08:00
|
|
|
.class = ACPI_FUJITSU_CLASS,
|
2017-02-08 21:46:24 +08:00
|
|
|
.ids = fujitsu_bl_device_ids,
|
2007-08-29 14:28:19 +08:00
|
|
|
.ops = {
|
2017-02-08 21:46:24 +08:00
|
|
|
.add = acpi_fujitsu_bl_add,
|
|
|
|
.notify = acpi_fujitsu_bl_notify,
|
2007-08-29 14:28:19 +08:00
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2017-02-08 21:46:25 +08:00
|
|
|
static const struct acpi_device_id fujitsu_laptop_device_ids[] = {
|
|
|
|
{ACPI_FUJITSU_LAPTOP_HID, 0},
|
2008-06-11 08:44:56 +08:00
|
|
|
{"", 0},
|
|
|
|
};
|
|
|
|
|
2017-02-08 21:46:25 +08:00
|
|
|
static struct acpi_driver acpi_fujitsu_laptop_driver = {
|
|
|
|
.name = ACPI_FUJITSU_LAPTOP_DRIVER_NAME,
|
2008-06-11 08:44:56 +08:00
|
|
|
.class = ACPI_FUJITSU_CLASS,
|
2017-02-08 21:46:25 +08:00
|
|
|
.ids = fujitsu_laptop_device_ids,
|
2008-06-11 08:44:56 +08:00
|
|
|
.ops = {
|
2017-02-08 21:46:25 +08:00
|
|
|
.add = acpi_fujitsu_laptop_add,
|
|
|
|
.remove = acpi_fujitsu_laptop_remove,
|
|
|
|
.notify = acpi_fujitsu_laptop_notify,
|
2008-06-11 08:44:56 +08:00
|
|
|
},
|
|
|
|
};
|
2007-08-29 14:28:19 +08:00
|
|
|
|
2014-09-09 06:21:59 +08:00
|
|
|
static const struct acpi_device_id fujitsu_ids[] __used = {
|
2017-02-08 21:46:24 +08:00
|
|
|
{ACPI_FUJITSU_BL_HID, 0},
|
2017-02-08 21:46:25 +08:00
|
|
|
{ACPI_FUJITSU_LAPTOP_HID, 0},
|
2014-09-09 06:21:59 +08:00
|
|
|
{"", 0}
|
|
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(acpi, fujitsu_ids);
|
|
|
|
|
2007-08-29 14:28:19 +08:00
|
|
|
static int __init fujitsu_init(void)
|
|
|
|
{
|
2017-03-10 18:50:33 +08:00
|
|
|
int ret;
|
2007-08-29 14:28:19 +08:00
|
|
|
|
|
|
|
if (acpi_disabled)
|
|
|
|
return -ENODEV;
|
|
|
|
|
2017-02-08 21:46:30 +08:00
|
|
|
ret = acpi_bus_register_driver(&acpi_fujitsu_bl_driver);
|
|
|
|
if (ret)
|
2017-05-19 15:44:42 +08:00
|
|
|
return ret;
|
2007-08-29 14:28:19 +08:00
|
|
|
|
|
|
|
/* Register platform stuff */
|
|
|
|
|
2017-02-08 21:46:26 +08:00
|
|
|
ret = platform_driver_register(&fujitsu_pf_driver);
|
2008-06-11 08:44:56 +08:00
|
|
|
if (ret)
|
2017-03-14 18:26:30 +08:00
|
|
|
goto err_unregister_acpi;
|
2008-06-11 08:44:56 +08:00
|
|
|
|
2017-02-08 21:46:25 +08:00
|
|
|
/* Register laptop driver */
|
2008-06-11 08:44:56 +08:00
|
|
|
|
2017-02-08 21:46:30 +08:00
|
|
|
ret = acpi_bus_register_driver(&acpi_fujitsu_laptop_driver);
|
|
|
|
if (ret)
|
2017-05-19 15:44:44 +08:00
|
|
|
goto err_unregister_platform_driver;
|
2008-06-11 08:44:56 +08:00
|
|
|
|
2011-03-30 06:21:39 +08:00
|
|
|
pr_info("driver " FUJITSU_DRIVER_VERSION " successfully loaded\n");
|
2007-08-29 14:28:19 +08:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
2017-03-10 18:50:36 +08:00
|
|
|
err_unregister_platform_driver:
|
2017-02-08 21:46:26 +08:00
|
|
|
platform_driver_unregister(&fujitsu_pf_driver);
|
2017-03-10 18:50:36 +08:00
|
|
|
err_unregister_acpi:
|
2017-02-08 21:46:24 +08:00
|
|
|
acpi_bus_unregister_driver(&acpi_fujitsu_bl_driver);
|
2007-08-29 14:28:19 +08:00
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void __exit fujitsu_cleanup(void)
|
|
|
|
{
|
2017-02-08 21:46:25 +08:00
|
|
|
acpi_bus_unregister_driver(&acpi_fujitsu_laptop_driver);
|
2009-01-01 02:19:59 +08:00
|
|
|
|
2017-02-08 21:46:26 +08:00
|
|
|
platform_driver_unregister(&fujitsu_pf_driver);
|
2009-07-31 16:46:02 +08:00
|
|
|
|
2017-02-08 21:46:24 +08:00
|
|
|
acpi_bus_unregister_driver(&acpi_fujitsu_bl_driver);
|
2008-06-11 08:44:56 +08:00
|
|
|
|
2011-03-30 06:21:39 +08:00
|
|
|
pr_info("driver unloaded\n");
|
2007-08-29 14:28:19 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
module_init(fujitsu_init);
|
|
|
|
module_exit(fujitsu_cleanup);
|
|
|
|
|
2017-04-05 14:49:05 +08:00
|
|
|
module_param(use_alt_lcd_levels, int, 0644);
|
|
|
|
MODULE_PARM_DESC(use_alt_lcd_levels, "Interface used for setting LCD brightness level (-1 = auto, 0 = force SBLL, 1 = force SBL2)");
|
2017-04-05 14:49:06 +08:00
|
|
|
module_param(disable_brightness_adjust, bool, 0644);
|
|
|
|
MODULE_PARM_DESC(disable_brightness_adjust, "Disable LCD brightness adjustment");
|
2008-06-11 08:44:56 +08:00
|
|
|
#ifdef CONFIG_FUJITSU_LAPTOP_DEBUG
|
|
|
|
module_param_named(debug, dbg_level, uint, 0644);
|
|
|
|
MODULE_PARM_DESC(debug, "Sets debug level bit-mask");
|
|
|
|
#endif
|
|
|
|
|
2009-01-01 02:19:59 +08:00
|
|
|
MODULE_AUTHOR("Jonathan Woithe, Peter Gruber, Tony Vroon");
|
2007-08-29 14:28:19 +08:00
|
|
|
MODULE_DESCRIPTION("Fujitsu laptop extras support");
|
|
|
|
MODULE_VERSION(FUJITSU_DRIVER_VERSION);
|
|
|
|
MODULE_LICENSE("GPL");
|