On Tue, Nov 30, 2010 at 6:27 PM, Herton Ronaldo Krzesinski
<[email protected]> wrote:
> Hi,
>
> Recently, I received some shuttle machines that are mostly a notebook "glued"
> to a desktop form factor. They are notebook hardware, have a webcam, wireless,
> etc., but they don't have a notebook keyboard, and because of this no way to
> control the hardware, like webcam on/off, etc. So, a driver is needed.
>
> The machines I have here is similar (not same) to this on shuttle website:
> http://us.shuttle.com/X50v2.aspx
>
> What follows is a patch which adds a driver which exposes the controls
> available through ACPI-WMI. For now just a RFC to get feedback. Also, on some
> shuttle machines, fn+<function> can have different values, and I need to
> implement a more flexible way to change command numbers (fn+n) depending on
> model of shuttle machine, so it isn't a final version yet. And on Shuttle
> DA18IE, the interface for video switch isn't final and could change, which
> may affect the driver.
>
> [PATCH] Add shuttle-wmi driver
>
> This adds a new platform driver for shuttle machines. On some desktop
> machines, shuttle is using laptop hardware, but without laptop keyboard.
> Thus, for some features like turn on/off webcam, a way is need to
> control the hardware. The driver uses ACPI-WMI interface provided to
> export available controls through sysfs.
>
> Signed-off-by: Herton Ronaldo Krzesinski <[email protected]>
> ---
>  .../ABI/testing/sysfs-platform-shuttle-wmi         |  161 ++++
>  drivers/platform/x86/Kconfig                       |   16 +
>  drivers/platform/x86/Makefile                      |    1 +
>  drivers/platform/x86/shuttle-wmi.c                 |  843 
> ++++++++++++++++++++
>  4 files changed, 1021 insertions(+), 0 deletions(-)
>  create mode 100644 Documentation/ABI/testing/sysfs-platform-shuttle-wmi
>  create mode 100644 drivers/platform/x86/shuttle-wmi.c
>
> diff --git a/Documentation/ABI/testing/sysfs-platform-shuttle-wmi 
> b/Documentation/ABI/testing/sysfs-platform-shuttle-wmi
> new file mode 100644
> index 0000000..63a8c8b
> --- /dev/null
> +++ b/Documentation/ABI/testing/sysfs-platform-shuttle-wmi
> @@ -0,0 +1,161 @@
> +What:          /sys/devices/platform/shuttle_wmi/brightness_down
> +Date:          November 2010
> +KernelVersion: 2.6.37
> +Contact:       "Herton Ronaldo Krzesinski" <[email protected]>
> +Description:
> +               This is a write only option (accepts any single value, eg.
> +               "echo 1 > brightness_down") that is equivalent of pressing
> +               fn+<brightness down> function on notebooks. This option exists
> +               because of shuttle machines that are notebooks in desktop form
> +               factor, and which don't have the notebook keyboard, thus no
> +               way to use fn+<brightness down>.
> +
> +What:          /sys/devices/platform/shuttle_wmi/brightness_up
> +Date:          November 2010
> +KernelVersion: 2.6.37
> +Contact:       "Herton Ronaldo Krzesinski" <[email protected]>
> +Description:
> +               This is a write only option (accepts any single value, eg.
> +               "echo 1 > brightness_up") that is equivalent of pressing
> +               fn+<brightness up> function on notebooks. This option exists
> +               because of shuttle machines that are notebooks in desktop form
> +               factor, and which don't have the notebook keyboard, thus no
> +               way to use fn+<brightness up>.

Why such files ? Can't we do the same using the backlight device ?

> +What:          /sys/devices/platform/shuttle_wmi/cut_lvds
> +Date:          November 2010
> +KernelVersion: 2.6.37
> +Contact:       "Herton Ronaldo Krzesinski" <[email protected]>
> +Description:
> +               This is a write only option. Writing any single non-zero value
> +               to it enables main screen output, 0 to disable.

Same, could be handled by the backlight device.

> +What:          /sys/devices/platform/shuttle_wmi/lcd_auto_adjust
> +Date:          November 2010
> +KernelVersion: 2.6.37
> +Contact:       "Herton Ronaldo Krzesinski" <[email protected]>
> +Description:
> +               This is a write only option (accepts any single value, eg.
> +               "echo 1 > lcd_auto_adjust") that starts LCD auto-adjust
> +               function. Some shuttle machines have LCD attached to analog
> +               VGA connector, so uses/needs auto-adjust.
> +
> +What:          /sys/devices/platform/shuttle_wmi/lbar_brightness_down
> +Date:          November 2010
> +KernelVersion: 2.6.37
> +Contact:       "Herton Ronaldo Krzesinski" <[email protected]>
> +Description:
> +               This is a write only option (accepts any single value, eg.
> +               "echo 1 > lbar_brightness_down"). Decreases one step of 
> lightbar
> +               brightness.
> +
> +What:          /sys/devices/platform/shuttle_wmi/lbar_brightness_up
> +Date:          November 2010
> +KernelVersion: 2.6.37
> +Contact:       "Herton Ronaldo Krzesinski" <[email protected]>
> +Description:
> +               This is a write only option (accepts any single value, eg.
> +               "echo 1 > lbar_brightness_up"). Increases one step of lightbar
> +               brightness.

What is the lightbar exactly ? Some kind of led ? Can't you use the
led class instead ?
Also the driver seems to reference keyboard brightness, if available,
this should be
implemented as a led named shuttle::kbd_backlight.

