From f23027ca3d48b6f93c5994069fb25b73539fdf34 Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Fri, 9 Oct 2020 16:11:25 +0200 Subject: platform/surface: Move Surface 3 WMI driver to platform/surface Move the Surface 3 WMI driver from platform/x86 to the newly created platform/surface directory. Signed-off-by: Maximilian Luz Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20201009141128.683254-3-luzmaximilian@gmail.com Signed-off-by: Hans de Goede --- drivers/platform/x86/Kconfig | 12 -- drivers/platform/x86/Makefile | 1 - drivers/platform/x86/surface3-wmi.c | 291 ------------------------------------ 3 files changed, 304 deletions(-) delete mode 100644 drivers/platform/x86/surface3-wmi.c (limited to 'drivers/platform/x86') diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 0d91d136bc3b..0759913c9846 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -870,18 +870,6 @@ config INTEL_VBTN To compile this driver as a module, choose M here: the module will be called intel_vbtn. -config SURFACE3_WMI - tristate "Surface 3 WMI Driver" - depends on ACPI_WMI - depends on DMI - depends on INPUT - depends on SPI - help - Say Y here if you have a Surface 3. - - To compile this driver as a module, choose M here: the module will - be called surface3-wmi. - config SURFACE_3_BUTTON tristate "Power/home/volume buttons driver for Microsoft Surface 3 tablet" depends on ACPI && KEYBOARD_GPIO && I2C diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 5f823f7eff45..29563a32b3e3 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -82,7 +82,6 @@ obj-$(CONFIG_INTEL_OAKTRAIL) += intel_oaktrail.o obj-$(CONFIG_INTEL_VBTN) += intel-vbtn.o # Microsoft -obj-$(CONFIG_SURFACE3_WMI) += surface3-wmi.o obj-$(CONFIG_SURFACE_3_BUTTON) += surface3_button.o obj-$(CONFIG_SURFACE_3_POWER_OPREGION) += surface3_power.o obj-$(CONFIG_SURFACE_PRO3_BUTTON) += surfacepro3_button.o diff --git a/drivers/platform/x86/surface3-wmi.c b/drivers/platform/x86/surface3-wmi.c deleted file mode 100644 index 130b6f52a600..000000000000 --- a/drivers/platform/x86/surface3-wmi.c +++ /dev/null @@ -1,291 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Driver for the LID cover switch of the Surface 3 - * - * Copyright (c) 2016 Red Hat Inc. - */ - - -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -MODULE_AUTHOR("Benjamin Tissoires "); -MODULE_DESCRIPTION("Surface 3 platform driver"); -MODULE_LICENSE("GPL"); - -#define ACPI_BUTTON_HID_LID "PNP0C0D" -#define SPI_CTL_OBJ_NAME "SPI" -#define SPI_TS_OBJ_NAME "NTRG" - -#define SURFACE3_LID_GUID "F7CC25EC-D20B-404C-8903-0ED4359C18AE" - -MODULE_ALIAS("wmi:" SURFACE3_LID_GUID); - -static const struct dmi_system_id surface3_dmi_table[] = { -#if defined(CONFIG_X86) - { - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_MATCH(DMI_PRODUCT_NAME, "Surface 3"), - }, - }, -#endif - { } -}; - -struct surface3_wmi { - struct acpi_device *touchscreen_adev; - struct acpi_device *pnp0c0d_adev; - struct acpi_hotplug_context hp; - struct input_dev *input; -}; - -static struct platform_device *s3_wmi_pdev; - -static struct surface3_wmi s3_wmi; - -static DEFINE_MUTEX(s3_wmi_lock); - -static int s3_wmi_query_block(const char *guid, int instance, int *ret) -{ - struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; - acpi_status status; - union acpi_object *obj; - int error = 0; - - mutex_lock(&s3_wmi_lock); - status = wmi_query_block(guid, instance, &output); - - obj = output.pointer; - - if (!obj || obj->type != ACPI_TYPE_INTEGER) { - if (obj) { - pr_err("query block returned object type: %d - buffer length:%d\n", - obj->type, - obj->type == ACPI_TYPE_BUFFER ? - obj->buffer.length : 0); - } - error = -EINVAL; - goto out_free_unlock; - } - *ret = obj->integer.value; - out_free_unlock: - kfree(obj); - mutex_unlock(&s3_wmi_lock); - return error; -} - -static inline int s3_wmi_query_lid(int *ret) -{ - return s3_wmi_query_block(SURFACE3_LID_GUID, 0, ret); -} - -static int s3_wmi_send_lid_state(void) -{ - int ret, lid_sw; - - ret = s3_wmi_query_lid(&lid_sw); - if (ret) - return ret; - - input_report_switch(s3_wmi.input, SW_LID, lid_sw); - input_sync(s3_wmi.input); - - return 0; -} - -static int s3_wmi_hp_notify(struct acpi_device *adev, u32 value) -{ - return s3_wmi_send_lid_state(); -} - -static acpi_status s3_wmi_attach_spi_device(acpi_handle handle, - u32 level, - void *data, - void **return_value) -{ - struct acpi_device *adev, **ts_adev; - - if (acpi_bus_get_device(handle, &adev)) - return AE_OK; - - ts_adev = data; - - if (strncmp(acpi_device_bid(adev), SPI_TS_OBJ_NAME, - strlen(SPI_TS_OBJ_NAME))) - return AE_OK; - - if (*ts_adev) { - pr_err("duplicate entry %s\n", SPI_TS_OBJ_NAME); - return AE_OK; - } - - *ts_adev = adev; - - return AE_OK; -} - -static int s3_wmi_check_platform_device(struct device *dev, void *data) -{ - struct acpi_device *adev, *ts_adev = NULL; - acpi_handle handle; - acpi_status status; - - /* ignore non ACPI devices */ - handle = ACPI_HANDLE(dev); - if (!handle || acpi_bus_get_device(handle, &adev)) - return 0; - - /* check for LID ACPI switch */ - if (!strcmp(ACPI_BUTTON_HID_LID, acpi_device_hid(adev))) { - s3_wmi.pnp0c0d_adev = adev; - return 0; - } - - /* ignore non SPI controllers */ - if (strncmp(acpi_device_bid(adev), SPI_CTL_OBJ_NAME, - strlen(SPI_CTL_OBJ_NAME))) - return 0; - - status = acpi_walk_namespace(ACPI_TYPE_DEVICE, handle, 1, - s3_wmi_attach_spi_device, NULL, - &ts_adev, NULL); - if (ACPI_FAILURE(status)) - dev_warn(dev, "failed to enumerate SPI slaves\n"); - - if (!ts_adev) - return 0; - - s3_wmi.touchscreen_adev = ts_adev; - - return 0; -} - -static int s3_wmi_create_and_register_input(struct platform_device *pdev) -{ - struct input_dev *input; - int error; - - input = devm_input_allocate_device(&pdev->dev); - if (!input) - return -ENOMEM; - - input->name = "Lid Switch"; - input->phys = "button/input0"; - input->id.bustype = BUS_HOST; - input->id.product = 0x0005; - - input_set_capability(input, EV_SW, SW_LID); - - error = input_register_device(input); - if (error) - goto out_err; - - s3_wmi.input = input; - - return 0; - out_err: - input_free_device(s3_wmi.input); - return error; -} - -static int __init s3_wmi_probe(struct platform_device *pdev) -{ - int error; - - if (!dmi_check_system(surface3_dmi_table)) - return -ENODEV; - - memset(&s3_wmi, 0, sizeof(s3_wmi)); - - bus_for_each_dev(&platform_bus_type, NULL, NULL, - s3_wmi_check_platform_device); - - if (!s3_wmi.touchscreen_adev) - return -ENODEV; - - acpi_bus_trim(s3_wmi.pnp0c0d_adev); - - error = s3_wmi_create_and_register_input(pdev); - if (error) - goto restore_acpi_lid; - - acpi_initialize_hp_context(s3_wmi.touchscreen_adev, &s3_wmi.hp, - s3_wmi_hp_notify, NULL); - - s3_wmi_send_lid_state(); - - return 0; - - restore_acpi_lid: - acpi_bus_scan(s3_wmi.pnp0c0d_adev->handle); - return error; -} - -static int s3_wmi_remove(struct platform_device *device) -{ - /* remove the hotplug context from the acpi device */ - s3_wmi.touchscreen_adev->hp = NULL; - - /* reinstall the actual PNPC0C0D LID default handle */ - acpi_bus_scan(s3_wmi.pnp0c0d_adev->handle); - return 0; -} - -static int __maybe_unused s3_wmi_resume(struct device *dev) -{ - s3_wmi_send_lid_state(); - return 0; -} -static SIMPLE_DEV_PM_OPS(s3_wmi_pm, NULL, s3_wmi_resume); - -static struct platform_driver s3_wmi_driver = { - .driver = { - .name = "surface3-wmi", - .pm = &s3_wmi_pm, - }, - .remove = s3_wmi_remove, -}; - -static int __init s3_wmi_init(void) -{ - int error; - - s3_wmi_pdev = platform_device_alloc("surface3-wmi", -1); - if (!s3_wmi_pdev) - return -ENOMEM; - - error = platform_device_add(s3_wmi_pdev); - if (error) - goto err_device_put; - - error = platform_driver_probe(&s3_wmi_driver, s3_wmi_probe); - if (error) - goto err_device_del; - - pr_info("Surface 3 WMI Extras loaded\n"); - return 0; - - err_device_del: - platform_device_del(s3_wmi_pdev); - err_device_put: - platform_device_put(s3_wmi_pdev); - return error; -} - -static void __exit s3_wmi_exit(void) -{ - platform_device_unregister(s3_wmi_pdev); - platform_driver_unregister(&s3_wmi_driver); -} - -module_init(s3_wmi_init); -module_exit(s3_wmi_exit); -- cgit v1.2.3 From 4df56c36944bece6a9b361f7fc7dc8906a9dbd20 Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Fri, 9 Oct 2020 16:11:26 +0200 Subject: platform/surface: Move Surface 3 Button driver to platform/surface Move the Surface 3 Button driver from platform/x86 to the newly created platform/surface directory. Signed-off-by: Maximilian Luz Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20201009141128.683254-4-luzmaximilian@gmail.com Signed-off-by: Hans de Goede --- drivers/platform/x86/Kconfig | 6 - drivers/platform/x86/Makefile | 1 - drivers/platform/x86/surface3_button.c | 247 --------------------------------- 3 files changed, 254 deletions(-) delete mode 100644 drivers/platform/x86/surface3_button.c (limited to 'drivers/platform/x86') diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 0759913c9846..5fba590a1a67 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -870,12 +870,6 @@ config INTEL_VBTN To compile this driver as a module, choose M here: the module will be called intel_vbtn. -config SURFACE_3_BUTTON - tristate "Power/home/volume buttons driver for Microsoft Surface 3 tablet" - depends on ACPI && KEYBOARD_GPIO && I2C - help - This driver handles the power/home/volume buttons on the Microsoft Surface 3 tablet. - config SURFACE_3_POWER_OPREGION tristate "Surface 3 battery platform operation region support" depends on ACPI && I2C diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 29563a32b3e3..0fd70d5d2cf3 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -82,7 +82,6 @@ obj-$(CONFIG_INTEL_OAKTRAIL) += intel_oaktrail.o obj-$(CONFIG_INTEL_VBTN) += intel-vbtn.o # Microsoft -obj-$(CONFIG_SURFACE_3_BUTTON) += surface3_button.o obj-$(CONFIG_SURFACE_3_POWER_OPREGION) += surface3_power.o obj-$(CONFIG_SURFACE_PRO3_BUTTON) += surfacepro3_button.o diff --git a/drivers/platform/x86/surface3_button.c b/drivers/platform/x86/surface3_button.c deleted file mode 100644 index 48d77e7aae76..000000000000 --- a/drivers/platform/x86/surface3_button.c +++ /dev/null @@ -1,247 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Supports for the button array on the Surface tablets. - * - * (C) Copyright 2016 Red Hat, Inc - * - * Based on soc_button_array.c: - * - * {C} Copyright 2014 Intel Corporation - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - - -#define SURFACE_BUTTON_OBJ_NAME "TEV2" -#define MAX_NBUTTONS 4 - -/* - * Some of the buttons like volume up/down are auto repeat, while others - * are not. To support both, we register two platform devices, and put - * buttons into them based on whether the key should be auto repeat. - */ -#define BUTTON_TYPES 2 - -/* - * Power button, Home button, Volume buttons support is supposed to - * be covered by drivers/input/misc/soc_button_array.c, which is implemented - * according to "Windows ACPI Design Guide for SoC Platforms". - * However surface 3 seems not to obey the specs, instead it uses - * device TEV2(MSHW0028) for declaring the GPIOs. The gpios are also slightly - * different in which the Home button is active high. - * Compared to surfacepro3_button.c which also handles MSHW0028, the Surface 3 - * is a reduce platform and thus uses GPIOs, not ACPI events. - * We choose an I2C driver here because we need to access the resources - * declared under the device node, while surfacepro3_button.c only needs - * the ACPI companion node. - */ -static const struct acpi_device_id surface3_acpi_match[] = { - { "MSHW0028", 0 }, - { } -}; -MODULE_DEVICE_TABLE(acpi, surface3_acpi_match); - -struct surface3_button_info { - const char *name; - int acpi_index; - unsigned int event_type; - unsigned int event_code; - bool autorepeat; - bool wakeup; - bool active_low; -}; - -struct surface3_button_data { - struct platform_device *children[BUTTON_TYPES]; -}; - -/* - * Get the Nth GPIO number from the ACPI object. - */ -static int surface3_button_lookup_gpio(struct device *dev, int acpi_index) -{ - struct gpio_desc *desc; - int gpio; - - desc = gpiod_get_index(dev, NULL, acpi_index, GPIOD_ASIS); - if (IS_ERR(desc)) - return PTR_ERR(desc); - - gpio = desc_to_gpio(desc); - - gpiod_put(desc); - - return gpio; -} - -static struct platform_device * -surface3_button_device_create(struct i2c_client *client, - const struct surface3_button_info *button_info, - bool autorepeat) -{ - const struct surface3_button_info *info; - struct platform_device *pd; - struct gpio_keys_button *gpio_keys; - struct gpio_keys_platform_data *gpio_keys_pdata; - int n_buttons = 0; - int gpio; - int error; - - gpio_keys_pdata = devm_kzalloc(&client->dev, - sizeof(*gpio_keys_pdata) + - sizeof(*gpio_keys) * MAX_NBUTTONS, - GFP_KERNEL); - if (!gpio_keys_pdata) - return ERR_PTR(-ENOMEM); - - gpio_keys = (void *)(gpio_keys_pdata + 1); - - for (info = button_info; info->name; info++) { - if (info->autorepeat != autorepeat) - continue; - - gpio = surface3_button_lookup_gpio(&client->dev, - info->acpi_index); - if (!gpio_is_valid(gpio)) - continue; - - gpio_keys[n_buttons].type = info->event_type; - gpio_keys[n_buttons].code = info->event_code; - gpio_keys[n_buttons].gpio = gpio; - gpio_keys[n_buttons].active_low = info->active_low; - gpio_keys[n_buttons].desc = info->name; - gpio_keys[n_buttons].wakeup = info->wakeup; - n_buttons++; - } - - if (n_buttons == 0) { - error = -ENODEV; - goto err_free_mem; - } - - gpio_keys_pdata->buttons = gpio_keys; - gpio_keys_pdata->nbuttons = n_buttons; - gpio_keys_pdata->rep = autorepeat; - - pd = platform_device_alloc("gpio-keys", PLATFORM_DEVID_AUTO); - if (!pd) { - error = -ENOMEM; - goto err_free_mem; - } - - error = platform_device_add_data(pd, gpio_keys_pdata, - sizeof(*gpio_keys_pdata)); - if (error) - goto err_free_pdev; - - error = platform_device_add(pd); - if (error) - goto err_free_pdev; - - return pd; - -err_free_pdev: - platform_device_put(pd); -err_free_mem: - devm_kfree(&client->dev, gpio_keys_pdata); - return ERR_PTR(error); -} - -static int surface3_button_remove(struct i2c_client *client) -{ - struct surface3_button_data *priv = i2c_get_clientdata(client); - - int i; - - for (i = 0; i < BUTTON_TYPES; i++) - if (priv->children[i]) - platform_device_unregister(priv->children[i]); - - return 0; -} - -static struct surface3_button_info surface3_button_surface3[] = { - { "power", 0, EV_KEY, KEY_POWER, false, true, true }, - { "home", 1, EV_KEY, KEY_LEFTMETA, false, true, false }, - { "volume_up", 2, EV_KEY, KEY_VOLUMEUP, true, false, true }, - { "volume_down", 3, EV_KEY, KEY_VOLUMEDOWN, true, false, true }, - { } -}; - -static int surface3_button_probe(struct i2c_client *client, - const struct i2c_device_id *id) -{ - struct device *dev = &client->dev; - struct surface3_button_data *priv; - struct platform_device *pd; - int i; - int error; - - if (strncmp(acpi_device_bid(ACPI_COMPANION(&client->dev)), - SURFACE_BUTTON_OBJ_NAME, - strlen(SURFACE_BUTTON_OBJ_NAME))) - return -ENODEV; - - error = gpiod_count(dev, NULL); - if (error < 0) { - dev_dbg(dev, "no GPIO attached, ignoring...\n"); - return error; - } - - priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); - if (!priv) - return -ENOMEM; - - i2c_set_clientdata(client, priv); - - for (i = 0; i < BUTTON_TYPES; i++) { - pd = surface3_button_device_create(client, - surface3_button_surface3, - i == 0); - if (IS_ERR(pd)) { - error = PTR_ERR(pd); - if (error != -ENODEV) { - surface3_button_remove(client); - return error; - } - continue; - } - - priv->children[i] = pd; - } - - if (!priv->children[0] && !priv->children[1]) - return -ENODEV; - - return 0; -} - -static const struct i2c_device_id surface3_id[] = { - { } -}; -MODULE_DEVICE_TABLE(i2c, surface3_id); - -static struct i2c_driver surface3_driver = { - .probe = surface3_button_probe, - .remove = surface3_button_remove, - .id_table = surface3_id, - .driver = { - .name = "surface3", - .acpi_match_table = ACPI_PTR(surface3_acpi_match), - }, -}; -module_i2c_driver(surface3_driver); - -MODULE_AUTHOR("Benjamin Tissoires "); -MODULE_DESCRIPTION("surface3 button array driver"); -MODULE_LICENSE("GPL v2"); -- cgit v1.2.3 From 85f7582cd484dbf491b6d9bb2af6ef1467a024d2 Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Fri, 9 Oct 2020 16:11:27 +0200 Subject: platform/surface: Move Surface 3 Power OpRegion driver to platform/surface Move the Surface 3 Power operation region driver from platform/x86 to the newly created platform/surface directory. Signed-off-by: Maximilian Luz Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20201009141128.683254-5-luzmaximilian@gmail.com Signed-off-by: Hans de Goede --- drivers/platform/x86/Kconfig | 7 - drivers/platform/x86/Makefile | 1 - drivers/platform/x86/surface3_power.c | 589 ---------------------------------- 3 files changed, 597 deletions(-) delete mode 100644 drivers/platform/x86/surface3_power.c (limited to 'drivers/platform/x86') diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 5fba590a1a67..8417ee0178d0 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -870,13 +870,6 @@ config INTEL_VBTN To compile this driver as a module, choose M here: the module will be called intel_vbtn. -config SURFACE_3_POWER_OPREGION - tristate "Surface 3 battery platform operation region support" - depends on ACPI && I2C - help - This driver provides support for ACPI operation - region of the Surface 3 battery platform driver. - config SURFACE_PRO3_BUTTON tristate "Power/home/volume buttons driver for Microsoft Surface Pro 3/4 tablet" depends on ACPI && INPUT diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 0fd70d5d2cf3..ffa31f57d9a2 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -82,7 +82,6 @@ obj-$(CONFIG_INTEL_OAKTRAIL) += intel_oaktrail.o obj-$(CONFIG_INTEL_VBTN) += intel-vbtn.o # Microsoft -obj-$(CONFIG_SURFACE_3_POWER_OPREGION) += surface3_power.o obj-$(CONFIG_SURFACE_PRO3_BUTTON) += surfacepro3_button.o # MSI diff --git a/drivers/platform/x86/surface3_power.c b/drivers/platform/x86/surface3_power.c deleted file mode 100644 index cc4f9cba6856..000000000000 --- a/drivers/platform/x86/surface3_power.c +++ /dev/null @@ -1,589 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0+ -/* - * Supports for the power IC on the Surface 3 tablet. - * - * (C) Copyright 2016-2018 Red Hat, Inc - * (C) Copyright 2016-2018 Benjamin Tissoires - * (C) Copyright 2016 Stephen Just - * - * This driver has been reverse-engineered by parsing the DSDT of the Surface 3 - * and looking at the registers of the chips. - * - * The DSDT allowed to find out that: - * - the driver is required for the ACPI BAT0 device to communicate to the chip - * through an operation region. - * - the various defines for the operation region functions to communicate with - * this driver - * - the DSM 3f99e367-6220-4955-8b0f-06ef2ae79412 allows to trigger ACPI - * events to BAT0 (the code is all available in the DSDT). - * - * Further findings regarding the 2 chips declared in the MSHW0011 are: - * - there are 2 chips declared: - * . 0x22 seems to control the ADP1 line status (and probably the charger) - * . 0x55 controls the battery directly - * - the battery chip uses a SMBus protocol (using plain SMBus allows non - * destructive commands): - * . the commands/registers used are in the range 0x00..0x7F - * . if bit 8 (0x80) is set in the SMBus command, the returned value is the - * same as when it is not set. There is a high chance this bit is the - * read/write - * . the various registers semantic as been deduced by observing the register - * dumps. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define SURFACE_3_POLL_INTERVAL (2 * HZ) -#define SURFACE_3_STRLEN 10 - -struct mshw0011_data { - struct i2c_client *adp1; - struct i2c_client *bat0; - unsigned short notify_mask; - struct task_struct *poll_task; - bool kthread_running; - - bool charging; - bool bat_charging; - u8 trip_point; - s32 full_capacity; -}; - -struct mshw0011_handler_data { - struct acpi_connection_info info; - struct i2c_client *client; -}; - -struct bix { - u32 revision; - u32 power_unit; - u32 design_capacity; - u32 last_full_charg_capacity; - u32 battery_technology; - u32 design_voltage; - u32 design_capacity_of_warning; - u32 design_capacity_of_low; - u32 cycle_count; - u32 measurement_accuracy; - u32 max_sampling_time; - u32 min_sampling_time; - u32 max_average_interval; - u32 min_average_interval; - u32 battery_capacity_granularity_1; - u32 battery_capacity_granularity_2; - char model[SURFACE_3_STRLEN]; - char serial[SURFACE_3_STRLEN]; - char type[SURFACE_3_STRLEN]; - char OEM[SURFACE_3_STRLEN]; -} __packed; - -struct bst { - u32 battery_state; - s32 battery_present_rate; - u32 battery_remaining_capacity; - u32 battery_present_voltage; -} __packed; - -struct gsb_command { - u8 arg0; - u8 arg1; - u8 arg2; -} __packed; - -struct gsb_buffer { - u8 status; - u8 len; - u8 ret; - union { - struct gsb_command cmd; - struct bst bst; - struct bix bix; - } __packed; -} __packed; - -#define ACPI_BATTERY_STATE_DISCHARGING BIT(0) -#define ACPI_BATTERY_STATE_CHARGING BIT(1) -#define ACPI_BATTERY_STATE_CRITICAL BIT(2) - -#define MSHW0011_CMD_DEST_BAT0 0x01 -#define MSHW0011_CMD_DEST_ADP1 0x03 - -#define MSHW0011_CMD_BAT0_STA 0x01 -#define MSHW0011_CMD_BAT0_BIX 0x02 -#define MSHW0011_CMD_BAT0_BCT 0x03 -#define MSHW0011_CMD_BAT0_BTM 0x04 -#define MSHW0011_CMD_BAT0_BST 0x05 -#define MSHW0011_CMD_BAT0_BTP 0x06 -#define MSHW0011_CMD_ADP1_PSR 0x07 -#define MSHW0011_CMD_BAT0_PSOC 0x09 -#define MSHW0011_CMD_BAT0_PMAX 0x0a -#define MSHW0011_CMD_BAT0_PSRC 0x0b -#define MSHW0011_CMD_BAT0_CHGI 0x0c -#define MSHW0011_CMD_BAT0_ARTG 0x0d - -#define MSHW0011_NOTIFY_GET_VERSION 0x00 -#define MSHW0011_NOTIFY_ADP1 0x01 -#define MSHW0011_NOTIFY_BAT0_BST 0x02 -#define MSHW0011_NOTIFY_BAT0_BIX 0x05 - -#define MSHW0011_ADP1_REG_PSR 0x04 - -#define MSHW0011_BAT0_REG_CAPACITY 0x0c -#define MSHW0011_BAT0_REG_FULL_CHG_CAPACITY 0x0e -#define MSHW0011_BAT0_REG_DESIGN_CAPACITY 0x40 -#define MSHW0011_BAT0_REG_VOLTAGE 0x08 -#define MSHW0011_BAT0_REG_RATE 0x14 -#define MSHW0011_BAT0_REG_OEM 0x45 -#define MSHW0011_BAT0_REG_TYPE 0x4e -#define MSHW0011_BAT0_REG_SERIAL_NO 0x56 -#define MSHW0011_BAT0_REG_CYCLE_CNT 0x6e - -#define MSHW0011_EV_2_5_MASK GENMASK(8, 0) - -/* 3f99e367-6220-4955-8b0f-06ef2ae79412 */ -static const guid_t mshw0011_guid = - GUID_INIT(0x3F99E367, 0x6220, 0x4955, 0x8B, 0x0F, 0x06, 0xEF, - 0x2A, 0xE7, 0x94, 0x12); - -static int -mshw0011_notify(struct mshw0011_data *cdata, u8 arg1, u8 arg2, - unsigned int *ret_value) -{ - union acpi_object *obj; - struct acpi_device *adev; - acpi_handle handle; - unsigned int i; - - handle = ACPI_HANDLE(&cdata->adp1->dev); - if (!handle || acpi_bus_get_device(handle, &adev)) - return -ENODEV; - - obj = acpi_evaluate_dsm_typed(handle, &mshw0011_guid, arg1, arg2, NULL, - ACPI_TYPE_BUFFER); - if (!obj) { - dev_err(&cdata->adp1->dev, "device _DSM execution failed\n"); - return -ENODEV; - } - - *ret_value = 0; - for (i = 0; i < obj->buffer.length; i++) - *ret_value |= obj->buffer.pointer[i] << (i * 8); - - ACPI_FREE(obj); - return 0; -} - -static const struct bix default_bix = { - .revision = 0x00, - .power_unit = 0x01, - .design_capacity = 0x1dca, - .last_full_charg_capacity = 0x1dca, - .battery_technology = 0x01, - .design_voltage = 0x10df, - .design_capacity_of_warning = 0x8f, - .design_capacity_of_low = 0x47, - .cycle_count = 0xffffffff, - .measurement_accuracy = 0x00015f90, - .max_sampling_time = 0x03e8, - .min_sampling_time = 0x03e8, - .max_average_interval = 0x03e8, - .min_average_interval = 0x03e8, - .battery_capacity_granularity_1 = 0x45, - .battery_capacity_granularity_2 = 0x11, - .model = "P11G8M", - .serial = "", - .type = "LION", - .OEM = "", -}; - -static int mshw0011_bix(struct mshw0011_data *cdata, struct bix *bix) -{ - struct i2c_client *client = cdata->bat0; - char buf[SURFACE_3_STRLEN]; - int ret; - - *bix = default_bix; - - /* get design capacity */ - ret = i2c_smbus_read_word_data(client, - MSHW0011_BAT0_REG_DESIGN_CAPACITY); - if (ret < 0) { - dev_err(&client->dev, "Error reading design capacity: %d\n", - ret); - return ret; - } - bix->design_capacity = ret; - - /* get last full charge capacity */ - ret = i2c_smbus_read_word_data(client, - MSHW0011_BAT0_REG_FULL_CHG_CAPACITY); - if (ret < 0) { - dev_err(&client->dev, - "Error reading last full charge capacity: %d\n", ret); - return ret; - } - bix->last_full_charg_capacity = ret; - - /* get serial number */ - ret = i2c_smbus_read_i2c_block_data(client, MSHW0011_BAT0_REG_SERIAL_NO, - sizeof(buf), buf); - if (ret != sizeof(buf)) { - dev_err(&client->dev, "Error reading serial no: %d\n", ret); - return ret; - } - snprintf(bix->serial, ARRAY_SIZE(bix->serial), "%3pE%6pE", buf + 7, buf); - - /* get cycle count */ - ret = i2c_smbus_read_word_data(client, MSHW0011_BAT0_REG_CYCLE_CNT); - if (ret < 0) { - dev_err(&client->dev, "Error reading cycle count: %d\n", ret); - return ret; - } - bix->cycle_count = ret; - - /* get OEM name */ - ret = i2c_smbus_read_i2c_block_data(client, MSHW0011_BAT0_REG_OEM, - 4, buf); - if (ret != 4) { - dev_err(&client->dev, "Error reading cycle count: %d\n", ret); - return ret; - } - snprintf(bix->OEM, ARRAY_SIZE(bix->OEM), "%3pE", buf); - - return 0; -} - -static int mshw0011_bst(struct mshw0011_data *cdata, struct bst *bst) -{ - struct i2c_client *client = cdata->bat0; - int rate, capacity, voltage, state; - s16 tmp; - - rate = i2c_smbus_read_word_data(client, MSHW0011_BAT0_REG_RATE); - if (rate < 0) - return rate; - - capacity = i2c_smbus_read_word_data(client, MSHW0011_BAT0_REG_CAPACITY); - if (capacity < 0) - return capacity; - - voltage = i2c_smbus_read_word_data(client, MSHW0011_BAT0_REG_VOLTAGE); - if (voltage < 0) - return voltage; - - tmp = rate; - bst->battery_present_rate = abs((s32)tmp); - - state = 0; - if ((s32) tmp > 0) - state |= ACPI_BATTERY_STATE_CHARGING; - else if ((s32) tmp < 0) - state |= ACPI_BATTERY_STATE_DISCHARGING; - bst->battery_state = state; - - bst->battery_remaining_capacity = capacity; - bst->battery_present_voltage = voltage; - - return 0; -} - -static int mshw0011_adp_psr(struct mshw0011_data *cdata) -{ - return i2c_smbus_read_byte_data(cdata->adp1, MSHW0011_ADP1_REG_PSR); -} - -static int mshw0011_isr(struct mshw0011_data *cdata) -{ - struct bst bst; - struct bix bix; - int ret; - bool status, bat_status; - - ret = mshw0011_adp_psr(cdata); - if (ret < 0) - return ret; - - status = ret; - if (status != cdata->charging) - mshw0011_notify(cdata, cdata->notify_mask, - MSHW0011_NOTIFY_ADP1, &ret); - - cdata->charging = status; - - ret = mshw0011_bst(cdata, &bst); - if (ret < 0) - return ret; - - bat_status = bst.battery_state; - if (bat_status != cdata->bat_charging) - mshw0011_notify(cdata, cdata->notify_mask, - MSHW0011_NOTIFY_BAT0_BST, &ret); - - cdata->bat_charging = bat_status; - - ret = mshw0011_bix(cdata, &bix); - if (ret < 0) - return ret; - - if (bix.last_full_charg_capacity != cdata->full_capacity) - mshw0011_notify(cdata, cdata->notify_mask, - MSHW0011_NOTIFY_BAT0_BIX, &ret); - - cdata->full_capacity = bix.last_full_charg_capacity; - - return 0; -} - -static int mshw0011_poll_task(void *data) -{ - struct mshw0011_data *cdata = data; - int ret = 0; - - cdata->kthread_running = true; - - set_freezable(); - - while (!kthread_should_stop()) { - schedule_timeout_interruptible(SURFACE_3_POLL_INTERVAL); - try_to_freeze(); - ret = mshw0011_isr(data); - if (ret) - break; - } - - cdata->kthread_running = false; - return ret; -} - -static acpi_status -mshw0011_space_handler(u32 function, acpi_physical_address command, - u32 bits, u64 *value64, - void *handler_context, void *region_context) -{ - struct gsb_buffer *gsb = (struct gsb_buffer *)value64; - struct mshw0011_handler_data *data = handler_context; - struct acpi_connection_info *info = &data->info; - struct acpi_resource_i2c_serialbus *sb; - struct i2c_client *client = data->client; - struct mshw0011_data *cdata = i2c_get_clientdata(client); - struct acpi_resource *ares; - u32 accessor_type = function >> 16; - acpi_status ret; - int status = 1; - - ret = acpi_buffer_to_resource(info->connection, info->length, &ares); - if (ACPI_FAILURE(ret)) - return ret; - - if (!value64 || ares->type != ACPI_RESOURCE_TYPE_SERIAL_BUS) { - ret = AE_BAD_PARAMETER; - goto err; - } - - sb = &ares->data.i2c_serial_bus; - if (sb->type != ACPI_RESOURCE_SERIAL_TYPE_I2C) { - ret = AE_BAD_PARAMETER; - goto err; - } - - if (accessor_type != ACPI_GSB_ACCESS_ATTRIB_RAW_PROCESS) { - ret = AE_BAD_PARAMETER; - goto err; - } - - if (gsb->cmd.arg0 == MSHW0011_CMD_DEST_ADP1 && - gsb->cmd.arg1 == MSHW0011_CMD_ADP1_PSR) { - status = mshw0011_adp_psr(cdata); - if (status >= 0) { - ret = AE_OK; - goto out; - } else { - ret = AE_ERROR; - goto err; - } - } - - if (gsb->cmd.arg0 != MSHW0011_CMD_DEST_BAT0) { - ret = AE_BAD_PARAMETER; - goto err; - } - - switch (gsb->cmd.arg1) { - case MSHW0011_CMD_BAT0_STA: - break; - case MSHW0011_CMD_BAT0_BIX: - ret = mshw0011_bix(cdata, &gsb->bix); - break; - case MSHW0011_CMD_BAT0_BTP: - cdata->trip_point = gsb->cmd.arg2; - break; - case MSHW0011_CMD_BAT0_BST: - ret = mshw0011_bst(cdata, &gsb->bst); - break; - default: - dev_info(&cdata->bat0->dev, "command(0x%02x) is not supported.\n", gsb->cmd.arg1); - ret = AE_BAD_PARAMETER; - goto err; - } - - out: - gsb->ret = status; - gsb->status = 0; - - err: - ACPI_FREE(ares); - return ret; -} - -static int mshw0011_install_space_handler(struct i2c_client *client) -{ - acpi_handle handle; - struct mshw0011_handler_data *data; - acpi_status status; - - handle = ACPI_HANDLE(&client->dev); - if (!handle) - return -ENODEV; - - data = kzalloc(sizeof(struct mshw0011_handler_data), - GFP_KERNEL); - if (!data) - return -ENOMEM; - - data->client = client; - status = acpi_bus_attach_private_data(handle, (void *)data); - if (ACPI_FAILURE(status)) { - kfree(data); - return -ENOMEM; - } - - status = acpi_install_address_space_handler(handle, - ACPI_ADR_SPACE_GSBUS, - &mshw0011_space_handler, - NULL, - data); - if (ACPI_FAILURE(status)) { - dev_err(&client->dev, "Error installing i2c space handler\n"); - acpi_bus_detach_private_data(handle); - kfree(data); - return -ENOMEM; - } - - acpi_walk_dep_device_list(handle); - return 0; -} - -static void mshw0011_remove_space_handler(struct i2c_client *client) -{ - struct mshw0011_handler_data *data; - acpi_handle handle; - acpi_status status; - - handle = ACPI_HANDLE(&client->dev); - if (!handle) - return; - - acpi_remove_address_space_handler(handle, - ACPI_ADR_SPACE_GSBUS, - &mshw0011_space_handler); - - status = acpi_bus_get_private_data(handle, (void **)&data); - if (ACPI_SUCCESS(status)) - kfree(data); - - acpi_bus_detach_private_data(handle); -} - -static int mshw0011_probe(struct i2c_client *client) -{ - struct i2c_board_info board_info; - struct device *dev = &client->dev; - struct i2c_client *bat0; - struct mshw0011_data *data; - int error, mask; - - data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); - if (!data) - return -ENOMEM; - - data->adp1 = client; - i2c_set_clientdata(client, data); - - memset(&board_info, 0, sizeof(board_info)); - strlcpy(board_info.type, "MSHW0011-bat0", I2C_NAME_SIZE); - - bat0 = i2c_acpi_new_device(dev, 1, &board_info); - if (IS_ERR(bat0)) - return PTR_ERR(bat0); - - data->bat0 = bat0; - i2c_set_clientdata(bat0, data); - - error = mshw0011_notify(data, 1, MSHW0011_NOTIFY_GET_VERSION, &mask); - if (error) - goto out_err; - - data->notify_mask = mask == MSHW0011_EV_2_5_MASK; - - data->poll_task = kthread_run(mshw0011_poll_task, data, "mshw0011_adp"); - if (IS_ERR(data->poll_task)) { - error = PTR_ERR(data->poll_task); - dev_err(&client->dev, "Unable to run kthread err %d\n", error); - goto out_err; - } - - error = mshw0011_install_space_handler(client); - if (error) - goto out_err; - - return 0; - -out_err: - if (data->kthread_running) - kthread_stop(data->poll_task); - i2c_unregister_device(data->bat0); - return error; -} - -static int mshw0011_remove(struct i2c_client *client) -{ - struct mshw0011_data *cdata = i2c_get_clientdata(client); - - mshw0011_remove_space_handler(client); - - if (cdata->kthread_running) - kthread_stop(cdata->poll_task); - - i2c_unregister_device(cdata->bat0); - - return 0; -} - -static const struct acpi_device_id mshw0011_acpi_match[] = { - { "MSHW0011", 0 }, - { } -}; -MODULE_DEVICE_TABLE(acpi, mshw0011_acpi_match); - -static struct i2c_driver mshw0011_driver = { - .probe_new = mshw0011_probe, - .remove = mshw0011_remove, - .driver = { - .name = "mshw0011", - .acpi_match_table = mshw0011_acpi_match, - }, -}; -module_i2c_driver(mshw0011_driver); - -MODULE_AUTHOR("Benjamin Tissoires "); -MODULE_DESCRIPTION("mshw0011 driver"); -MODULE_LICENSE("GPL v2"); -- cgit v1.2.3 From 411269babe8374b7777a0f154a2ad27c3c6dc218 Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Fri, 9 Oct 2020 16:11:28 +0200 Subject: platform/surface: Move Surface Pro 3 Button driver to platform/surface Move the Surface Pro 3 Button driver from platform/x86 to the newly created platform/surface directory. Signed-off-by: Maximilian Luz Reviewed-by: Andy Shevchenko Acked-by: Chen Yu Link: https://lore.kernel.org/r/20201009141128.683254-6-luzmaximilian@gmail.com Signed-off-by: Hans de Goede --- drivers/platform/x86/Kconfig | 6 - drivers/platform/x86/Makefile | 3 - drivers/platform/x86/surfacepro3_button.c | 268 ------------------------------ 3 files changed, 277 deletions(-) delete mode 100644 drivers/platform/x86/surfacepro3_button.c (limited to 'drivers/platform/x86') diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 8417ee0178d0..6083f8241b7d 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -870,12 +870,6 @@ config INTEL_VBTN To compile this driver as a module, choose M here: the module will be called intel_vbtn. -config SURFACE_PRO3_BUTTON - tristate "Power/home/volume buttons driver for Microsoft Surface Pro 3/4 tablet" - depends on ACPI && INPUT - help - This driver handles the power/home/volume buttons on the Microsoft Surface Pro 3/4 tablet. - config MSI_LAPTOP tristate "MSI Laptop Extras" depends on ACPI diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index ffa31f57d9a2..aeff497e23a5 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -81,9 +81,6 @@ obj-$(CONFIG_INTEL_MENLOW) += intel_menlow.o obj-$(CONFIG_INTEL_OAKTRAIL) += intel_oaktrail.o obj-$(CONFIG_INTEL_VBTN) += intel-vbtn.o -# Microsoft -obj-$(CONFIG_SURFACE_PRO3_BUTTON) += surfacepro3_button.o - # MSI obj-$(CONFIG_MSI_LAPTOP) += msi-laptop.o obj-$(CONFIG_MSI_WMI) += msi-wmi.o diff --git a/drivers/platform/x86/surfacepro3_button.c b/drivers/platform/x86/surfacepro3_button.c deleted file mode 100644 index d8afed5db94c..000000000000 --- a/drivers/platform/x86/surfacepro3_button.c +++ /dev/null @@ -1,268 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * power/home/volume button support for - * Microsoft Surface Pro 3/4 tablet. - * - * Copyright (c) 2015 Intel Corporation. - * All rights reserved. - */ - -#include -#include -#include -#include -#include -#include -#include - -#define SURFACE_PRO3_BUTTON_HID "MSHW0028" -#define SURFACE_PRO4_BUTTON_HID "MSHW0040" -#define SURFACE_BUTTON_OBJ_NAME "VGBI" -#define SURFACE_BUTTON_DEVICE_NAME "Surface Pro 3/4 Buttons" - -#define MSHW0040_DSM_REVISION 0x01 -#define MSHW0040_DSM_GET_OMPR 0x02 // get OEM Platform Revision -static const guid_t MSHW0040_DSM_UUID = - GUID_INIT(0x6fd05c69, 0xcde3, 0x49f4, 0x95, 0xed, 0xab, 0x16, 0x65, - 0x49, 0x80, 0x35); - -#define SURFACE_BUTTON_NOTIFY_TABLET_MODE 0xc8 - -#define SURFACE_BUTTON_NOTIFY_PRESS_POWER 0xc6 -#define SURFACE_BUTTON_NOTIFY_RELEASE_POWER 0xc7 - -#define SURFACE_BUTTON_NOTIFY_PRESS_HOME 0xc4 -#define SURFACE_BUTTON_NOTIFY_RELEASE_HOME 0xc5 - -#define SURFACE_BUTTON_NOTIFY_PRESS_VOLUME_UP 0xc0 -#define SURFACE_BUTTON_NOTIFY_RELEASE_VOLUME_UP 0xc1 - -#define SURFACE_BUTTON_NOTIFY_PRESS_VOLUME_DOWN 0xc2 -#define SURFACE_BUTTON_NOTIFY_RELEASE_VOLUME_DOWN 0xc3 - -ACPI_MODULE_NAME("surface pro 3 button"); - -MODULE_AUTHOR("Chen Yu"); -MODULE_DESCRIPTION("Surface Pro3 Button Driver"); -MODULE_LICENSE("GPL v2"); - -/* - * Power button, Home button, Volume buttons support is supposed to - * be covered by drivers/input/misc/soc_button_array.c, which is implemented - * according to "Windows ACPI Design Guide for SoC Platforms". - * However surface pro3 seems not to obey the specs, instead it uses - * device VGBI(MSHW0028) for dispatching the events. - * We choose acpi_driver rather than platform_driver/i2c_driver because - * although VGBI has an i2c resource connected to i2c controller, it - * is not embedded in any i2c controller's scope, thus neither platform_device - * will be created, nor i2c_client will be enumerated, we have to use - * acpi_driver. - */ -static const struct acpi_device_id surface_button_device_ids[] = { - {SURFACE_PRO3_BUTTON_HID, 0}, - {SURFACE_PRO4_BUTTON_HID, 0}, - {"", 0}, -}; -MODULE_DEVICE_TABLE(acpi, surface_button_device_ids); - -struct surface_button { - unsigned int type; - struct input_dev *input; - char phys[32]; /* for input device */ - unsigned long pushed; - bool suspended; -}; - -static void surface_button_notify(struct acpi_device *device, u32 event) -{ - struct surface_button *button = acpi_driver_data(device); - struct input_dev *input; - int key_code = KEY_RESERVED; - bool pressed = false; - - switch (event) { - /* Power button press,release handle */ - case SURFACE_BUTTON_NOTIFY_PRESS_POWER: - pressed = true; - fallthrough; - case SURFACE_BUTTON_NOTIFY_RELEASE_POWER: - key_code = KEY_POWER; - break; - /* Home button press,release handle */ - case SURFACE_BUTTON_NOTIFY_PRESS_HOME: - pressed = true; - fallthrough; - case SURFACE_BUTTON_NOTIFY_RELEASE_HOME: - key_code = KEY_LEFTMETA; - break; - /* Volume up button press,release handle */ - case SURFACE_BUTTON_NOTIFY_PRESS_VOLUME_UP: - pressed = true; - fallthrough; - case SURFACE_BUTTON_NOTIFY_RELEASE_VOLUME_UP: - key_code = KEY_VOLUMEUP; - break; - /* Volume down button press,release handle */ - case SURFACE_BUTTON_NOTIFY_PRESS_VOLUME_DOWN: - pressed = true; - fallthrough; - case SURFACE_BUTTON_NOTIFY_RELEASE_VOLUME_DOWN: - key_code = KEY_VOLUMEDOWN; - break; - case SURFACE_BUTTON_NOTIFY_TABLET_MODE: - dev_warn_once(&device->dev, "Tablet mode is not supported\n"); - break; - default: - dev_info_ratelimited(&device->dev, - "Unsupported event [0x%x]\n", event); - break; - } - input = button->input; - if (key_code == KEY_RESERVED) - return; - if (pressed) - pm_wakeup_dev_event(&device->dev, 0, button->suspended); - if (button->suspended) - return; - input_report_key(input, key_code, pressed?1:0); - input_sync(input); -} - -#ifdef CONFIG_PM_SLEEP -static int surface_button_suspend(struct device *dev) -{ - struct acpi_device *device = to_acpi_device(dev); - struct surface_button *button = acpi_driver_data(device); - - button->suspended = true; - return 0; -} - -static int surface_button_resume(struct device *dev) -{ - struct acpi_device *device = to_acpi_device(dev); - struct surface_button *button = acpi_driver_data(device); - - button->suspended = false; - return 0; -} -#endif - -/* - * Surface Pro 4 and Surface Book 2 / Surface Pro 2017 use the same device - * ID (MSHW0040) for the power/volume buttons. Make sure this is the right - * device by checking for the _DSM method and OEM Platform Revision. - * - * Returns true if the driver should bind to this device, i.e. the device is - * either MSWH0028 (Pro 3) or MSHW0040 on a Pro 4 or Book 1. - */ -static bool surface_button_check_MSHW0040(struct acpi_device *dev) -{ - acpi_handle handle = dev->handle; - union acpi_object *result; - u64 oem_platform_rev = 0; // valid revisions are nonzero - - // get OEM platform revision - result = acpi_evaluate_dsm_typed(handle, &MSHW0040_DSM_UUID, - MSHW0040_DSM_REVISION, - MSHW0040_DSM_GET_OMPR, - NULL, ACPI_TYPE_INTEGER); - - /* - * If evaluating the _DSM fails, the method is not present. This means - * that we have either MSHW0028 or MSHW0040 on Pro 4 or Book 1, so we - * should use this driver. We use revision 0 indicating it is - * unavailable. - */ - - if (result) { - oem_platform_rev = result->integer.value; - ACPI_FREE(result); - } - - dev_dbg(&dev->dev, "OEM Platform Revision %llu\n", oem_platform_rev); - - return oem_platform_rev == 0; -} - - -static int surface_button_add(struct acpi_device *device) -{ - struct surface_button *button; - struct input_dev *input; - const char *hid = acpi_device_hid(device); - char *name; - int error; - - if (strncmp(acpi_device_bid(device), SURFACE_BUTTON_OBJ_NAME, - strlen(SURFACE_BUTTON_OBJ_NAME))) - return -ENODEV; - - if (!surface_button_check_MSHW0040(device)) - return -ENODEV; - - button = kzalloc(sizeof(struct surface_button), GFP_KERNEL); - if (!button) - return -ENOMEM; - - device->driver_data = button; - button->input = input = input_allocate_device(); - if (!input) { - error = -ENOMEM; - goto err_free_button; - } - - name = acpi_device_name(device); - strcpy(name, SURFACE_BUTTON_DEVICE_NAME); - snprintf(button->phys, sizeof(button->phys), "%s/buttons", hid); - - input->name = name; - input->phys = button->phys; - input->id.bustype = BUS_HOST; - input->dev.parent = &device->dev; - input_set_capability(input, EV_KEY, KEY_POWER); - input_set_capability(input, EV_KEY, KEY_LEFTMETA); - input_set_capability(input, EV_KEY, KEY_VOLUMEUP); - input_set_capability(input, EV_KEY, KEY_VOLUMEDOWN); - - error = input_register_device(input); - if (error) - goto err_free_input; - - device_init_wakeup(&device->dev, true); - dev_info(&device->dev, - "%s [%s]\n", name, acpi_device_bid(device)); - return 0; - - err_free_input: - input_free_device(input); - err_free_button: - kfree(button); - return error; -} - -static int surface_button_remove(struct acpi_device *device) -{ - struct surface_button *button = acpi_driver_data(device); - - input_unregister_device(button->input); - kfree(button); - return 0; -} - -static SIMPLE_DEV_PM_OPS(surface_button_pm, - surface_button_suspend, surface_button_resume); - -static struct acpi_driver surface_button_driver = { - .name = "surface_pro3_button", - .class = "SurfacePro3", - .ids = surface_button_device_ids, - .ops = { - .add = surface_button_add, - .remove = surface_button_remove, - .notify = surface_button_notify, - }, - .drv.pm = &surface_button_pm, -}; - -module_acpi_driver(surface_button_driver); -- cgit v1.2.3 From 56afb8d48017cbc5216ce3923f11d65683a8e0b6 Mon Sep 17 00:00:00 2001 From: Yongxin Liu Date: Fri, 15 Nov 2019 13:27:10 +0800 Subject: Revert "platform/x86: wmi: Destroy on cleanup rather than unregister" This reverts commit 7b11e8989618581bc0226ad313264cdc05d48d86. Consider the following hardware setting. |-PNP0C14:00 | |-- device #1 |-PNP0C14:01 | |-- device #2 When unloading wmi driver module, device #2 will be first unregistered. But device_destroy() using MKDEV(0, 0) will locate PNP0C14:00 first and unregister it. This is incorrect. Should use device_unregister() to unregister the real parent device. Signed-off-by: Yongxin Liu Link: https://lore.kernel.org/r/20191115052710.46880-1-yongxin.liu@windriver.com Signed-off-by: Hans de Goede --- drivers/platform/x86/wmi.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers/platform/x86') diff --git a/drivers/platform/x86/wmi.c b/drivers/platform/x86/wmi.c index d88f388a3450..d5e84946a1da 100644 --- a/drivers/platform/x86/wmi.c +++ b/drivers/platform/x86/wmi.c @@ -1347,7 +1347,7 @@ static int acpi_wmi_remove(struct platform_device *device) acpi_remove_address_space_handler(acpi_device->handle, ACPI_ADR_SPACE_EC, &acpi_wmi_ec_space_handler); wmi_free_devices(acpi_device); - device_destroy(&wmi_bus_class, MKDEV(0, 0)); + device_unregister((struct device *)dev_get_drvdata(&device->dev)); return 0; } @@ -1401,7 +1401,7 @@ static int acpi_wmi_probe(struct platform_device *device) return 0; err_remove_busdev: - device_destroy(&wmi_bus_class, MKDEV(0, 0)); + device_unregister(wmi_bus_dev); err_remove_notify_handler: acpi_remove_notify_handler(acpi_device->handle, ACPI_DEVICE_NOTIFY, -- cgit v1.2.3 From e8a60aa7404bfef37705da5607c97737073ac38d Mon Sep 17 00:00:00 2001 From: Divya Bharathi Date: Tue, 27 Oct 2020 19:19:44 +0530 Subject: platform/x86: Introduce support for Systems Management Driver over WMI for Dell Systems The Dell WMI Systems Management Driver provides a sysfs interface for systems management to enable BIOS configuration capability on certain Dell Systems. This driver allows user to configure Dell systems with a uniform common interface. To facilitate this, the patch introduces a generic way for driver to be able to create configurable BIOS Attributes available in Setup (F2) screen. Cc: Hans de Goede Cc: Andy Shevchenko Cc: mark gross Co-developed-by: Mario Limonciello Signed-off-by: Mario Limonciello Co-developed-by: Prasanth KSR Signed-off-by: Prasanth KSR Signed-off-by: Divya Bharathi Link: https://lore.kernel.org/r/20201027134944.316730-1-divya.bharathi@dell.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/Kconfig | 12 + drivers/platform/x86/Makefile | 1 + drivers/platform/x86/dell-wmi-sysman/Makefile | 8 + .../x86/dell-wmi-sysman/biosattr-interface.c | 186 ++++++ .../platform/x86/dell-wmi-sysman/dell-wmi-sysman.h | 191 +++++++ .../platform/x86/dell-wmi-sysman/enum-attributes.c | 189 +++++++ .../platform/x86/dell-wmi-sysman/int-attributes.c | 173 ++++++ .../x86/dell-wmi-sysman/passobj-attributes.c | 194 +++++++ .../x86/dell-wmi-sysman/passwordattr-interface.c | 153 +++++ .../x86/dell-wmi-sysman/string-attributes.c | 159 ++++++ drivers/platform/x86/dell-wmi-sysman/sysman.c | 625 +++++++++++++++++++++ 11 files changed, 1891 insertions(+) create mode 100644 drivers/platform/x86/dell-wmi-sysman/Makefile create mode 100644 drivers/platform/x86/dell-wmi-sysman/biosattr-interface.c create mode 100644 drivers/platform/x86/dell-wmi-sysman/dell-wmi-sysman.h create mode 100644 drivers/platform/x86/dell-wmi-sysman/enum-attributes.c create mode 100644 drivers/platform/x86/dell-wmi-sysman/int-attributes.c create mode 100644 drivers/platform/x86/dell-wmi-sysman/passobj-attributes.c create mode 100644 drivers/platform/x86/dell-wmi-sysman/passwordattr-interface.c create mode 100644 drivers/platform/x86/dell-wmi-sysman/string-attributes.c create mode 100644 drivers/platform/x86/dell-wmi-sysman/sysman.c (limited to 'drivers/platform/x86') diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 6083f8241b7d..65fee84706ec 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -430,6 +430,18 @@ config DELL_WMI To compile this driver as a module, choose M here: the module will be called dell-wmi. +config DELL_WMI_SYSMAN + tristate "Dell WMI-based Systems management driver" + depends on ACPI_WMI + depends on DMI + select NLS + help + This driver allows changing BIOS settings on many Dell machines from + 2018 and newer without the use of any additional software. + + To compile this driver as a module, choose M here: the module will + be called dell-wmi-sysman. + config DELL_WMI_DESCRIPTOR tristate depends on ACPI_WMI diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index aeff497e23a5..2643c1c42bd7 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -47,6 +47,7 @@ obj-$(CONFIG_DELL_WMI) += dell-wmi.o obj-$(CONFIG_DELL_WMI_DESCRIPTOR) += dell-wmi-descriptor.o obj-$(CONFIG_DELL_WMI_AIO) += dell-wmi-aio.o obj-$(CONFIG_DELL_WMI_LED) += dell-wmi-led.o +obj-$(CONFIG_DELL_WMI_SYSMAN) += dell-wmi-sysman/ # Fujitsu obj-$(CONFIG_AMILO_RFKILL) += amilo-rfkill.o diff --git a/drivers/platform/x86/dell-wmi-sysman/Makefile b/drivers/platform/x86/dell-wmi-sysman/Makefile new file mode 100644 index 000000000000..825fb2fbeea8 --- /dev/null +++ b/drivers/platform/x86/dell-wmi-sysman/Makefile @@ -0,0 +1,8 @@ +obj-$(CONFIG_DELL_WMI_SYSMAN) += dell-wmi-sysman.o +dell-wmi-sysman-objs := sysman.o \ + enum-attributes.o \ + int-attributes.o \ + string-attributes.o \ + passobj-attributes.o \ + biosattr-interface.o \ + passwordattr-interface.o diff --git a/drivers/platform/x86/dell-wmi-sysman/biosattr-interface.c b/drivers/platform/x86/dell-wmi-sysman/biosattr-interface.c new file mode 100644 index 000000000000..f95d8ddace5a --- /dev/null +++ b/drivers/platform/x86/dell-wmi-sysman/biosattr-interface.c @@ -0,0 +1,186 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Functions corresponding to SET methods under BIOS attributes interface GUID for use + * with dell-wmi-sysman + * + * Copyright (c) 2020 Dell Inc. + */ + +#include +#include "dell-wmi-sysman.h" + +#define SETDEFAULTVALUES_METHOD_ID 0x02 +#define SETBIOSDEFAULTS_METHOD_ID 0x03 +#define SETATTRIBUTE_METHOD_ID 0x04 + +static int call_biosattributes_interface(struct wmi_device *wdev, char *in_args, size_t size, + int method_id) +{ + struct acpi_buffer output = {ACPI_ALLOCATE_BUFFER, NULL}; + struct acpi_buffer input; + union acpi_object *obj; + acpi_status status; + int ret = -EIO; + + input.length = (acpi_size) size; + input.pointer = in_args; + status = wmidev_evaluate_method(wdev, 0, method_id, &input, &output); + if (ACPI_FAILURE(status)) + return -EIO; + obj = (union acpi_object *)output.pointer; + if (obj->type == ACPI_TYPE_INTEGER) + ret = obj->integer.value; + + if (wmi_priv.pending_changes == 0) { + wmi_priv.pending_changes = 1; + /* let userland know it may need to check reboot pending again */ + kobject_uevent(&wmi_priv.class_dev->kobj, KOBJ_CHANGE); + } + kfree(output.pointer); + return map_wmi_error(ret); +} + +/** + * set_attribute() - Update an attribute value + * @a_name: The attribute name + * @a_value: The attribute value + * + * Sets an attribute to new value + */ +int set_attribute(const char *a_name, const char *a_value) +{ + size_t security_area_size, buffer_size; + size_t a_name_size, a_value_size; + char *buffer = NULL, *start; + int ret; + + mutex_lock(&wmi_priv.mutex); + if (!wmi_priv.bios_attr_wdev) { + ret = -ENODEV; + goto out; + } + + /* build/calculate buffer */ + security_area_size = calculate_security_buffer(wmi_priv.current_admin_password); + a_name_size = calculate_string_buffer(a_name); + a_value_size = calculate_string_buffer(a_value); + buffer_size = security_area_size + a_name_size + a_value_size; + buffer = kzalloc(buffer_size, GFP_KERNEL); + if (!buffer) { + ret = -ENOMEM; + goto out; + } + + /* build security area */ + populate_security_buffer(buffer, wmi_priv.current_admin_password); + + /* build variables to set */ + start = buffer + security_area_size; + ret = populate_string_buffer(start, a_name_size, a_name); + if (ret < 0) + goto out; + start += ret; + ret = populate_string_buffer(start, a_value_size, a_value); + if (ret < 0) + goto out; + + print_hex_dump_bytes("set attribute data: ", DUMP_PREFIX_NONE, buffer, buffer_size); + ret = call_biosattributes_interface(wmi_priv.bios_attr_wdev, + buffer, buffer_size, + SETATTRIBUTE_METHOD_ID); + if (ret == -EOPNOTSUPP) + dev_err(&wmi_priv.bios_attr_wdev->dev, "admin password must be configured\n"); + else if (ret == -EACCES) + dev_err(&wmi_priv.bios_attr_wdev->dev, "invalid password\n"); + +out: + kfree(buffer); + mutex_unlock(&wmi_priv.mutex); + return ret; +} + +/** + * set_bios_defaults() - Resets BIOS defaults + * @deftype: the type of BIOS value reset to issue. + * + * Resets BIOS defaults + */ +int set_bios_defaults(u8 deftype) +{ + size_t security_area_size, buffer_size; + size_t integer_area_size = sizeof(u8); + char *buffer = NULL; + u8 *defaultType; + int ret; + + mutex_lock(&wmi_priv.mutex); + if (!wmi_priv.bios_attr_wdev) { + ret = -ENODEV; + goto out; + } + + security_area_size = calculate_security_buffer(wmi_priv.current_admin_password); + buffer_size = security_area_size + integer_area_size; + buffer = kzalloc(buffer_size, GFP_KERNEL); + if (!buffer) { + ret = -ENOMEM; + goto out; + } + + /* build security area */ + populate_security_buffer(buffer, wmi_priv.current_admin_password); + + defaultType = buffer + security_area_size; + *defaultType = deftype; + + ret = call_biosattributes_interface(wmi_priv.bios_attr_wdev, buffer, buffer_size, + SETBIOSDEFAULTS_METHOD_ID); + if (ret) + dev_err(&wmi_priv.bios_attr_wdev->dev, "reset BIOS defaults failed: %d\n", ret); + + kfree(buffer); +out: + mutex_unlock(&wmi_priv.mutex); + return ret; +} + +static int bios_attr_set_interface_probe(struct wmi_device *wdev, const void *context) +{ + mutex_lock(&wmi_priv.mutex); + wmi_priv.bios_attr_wdev = wdev; + mutex_unlock(&wmi_priv.mutex); + return 0; +} + +static int bios_attr_set_interface_remove(struct wmi_device *wdev) +{ + mutex_lock(&wmi_priv.mutex); + wmi_priv.bios_attr_wdev = NULL; + mutex_unlock(&wmi_priv.mutex); + return 0; +} + +static const struct wmi_device_id bios_attr_set_interface_id_table[] = { + { .guid_string = DELL_WMI_BIOS_ATTRIBUTES_INTERFACE_GUID }, + { }, +}; +static struct wmi_driver bios_attr_set_interface_driver = { + .driver = { + .name = DRIVER_NAME + }, + .probe = bios_attr_set_interface_probe, + .remove = bios_attr_set_interface_remove, + .id_table = bios_attr_set_interface_id_table, +}; + +int init_bios_attr_set_interface(void) +{ + return wmi_driver_register(&bios_attr_set_interface_driver); +} + +void exit_bios_attr_set_interface(void) +{ + wmi_driver_unregister(&bios_attr_set_interface_driver); +} + +MODULE_DEVICE_TABLE(wmi, bios_attr_set_interface_id_table); diff --git a/drivers/platform/x86/dell-wmi-sysman/dell-wmi-sysman.h b/drivers/platform/x86/dell-wmi-sysman/dell-wmi-sysman.h new file mode 100644 index 000000000000..b80f2a62ea3f --- /dev/null +++ b/drivers/platform/x86/dell-wmi-sysman/dell-wmi-sysman.h @@ -0,0 +1,191 @@ +/* SPDX-License-Identifier: GPL-2.0 + * Definitions for kernel modules using Dell WMI System Management Driver + * + * Copyright (c) 2020 Dell Inc. + */ + +#ifndef _DELL_WMI_BIOS_ATTR_H_ +#define _DELL_WMI_BIOS_ATTR_H_ + +#include +#include +#include +#include +#include + +#define DRIVER_NAME "dell-wmi-sysman" +#define MAX_BUFF 512 + +#define DELL_WMI_BIOS_ENUMERATION_ATTRIBUTE_GUID "F1DDEE52-063C-4784-A11E-8A06684B9BF5" +#define DELL_WMI_BIOS_INTEGER_ATTRIBUTE_GUID "F1DDEE52-063C-4784-A11E-8A06684B9BFA" +#define DELL_WMI_BIOS_STRING_ATTRIBUTE_GUID "F1DDEE52-063C-4784-A11E-8A06684B9BF9" +#define DELL_WMI_BIOS_PASSOBJ_ATTRIBUTE_GUID "0894B8D6-44A6-4719-97D7-6AD24108BFD4" +#define DELL_WMI_BIOS_ATTRIBUTES_INTERFACE_GUID "F1DDEE52-063C-4784-A11E-8A06684B9BF4" +#define DELL_WMI_BIOS_PASSWORD_INTERFACE_GUID "70FE8229-D03B-4214-A1C6-1F884B1A892A" + +struct enumeration_data { + struct kobject *attr_name_kobj; + char display_name_language_code[MAX_BUFF]; + char dell_value_modifier[MAX_BUFF]; + char possible_values[MAX_BUFF]; + char attribute_name[MAX_BUFF]; + char default_value[MAX_BUFF]; + char dell_modifier[MAX_BUFF]; + char display_name[MAX_BUFF]; +}; + +struct integer_data { + struct kobject *attr_name_kobj; + char display_name_language_code[MAX_BUFF]; + char attribute_name[MAX_BUFF]; + char dell_modifier[MAX_BUFF]; + char display_name[MAX_BUFF]; + int scalar_increment; + int default_value; + int min_value; + int max_value; +}; + +struct str_data { + struct kobject *attr_name_kobj; + char display_name_language_code[MAX_BUFF]; + char attribute_name[MAX_BUFF]; + char display_name[MAX_BUFF]; + char default_value[MAX_BUFF]; + char dell_modifier[MAX_BUFF]; + int min_length; + int max_length; +}; + +struct po_data { + struct kobject *attr_name_kobj; + char attribute_name[MAX_BUFF]; + int min_password_length; + int max_password_length; +}; + +struct wmi_sysman_priv { + char current_admin_password[MAX_BUFF]; + char current_system_password[MAX_BUFF]; + struct wmi_device *password_attr_wdev; + struct wmi_device *bios_attr_wdev; + struct kset *authentication_dir_kset; + struct kset *main_dir_kset; + struct device *class_dev; + struct enumeration_data *enumeration_data; + int enumeration_instances_count; + struct integer_data *integer_data; + int integer_instances_count; + struct str_data *str_data; + int str_instances_count; + struct po_data *po_data; + int po_instances_count; + bool pending_changes; + struct mutex mutex; +}; + +/* global structure used by multiple WMI interfaces */ +extern struct wmi_sysman_priv wmi_priv; + +enum { ENUM, INT, STR, PO }; + +enum { + ATTR_NAME, + DISPL_NAME_LANG_CODE, + DISPLAY_NAME, + DEFAULT_VAL, + CURRENT_VAL, + MODIFIER +}; + +#define get_instance_id(type) \ +static int get_##type##_instance_id(struct kobject *kobj) \ +{ \ + int i; \ + for (i = 0; i <= wmi_priv.type##_instances_count; i++) { \ + if (!(strcmp(kobj->name, wmi_priv.type##_data[i].attribute_name)))\ + return i; \ + } \ + return -EIO; \ +} + +#define attribute_s_property_show(name, type) \ +static ssize_t name##_show(struct kobject *kobj, struct kobj_attribute *attr, \ + char *buf) \ +{ \ + int i = get_##type##_instance_id(kobj); \ + if (i >= 0) \ + return sprintf(buf, "%s\n", wmi_priv.type##_data[i].name); \ + return 0; \ +} + +#define attribute_n_property_show(name, type) \ +static ssize_t name##_show(struct kobject *kobj, struct kobj_attribute *attr, \ + char *buf) \ +{ \ + int i = get_##type##_instance_id(kobj); \ + if (i >= 0) \ + return sprintf(buf, "%d\n", wmi_priv.type##_data[i].name); \ + return 0; \ +} + +#define attribute_property_store(curr_val, type) \ +static ssize_t curr_val##_store(struct kobject *kobj, \ + struct kobj_attribute *attr, \ + const char *buf, size_t count) \ +{ \ + char *p, *buf_cp; \ + int i, ret = -EIO; \ + buf_cp = kstrdup(buf, GFP_KERNEL); \ + if (!buf_cp) \ + return -ENOMEM; \ + p = memchr(buf_cp, '\n', count); \ + \ + if (p != NULL) \ + *p = '\0'; \ + i = get_##type##_instance_id(kobj); \ + if (i >= 0) \ + ret = validate_##type##_input(i, buf_cp); \ + if (!ret) \ + ret = set_attribute(kobj->name, buf_cp); \ + kfree(buf_cp); \ + return ret ? ret : count; \ +} + +union acpi_object *get_wmiobj_pointer(int instance_id, const char *guid_string); +int get_instance_count(const char *guid_string); +void strlcpy_attr(char *dest, char *src); + +int populate_enum_data(union acpi_object *enumeration_obj, int instance_id, + struct kobject *attr_name_kobj); +int alloc_enum_data(void); +void exit_enum_attributes(void); + +int populate_int_data(union acpi_object *integer_obj, int instance_id, + struct kobject *attr_name_kobj); +int alloc_int_data(void); +void exit_int_attributes(void); + +int populate_str_data(union acpi_object *str_obj, int instance_id, struct kobject *attr_name_kobj); +int alloc_str_data(void); +void exit_str_attributes(void); + +int populate_po_data(union acpi_object *po_obj, int instance_id, struct kobject *attr_name_kobj); +int alloc_po_data(void); +void exit_po_attributes(void); + +int set_attribute(const char *a_name, const char *a_value); +int set_bios_defaults(u8 defType); + +void exit_bios_attr_set_interface(void); +int init_bios_attr_set_interface(void); +int map_wmi_error(int error_code); +size_t calculate_string_buffer(const char *str); +size_t calculate_security_buffer(char *authentication); +void populate_security_buffer(char *buffer, char *authentication); +ssize_t populate_string_buffer(char *buffer, size_t buffer_len, const char *str); +int set_new_password(const char *password_type, const char *new); +int init_bios_attr_pass_interface(void); +void exit_bios_attr_pass_interface(void); + +#endif diff --git a/drivers/platform/x86/dell-wmi-sysman/enum-attributes.c b/drivers/platform/x86/dell-wmi-sysman/enum-attributes.c new file mode 100644 index 000000000000..80f4b7785c6c --- /dev/null +++ b/drivers/platform/x86/dell-wmi-sysman/enum-attributes.c @@ -0,0 +1,189 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Functions corresponding to enumeration type attributes under + * BIOS Enumeration GUID for use with dell-wmi-sysman + * + * Copyright (c) 2020 Dell Inc. + */ + +#include "dell-wmi-sysman.h" + +get_instance_id(enumeration); + +static ssize_t current_value_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) +{ + int instance_id = get_enumeration_instance_id(kobj); + union acpi_object *obj; + ssize_t ret; + + if (instance_id < 0) + return instance_id; + + /* need to use specific instance_id and guid combination to get right data */ + obj = get_wmiobj_pointer(instance_id, DELL_WMI_BIOS_ENUMERATION_ATTRIBUTE_GUID); + if (!obj) + return -EIO; + if (obj->package.elements[CURRENT_VAL].type != ACPI_TYPE_STRING) { + kfree(obj); + return -EINVAL; + } + ret = snprintf(buf, PAGE_SIZE, "%s\n", obj->package.elements[CURRENT_VAL].string.pointer); + kfree(obj); + return ret; +} + +/** + * validate_enumeration_input() - Validate input of current_value against possible values + * @instance_id: The instance on which input is validated + * @buf: Input value + */ +static int validate_enumeration_input(int instance_id, const char *buf) +{ + char *options, *tmp, *p; + int ret = -EINVAL; + + options = tmp = kstrdup(wmi_priv.enumeration_data[instance_id].possible_values, + GFP_KERNEL); + if (!options) + return -ENOMEM; + + while ((p = strsep(&options, ";")) != NULL) { + if (!*p) + continue; + if (!strcasecmp(p, buf)) { + ret = 0; + break; + } + } + + kfree(tmp); + return ret; +} + +attribute_s_property_show(display_name_language_code, enumeration); +static struct kobj_attribute displ_langcode = + __ATTR_RO(display_name_language_code); + +attribute_s_property_show(display_name, enumeration); +static struct kobj_attribute displ_name = + __ATTR_RO(display_name); + +attribute_s_property_show(default_value, enumeration); +static struct kobj_attribute default_val = + __ATTR_RO(default_value); + +attribute_property_store(current_value, enumeration); +stat