On Tue, Nov 30, 2010 at 6:27 PM, Herton Ronaldo Krzesinski <herton@xxxxxxxxxxxxxxx> 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 <herton@xxxxxxxxxxxxxxx> > --- > Â.../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" <herton@xxxxxxxxxxxxxxx> > +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" <herton@xxxxxxxxxxxxxxx> > +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" <herton@xxxxxxxxxxxxxxx> > +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" <herton@xxxxxxxxxxxxxxx> > +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" <herton@xxxxxxxxxxxxxxx> > +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" <herton@xxxxxxxxxxxxxxx> > +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" <herton@xxxxxxxxxxxxxxx> > +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" <herton@xxxxxxxxxxxxxxx> > +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" <herton@xxxxxxxxxxxxxxx> > +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" <herton@xxxxxxxxxxxxxxx> > +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" <herton@xxxxxxxxxxxxxxx> > +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" <herton@xxxxxxxxxxxxxxx> > +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" <herton@xxxxxxxxxxxxxxx> > +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" <herton@xxxxxxxxxxxxxxx> > +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" <herton@xxxxxxxxxxxxxxx> > +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" <herton@xxxxxxxxxxxxxxx> > +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" <herton@xxxxxxxxxxxxxxx> > +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 <herton@xxxxxxxxxxxxxxx> 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 majordomo@xxxxxxxxxxxxxxx > 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 majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html