> +What:          /sys/devices/platform/shuttle_wmi/model_name
> +Date:          November 2010
> +KernelVersion: 2.6.37
> +Contact:       "Herton Ronaldo Krzesinski" <[email protected]>
> +Description:
> +               This is a read only attribute which outputs a string with 
> model
> +               name of the machine. When shuttle-wmi can't determine which
> +               model it is, "Unknown" is returned. Otherwise, the possible
> +               models are "Shuttle MA", "Shuttle DA18IE" or "Shuttle DA18IM".
> +
> +What:          /sys/devices/platform/shuttle_wmi/panel_set_default
> +Date:          November 2010
> +KernelVersion: 2.6.37
> +Contact:       "Herton Ronaldo Krzesinski" <[email protected]>
> +Description:
> +               This is a write only option (accepts any single value, eg.
> +               "echo 1 > panel_set_default"). Probably resets panel/lcd to
> +               default configuration, function not explained in shuttle wmi
> +               documentation.
> +
> +What:          /sys/devices/platform/shuttle_wmi/powersave
> +Date:          November 2010
> +KernelVersion: 2.6.37
> +Contact:       "Herton Ronaldo Krzesinski" <[email protected]>
> +Description:
> +               Control powersave state. 1 means on, 0 means off.
> +               When enabled, it basically forces the cpu to stay on powersave
> +               state (similar to powersave governor in cpufreq) when machine 
> is
> +               only running on battery. If not running on battery, this
> +               function isn't expected to work, any attempt to enable this
> +               returns -EIO. The function is equal to fn+<cpu> switch on some
> +               notebooks.
> +
> +What:          /sys/devices/platform/shuttle_wmi/sleep
> +Date:          November 2010
> +KernelVersion: 2.6.37
> +Contact:       "Herton Ronaldo Krzesinski" <[email protected]>
> +Description:
> +               This is a write only option (accepts any single value, eg.
> +               "echo 1 > sleep") that is equivalent of pressing fn+<sleep>
> +               function on notebooks.
> +
> +What:          /sys/devices/platform/shuttle_wmi/sound_mute
> +Date:          November 2010
> +KernelVersion: 2.6.37
> +Contact:       "Herton Ronaldo Krzesinski" <[email protected]>
> +Description:
> +               This is a write only option (accepts any single value, eg.
> +               "echo 1 > sound_mute") that is equivalent of pressing 
> fn+<mute>
> +               function on notebooks.
> +
> +What:          /sys/devices/platform/shuttle_wmi/switch_video
> +Date:          November 2010
> +KernelVersion: 2.6.37
> +Contact:       "Herton Ronaldo Krzesinski" <[email protected]>
> +Description:
> +               This is a write only option (accepts any single value, eg.
> +               "echo 1 > switch_video") that is equivalent of pressing
> +               fn+<display switch> function on notebooks.
> +
> +What:          /sys/devices/platform/shuttle_wmi/touchpad
> +Date:          November 2010
> +KernelVersion: 2.6.37
> +Contact:       "Herton Ronaldo Krzesinski" <[email protected]>
> +Description:
> +               Control touchpad state. 1 means on, 0 means off.
> +
> +What:          /sys/devices/platform/shuttle_wmi/volume_down
> +Date:          November 2010
> +KernelVersion: 2.6.37
> +Contact:       "Herton Ronaldo Krzesinski" <[email protected]>
> +Description:
> +               This is a write only option (accepts any single value, eg.
> +               "echo 1 > volume_down") that is equivalent of pressing
> +               fn+<volume down> function on notebooks.
> +
> +What:          /sys/devices/platform/shuttle_wmi/volume_up
> +Date:          November 2010
> +KernelVersion: 2.6.37
> +Contact:       "Herton Ronaldo Krzesinski" <[email protected]>
> +Description:
> +               This is a write only option (accepts any single value, eg.
> +               "echo 1 > volume_up") that is equivalent of pressing
> +               fn+<volume up> function on notebooks.

Are volume_down and volume_up really needed ? can't alsamixer do the same ?

> +What:          /sys/devices/platform/shuttle_wmi/webcam
> +Date:          November 2010
> +KernelVersion: 2.6.37
> +Contact:       "Herton Ronaldo Krzesinski" <[email protected]>
> +Description:
> +               Control webcam state. 1 means on, 0 means off.
> +
> +What:          /sys/devices/platform/shuttle_wmi/white_balance
> +Date:          November 2010
> +KernelVersion: 2.6.37
> +Contact:       "Herton Ronaldo Krzesinski" <[email protected]>
> +Description:
> +               This is a write only option (accepts any single value, eg.
> +               "echo 1 > white_balance"). Probably triggers an automatic
> +               white balance adjustment for lcd, function not explained in
> +               shuttle wmi documentation.


Here, I don't really understand why you reference "fn+<keys>". We
don't really care about the keys right ? What we want is make the feature
available for the user.

Some of the files likes volume_up/volume_down seems to be here for
debug purpose. Maybe you could add a simple debugfs interface
to send custom commands to the wmi device.

> diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
> index faec777..ef84a4d 100644
> --- a/drivers/platform/x86/Kconfig
> +++ b/drivers/platform/x86/Kconfig
> @@ -639,4 +639,20 @@ config XO1_RFKILL
>          Support for enabling/disabling the WLAN interface on the OLPC XO-1
>          laptop.
>
> +config SHUTTLE_WMI
> +       tristate "Shuttle WMI Extras"
> +       depends on ACPI
> +       depends on BACKLIGHT_CLASS_DEVICE
> +       depends on RFKILL
> +       depends on INPUT
> +       select ACPI_WMI
> +       select INPUT_SPARSEKMAP
> +       ---help---
> +         This is a driver for Shuttle machines (mainly for laptops in desktop
> +         form factor). It adds controls for wireless, bluetooth, and 3g 
> control
> +         radios, webcam switch, backlight controls, among others.
> +
> +         If you have an Shuttle machine with ACPI-WMI interface say Y or M
> +         here.
> +

Add some DA18IE/DA18IM/MA ref here ?

