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). Signed-off-by: Michał Kępień <kernel@xxxxxxxxxx> --- First of all, this patch raises a couple of checkpatch warnings. I opted for consistency with existing code, which checkpatch doesn't like as well. Please let me know if you'd like me to fix the warnings (if that's desired, I could also clean up the whole driver to make checkpatch happy). Now, about the LED itself. As 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 doesn't change the hardware state of the transmitters. Consider the following scenario: 1. Power the machine up. Radio LED is on. 2. Press the radio toggle button before the bootloader kicks in. 3. Radio LED is turned off. 4. Wait for Linux to boot. Reading /sys/devices/platform/fujitsu-laptop/radios then yields "killed", but you can still connect to Bluetooth devices and wireless networks. So it looks like this machine only 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. I might be completely wrong and this needs testing on a broader set of devices. See also three paragraphs below for an alternative. 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). Here is the relevant part: Method (S000, 3, Serialized) { Name (_T_0, Zero) // _T_x: Emitted by ASL Compiler Local0 = Zero While (One) { _T_0 = Arg0 If ((_T_0 == Zero)) { Local0 |= 0x00020000 Local0 |= 0x0200 Local0 |= 0x0100 Local0 |= 0x20 } ... ElseIf ((_T_0 == 0x04)) { Arg1 = ((Arg2 << 0x10) | (Arg1 & 0xFFFF)) Local0 = FSMI (0x91, Arg0, Arg1) If ((Local0 & 0x20)) { RFSW = One } Else { RFSW = Zero } Local0 |= (RFSW << 0x05) Local0 |= (DKON << 0x09) Local0 |= (^^LID._LID () << 0x08) } ElseIf ((_T_0 == 0x05)) { If ((Arg1 & 0x20)) { If ((Arg2 & 0x20)) { RFSW = One } Else { RFSW = Zero } } Arg1 = ((Arg2 << 0x10) | (Arg1 & 0xFFFF)) Local0 = FSMI (0x91, Arg0, Arg1) } ... Break } Return (Local0) } When Arg0 is 0x04, current hardware state is queried and returned (this is already done by the driver). When Arg0 is 0x05 and Arg1 is 0x20, one can change the contents of RFSW (RF SWitch?), which eventually results in turning the LED on or off (depending on the value of Arg2). The 0x05 branch is not present in a DSDT dump from a Lifebook E8420, which has a slider instead of a radio toggle button. Note that when called with Arg0 set to 0x00, S000 returns 0x00020320 on a Lifebook E744 (this is the value saved in the rfkill_supported field of struct fujitsu_hotkey_t). Bit 16 is not set on a Lifebook E8420, so it might mean that this is an indicator of a radio toggle button being present. Sadly, this implementation is unsuitable for use with "heavy" LED triggers, like phy0rx. Once blinking frequency achieves a certain level, the system hangs. I'm not sure how much of an issue this is as I'm pretty sure other LEDs registered by fujitsu-laptop would also cause a hang when assigned to a similar trigger as they are also controlled using ACPI. While it's not essential, it would be nice to initialize soft rfkill state of all radio transmitters to the value of RFSW upon boot. Note that this value is persisted between reboots until the battery is removed, but can be changed before the kernel is booted. I haven't found an rfkill function which would enable one to set all rfkill devices to a chosen initial soft state (note that fujitsu-laptop doesn't create any rfkill devices on its own). Is this possible/desired or should this task simply be delegated to userspace? One last remark is that I think this LED would best be driven by an inverted airplane mode LED trigger (as proposed by João Paulo Rechi Vita). As the code for that trigger is not yet merged, I refrained from setting the default_trigger field in struct led_classdev radio_led. Perhaps it's a candidate for a follow-up patch in the future. And finally, perhaps some of the remarks above belong in the commit message for future reference. Please advise. drivers/platform/x86/fujitsu-laptop.c | 45 +++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/drivers/platform/x86/fujitsu-laptop.c b/drivers/platform/x86/fujitsu-laptop.c index ffc84cc..7813e482 100644 --- a/drivers/platform/x86/fujitsu-laptop.c +++ b/drivers/platform/x86/fujitsu-laptop.c @@ -107,6 +107,7 @@ #define KEYBOARD_LAMPS 0x100 #define LOGOLAMP_POWERON 0x2000 #define LOGOLAMP_ALWAYS 0x4000 +#define RADIO_LED_ON 0x20 #endif /* Hotkey details */ @@ -174,6 +175,7 @@ struct fujitsu_hotkey_t { int rfkill_state; int logolamp_registered; int kblamps_registered; + int radio_led_registered; }; static struct fujitsu_hotkey_t *fujitsu_hotkey; @@ -200,6 +202,16 @@ static struct led_classdev kblamps_led = { .brightness_get = kblamps_get, .brightness_set = kblamps_set }; + +static enum led_brightness radio_led_get(struct led_classdev *cdev); +static void radio_led_set(struct led_classdev *cdev, + enum led_brightness brightness); + +static struct led_classdev radio_led = { + .name = "fujitsu::radio_led", + .brightness_get = radio_led_get, + .brightness_set = radio_led_set +}; #endif #ifdef CONFIG_FUJITSU_LAPTOP_DEBUG @@ -275,6 +287,15 @@ static void kblamps_set(struct led_classdev *cdev, call_fext_func(FUNC_LEDS, 0x1, KEYBOARD_LAMPS, FUNC_LED_OFF); } +static void radio_led_set(struct led_classdev *cdev, + enum led_brightness brightness) +{ + if (brightness >= LED_FULL) + call_fext_func(FUNC_RFKILL, 0x5, RADIO_LED_ON, RADIO_LED_ON); + else + call_fext_func(FUNC_RFKILL, 0x5, RADIO_LED_ON, 0x0); +} + static enum led_brightness logolamp_get(struct led_classdev *cdev) { enum led_brightness brightness = LED_OFF; @@ -299,6 +320,16 @@ static enum led_brightness kblamps_get(struct led_classdev *cdev) return brightness; } + +static enum led_brightness radio_led_get(struct led_classdev *cdev) +{ + enum led_brightness brightness = LED_OFF; + + if (call_fext_func(FUNC_RFKILL, 0x4, 0x0, 0x0) & RADIO_LED_ON) + brightness = LED_FULL; + + return brightness; +} #endif /* Hardware access for LCD brightness control */ @@ -895,6 +926,17 @@ static int acpi_fujitsu_hotkey_add(struct acpi_device *device) result); } } + + if (call_fext_func(FUNC_BUTTONS, 0x0, 0x0, 0x0) & BIT(24)) { + result = led_classdev_register(&fujitsu->pf_device->dev, + &radio_led); + if (result == 0) { + fujitsu_hotkey->radio_led_registered = 1; + } else { + pr_err("Could not register LED handler for radio LED, error %i\n", + result); + } + } #endif return result; @@ -921,6 +963,9 @@ static int acpi_fujitsu_hotkey_remove(struct acpi_device *device) if (fujitsu_hotkey->kblamps_registered) led_classdev_unregister(&kblamps_led); + + if (fujitsu_hotkey->radio_led_registered) + led_classdev_unregister(&radio_led); #endif input_unregister_device(input); -- 1.7.10.4 -- To unsubscribe from this list: send the line "unsubscribe platform-driver-x86" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html