>  endif # X86_PLATFORM_DEVICES
> diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
> index 9950ccc..6a8fa82 100644
> --- a/drivers/platform/x86/Makefile
> +++ b/drivers/platform/x86/Makefile
> @@ -33,3 +33,4 @@ obj-$(CONFIG_INTEL_IPS)               += intel_ips.o
>  obj-$(CONFIG_GPIO_INTEL_PMIC)  += intel_pmic_gpio.o
>  obj-$(CONFIG_XO1_RFKILL)       += xo1-rfkill.o
>  obj-$(CONFIG_IBM_RTL)          += ibm_rtl.o
> +obj-$(CONFIG_SHUTTLE_WMI)      += shuttle-wmi.o
> diff --git a/drivers/platform/x86/shuttle-wmi.c 
> b/drivers/platform/x86/shuttle-wmi.c
> new file mode 100644
> index 0000000..389a16d
> --- /dev/null
> +++ b/drivers/platform/x86/shuttle-wmi.c
> @@ -0,0 +1,843 @@
> +/*
> + * ACPI-WMI driver for Shuttle DA18IE/DA18IM/MA
> + *
> + * Copyright (c) 2009 Herton Ronaldo Krzesinski <[email protected]>

2010 ?

> + * Development of this driver was funded by Positivo Informatica S.A.
> + * Parts of the driver were based on some WMI documentation provided by 
> Shuttle
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
> +
> +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
> +
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/init.h>
> +#include <linux/types.h>
> +#include <linux/acpi.h>
> +#include <linux/platform_device.h>
> +#include <linux/rfkill.h>
> +#include <linux/input.h>
> +#include <linux/input/sparse-keymap.h>
> +#include <linux/backlight.h>
> +#include <linux/fb.h>
> +
> +MODULE_AUTHOR("Herton Ronaldo Krzesinski");
> +MODULE_DESCRIPTION("Shuttle DA18IE/DA18IM/MA WMI Extras Driver");
> +MODULE_LICENSE("GPL");
> +
> +#define SHUTTLE_WMI_SETGET_GUID "abbc0f6f-8ea1-11d1-00a0-c90629100000"
> +#define SHUTTLE_WMI_EVENT_GUID "abbc0f72-8ea1-11d1-00a0-c90629100000"
> +MODULE_ALIAS("wmi:"SHUTTLE_WMI_SETGET_GUID);
> +MODULE_ALIAS("wmi:"SHUTTLE_WMI_EVENT_GUID);
> +
> +#define CMD_WRITEEC     0x00
> +#define CMD_READEC      0x01
> +#define CMD_SCMD        0x02
> +#define CMD_INT15       0x03
> +#define CMD_HWSW        0x07
> +#define CMD_LCTRL       0x09
> +#define CMD_CUTLVDS     0x11
> +#define CMD_MA          0x18
> +#define CMD_DA18IE      0x19
> +#define CMD_DA18IM      0x20
> +
> +#define ECRAM_ER0       0x443
> +#define ECRAM_ER1       0x47b
> +#define ECRAM_ER2       0x758
> +
> +struct shuttle_id {
> +       unsigned char cmd_id;
> +       char *model_name;
> +       bool step_brightness;
> +};
> +
> +static struct shuttle_id shuttle_ids[] = {
> +       { CMD_MA, "Shuttle MA", false },
> +       { CMD_DA18IE, "Shuttle DA18IE", true },
> +       { CMD_DA18IM, "Shuttle DA18IM", false }
> +};
> +
> +struct shuttle_wmi_priv {
> +       struct platform_device *pdev;
> +       struct input_dev *inputdev;
> +       struct shuttle_id *id;
> +       struct backlight_device *bd;
> +};

I would only name it struct shuttle_wmi, but not very important.

> +struct shuttle_cmd {
> +       u16 param2;
> +       u16 param1;
> +       u8 arg;
> +       u8 cmd;
> +       u16 hdr;
> +};
> +
> +static acpi_status wmi_setget_mtd(struct shuttle_cmd *scmd, u32 **res)
> +{
> +       acpi_status status;
> +       union acpi_object *obj;
> +       struct acpi_buffer input;
> +       static DEFINE_MUTEX(mtd_lock);
> +       struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
> +
> +       input.length = sizeof(struct shuttle_cmd);
> +       scmd->hdr = 0xec00;
> +       input.pointer = (u8 *) scmd;
> +
> +       mutex_lock(&mtd_lock);
> +       status = wmi_evaluate_method(SHUTTLE_WMI_SETGET_GUID, 0, 2,
> +                                    &input, &output);
> +       mutex_unlock(&mtd_lock);

I'm not sure why you need a mutex here ?

> +       if (ACPI_FAILURE(status))
> +               return status;
> +
> +       obj = output.pointer;
> +       if (obj) {
> +               if (obj->type == ACPI_TYPE_INTEGER) {
> +                       if (*res)
> +                               **res = obj->integer.value;
> +               } else
> +                       pr_err("Unsupported object returned (%s)", __func__);
> +               kfree(obj);
> +       } else {
> +               if (*res) {
> +                       pr_warning("No result from WMI method (%s)", 
> __func__);
> +                       *res = NULL;
> +               }
> +       }
> +       return AE_OK;
> +}
>

wmi_setget_mtd would probably be simpler like that:

static int wmi_setget_mtd(struct shuttle_cmd *scmd, u32 *res).
You don't really need to return the acpi_status, you just want to
return -1, 0, 1 for wmi_ec_cmd.

> +static int wmi_ec_cmd(unsigned char cmd, unsigned char arg,
> +                     unsigned short param1, unsigned short param2,
> +                     u32 *res)
> +{
> +       struct shuttle_cmd scmd = {
> +               .cmd = cmd,
> +               .arg = arg,
> +               .param1 = param1,
> +               .param2 = param2
> +       };
> +
> +       if (ACPI_FAILURE(wmi_setget_mtd(&scmd, &res)))
> +               return -1;
> +       return (res) ? 0 : 1;
> +}
> +
> +struct shuttle_rfkill {
> +       char *name;
> +       struct rfkill *rfk;
> +       enum rfkill_type type;
> +       unsigned short fn;
> +       u32 mask;
> +       u32 list_on[3];
> +       u32 list_off[3];
> +};
> +
> +static struct shuttle_rfkill shuttle_rfk_list[] = {
> +       { "shuttle_wlan", NULL, RFKILL_TYPE_WLAN,
> +         0x04, 0x80, { 0x08 }, { 0x09 } },
> +       { "shuttle_bluetooth", NULL, RFKILL_TYPE_BLUETOOTH,
> +         0x0d, 0x20, { 0x0c, 0x29 }, { 0x0d, 0x2a } },
> +       { "shuttle_3g", NULL, RFKILL_TYPE_WWAN,
> +         0x05, 0x40, { 0x10, 0x29 }, { 0x11, 0x2a } },
> +};

I would rather use simple if/else constructs instead of list_on/list_off,
it would probably be more easy to read.

> +static int rfkill_common_set_block(void *data, bool blocked)
> +{
> +       u32 val;
> +       bool sw;
> +       struct shuttle_rfkill *srfk = data;
> +
> +       if (wmi_ec_cmd(CMD_READEC, 0, 0, ECRAM_ER1, &val))
> +               return -EIO;
> +       sw = val & srfk->mask;
> +
> +       if ((blocked && sw) || (!blocked && !sw))

Maybe
sw = !!(val &  srfk->mask);
if (blocked == sw)
?

> +               wmi_ec_cmd(CMD_SCMD, 0, 0, srfk->fn, NULL);
> +       else
> +               return 0;
> +
> +       if (wmi_ec_cmd(CMD_READEC, 0, 0, ECRAM_ER1, &val))
> +               return -EIO;
> +       return ((val & srfk->mask) != blocked) ? 0 : -EIO;
> +}
> +
> +static const struct rfkill_ops rfkill_common_ops = {
> +       .set_block = rfkill_common_set_block,
> +};
> +
> +static int common_rfkill_init(struct shuttle_rfkill *srfk, struct device 
> *dev,
> +                             u32 init_val)
> +{
> +       int rc;
> +
> +       srfk->rfk = rfkill_alloc(srfk->name, dev, srfk->type,
> +                                &rfkill_common_ops, srfk);
> +       if (!srfk->rfk)
> +               return -ENOMEM;
> +
> +       rfkill_init_sw_state(srfk->rfk, !(init_val & srfk->mask));
> +
> +       rc = rfkill_register(srfk->rfk);
> +       if (rc) {
> +               rfkill_destroy(srfk->rfk);
> +               srfk->rfk = NULL;
> +               return rc;
> +       }
> +
> +       return 0;
> +}
> +
> +static int shuttle_rfkill_init(struct platform_device *pdev)
> +{
> +       int rc, i;
> +       u32 val;
> +       struct shuttle_rfkill *srfk;
> +
> +       if (wmi_ec_cmd(CMD_READEC, 0, 0, ECRAM_ER1, &val))
> +               return -EIO;
> +
> +       for (i = 0; i < ARRAY_SIZE(shuttle_rfk_list); i++) {
> +               srfk = &shuttle_rfk_list[i];
> +
> +               /* check that hardware is available (when missing we can't
> +                * unblock it), to avoid having rfkill switch when not needed;
> +                * after check, reset to initial setting */
> +               if (rfkill_common_set_block(srfk, false))
> +                       continue;
> +               if (!(val & srfk->mask))
> +                       rfkill_common_set_block(srfk, true);

There is no other solution  to guess if the hardware is present ? Because
I don't think it's a good idea to toggle every devices on load (may take time,
etc...)

> +               rc = common_rfkill_init(srfk, &pdev->dev, val);
> +               if (rc)
> +                       goto err_rfk_init;
> +       }
> +       return 0;
> +
> +err_rfk_init:
> +       for (i--; i >= 0; i--) {
> +               srfk = &shuttle_rfk_list[i];
> +               if (srfk->rfk) {
> +                       rfkill_unregister(srfk->rfk);
> +                       rfkill_destroy(srfk->rfk);
> +                       srfk->rfk = NULL;
> +               }
> +       }

Just call shuttfle_rfkill_remove, the if(srfk->rfk) should
make it work without any issue.

> +       return rc;
> +}
> +
> +static void shuttle_rfkill_remove(void)
> +{
> +       int i;
> +       struct shuttle_rfkill *srfk;
> +
> +       for (i = 0; i < ARRAY_SIZE(shuttle_rfk_list); i++) {
> +               srfk = &shuttle_rfk_list[i];
> +               if (srfk->rfk) {
> +                       rfkill_unregister(srfk->rfk);
> +                       rfkill_destroy(srfk->rfk);
> +                       srfk->rfk = NULL;
> +               }
> +       }
> +}
> +
> +static bool set_rfkill_sw(u32 *list, u32 code, struct rfkill *rfk, bool 
> blocked)
> +{
> +       while (*list) {
> +               if (*list == code) {
> +                       rfkill_set_sw_state(rfk, blocked);
> +                       return true;
> +               }
> +               list++;
> +       }
> +       return false;
> +}
> +
> +static bool notify_switch_rfkill(u32 code)
> +{
> +       int i;
> +       struct rfkill *rfk;
> +       u32 *rfk_evnt;
> +       bool res = false;
> +
> +       for (i = 0; i < ARRAY_SIZE(shuttle_rfk_list); i++) {
> +               rfk = shuttle_rfk_list[i].rfk;
> +               if (!rfk)
> +                       continue;
> +
> +               rfk_evnt = shuttle_rfk_list[i].list_on;
> +               if (set_rfkill_sw(rfk_evnt, code, rfk, false)) {
> +                       res = true;
> +                       continue;
> +               }
> +
> +               rfk_evnt = shuttle_rfk_list[i].list_off;
> +               if (set_rfkill_sw(rfk_evnt, code, rfk, true))
> +                       res = true;
> +       }
> +       return res;
> +}

See my previous comment, but I believe that the code would be
more obvious with basic if/else (or at least a command explaining
what is does).

> +static bool notify_switch_attr(struct platform_device *pdev, u32 code)
> +{
> +       int i;
> +       struct shuttle_switch {
> +               u32 switch_on;
> +               u32 switch_off;
> +               char *sys_attr;
> +       };
> +       static const struct shuttle_switch codes[] = {
> +               { 0x04, 0x05, "touchpad" },
> +               { 0x12, 0x13, "webcam" },
> +               { 0x31, 0x32, "powersave" }
> +       };
> +
> +       for (i = 0; i < ARRAY_SIZE(codes); i++) {
> +               if (codes[i].switch_on == code || codes[i].switch_off == 
> code) {
> +                       sysfs_notify(&pdev->dev.kobj, NULL, 
> codes[i].sys_attr);
> +                       return true;
> +               }
> +       }
> +       return false;
> +}
> +
> +static void shuttle_wmi_notify(u32 value, void *data)
> +{
> +       acpi_status status;
> +       union acpi_object *obj;
> +       u8 type;
> +       u32 code;
> +       struct acpi_buffer res = { ACPI_ALLOCATE_BUFFER, NULL };
> +       struct shuttle_wmi_priv *priv = data;
> +
> +       status = wmi_get_event_data(value, &res);
> +       if (status != AE_OK) {
> +               pr_warning("unable to retrieve wmi event status"
> +                          " (error=0x%x)\n", status);
> +               return;
> +       }
> +
> +       obj = (union acpi_object *) res.pointer;
> +       if (!obj)
> +               return;
> +       if (obj->type != ACPI_TYPE_INTEGER) {
> +               pr_info("unknown object returned in wmi event\n");
> +               goto notify_exit;
> +       }
> +
> +       type = (obj->integer.value >> 24) & 0xFF;
> +       switch (type) {
> +       case 0: /* OSD/Scancode event */
> +               code = obj->integer.value & 0xFFFFFF;
> +
> +               /* update rfkill switches */
> +               if (notify_switch_rfkill(code))
> +                       break;
> +
> +               /* send notification on state switch attributes */
> +               if (notify_switch_attr(priv->pdev, code))
> +                       break;
> +
> +               if (priv->bd && (code == 0x14 || code == 0x15))
> +                       backlight_force_update(priv->bd,
> +                                              BACKLIGHT_UPDATE_HOTKEY);
> +
> +               if (!sparse_keymap_report_event(priv->inputdev, code, 1, 
> true))
> +                       pr_info("unhandled scancode (0x%06x)\n", code);
> +               break;
> +       case 1: /* Power management event */
> +               /* Events not used.
> +                * Possible values for obj->integer.value:
> +                * 0x01000000 - silent mode
> +                * 0x01010000 - brightness sync */
> +       case 2: /* i-PowerXross event */
> +               /* i-PowerXross is a overclocking feature, not
> +                * implemented, there are no further details, possible
> +                * values for obj->integer.value in documentation:
> +                * 0x02000000 - idle mode
> +                * 0x02010000 - action mode
> +                * 0x02020000 - entry s3 */
> +               break;
> +       case 0xec: /* Lost event */
> +               if (printk_ratelimit())
> +                       pr_warning("lost event because of buggy BIOS");
> +               break;
> +       default:
> +               pr_info("unknown wmi notification type (0x%02x)\n", type);
> +       }
> +
> +notify_exit:
> +       kfree(obj);
> +}
> +
> +static const struct key_entry shuttle_wmi_keymap[] = {
> +       { KE_KEY, 0x14, { KEY_BRIGHTNESSUP } },
> +       { KE_KEY, 0x15, { KEY_BRIGHTNESSDOWN } },
> +       { KE_KEY, 0x16, { KEY_FASTFORWARD } },
> +       { KE_KEY, 0x17, { KEY_REWIND } },
> +       { KE_KEY, 0x18, { KEY_F13 } }, /* OSD Beep */
> +       { KE_KEY, 0x2b, { KEY_F14 } }, /* OSD menu 1 */
> +       { KE_KEY, 0x2c, { KEY_F15 } }, /* OSD menu 2 */
> +       { KE_KEY, 0x2d, { KEY_F16 } }, /* OSD menu 3 */
> +       { KE_KEY, 0x2e, { KEY_F17 } }, /* OSD menu 4 */
> +       { KE_KEY, 0x33, { KEY_F18 } }, /* Update OSD bar status */
> +       { KE_KEY, 0x90, { KEY_WWW } },
> +       { KE_KEY, 0x95, { KEY_PREVIOUSSONG } },
> +       { KE_KEY, 0xa0, { KEY_PROG1 } }, /* Call OSD software */
> +       { KE_KEY, 0xa1, { KEY_VOLUMEDOWN } },
> +       { KE_KEY, 0xa3, { KEY_MUTE } },
> +       { KE_KEY, 0xb2, { KEY_VOLUMEUP } },
> +       { KE_KEY, 0xb4, { KEY_PLAYPAUSE } },
> +       { KE_KEY, 0xbb, { KEY_STOPCD } },
> +       { KE_KEY, 0xc8, { KEY_MAIL } },
> +       { KE_KEY, 0xcd, { KEY_NEXTSONG } },
> +       { KE_KEY, 0xd0, { KEY_MEDIA } },
> +
> +       /* Known non hotkey events don't handled or that we don't care */
> +       { KE_IGNORE, 0x01, }, /* Caps Lock toggled */
> +       { KE_IGNORE, 0x02, }, /* Num Lock toggled */
> +       { KE_IGNORE, 0x03, }, /* Scroll Lock toggled */
> +       { KE_IGNORE, 0x06, }, /* Downclock/Silent on */
> +       { KE_IGNORE, 0x07, }, /* Downclock/Silent off */
> +       { KE_IGNORE, 0x0a, }, /* WiMax on */
> +       { KE_IGNORE, 0x0b, }, /* WiMax off */
> +       { KE_IGNORE, 0x0e, }, /* RF on */
> +       { KE_IGNORE, 0x0f, }, /* RF off */
> +       { KE_IGNORE, 0x1a, }, /* Auto Brightness on */
> +       { KE_IGNORE, 0x1b, }, /* Auto Brightness off */
> +       { KE_IGNORE, 0x1c, }, /* Auto-KB Brightness on */
> +       { KE_IGNORE, 0x1d, }, /* Auto-KB Brightness off */
> +       { KE_IGNORE, 0x1e, }, /* Light Bar Brightness up */
> +       { KE_IGNORE, 0x1f, }, /* Light Bar Brightness down */
> +       { KE_IGNORE, 0x20, }, /* China Telecom AP enable */
> +       { KE_IGNORE, 0x21, }, /* China Mobile AP enable */
> +       { KE_IGNORE, 0x22, }, /* Huawei AP enable */
> +       { KE_IGNORE, 0x23, }, /* Docking in */
> +       { KE_IGNORE, 0x24, }, /* Docking out */
> +       { KE_IGNORE, 0x25, }, /* Device no function */
> +       { KE_IGNORE, 0x26, }, /* i-PowerXross OverClocking */
> +       { KE_IGNORE, 0x27, }, /* i-PowerXross PowerSaving */
> +       { KE_IGNORE, 0x28, }, /* i-PowerXross off */
> +       { KE_IGNORE, 0x2f, }, /* Optimus on */
> +       { KE_IGNORE, 0x30, }, /* Optimus off */
> +       { KE_IGNORE, 0x91, }, /* ICO 2 on */
> +       { KE_IGNORE, 0x92, }, /* ICO 2 off */
> +
> +       { KE_END, 0 }
> +};
> +
> +static int shuttle_wmi_input_init(struct shuttle_wmi_priv *priv)
> +{
> +       struct input_dev *input;
> +       int rc;
> +
> +       input = input_allocate_device();
> +       if (!input)
> +               return -ENOMEM;
> +
> +       input->name = "Shuttle WMI hotkeys";
> +       input->phys = KBUILD_MODNAME "/input0";
> +       input->id.bustype = BUS_HOST;
> +
> +       rc = sparse_keymap_setup(input, shuttle_wmi_keymap, NULL);
> +       if (rc)
> +               goto err_free_dev;
> +
> +       rc = input_register_device(input);
> +       if (rc)
> +               goto err_free_keymap;
> +
> +       priv->inputdev = input;
> +       return 0;
> +
> +err_free_keymap:
> +       sparse_keymap_free(input);
> +err_free_dev:
> +       input_free_device(input);
> +       return rc;
> +}
> +
> +static void shuttle_wmi_input_remove(struct shuttle_wmi_priv *priv)
> +{
> +       struct input_dev *input = priv->inputdev;
> +
> +       sparse_keymap_free(input);
> +       input_unregister_device(input);
> +}
> +
> +static int shuttle_wmi_get_bl(struct backlight_device *bd)
> +{
> +       u32 val;
> +
> +       if (wmi_ec_cmd(CMD_READEC, 0, 0, ECRAM_ER0, &val))
> +               return -EIO;
> +       return val & 0x7;
> +}
> +
> +static int shuttle_wmi_update_bl(struct backlight_device *bd)
> +{
> +       int rc, steps;
> +       u32 val;
> +       struct shuttle_wmi_priv *priv = bl_get_data(bd);
> +
> +       if (!priv->id->step_brightness) {
> +               rc = ec_write(0x79, bd->props.brightness);
> +               if (rc)
> +                       return rc;
> +       } else {
> +               /* change brightness by steps, this is a quirk for shuttle
> +                * machines which don't accept direct write to ec for this */
> +               if (wmi_ec_cmd(CMD_READEC, 0, 0, ECRAM_ER0, &val))
> +                       return -EIO;
> +               val &= 0x7;
> +               steps = bd->props.brightness - val;
> +               while (steps > 0) {
> +                       wmi_ec_cmd(CMD_SCMD, 0, 0, 0x0c, NULL);
> +                       steps--;
> +               }
> +               while (steps < 0) {
> +                       wmi_ec_cmd(CMD_SCMD, 0, 0, 0x0b, NULL);
> +                       steps++;
> +               }
> +       }
> +       return 0;
> +}
> +
> +static const struct backlight_ops shuttle_wmi_bl_ops = {
> +       .get_brightness = shuttle_wmi_get_bl,
> +       .update_status = shuttle_wmi_update_bl,
> +};
> +
> +static int shuttle_wmi_backlight_init(struct shuttle_wmi_priv *priv)
> +{
> +       u32 val;
> +       struct backlight_properties props;
> +       struct backlight_device *bd;
> +
> +       if (wmi_ec_cmd(CMD_READEC, 0, 0, ECRAM_ER0, &val))
> +               return -EIO;
> +       val &= 0x7;
> +       memset(&props, 0, sizeof(struct backlight_properties));
> +       props.max_brightness = 7;
> +       props.brightness = val;
> +       props.power = FB_BLANK_UNBLANK;
> +
> +       bd = backlight_device_register(KBUILD_MODNAME, &priv->pdev->dev, priv,
> +                                      &shuttle_wmi_bl_ops, &props);
> +       if (IS_ERR(bd))
> +               return PTR_ERR(bd);
> +       priv->bd = bd;
> +       return 0;
> +}
> +
> +static void shuttle_wmi_backlight_exit(struct shuttle_wmi_priv *priv)
> +{
> +       if (priv->bd)
> +               backlight_device_unregister(priv->bd);
> +}
> +
> +static int __devinit shuttle_wmi_probe(struct platform_device *pdev)
> +{
> +       struct shuttle_wmi_priv *priv;
> +       int rc, i;
> +       acpi_status status;
> +       u32 val;
> +       static struct shuttle_id id_unknown = {
> +               .model_name = "Unknown",
> +       };
> +
> +       priv = kzalloc(sizeof(struct shuttle_wmi_priv), GFP_KERNEL);
> +       if (!priv)
> +               return -ENOMEM;
> +       priv->pdev = pdev;
> +       platform_set_drvdata(pdev, priv);
> +
> +       rc = shuttle_rfkill_init(pdev);
> +       if (rc)
> +               goto err_rfk;
> +
> +       rc = shuttle_wmi_input_init(priv);
> +       if (rc)
> +               goto err_input;
> +
> +       status = wmi_install_notify_handler(SHUTTLE_WMI_EVENT_GUID,
> +                                           shuttle_wmi_notify, priv);
> +       if (ACPI_FAILURE(status)) {
> +               rc = -EIO;
> +               goto err_notify;
> +       }
> +
> +       for (i = 0; i < ARRAY_SIZE(shuttle_ids); i++) {
> +               rc = wmi_ec_cmd(shuttle_ids[i].cmd_id, 0, 0, 0, &val);
> +               if (!rc && val == 1) {
> +                       priv->id = &shuttle_ids[i];
> +                       break;
> +               }
> +       }
> +       if (i == ARRAY_SIZE(shuttle_ids))
> +               priv->id = &id_unknown;
> +
> +       if (!acpi_video_backlight_support()) {
> +               rc = shuttle_wmi_backlight_init(priv);
> +               if (rc)
> +                       goto err_backlight;
> +       }
> +       return 0;
> +
> +err_backlight:
> +       wmi_remove_notify_handler(SHUTTLE_WMI_EVENT_GUID);
> +err_notify:
> +       shuttle_wmi_input_remove(priv);
> +err_input:
> +       shuttle_rfkill_remove();
> +err_rfk:
> +       kfree(priv);
> +       return rc;
> +}
> +
> +static int __devexit shuttle_wmi_remove(struct platform_device *pdev)
> +{
> +       struct shuttle_wmi_priv *priv = platform_get_drvdata(pdev);
> +
> +       shuttle_wmi_backlight_exit(priv);
> +       wmi_remove_notify_handler(SHUTTLE_WMI_EVENT_GUID);
> +       shuttle_wmi_input_remove(priv);
> +       shuttle_rfkill_remove();
> +       kfree(priv);
> +       return 0;
> +}
> +
> +#ifdef CONFIG_PM

WMI depends on ACPI
ACPI depends on PM

So it's not really needed.

> +static int shuttle_wmi_resume(struct device *dev)
> +{
> +       u32 val;
> +       int i;
> +       struct shuttle_rfkill *srfk;
> +
> +       if (wmi_ec_cmd(CMD_READEC, 0, 0, ECRAM_ER1, &val))
> +               return -EIO;
> +
> +       for (i = 0; i < ARRAY_SIZE(shuttle_rfk_list); i++) {
> +               srfk = &shuttle_rfk_list[i];
> +               if (srfk->rfk)
> +                       rfkill_set_sw_state(srfk->rfk, !(val & srfk->mask));
> +       }
> +       return 0;
> +}

You're code would probably be cleaner with a rfkill helper to get the current
state of a given deivce, to avoid playing with masks everytime.

> +
> +static const struct dev_pm_ops shuttle_wmi_pm_ops = {
> +       .restore = shuttle_wmi_resume,
> +       .resume = shuttle_wmi_resume,
> +};
> +#endif
> +
> +static struct platform_driver shuttle_wmi_driver = {
> +       .driver = {
> +               .name = KBUILD_MODNAME,
> +               .owner = THIS_MODULE,
> +#ifdef CONFIG_PM
> +               .pm = &shuttle_wmi_pm_ops,
> +#endif
> +       },
> +       .probe = shuttle_wmi_probe,
> +       .remove = __devexit_p(shuttle_wmi_remove),
> +};
> +
> +static struct platform_device *shuttle_wmi_device;
> +
> +static ssize_t show_state_common(char *buf, unsigned short ecram, u32 mask)
> +{
> +       u32 val;
> +
> +       if (wmi_ec_cmd(CMD_READEC, 0, 0, ecram, &val))
> +               return -EIO;
> +       return sprintf(buf, "%d\n", (val & mask) ? 1 : 0);
> +}
> +
> +static ssize_t store_state_common(const char *buf, size_t count,
> +                                 unsigned short ecram, u32 mask,
> +                                 unsigned short fn)
> +{
> +       int enable;
> +       int rc;
> +       u32 val;
> +
> +       if (sscanf(buf, "%i", &enable) != 1)
> +               return -EINVAL;
> +
> +       if (wmi_ec_cmd(CMD_READEC, 0, 0, ecram, &val))
> +               return -EIO;
> +       val &= mask;

Here again, having to use a mask makes the code harder to read.
Can't you use !!(val & mask) instead of val & mask ? Maybe a wrapper
around CMD_READEC to do that ? int wmi_ec_read_state(ecram, mask) ?

> +       if ((val && !enable) ||
> +           (!val && enable)) {
> +               wmi_ec_cmd(CMD_SCMD, 0, 0, fn, NULL);
> +               rc = wmi_ec_cmd(CMD_READEC, 0, 0, ecram, &val);
> +               val &= mask;
> +               if (rc || (!val && enable) ||
> +                   (val && !enable))
> +                       return -EIO;
> +       }
> +       return count;
> +}
> +
> +#define SHUTTLE_STATE_ATTR(_name, _ecram, _mask, _fn)                  \
> +       static ssize_t show_##_name(struct device *dev,                 \
> +                                   struct device_attribute *attr,      \
> +                                   char *buf)                          \
> +       {                                                               \
> +               return show_state_common(buf, _ecram, _mask);           \
> +       }                                                               \
> +       static ssize_t store_##_name(struct device *dev,                \
> +                                    struct device_attribute *attr,     \
> +                                    const char *buf, size_t count)     \
> +       {                                                               \
> +               return store_state_common(buf, count, _ecram, _mask,    \
> +                                         _fn);                         \
> +       }                                                               \
> +       static DEVICE_ATTR(_name, 0644, show_##_name, store_##_name);
> +
> +SHUTTLE_STATE_ATTR(powersave, ECRAM_ER2, 0x10, 0x02)
> +SHUTTLE_STATE_ATTR(touchpad, ECRAM_ER1, 0x02, 0x06)
> +SHUTTLE_STATE_ATTR(webcam, ECRAM_ER1, 0x10, 0x07)
> +
> +#define SHUTTLE_CMD_ATTR(_name, _cmd, _arg, _fn)                       \
> +       static ssize_t store_##_name(struct device *dev,                \
> +                                    struct device_attribute *attr,     \
> +                                    const char *buf, size_t count)     \
> +       {                                                               \
> +               wmi_ec_cmd(_cmd, _arg, 0, _fn, NULL);                   \
> +               return count;                                           \
> +       }                                                               \
> +       static DEVICE_ATTR(_name, 0200, NULL, store_##_name);
> +
> +SHUTTLE_CMD_ATTR(sleep, CMD_SCMD, 0, 0x01)
> +SHUTTLE_CMD_ATTR(switch_video, CMD_SCMD, 0, 0x03)
> +SHUTTLE_CMD_ATTR(sound_mute, CMD_SCMD, 0, 0x08)
> +SHUTTLE_CMD_ATTR(volume_down, CMD_SCMD, 0, 0x09)
> +SHUTTLE_CMD_ATTR(volume_up, CMD_SCMD, 0, 0x0a)
> +SHUTTLE_CMD_ATTR(brightness_down, CMD_SCMD, 0, 0x0b)
> +SHUTTLE_CMD_ATTR(brightness_up, CMD_SCMD, 0, 0x0c)
> +SHUTTLE_CMD_ATTR(lcd_auto_adjust, CMD_SCMD, 0, 0x81)
> +SHUTTLE_CMD_ATTR(white_balance, CMD_SCMD, 0, 0x82)
> +SHUTTLE_CMD_ATTR(panel_set_default, CMD_SCMD, 0, 0x83)
> +SHUTTLE_CMD_ATTR(lbar_brightness_down, CMD_LCTRL, 1, 0)
> +SHUTTLE_CMD_ATTR(lbar_brightness_up, CMD_LCTRL, 1, 1)
> +
> +static ssize_t show_model_name(struct device *dev,
> +                              struct device_attribute *attr,
> +                              char *buf)
> +{
> +       struct platform_device *pdev = to_platform_device(dev);
> +       struct shuttle_wmi_priv *priv = platform_get_drvdata(pdev);
> +
> +       return sprintf(buf, "%s\n", priv->id->model_name);
> +}
> +
> +static DEVICE_ATTR(model_name, 0444, show_model_name, NULL);
> +
> +static ssize_t store_cut_lvds(struct device *dev,
> +                             struct device_attribute *attr,
> +                             const char *buf, size_t count)
> +{
> +       int cut;
> +
> +       if (sscanf(buf, "%i", &cut) != 1)
> +               return -EINVAL;
> +
> +       if (cut)
> +               wmi_ec_cmd(CMD_CUTLVDS, 0, 0, 1, NULL);
> +       else
> +               wmi_ec_cmd(CMD_CUTLVDS, 0, 0, 0, NULL);
> +
> +       return count;
> +}
> +static DEVICE_ATTR(cut_lvds, 0200, NULL, store_cut_lvds);
> +
> +static struct attribute *shuttle_platform_attributes[] = {
> +       &dev_attr_powersave.attr,
> +       &dev_attr_touchpad.attr,
> +       &dev_attr_webcam.attr,
> +       &dev_attr_sleep.attr,
> +       &dev_attr_switch_video.attr,
> +       &dev_attr_sound_mute.attr,
> +       &dev_attr_volume_down.attr,
> +       &dev_attr_volume_up.attr,
> +       &dev_attr_brightness_down.attr,
> +       &dev_attr_brightness_up.attr,
> +       &dev_attr_lcd_auto_adjust.attr,
> +       &dev_attr_white_balance.attr,
> +       &dev_attr_panel_set_default.attr,
> +       &dev_attr_lbar_brightness_down.attr,
> +       &dev_attr_lbar_brightness_up.attr,
> +       &dev_attr_model_name.attr,
> +       &dev_attr_cut_lvds.attr,
> +       NULL
> +};
> +
> +static struct attribute_group shuttle_attribute_group = {
> +       .attrs = shuttle_platform_attributes
> +};
> +
> +static int __init shuttle_wmi_init(void)
> +{
> +       int rc;
> +       u32 val;
> +
> +       if (!wmi_has_guid(SHUTTLE_WMI_SETGET_GUID) ||
> +           !wmi_has_guid(SHUTTLE_WMI_EVENT_GUID)) {
> +               pr_err("Required WMI GUID not available\n");
> +               return -ENODEV;
> +       }
> +
> +       /* Check that we are really on a shuttle BIOS */
> +       rc = wmi_ec_cmd(CMD_INT15, 0, 0, 0, &val);
> +       if (rc || val != 0x534c) {
> +               pr_err("Shuttle WMI device not found or unsupported"
> +                      " (val=0x%08x)\n", val);
> +               return -ENODEV;
> +       }
> +
> +       rc = platform_driver_register(&shuttle_wmi_driver);
> +       if (rc)
> +               goto err_driver_register;
> +       shuttle_wmi_device = platform_device_alloc(KBUILD_MODNAME, -1);
> +       if (!shuttle_wmi_device) {
> +               rc = -ENOMEM;
> +               goto err_device_alloc;
> +       }
> +       rc = platform_device_add(shuttle_wmi_device);
> +       if (rc)
> +               goto err_device_add;
> +
> +       rc = sysfs_create_group(&shuttle_wmi_device->dev.kobj,
> +                               &shuttle_attribute_group);
> +       if (rc)
> +               goto err_sysfs;
> +
> +       return 0;
> +
> +err_sysfs:
> +       platform_device_del(shuttle_wmi_device);
> +err_device_add:
> +       platform_device_put(shuttle_wmi_device);
> +err_device_alloc:
> +       platform_driver_unregister(&shuttle_wmi_driver);
> +err_driver_register:
> +       return rc;
> +}
> +
> +static void __exit shuttle_wmi_exit(void)
> +{
> +       sysfs_remove_group(&shuttle_wmi_device->dev.kobj,
> +                          &shuttle_attribute_group);
> +       platform_device_unregister(shuttle_wmi_device);
> +       platform_driver_unregister(&shuttle_wmi_driver);
> +}
> +
> +module_init(shuttle_wmi_init);
> +module_exit(shuttle_wmi_exit);
> --
> 1.7.3.2
>
> --
> []'s
> Herton
> --
> To unsubscribe from this list: send the line "unsubscribe 
> platform-driver-x86" in
> the body of a message to [email protected]
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
>



-- 
Corentin Chary
http://xf.iksaif.net
--
To unsubscribe from this list: send the line "unsubscribe platform-driver-x86" 
in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to