diff options
Diffstat (limited to 'drivers/platform')
47 files changed, 3848 insertions, 1567 deletions
diff --git a/drivers/platform/chrome/Kconfig b/drivers/platform/chrome/Kconfig index ccc23d8686e8..75e93efd669f 100644 --- a/drivers/platform/chrome/Kconfig +++ b/drivers/platform/chrome/Kconfig @@ -243,6 +243,17 @@ config CROS_USBPD_NOTIFY To compile this driver as a module, choose M here: the module will be called cros_usbpd_notify. +config CHROMEOS_PRIVACY_SCREEN + tristate "ChromeOS Privacy Screen support" + depends on ACPI + depends on DRM + select DRM_PRIVACY_SCREEN + help + This driver provides the support needed for the in-built electronic + privacy screen that is present on some ChromeOS devices. When enabled, + this should probably always be built into the kernel to avoid or + minimize drm probe deferral. + source "drivers/platform/chrome/wilco_ec/Kconfig" endif # CHROMEOS_PLATFORMS diff --git a/drivers/platform/chrome/Makefile b/drivers/platform/chrome/Makefile index f901d2e43166..5d4be9735d9d 100644 --- a/drivers/platform/chrome/Makefile +++ b/drivers/platform/chrome/Makefile @@ -4,6 +4,7 @@ CFLAGS_cros_ec_trace.o:= -I$(src) obj-$(CONFIG_CHROMEOS_LAPTOP) += chromeos_laptop.o +obj-$(CONFIG_CHROMEOS_PRIVACY_SCREEN) += chromeos_privacy_screen.o obj-$(CONFIG_CHROMEOS_PSTORE) += chromeos_pstore.o obj-$(CONFIG_CHROMEOS_TBMC) += chromeos_tbmc.o obj-$(CONFIG_CROS_EC) += cros_ec.o diff --git a/drivers/platform/chrome/chromeos_privacy_screen.c b/drivers/platform/chrome/chromeos_privacy_screen.c new file mode 100644 index 000000000000..77e9f5ee8e33 --- /dev/null +++ b/drivers/platform/chrome/chromeos_privacy_screen.c @@ -0,0 +1,153 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* + * ChromeOS Privacy Screen support + * + * Copyright (C) 2022 Google LLC + * + * This is the Chromeos privacy screen provider, present on certain chromebooks, + * represented by a GOOG0010 device in the ACPI. This ACPI device, if present, + * will cause the i915 drm driver to probe defer until this driver registers + * the privacy-screen. + */ + +#include <linux/acpi.h> +#include <drm/drm_privacy_screen_driver.h> + +/* + * The DSM (Device Specific Method) constants below are the agreed API with + * the firmware team, on how to control privacy screen using ACPI methods. + */ +#define PRIV_SCRN_DSM_REVID 1 /* DSM version */ +#define PRIV_SCRN_DSM_FN_GET_STATUS 1 /* Get privacy screen status */ +#define PRIV_SCRN_DSM_FN_ENABLE 2 /* Enable privacy screen */ +#define PRIV_SCRN_DSM_FN_DISABLE 3 /* Disable privacy screen */ + +static const guid_t chromeos_privacy_screen_dsm_guid = + GUID_INIT(0xc7033113, 0x8720, 0x4ceb, + 0x90, 0x90, 0x9d, 0x52, 0xb3, 0xe5, 0x2d, 0x73); + +static void +chromeos_privacy_screen_get_hw_state(struct drm_privacy_screen + *drm_privacy_screen) +{ + union acpi_object *obj; + acpi_handle handle; + struct device *privacy_screen = + drm_privacy_screen_get_drvdata(drm_privacy_screen); + + handle = acpi_device_handle(to_acpi_device(privacy_screen)); + obj = acpi_evaluate_dsm(handle, &chromeos_privacy_screen_dsm_guid, + PRIV_SCRN_DSM_REVID, + PRIV_SCRN_DSM_FN_GET_STATUS, NULL); + if (!obj) { + dev_err(privacy_screen, + "_DSM failed to get privacy-screen state\n"); + return; + } + + if (obj->type != ACPI_TYPE_INTEGER) + dev_err(privacy_screen, + "Bad _DSM to get privacy-screen state\n"); + else if (obj->integer.value == 1) + drm_privacy_screen->hw_state = drm_privacy_screen->sw_state = + PRIVACY_SCREEN_ENABLED; + else + drm_privacy_screen->hw_state = drm_privacy_screen->sw_state = + PRIVACY_SCREEN_DISABLED; + + ACPI_FREE(obj); +} + +static int +chromeos_privacy_screen_set_sw_state(struct drm_privacy_screen + *drm_privacy_screen, + enum drm_privacy_screen_status state) +{ + union acpi_object *obj = NULL; + acpi_handle handle; + struct device *privacy_screen = + drm_privacy_screen_get_drvdata(drm_privacy_screen); + + handle = acpi_device_handle(to_acpi_device(privacy_screen)); + + if (state == PRIVACY_SCREEN_DISABLED) { + obj = acpi_evaluate_dsm(handle, + &chromeos_privacy_screen_dsm_guid, + PRIV_SCRN_DSM_REVID, + PRIV_SCRN_DSM_FN_DISABLE, NULL); + } else if (state == PRIVACY_SCREEN_ENABLED) { + obj = acpi_evaluate_dsm(handle, + &chromeos_privacy_screen_dsm_guid, + PRIV_SCRN_DSM_REVID, + PRIV_SCRN_DSM_FN_ENABLE, NULL); + } else { + dev_err(privacy_screen, + "Bad attempt to set privacy-screen status to %u\n", + state); + return -EINVAL; + } + + if (!obj) { + dev_err(privacy_screen, + "_DSM failed to set privacy-screen state\n"); + return -EIO; + } + + drm_privacy_screen->hw_state = drm_privacy_screen->sw_state = state; + ACPI_FREE(obj); + return 0; +} + +static const struct drm_privacy_screen_ops chromeos_privacy_screen_ops = { + .get_hw_state = chromeos_privacy_screen_get_hw_state, + .set_sw_state = chromeos_privacy_screen_set_sw_state, +}; + +static int chromeos_privacy_screen_add(struct acpi_device *adev) +{ + struct drm_privacy_screen *drm_privacy_screen = + drm_privacy_screen_register(&adev->dev, + &chromeos_privacy_screen_ops, + &adev->dev); + + if (IS_ERR(drm_privacy_screen)) { + dev_err(&adev->dev, "Error registering privacy-screen\n"); + return PTR_ERR(drm_privacy_screen); + } + + adev->driver_data = drm_privacy_screen; + dev_info(&adev->dev, "registered privacy-screen '%s'\n", + dev_name(&drm_privacy_screen->dev)); + + return 0; +} + +static int chromeos_privacy_screen_remove(struct acpi_device *adev) +{ + struct drm_privacy_screen *drm_privacy_screen = acpi_driver_data(adev); + + drm_privacy_screen_unregister(drm_privacy_screen); + return 0; +} + +static const struct acpi_device_id chromeos_privacy_screen_device_ids[] = { + {"GOOG0010", 0}, /* Google's electronic privacy screen for eDP-1 */ + {} +}; +MODULE_DEVICE_TABLE(acpi, chromeos_privacy_screen_device_ids); + +static struct acpi_driver chromeos_privacy_screen_driver = { + .name = "chromeos_privacy_screen_driver", + .class = "ChromeOS", + .ids = chromeos_privacy_screen_device_ids, + .ops = { + .add = chromeos_privacy_screen_add, + .remove = chromeos_privacy_screen_remove, + }, +}; + +module_acpi_driver(chromeos_privacy_screen_driver); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("ChromeOS ACPI Privacy Screen driver"); +MODULE_AUTHOR("Rajat Jain <rajatja@google.com>"); diff --git a/drivers/platform/chrome/cros_ec.c b/drivers/platform/chrome/cros_ec.c index fc5aa1525d13..d49a4efe46c8 100644 --- a/drivers/platform/chrome/cros_ec.c +++ b/drivers/platform/chrome/cros_ec.c @@ -302,13 +302,11 @@ EXPORT_SYMBOL(cros_ec_register); * * Return: 0 on success or negative error code. */ -int cros_ec_unregister(struct cros_ec_device *ec_dev) +void cros_ec_unregister(struct cros_ec_device *ec_dev) { if (ec_dev->pd) platform_device_unregister(ec_dev->pd); platform_device_unregister(ec_dev->ec); - - return 0; } EXPORT_SYMBOL(cros_ec_unregister); diff --git a/drivers/platform/chrome/cros_ec.h b/drivers/platform/chrome/cros_ec.h index 78363dcfdf23..bbca0096868a 100644 --- a/drivers/platform/chrome/cros_ec.h +++ b/drivers/platform/chrome/cros_ec.h @@ -11,7 +11,7 @@ #include <linux/interrupt.h> int cros_ec_register(struct cros_ec_device *ec_dev); -int cros_ec_unregister(struct cros_ec_device *ec_dev); +void cros_ec_unregister(struct cros_ec_device *ec_dev); int cros_ec_suspend(struct cros_ec_device *ec_dev); int cros_ec_resume(struct cros_ec_device *ec_dev); diff --git a/drivers/platform/chrome/cros_ec_i2c.c b/drivers/platform/chrome/cros_ec_i2c.c index 30c8938c27d5..22feb0fd4ce7 100644 --- a/drivers/platform/chrome/cros_ec_i2c.c +++ b/drivers/platform/chrome/cros_ec_i2c.c @@ -313,7 +313,9 @@ static int cros_ec_i2c_remove(struct i2c_client *client) { struct cros_ec_device *ec_dev = i2c_get_clientdata(client); - return cros_ec_unregister(ec_dev); + cros_ec_unregister(ec_dev); + + return 0; } #ifdef CONFIG_PM_SLEEP diff --git a/drivers/platform/chrome/cros_ec_lpc.c b/drivers/platform/chrome/cros_ec_lpc.c index d6306d2a096f..7651417b4a25 100644 --- a/drivers/platform/chrome/cros_ec_lpc.c +++ b/drivers/platform/chrome/cros_ec_lpc.c @@ -439,7 +439,9 @@ static int cros_ec_lpc_remove(struct platform_device *pdev) acpi_remove_notify_handler(adev->handle, ACPI_ALL_NOTIFY, cros_ec_lpc_acpi_notify); - return cros_ec_unregister(ec_dev); + cros_ec_unregister(ec_dev); + + return 0; } static const struct acpi_device_id cros_ec_lpc_acpi_device_ids[] = { diff --git a/drivers/platform/chrome/cros_ec_spi.c b/drivers/platform/chrome/cros_ec_spi.c index 14c4046fa04d..8493af0f680e 100644 --- a/drivers/platform/chrome/cros_ec_spi.c +++ b/drivers/platform/chrome/cros_ec_spi.c @@ -786,11 +786,11 @@ static int cros_ec_spi_probe(struct spi_device *spi) return 0; } -static int cros_ec_spi_remove(struct spi_device *spi) +static void cros_ec_spi_remove(struct spi_device *spi) { struct cros_ec_device *ec_dev = spi_get_drvdata(spi); - return cros_ec_unregister(ec_dev); + cros_ec_unregister(ec_dev); } #ifdef CONFIG_PM_SLEEP diff --git a/drivers/platform/olpc/olpc-xo175-ec.c b/drivers/platform/olpc/olpc-xo175-ec.c index 0d46706afd2d..4823bd2819f6 100644 --- a/drivers/platform/olpc/olpc-xo175-ec.c +++ b/drivers/platform/olpc/olpc-xo175-ec.c @@ -648,7 +648,7 @@ static struct olpc_ec_driver olpc_xo175_ec_driver = { .ec_cmd = olpc_xo175_ec_cmd, }; -static int olpc_xo175_ec_remove(struct spi_device *spi) +static void olpc_xo175_ec_remove(struct spi_device *spi) { if (pm_power_off == olpc_xo175_ec_power_off) pm_power_off = NULL; @@ -657,8 +657,6 @@ static int olpc_xo175_ec_remove(struct spi_device *spi) platform_device_unregister(olpc_ec); olpc_ec = NULL; - - return 0; } static int olpc_xo175_ec_probe(struct spi_device *spi) diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig index 463f1ec5c14e..eb79fbed8059 100644 --- a/drivers/platform/surface/Kconfig +++ b/drivers/platform/surface/Kconfig @@ -28,13 +28,6 @@ config SURFACE3_WMI 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 - depends on 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 diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile index 32889482de55..0fc9cd3e4dd9 100644 --- a/drivers/platform/surface/Makefile +++ b/drivers/platform/surface/Makefile @@ -5,7 +5,6 @@ # 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_ACPI_NOTIFY) += surface_acpi_notify.o obj-$(CONFIG_SURFACE_AGGREGATOR) += aggregator/ diff --git a/drivers/platform/surface/surface3-wmi.c b/drivers/platform/surface/surface3-wmi.c index 09ac9cfc40d8..ca4602bcc7de 100644 --- a/drivers/platform/surface/surface3-wmi.c +++ b/drivers/platform/surface/surface3-wmi.c @@ -116,15 +116,11 @@ static acpi_status s3_wmi_attach_spi_device(acpi_handle handle, void *data, void **return_value) { - struct acpi_device *adev, **ts_adev; + struct acpi_device *adev = acpi_fetch_acpi_dev(handle); + struct acpi_device **ts_adev = data; - 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))) + if (!adev || strncmp(acpi_device_bid(adev), SPI_TS_OBJ_NAME, + strlen(SPI_TS_OBJ_NAME))) return AE_OK; if (*ts_adev) { @@ -190,14 +186,11 @@ static int s3_wmi_create_and_register_input(struct platform_device *pdev) error = input_register_device(input); if (error) - goto out_err; + return error; 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) diff --git a/drivers/platform/surface/surface3_button.c b/drivers/platform/surface/surface3_button.c deleted file mode 100644 index 48d77e7aae76..000000000000 --- a/drivers/platform/surface/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 <linux/module.h> -#include <linux/input.h> -#include <linux/init.h> -#include <linux/kernel.h> -#include <linux/i2c.h> -#include <linux/slab.h> -#include <linux/acpi.h> -#include <linux/gpio/consumer.h> -#include <linux/gpio_keys.h> -#include <linux/gpio.h> -#include <linux/platform_device.h> - - -#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 <benjamin.tissoires@gmail.com>"); -MODULE_DESCRIPTION("surface3 button array driver"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/platform/surface/surface_acpi_notify.c b/drivers/platform/surface/surface_acpi_notify.c index 8339988d95c1..7b758f8cc137 100644 --- a/drivers/platform/surface/surface_acpi_notify.c +++ b/drivers/platform/surface/surface_acpi_notify.c @@ -770,7 +770,8 @@ static acpi_status san_consumer_setup(acpi_handle handle, u32 lvl, return AE_OK; /* Ignore ACPI devices that are not present. */ - if (acpi_bus_get_device(handle, &adev) != 0) + adev = acpi_fetch_acpi_dev(handle); + if (!adev) return AE_OK; san_consumer_dbg(&pdev->dev, handle, "creating device link\n"); diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 24deeeb29af2..5d9dd70e4e0f 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -210,6 +210,19 @@ config AMD_PMC If you choose to compile this driver as a module the module will be called amd-pmc. +config AMD_HSMP + tristate "AMD HSMP Driver" + depends on AMD_NB && X86_64 + help + The driver provides a way for user space tools to monitor and manage + system management functionality on EPYC server CPUs from AMD. + + Host System Management Port (HSMP) interface is a mailbox interface + between the x86 core and the System Management Unit (SMU) firmware. + + If you choose to compile this driver as a module the module will be + called amd_hsmp. + config ADV_SWBUTTON tristate "Advantech ACPI Software Button Driver" depends on ACPI && INPUT @@ -915,6 +928,7 @@ config COMPAL_LAPTOP config LG_LAPTOP tristate "LG Laptop Extras" depends on ACPI + depends on ACPI_BATTERY depends on ACPI_WMI depends on INPUT select INPUT_SPARSEKMAP @@ -990,16 +1004,16 @@ config TOPSTAR_LAPTOP If you have a Topstar laptop, say Y or M here. -config I2C_MULTI_INSTANTIATE - tristate "I2C multi instantiate pseudo device driver" - depends on I2C && ACPI +config SERIAL_MULTI_INSTANTIATE + tristate "Serial bus multi instantiate pseudo device driver" + depends on I2C && SPI && ACPI help - Some ACPI-based systems list multiple i2c-devices in a single ACPI - firmware-node. This driver will instantiate separate i2c-clients + Some ACPI-based systems list multiple devices in a single ACPI + firmware-node. This driver will instantiate separate clients for each device in the firmware-node. To compile this driver as a module, choose M here: the module - will be called i2c-multi-instantiate. + will be called serial-multi-instantiate. config MLX_PLATFORM tristate "Mellanox Technologies platform support" @@ -1027,7 +1041,7 @@ config TOUCHSCREEN_DMI config X86_ANDROID_TABLETS tristate "X86 Android tablet support" - depends on I2C && SERIAL_DEV_BUS && ACPI && GPIOLIB + depends on I2C && SPI && SERIAL_DEV_BUS && ACPI && EFI && GPIOLIB help X86 tablets which ship with Android as (part of) the factory image typically have various problems with their DSDTs. The factory kernels diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index c12a9b044fd8..fe4d4c8970ef 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -24,6 +24,7 @@ obj-$(CONFIG_ACER_WMI) += acer-wmi.o # AMD obj-$(CONFIG_AMD_PMC) += amd-pmc.o +obj-$(CONFIG_AMD_HSMP) += amd_hsmp.o # Advantech obj-$(CONFIG_ADV_SWBUTTON) += adv_swbutton.o @@ -110,7 +111,7 @@ obj-$(CONFIG_TOPSTAR_LAPTOP) += topstar-laptop.o # Platform drivers obj-$(CONFIG_FW_ATTR_CLASS) += firmware_attributes_class.o -obj-$(CONFIG_I2C_MULTI_INSTANTIATE) += i2c-multi-instantiate.o +obj-$(CONFIG_SERIAL_MULTI_INSTANTIATE) += serial-multi-instantiate.o obj-$(CONFIG_MLX_PLATFORM) += mlx-platform.o obj-$(CONFIG_TOUCHSCREEN_DMI) += touchscreen_dmi.o obj-$(CONFIG_WIRELESS_HOTKEY) += wireless-hotkey.o diff --git a/drivers/platform/x86/amd-pmc.c b/drivers/platform/x86/amd-pmc.c index b1103f85a85a..e9d0dbbb2887 100644 --- a/drivers/platform/x86/amd-pmc.c +++ b/drivers/platform/x86/amd-pmc.c @@ -21,7 +21,6 @@ #include <linux/module.h> #include <linux/pci.h> #include <linux/platform_device.h> -#include <linux/pm_qos.h> #include <linux/rtc.h> #include <linux/suspend.h> #include <linux/seq_file.h> @@ -42,6 +41,16 @@ #define AMD_PMC_STB_PMI_0 0x03E30600 #define AMD_PMC_STB_PREDEF 0xC6000001 +/* STB S2D(Spill to DRAM) has different message port offset */ +#define STB_SPILL_TO_DRAM 0xBE +#define AMD_S2D_REGISTER_MESSAGE 0xA20 +#define AMD_S2D_REGISTER_RESPONSE 0xA80 +#define AMD_S2D_REGISTER_ARGUMENT 0xA88 + +/* STB Spill to DRAM Parameters */ +#define S2D_TELEMETRY_BYTES_MAX 0x100000 +#define S2D_TELEMETRY_DRAMBYTES_MAX 0x1000000 + /* Base address of SMU for mapping physical address to virtual address */ #define AMD_PMC_SMU_INDEX_ADDRESS 0xB8 #define AMD_PMC_SMU_INDEX_DATA 0xBC @@ -86,9 +95,6 @@ #define PMC_MSG_DELAY_MIN_US 50 #define RESPONSE_REGISTER_LOOP_MAX 20000 -/* QoS request for letting CPUs in idle states, but not the deepest */ -#define AMD_PMC_MAX_IDLE_STATE_LATENCY 3 - #define SOC_SUBSYSTEM_IP_MAX 12 #define DELAY_MIN_US 2000 #define DELAY_MAX_US 3000 @@ -99,6 +105,12 @@ enum amd_pmc_def { MSG_OS_HINT_RN, }; +enum s2d_arg { + S2D_TELEMETRY_SIZE = 0x01, + S2D_PHYS_ADDR_LOW, + S2D_PHYS_ADDR_HIGH, +}; + struct amd_pmc_bit_map { const char *name; u32 bit_mask; @@ -123,7 +135,9 @@ static const struct amd_pmc_bit_map soc15_ip_blk[] = { struct amd_pmc_dev { void __iomem *regbase; void __iomem *smu_virt_addr; + void __iomem *stb_virt_addr; void __iomem *fch_virt_addr; + bool msg_port; u32 base_addr; u32 cpu_id; u32 active_ips; @@ -135,7 +149,6 @@ struct amd_pmc_dev { struct device *dev; struct pci_dev *rdev; struct mutex lock; /* generic mutex lock */ - struct pm_qos_request amd_pmc_pm_qos_req; #if IS_ENABLED(CONFIG_DEBUG_FS) struct dentry *dbgfs_dir; #endif /* CONFIG_DEBUG_FS */ @@ -241,6 +254,44 @@ static const struct file_operations amd_pmc_stb_debugfs_fops = { .release = amd_pmc_stb_debugfs_release, }; +static int amd_pmc_stb_debugfs_open_v2(struct inode *inode, struct file *filp) +{ + struct amd_pmc_dev *dev = filp->f_inode->i_private; + u32 *buf; + + buf = kzalloc(S2D_TELEMETRY_BYTES_MAX, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + memcpy_fromio(buf, dev->stb_virt_addr, S2D_TELEMETRY_BYTES_MAX); + filp->private_data = buf; + + return 0; +} + +static ssize_t amd_pmc_stb_debugfs_read_v2(struct file *filp, char __user *buf, size_t size, + loff_t *pos) +{ + if (!filp->private_data) + return -EINVAL; + + return simple_read_from_buffer(buf, size, pos, filp->private_data, + S2D_TELEMETRY_BYTES_MAX); +} + +static int amd_pmc_stb_debugfs_release_v2(struct inode *inode, struct file *filp) +{ + kfree(filp->private_data); + return 0; +} + +static const struct file_operations amd_pmc_stb_debugfs_fops_v2 = { + .owner = THIS_MODULE, + .open = amd_pmc_stb_debugfs_open_v2, + .read = amd_pmc_stb_debugfs_read_v2, + .release = amd_pmc_stb_debugfs_release_v2, +}; + static int amd_pmc_idlemask_read(struct amd_pmc_dev *pdev, struct device *dev, struct seq_file *s) { @@ -266,6 +317,28 @@ static int amd_pmc_idlemask_read(struct amd_pmc_dev *pdev, struct device *dev, return 0; } +static int get_metrics_table(struct amd_pmc_dev *pdev, struct smu_metrics *table) +{ + if (pdev->cpu_id == AMD_CPU_ID_PCO) + return -ENODEV; + memcpy_fromio(table, pdev->smu_virt_addr, sizeof(struct smu_metrics)); + return 0; +} + +static void amd_pmc_validate_deepest(struct amd_pmc_dev *pdev) +{ + struct smu_metrics table; + + if (get_metrics_table(pdev, &table)) + return; + + if (!table.s0i3_last_entry_status) + dev_warn(pdev->dev, "Last suspend didn't reach deepest state\n"); + else + dev_dbg(pdev->dev, "Last suspend in deepest state for %lluus\n", + table.timein_s0i3_lastcapture); +} + #ifdef CONFIG_DEBUG_FS static int smu_fw_info_show(struct seq_file *s, void *unused) { @@ -273,11 +346,9 @@ static int smu_fw_info_show(struct seq_file *s, void *unused) struct smu_metrics table; int idx; - if (dev->cpu_id == AMD_CPU_ID_PCO) + if (get_metrics_table(dev, &table)) return -EINVAL; - memcpy_fromio(&table, dev->smu_virt_addr, sizeof(struct smu_metrics)); - seq_puts(s, "\n=== SMU Statistics ===\n"); seq_printf(s, "Table Version: %d\n", table.table_version); seq_printf(s, "Hint Count: %d\n", table.hint_count); @@ -355,9 +426,14 @@ static void amd_pmc_dbgfs_register(struct amd_pmc_dev *dev) debugfs_create_file("amd_pmc_idlemask", 0644, dev->dbgfs_dir, dev, &amd_pmc_idlemask_fops); /* Enable STB only when the module_param is set */ - if (enable_stb) - debugfs_create_file("stb_read", 0644, dev->dbgfs_dir, dev, - &amd_pmc_stb_debugfs_fops); + if (enable_stb) { + if (dev->cpu_id == AMD_CPU_ID_YC) + debugfs_create_file("stb_read", 0644, dev->dbgfs_dir, dev, + &amd_pmc_stb_debugfs_fops_v2); + else + debugfs_create_file("stb_read", 0644, dev->dbgfs_dir, dev, + &amd_pmc_stb_debugfs_fops); + } } #else static inline void amd_pmc_dbgfs_register(struct amd_pmc_dev *dev) @@ -397,26 +473,47 @@ static int amd_pmc_setup_smu_logging(struct amd_pmc_dev *dev) static void amd_pmc_dump_registers(struct amd_pmc_dev *dev) { - u32 value; + u32 value, message, argument, response; + + if (dev->msg_port) { + message = AMD_S2D_REGISTER_MESSAGE; + argument = AMD_S2D_REGISTER_ARGUMENT; + response = AMD_S2D_REGISTER_RESPONSE; + } else { + message = AMD_PMC_REGISTER_MESSAGE; + argument = AMD_PMC_REGISTER_ARGUMENT; + response = AMD_PMC_REGISTER_RESPONSE; + } - value = amd_pmc_reg_read(dev, AMD_PMC_REGISTER_RESPONSE); + value = amd_pmc_reg_read(dev, response); dev_dbg(dev->dev, "AMD_PMC_REGISTER_RESPONSE:%x\n", value); - value = amd_pmc_reg_read(dev, AMD_PMC_REGISTER_ARGUMENT); + value = amd_pmc_reg_read(dev, argument); dev_dbg(dev->dev, "AMD_PMC_REGISTER_ARGUMENT:%x\n", value); - value = amd_pmc_reg_read(dev, AMD_PMC_REGISTER_MESSAGE); + value = amd_pmc_reg_read(dev, message); dev_dbg(dev->dev, "AMD_PMC_REGISTER_MESSAGE:%x\n", value); } static int amd_pmc_send_cmd(struct amd_pmc_dev *dev, u32 arg, u32 *data, u8 msg, bool ret) { int rc; - u32 val; + u32 val, message, argument, response; mutex_lock(&dev->lock); + + if (dev->msg_port) { + message = AMD_S2D_REGISTER_MESSAGE; + argument = AMD_S2D_REGISTER_ARGUMENT; + response = AMD_S2D_REGISTER_RESPONSE; + } else { + message = AMD_PMC_REGISTER_MESSAGE; + argument = AMD_PMC_REGISTER_ARGUMENT; + response = AMD_PMC_REGISTER_RESPONSE; + } + /* Wait until we get a valid response */ - rc = readx_poll_timeout(ioread32, dev->regbase + AMD_PMC_REGISTER_RESPONSE, + rc = readx_poll_timeout(ioread32, dev->regbase + response, val, val != 0, PMC_MSG_DELAY_MIN_US, PMC_MSG_DELAY_MIN_US * RESPONSE_REGISTER_LOOP_MAX); if (rc) { @@ -425,16 +522,16 @@ static int amd_pmc_send_cmd(struct amd_pmc_dev *dev, u32 arg, u32 *data, u8 msg, } /* Write zero to response register */ - amd_pmc_reg_write(dev, AMD_PMC_REGISTER_RESPONSE, 0); + amd_pmc_reg_write(dev, response, 0); /* Write argument into response register */ - amd_pmc_reg_write(dev, AMD_PMC_REGISTER_ARGUMENT, arg); + amd_pmc_reg_write(dev, argument, arg); /* Write message ID to message ID register */ - amd_pmc_reg_write(dev, AMD_PMC_REGISTER_MESSAGE, msg); + amd_pmc_reg_write(dev, message, msg); /* Wait until we get a valid response */ - rc = readx_poll_timeout(ioread32, dev->regbase + AMD_PMC_REGISTER_RESPONSE, + rc = readx_poll_timeout(ioread32, dev->regbase + response, val, val != 0, PMC_MSG_DELAY_MIN_US, PMC_MSG_DELAY_MIN_US * RESPONSE_REGISTER_LOOP_MAX); if (rc) { @@ -447,7 +544,7 @@ static int amd_pmc_send_cmd(struct amd_pmc_dev *dev, u32 arg, u32 *data, u8 msg, if (ret) { /* PMFW may take longer time to return back the data */ usleep_range(DELAY_MIN_US, 10 * DELAY_MAX_US); - *data = amd_pmc_reg_read(dev, AMD_PMC_REGISTER_ARGUMENT); + *data = amd_pmc_reg_read(dev, argument); } break; case AMD_PMC_RESULT_CMD_REJECT_BUSY: @@ -526,20 +623,12 @@ static int amd_pmc_verify_czn_rtc(struct amd_pmc_dev *pdev, u32 *arg) rc = rtc_alarm_irq_enable(rtc_device, 0); dev_dbg(pdev->dev, "wakeup timer programmed for %lld seconds\n", duration); - /* - * Prevent CPUs from getting into deep idle states while sending OS_HINT - * which is otherwise generally safe to send when at least one of the CPUs - * is not in deep idle states. - */ - cpu_latency_qos_update_request(&pdev->amd_pmc_pm_qos_req, AMD_PMC_MAX_IDLE_STATE_LATENCY); - wake_up_all_idle_cpus(); - return rc; } -static int __maybe_unused amd_pmc_suspend(struct device *dev) +static void amd_pmc_s2idle_prepare(void) { - struct amd_pmc_dev *pdev = dev_get_drvdata(dev); + struct amd_pmc_dev *pdev = &pmc; int rc; u8 msg; u32 arg = 1; @@ -551,68 +640,59 @@ static int __maybe_unused amd_pmc_suspend(struct device *dev) /* Activate CZN specific RTC functionality */ if (pdev->cpu_id == AMD_CPU_ID_CZN) { rc = amd_pmc_verify_czn_rtc(pdev, &arg); - if (rc) - goto fail; + if (rc) { + dev_err(pdev->dev, "failed to set RTC: %d\n", rc); + return; + } } /* Dump the IdleMask before we send hint to SMU */ - amd_pmc_idlemask_read(pdev, dev, NULL); + amd_pmc_idlemask_read(pdev, pdev->dev, NULL); msg = amd_pmc_get_os_hint(pdev); rc = amd_pmc_send_cmd(pdev, arg, NULL, msg, 0); if (rc) { - dev_err(pdev->dev, "suspend failed\n"); - goto fail; + dev_err(pdev->dev, "suspend failed: %d\n", rc); + return; } - if (enable_stb) + if (enable_stb) { rc = amd_pmc_write_stb(pdev, AMD_PMC_STB_PREDEF); - if (rc) { - dev_err(pdev->dev, "error writing to STB\n"); - goto fail; + if (rc) + dev_err(pdev->dev, "error writing to STB: %d\n", rc); } - - return 0; -fail: - if (pdev->cpu_id == AMD_CPU_ID_CZN) - cpu_latency_qos_update_request(&pdev->amd_pmc_pm_qos_req, - PM_QOS_DEFAULT_VALUE); - return rc; } -static int __maybe_unused amd_pmc_resume(struct device *dev) +static void amd_pmc_s2idle_restore(void) { - struct amd_pmc_dev *pdev = dev_get_drvdata(dev); + struct amd_pmc_dev *pdev = &pmc; int rc; u8 msg; msg = amd_pmc_get_os_hint(pdev); rc = amd_pmc_send_cmd(pdev, 0, NULL, msg, 0); if (rc) - dev_err(pdev->dev, "resume failed\n"); + dev_err(pdev->dev, "resume failed: %d\n", rc); /* Let SMU know that we are looking for stats */ amd_pmc_send_cmd(pdev, 0, NULL, SMU_MSG_LOG_DUMP_DATA, 0); /* Dump the IdleMask to see the blockers */ - amd_pmc_idlemask_read(pdev, dev, NULL); + amd_pmc_idlemask_read(pdev, pdev->dev, NULL); /* Write data incremented by 1 to distinguish in stb_read */ - if (enable_stb) + if (enable_stb) { rc = amd_pmc_write_stb(pdev, AMD_PMC_STB_PREDEF + 1); - if (rc) - dev_err(pdev->dev, "error writing to STB\n"); - - /* Restore the QoS request back to defaults if it was set */ - if (pdev->cpu_id == AMD_CPU_ID_CZN) - cpu_latency_qos_update_request(&pdev->amd_pmc_pm_qos_req, - PM_QOS_DEFAULT_VALUE); + if (rc) + dev_err(pdev->dev, "error writing to STB: %d\n", rc); + } - return rc; + /* Notify on failed entry */ + amd_pmc_validate_deepest(pdev); } -static const struct dev_pm_ops amd_pmc_pm_ops = { - .suspend_noirq = amd_pmc_suspend, - .resume_noirq = amd_pmc_resume, +static struct acpi_s2idle_dev_ops amd_pmc_s2idle_dev_ops = { + .prepare = amd_pmc_s2idle_prepare, + .restore = amd_pmc_s2idle_restore, }; static const struct pci_device_id pmc_pci_ids[] = { @@ -624,6 +704,35 @@ static const struct pci_device_id pmc_pci_ids[] = { { } }; +static int amd_pmc_s2d_init(struct amd_pmc_dev *dev) +{ + u32 phys_addr_low, phys_addr_hi; + u64 stb_phys_addr; + u32 size = 0; + + /* Spill to DRAM feature uses separate SMU message port */ + dev->msg_port = 1; + + amd_pmc_send_cmd(dev, S2D_TELEMETRY_SIZE, &size, STB_SPILL_TO_DRAM, 1); + if (size != S2D_TELEMETRY_BYTES_MAX) + return -EIO; + + /* Get STB DRAM address */ + amd_pmc_send_cmd(dev, S2D_PHYS_ADDR_LOW, &phys_addr_low, STB_SPILL_TO_DRAM, 1); + amd_pmc_send_cmd(dev, S2D_PHYS_ADDR_HIGH, &phys_addr_hi, STB_SPILL_TO_DRAM, 1); + + stb_phys_addr = ((u64)phys_addr_hi << 32 | phys_addr_low); + + /* Clear msg_port for other SMU operation */ + dev->msg_port = 0; + + dev->stb_virt_addr = devm_ioremap(dev->dev, stb_phys_addr, S2D_TELEMETRY_DRAMBYTES_MAX); + if (!dev->stb_virt_addr) + return -ENOMEM; + + return 0; +} + static int amd_pmc_write_stb(struct amd_pmc_dev *dev, u32 data) { int err; @@ -742,10 +851,19 @@ static int amd_pmc_probe(struct platform_device *pdev) if (err) dev_err(dev->dev, "SMU debugging info not supported on this platform\n"); + if (enable_stb && dev->cpu_id == AMD_CPU_ID_YC) { + err = amd_pmc_s2d_init(dev); + if (err) + return err; + } + amd_pmc_get_smu_version(dev); platform_set_drvdata(pdev, dev); + err = acpi_register_lps0_dev(&amd_pmc_s2idle_dev_ops); + if (err) + dev_warn(dev->dev, "failed to register LPS0 sleep handler, expect increased power consumption\n"); + amd_pmc_dbgfs_register(dev); - cpu_latency_qos_add_request(&dev->amd_pmc_pm_qos_req, PM_QOS_DEFAULT_VALUE); return 0; err_pci_dev_put: @@ -757,6 +875,7 @@ static int amd_pmc_remove(struct platform_device *pdev) { struct amd_pmc_dev *dev = platform_get_drvdata(pdev); + acpi_unregister_lps0_dev(&amd_pmc_s2idle_dev_ops); amd_pmc_dbgfs_unregister(dev); pci_dev_put(dev->rdev); mutex_destroy(&dev->lock); @@ -777,7 +896,6 @@ static struct platform_driver amd_pmc_driver = { .driver = { .name = "amd_pmc", .acpi_match_table = amd_pmc_acpi_ids, - .pm = &amd_pmc_pm_ops, }, .probe = amd_pmc_probe, .remove = amd_pmc_remove, diff --git a/drivers/platform/x86/amd_hsmp.c b/drivers/platform/x86/amd_hsmp.c new file mode 100644 index 000000000000..a0c54b838c11 --- /dev/null +++ b/drivers/platform/x86/amd_hsmp.c @@ -0,0 +1,425 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * AMD HSMP Platform Driver + * Copyright (c) 2022, AMD. + * All Rights Reserved. + * + * This file provides a device implementation for HSMP interface + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <asm/amd_hsmp.h> +#include <asm/amd_nb.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/miscdevice.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/platform_device.h> +#include <linux/semaphore.h> + +#define DRIVER_NAME "amd_hsmp" +#define DRIVER_VERSION "1.0" + +/* HSMP Status / Error codes */ +#define HSMP_STATUS_NOT_READY 0x00 +#define HSMP_STATUS_OK 0x01 +#define HSMP_ERR_INVALID_MSG 0xFE +#define HSMP_ERR_INVALID_INPUT 0xFF + +/* Timeout in millsec */ +#define HSMP_MSG_TIMEOUT 100 +#define HSMP_SHORT_SLEEP 1 + +#define HSMP_WR true +#define HSMP_RD false + +/* + * To access specific HSMP mailbox register, s/w writes the SMN address of HSMP mailbox + * register into the SMN_INDEX register, and reads/writes the SMN_DATA reg. + * Below are required SMN address for HSMP Mailbox register offsets in SMU address space + */ +#define SMN_HSMP_MSG_ID 0x3B10534 +#define SMN_HSMP_MSG_RESP 0x3B10980 +#define SMN_HSMP_MSG_DATA 0x3B109E0 + +#define HSMP_INDEX_REG 0xc4 +#define HSMP_DATA_REG 0xc8 + +static struct semaphore *hsmp_sem; + +static struct miscdevice hsmp_device; + +static int amd_hsmp_rdwr(struct pci_dev *root, u32 address, + u32 *value, bool write) +{ + int ret; + + ret = pci_write_config_dword(root, HSMP_INDEX_REG, address); + if (ret) + return ret; + + ret = (write ? pci_write_config_dword(root, HSMP_DATA_REG, *value) + : pci_read_config_dword(root, HSMP_DATA_REG, value)); + + return ret; +} + +/* + * Send a message to the HSMP port via PCI-e config space registers. + * + * The caller is expected to zero out any unused arguments. + * If a response is expected, the number of response words should be greater than 0. + * + * Returns 0 for success and populates the requested number of arguments. + * Returns a negative error code for failure. + */ +static int __hsmp_send_message(struct pci_dev *root, struct hsmp_message *msg) +{ + unsigned long timeout, short_sleep; + u32 mbox_status; + u32 index; + int ret; + + /* Clear the status register */ + mbox_status = HSMP_STATUS_NOT_READY; + ret = amd_hsmp_rdwr(root, SMN_HSMP_MSG_RESP, &mbox_status, HSMP_WR); + if (ret) { + pr_err("Error %d clearing mailbox status register\n", ret); + return ret; + } + + index = 0; + /* Write any message arguments */ + while (index < msg->num_args) { + ret = amd_hsmp_rdwr(root, SMN_HSMP_MSG_DATA + (index << 2), + &msg->args[index], HSMP_WR); + if (ret) { + pr_err("Error %d writing message argument %d\n", ret, index); + return ret; + } + index++; + } + + /* Write the message ID which starts the operation */ + ret = amd_hsmp_rdwr(root, SMN_HSMP_MSG_ID, &msg->msg_id, HSMP_WR); + if (ret) { + pr_err("Error %d writing message ID %u\n", ret, msg->msg_id); + return ret; + } + + /* + * Depending on when the trigger write completes relative to the SMU + * firmware 1 ms cycle, the operation may take from tens of us to 1 ms + * to complete. Some operations may take more. Therefore we will try + * a few short duration sleeps and switch to long sleeps if we don't + * succeed quickly. + */ + short_sleep = jiffies + msecs_to_jiffies(HSMP_SHORT_SLEEP); + timeout = jiffies + msecs_to_jiffies(HSMP_MSG_TIMEOUT); + + while (time_before(jiffies, timeout)) { + ret = amd_hsmp_rdwr(root, SMN_HSMP_MSG_RESP, &mbox_status, HSMP_RD); + if (ret) { + pr_err("Error %d reading mailbox status\n", ret); + return ret; + } + + if (mbox_status != HSMP_STATUS_NOT_READY) + break; + if (time_before(jiffies, short_sleep)) + usleep_range(50, 100); + else + usleep_range(1000, 2000); + } + + if (unlikely(mbox_status == HSMP_STATUS_NOT_READY)) { + return -ETIMEDOUT; + } else if (unlikely(mbox_status == HSMP_ERR_INVALID_MSG)) { + return -ENOMSG; + } else if (unlikely(mbox_status == HSMP_ERR_INVALID_INPUT)) { + return -EINVAL; + } else if (unlikely(mbox_status != HSMP_STATUS_OK)) { + pr_err("Message ID %u unknown failure (status = 0x%X)\n", + msg->msg_id, mbox_status); + return -EIO; + } + + /* + * SMU has responded OK. Read response data. + * SMU reads the input arguments from eight 32 bit registers starting + * from SMN_HSMP_MSG_DATA and writes the response data to the same + * SMN_HSMP_MSG_DATA address. + * We copy the response data if any, back to the args[]. + */ + index = 0; + while (index < msg->response_sz) { + ret = amd_hsmp_rdwr(root, SMN_HSMP_MSG_DATA + (index << 2), + &msg->args[index], HSMP_RD); + if (ret) { + pr_err("Error %d reading response %u for message ID:%u\n", + ret, index, msg->msg_id); + break; + } + index++; + } + + return ret; +} + +static int validate_message(struct hsmp_message *msg) +{ + /* msg_id against valid range of message IDs */ + if (msg->msg_id < HSMP_TEST || msg->msg_id >= HSMP_MSG_ID_MAX) + return -ENOMSG; + + /* msg_id is a reserved message ID */ + if (hsmp_msg_desc_table[msg->msg_id].type == HSMP_RSVD) + return -ENOMSG; + + /* num_args and response_sz against the HSMP spec */ + if (msg->num_args != hsmp_msg_desc_table[msg->msg_id].num_args || + msg->response_sz != hsmp_msg_desc_table[msg->msg_id].response_sz) + return -EINVAL; + + return 0; +} + +int hsmp_send_message(struct hsmp_message *msg) +{ + struct amd_northbridge *nb; + int ret; + + if (!msg) + return -EINVAL; + + nb = node_to_amd_nb(msg->sock_ind); + if (!nb || !nb->root) + return -ENODEV; + + ret = validate_message(msg); + if (ret) + return ret; + + /* + * The time taken by smu operation to complete is between + * 10us to 1ms. Sometime it may take more time. + * In SMP system timeout of 100 millisecs should + * be enough for the previous thread to finish the operation + */ + ret = down_timeout(&hsmp_sem[msg->sock_ind], + msecs_to_jiffies(HSMP_MSG_TIMEOUT)); + if (ret < 0) + return ret; + + ret = __hsmp_send_message(nb->root, msg); + + up(&hsmp_sem[msg->sock_ind]); + + return ret; +} +EXPORT_SYMBOL_GPL(hsmp_send_message); + +static int hsmp_test(u16 sock_ind, u32 value) +{ + struct hsmp_message msg = { 0 }; + struct amd_northbridge *nb; + int ret = -ENODEV; + + nb = node_to_amd_nb(sock_ind); + if (!nb || !nb->root) + return ret; + + /* + * Test the hsmp port by performing TEST command. The test message + * takes one argument and returns the value of that argument + 1. + */ + msg.msg_id = HSMP_TEST; + msg.num_args = 1; + msg.response_sz = 1; + msg.args[0] = value; + msg.sock_ind = sock_ind; + + ret = __hsmp_send_message(nb->root, &msg); + if (ret) + return ret; + + /* Check the response value */ + if (msg.args[0] != (value + 1)) { + pr_err("Socket %d test message failed, Expected 0x%08X, received 0x%08X\n", + sock_ind, (value + 1), msg.args[0]); + return -EBADE; + } + + return ret; +} + +static long hsmp_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) +{ + int __user *arguser = (int __user *)arg; + struct hsmp_message msg = { 0 }; + int ret; + + if (copy_struct_from_user(&msg, sizeof(msg), arguser, sizeof(struct hsmp_message))) + return -EFAULT; + + /* + * Check msg_id is within the range of supported msg ids + * i.e within the array bounds of hsmp_msg_desc_table + */ + if (msg.msg_id < HSMP_TEST || msg.msg_id >= HSMP_MSG_ID_MAX) + return -ENOMSG; + + switch (fp->f_mode & (FMODE_WRITE | FMODE_READ)) { + case FMODE_WRITE: + /* + * Device is opened in O_WRONLY mode + * Execute only set/configure commands + */ + if (hsmp_msg_desc_table[msg.msg_id].type != HSMP_SET) + return -EINVAL; + break; + case FMODE_READ: + /* + * Device is opened in O_RDONLY mode + * Execute only get/monitor commands + */ + if (hsmp_msg_desc_table[msg.msg_id].type != HSMP_GET) + return -EINVAL; + break; + case FMODE_READ | FMODE_WRITE: + /* + * Device is opened in O_RDWR mode + * Execute both get/monitor and set/configure commands + */ + break; + default: + return -EINVAL; + } + + ret = hsmp_send_message(&msg); + if (ret) + return ret; + + if (hsmp_msg_desc_table[msg.msg_id].response_sz > 0) { + /* Copy results back to user for get/monitor commands */ + if (copy_to_user(arguser, &msg, sizeof(struct hsmp_message))) + return -EFAULT; + } + + return 0; +} + +static const struct file_operations hsmp_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = hsmp_ioctl, + .compat_ioctl = hsmp_ioctl, +}; + +static int hsmp_pltdrv_probe(struct platform_device *pdev) +{ + int i; + + hsmp_sem = devm_kzalloc(&pdev->dev, + (amd_nb_num() * sizeof(struct semaphore)), + GFP_KERNEL); + if (!hsmp_sem) + return -ENOMEM; + + for (i = 0; i < amd_nb_num(); i++) + sema_init(&hsmp_sem[i], 1); + + hsmp_device.name = "hsmp_cdev"; + hsmp_device.minor = MISC_DYNAMIC_MINOR; + hsmp_device.fops = &hsmp_fops; + hsmp_device.parent = &pdev->dev; + hsmp_device.nodename = "hsmp"; + hsmp_device.mode = 0644; + + return misc_register(&hsmp_device); +} + +static int hsmp_pltdrv_remove(struct platform_device *pdev) +{ + misc_deregister(&hsmp_device); + + return 0; +} + +static struct platform_driver amd_hsmp_driver = { + .probe = hsmp_pltdrv_probe, + .remove = hsmp_pltdrv_remove, + .driver = { + .name = DRIVER_NAME, + }, +}; + +static struct platform_device *amd_hsmp_platdev; + +static int __init hsmp_plt_init(void) +{ + int ret = -ENODEV; + u16 num_sockets; + int i; + + if (boot_cpu_data.x86_vendor != X86_VENDOR_AMD || boot_cpu_data.x86 < 0x19) { + pr_err("HSMP is not supported on Family:%x model:%x\n", + boot_cpu_data.x86, boot_cpu_data.x86_model); + return ret; + } + + /* + * amd_nb_num() returns number of SMN/DF interfaces present in the system + * if we have N SMN/DF interfaces that ideally means N sockets + */ + num_sockets = amd_nb_num(); + if (num_sockets == 0) + return ret; + + /* Test the hsmp interface on each socket */ + for (i = 0; i < num_sockets; i++) { + ret = hsmp_test(i, 0xDEADBEEF); + if (ret) { + pr_err("HSMP is not supported on Fam:%x model:%x\n", + boot_cpu_data.x86, boot_cpu_data.x86_model); + pr_err("Or Is HSMP disabled in BIOS ?\n"); + return -EOPNOTSUPP; + } + } + + ret = platform_driver_register(&amd_hsmp_driver); + if (ret) + return ret; + + amd_hsmp_platdev = platform_device_alloc(DRIVER_NAME, -1); + if (!amd_hsmp_platdev) { + ret = -ENOMEM; + goto drv_unregister; + } + + ret = platform_device_add(amd_hsmp_platdev); + if (ret) { + platform_device_put(amd_hsmp_platdev); + goto drv_unregister; + } + + return 0; + +drv_unregister: + platform_driver_unregister(&amd_hsmp_driver); + return ret; +} + +static void __exit hsmp_plt_exit(void) +{ + platform_device_unregister(amd_hsmp_platdev); + platform_driver_unregister(&amd_hsmp_driver); +} + +device_initcall(hsmp_plt_init); +module_exit(hsmp_plt_exit); + +MODULE_DESCRIPTION("AMD HSMP Platform Interface Driver"); +MODULE_VERSION(DRIVER_VERSION); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/platform/x86/dell/dcdbas.c b/drivers/platform/x86/dell/dcdbas.c index 5e63d6225048..db3633fafbd5 100644 --- a/drivers/platform/x86/dell/dcdbas.c +++ b/drivers/platform/x86/dell/dcdbas.c @@ -284,6 +284,7 @@ int dcdbas_smi_request(struct smi_cmd *smi_cmd) return ret; } +EXPORT_SYMBOL(dcdbas_smi_request); /** * smi_request_store: @@ -351,7 +352,6 @@ out: mutex_unlock(&smi_data_lock); return ret; } -EXPORT_SYMBOL(dcdbas_smi_request); /** * host_control_smi: generate host control SMI diff --git a/drivers/platform/x86/hp-wmi.c b/drivers/platform/x86/hp-wmi.c index 48a46466f086..0e9a25b56e0e 100644 --- a/drivers/platform/x86/hp-wmi.c +++ b/drivers/platform/x86/hp-wmi.c @@ -35,10 +35,6 @@ MODULE_LICENSE("GPL"); MODULE_ALIAS("wmi:95F24279-4D7B-4334-9387-ACCDC67EF61C"); MODULE_ALIAS("wmi:5FB7F034-2C63-45e9-BE91-3D44E2C707E4"); -static int enable_tablet_mode_sw = -1; -module_param(enable_tablet_mode_sw, int, 0444); -MODULE_PARM_DESC(enable_tablet_mode_sw, "Enable SW_TABLET_MODE reporting (-1=auto, 0=no, 1=yes)"); - #define HPWMI_EVENT_GUID "95F24279-4D7B-4334-9387-ACCDC67EF61C" #define HPWMI_BIOS_GUID "5FB7F034-2C63-45e9-BE91-3D44E2C707E4" #define HP_OMEN_EC_THERMAL_PROFILE_OFFSET 0x95 @@ -61,6 +57,14 @@ static const char * const omen_thermal_profile_boards[] = { "8917", "8918", "8949", "894A", "89EB" }; +/* DMI Board names of Omen laptops that are specifically set to be thermal + * profile version 0 by the Omen Command Center app, regardless of what + * the get system design information WMI call returns + */ +static const char *const omen_thermal_profile_force_v0_boards[] = { + "8607", "8746", "8747", "8749", "874A", "8748" +}; + enum hp_wmi_radio { HPWMI_WIFI = 0x0, HPWMI_BLUETOOTH = 0x1, @@ -86,12 +90,17 @@ enum hp_wmi_event_ids { HPWMI_BATTERY_CHARGE_PERIOD = 0x10, }; +/* + * struct bios_args buffer is dynamically allocated. New WMI command types + * were introduced that exceeds 128-byte data size. Changes to handle + * the data size allocation scheme were kept in hp_wmi_perform_qurey function. + */ struct bios_args { u32 signature; u32 command; u32 commandtype; u32 datasize; - u8 data[128]; + u8 data[]; }; enum hp_wmi_commandtype { @@ -107,6 +116,7 @@ enum hp_wmi_commandtype { HPWMI_FEATURE2_QUERY = 0x0d, HPWMI_WIRELESS2_QUERY = 0x1b, HPWMI_POSTCODEERROR_QUERY = 0x2a, + HPWMI_SYSTEM_DEVICE_MODE = 0x40, HPWMI_THERMAL_PROFILE_QUERY = 0x4c, }; @@ -115,6 +125,7 @@ enum hp_wmi_gm_commandtype { HPWMI_SET_PERFORMANCE_MODE = 0x1A, HPWMI_FAN_SPEED_MAX_GET_QUERY = 0x26, HPWMI_FAN_SPEED_MAX_SET_QUERY = 0x27, + HPWMI_GET_SYSTEM_DESIGN_DATA = 0x28, }; enum hp_wmi_command { @@ -149,10 +160,16 @@ enum hp_wireless2_bits { HPWMI_POWER_FW_OR_HW = HPWMI_POWER_BIOS | HPWMI_POWER_HARD, }; -enum hp_thermal_profile_omen { - HP_OMEN_THERMAL_PROFILE_DEFAULT = 0x00, - HP_OMEN_THERMAL_PROFILE_PERFORMANCE = 0x01, - HP_OMEN_THERMAL_PROFILE_COOL = 0x02, +enum hp_thermal_profile_omen_v0 { + HP_OMEN_V0_THERMAL_PROFILE_DEFAULT = 0x00, + HP_OMEN_V0_THERMAL_PROFILE_PERFORMANCE = 0x01, + HP_OMEN_V0_THERMAL_PROFILE_COOL = 0x02, +}; + +enum hp_thermal_profile_omen_v1 { + HP_OMEN_V1_THERMAL_PROFILE_DEFAULT = 0x30, + HP_OMEN_V1_THERMAL_PROFILE_PERFORMANCE = 0x31, + HP_OMEN_V1_THERMAL_PROFILE_COOL = 0x50, }; enum hp_thermal_profile { @@ -217,6 +234,19 @@ struct rfkill2_device { static int rfkill2_count; static struct rfkill2_device rfkill2[HPWMI_MAX_RFKILL2_DEVICES]; +/* + * Chassis Types values were obtained from SMBIOS reference + * specification version 3.00. A complete list of system enclosures + * and chassis types is available on Table 17. + */ +static const char * const tablet_chassis_types[] = { + "30", /* Tablet*/ + "31", /* Convertible */ + "32" /* Detachable */ +}; + +#define DEVICE_MODE_TABLET 0x06 + /* map output size to the corresponding WMI method id */ static inline int encode_outsize_for_pvsz(int outsize) { @@ -256,37 +286,43 @@ static inline int encode_outsize_for_pvsz(int outsize) static int hp_wmi_perform_query(int query, enum hp_wmi_command command, void *buffer, int insize, int outsize) { - int mid; + struct acpi_buffer input, output = { ACPI_ALLOCATE_BUFFER, NULL }; struct bios_return *bios_return; - int actual_outsize; - union acpi_object *obj; - struct bios_args args = { - .signature = 0x55434553, - .command = command, - .commandtype = query, - .datasize = insize, - .data = { 0 }, - }; - struct acpi_buffer input = { sizeof(struct bios_args), &args }; - struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; - int ret = 0; + union acpi_object *obj = NULL; + struct bios_args *args = NULL; + int mid, actual_outsize, ret; + size_t bios_args_size; mid = encode_outsize_for_pvsz(outsize); if (WARN_ON(mid < 0)) return mid; - if (WARN_ON(insize > sizeof(args.data))) - return -EINVAL; - memcpy(&args.data[0], buffer, insize); + bios_args_size = struct_size(args, data, insize); + args = kmalloc(bios_args_size, GFP_KERNEL); + if (!args) + return -ENOMEM; - wmi_evaluate_method(HPWMI_BIOS_GUID, 0, mid, &input, &output); + input.length = bios_args_size; + input.pointer = args; - obj = output.pointer; + args->signature = 0x55434553; + args->command = command; + args->commandtype = query; + args->datasize = insize; + memcpy(args->data, buffer, flex_array_size(args, data, insize)); - if (!obj) - return -EINVAL; + ret = wmi_evaluate_method(HPWMI_BIOS_GUID, 0, mid, &input, &output); + if (ret) + goto out_free; + + obj = output.pointer; + if (!obj) { + ret = -EINVAL; + goto out_free; + } if (obj->type != ACPI_TYPE_BUFFER) { + pr_warn("query 0x%x returned an invalid object 0x%x\n", query, ret); ret = -EINVAL; goto out_free; } @@ -311,6 +347,7 @@ static int hp_wmi_perform_query(int query, enum hp_wmi_command command, out_free: kfree(obj); + kfree(args); return ret; } @@ -320,7 +357,7 @@ static int hp_wmi_get_fan_speed(int fan) char fan_data[4] = { fan, 0, 0, 0 }; int ret = hp_wmi_perform_query(HPWMI_FAN_SPEED_GET_QUERY, HPWMI_GM, - &fan_data, sizeof(fan_data), + &fan_data, sizeof(char), sizeof(fan_data)); if (ret != 0) @@ -337,7 +374,7 @@ static int hp_wmi_read_int(int query) int val = 0, ret; ret = hp_wmi_perform_query(query, HPWMI_READ, &val, - sizeof(val), sizeof(val)); + 0, sizeof(val)); if (ret) return ret < 0 ? ret : -EINVAL; @@ -345,14 +382,39 @@ static int hp_wmi_read_int(int query) return val; } -static int hp_wmi_hw_state(int mask) +static int hp_wmi_get_dock_state(void) { int state = hp_wmi_read_int(HPWMI_HARDWARE_QUERY); if (state < 0) return state; - return !!(state & mask); + return !!(state & HPWMI_DOCK_MASK); +} + +static int hp_wmi_get_tablet_mode(void) +{ + char system_device_mode[4] = { 0 }; + const char *chassis_type; + bool tablet_found; + int ret; + + chassis_type = dmi_get_system_info(DMI_CHASSIS_TYPE); + if (!chassis_type) + return -ENODEV; + + tablet_found = match_string(tablet_chassis_types, + ARRAY_SIZE(tablet_chassis_types), + chassis_type) >= 0; + if (!tablet_found) + return -ENODEV; + + ret = hp_wmi_perform_query(HPWMI_SYSTEM_DEVICE_MODE, HPWMI_READ, + system_device_mode, 0, sizeof(system_device_mode)); + if (ret < 0) + return ret; + + return system_device_mode[0] == DEVICE_MODE_TABLET; } static int omen_thermal_profile_set(int mode) @@ -360,11 +422,8 @@ static int omen_thermal_profile_set(int mode) char buffer[2] = {0, mode}; int ret; - if (mode < 0 || mode > 2) - return -EINVAL; - ret = hp_wmi_perform_query(HPWMI_SET_PERFORMANCE_MODE, HPWMI_GM, - &buffer, sizeof(buffer), sizeof(buffer)); + &buffer, sizeof(buffer), 0); if (ret) return ret < 0 ? ret : -EINVAL; @@ -384,6 +443,30 @@ static bool is_omen_thermal_profile(void) board_name) >= 0; } +static int omen_get_thermal_policy_version(void) +{ + unsigned char buffer[8] = { 0 }; + int ret; + + const char *board_name = dmi_get_system_info(DMI_BOARD_NAME); + + if (board_name) { + int matches = match_string(omen_thermal_profile_force_v0_boards, + ARRAY_SIZE(omen_thermal_profile_force_v0_boards), + board_name); + if (matches >= 0) + return 0; + } + + ret = hp_wmi_perform_query(HPWMI_GET_SYSTEM_DESIGN_DATA, HPWMI_GM, + &buffer, sizeof(buffer), sizeof(buffer)); + + if (ret) + return ret < 0 ? ret : -EINVAL; + + return buffer[3]; +} + static int omen_thermal_profile_get(void) { u8 data; @@ -401,7 +484,7 @@ static int hp_wmi_fan_speed_max_set(int enabled) int ret; ret = hp_wmi_perform_query(HPWMI_FAN_SPEED_MAX_SET_QUERY, HPWMI_GM, - &enabled, sizeof(enabled), sizeof(enabled)); + &enabled, sizeof(enabled), 0); if (ret) return ret < 0 ? ret : -EINVAL; @@ -414,7 +497,7 @@ static int hp_wmi_fan_speed_max_get(void) int val = 0, ret; ret = hp_wmi_perform_query(HPWMI_FAN_SPEED_MAX_GET_QUERY, HPWMI_GM, - &val, sizeof(val), sizeof(val)); + &val, 0, sizeof(val)); if (ret) return ret < 0 ? ret : -EINVAL; @@ -426,7 +509,7 @@ static int __init hp_wmi_bios_2008_later(void) { int state = 0; int ret = hp_wmi_perform_query(HPWMI_FEATURE_QUERY, HPWMI_READ, &state, - sizeof(state), sizeof(state)); + 0, sizeof(state)); if (!ret) return 1; @@ -437,7 +520,7 @@ static int __init hp_wmi_bios_2009_later(void) { u8 state[128]; int ret = hp_wmi_perform_query(HPWMI_FEATURE2_QUERY, HPWMI_READ, &state, - sizeof(state), sizeof(state)); + 0, sizeof(state)); if (!ret) return 1; @@ -515,7 +598,7 @@ static int hp_wmi_rfkill2_refresh(void) int err, i; err = hp_wmi_perform_query(HPWMI_WIRELESS2_QUERY, HPWMI_READ, &state, - sizeof(state), sizeof(state)); + 0, sizeof(state)); if (err) return err; @@ -568,7 +651,7 @@ static ssize_t als_show(struct device *dev, struct device_attribute *attr, static ssize_t dock_show(struct device *dev, struct device_attribute *attr, char *buf) { - int value = hp_wmi_hw_state(HPWMI_DOCK_MASK); + int value = hp_wmi_get_dock_state(); if (value < 0) return value; return sprintf(buf, "%d\n", value); @@ -577,7 +660,7 @@ static ssize_t dock_show(struct device *dev, struct device_attribute *attr, static ssize_t tablet_show(struct device *dev, struct device_attribute *attr, char *buf) { - int value = hp_wmi_hw_state(HPWMI_TABLET_MASK); + int value = hp_wmi_get_tablet_mode(); if (value < 0) return value; return sprintf(buf, "%d\n", value); @@ -604,7 +687,7 @@ static ssize_t als_store(struct device *dev, struct device_attribute *attr, return ret; ret = hp_wmi_perform_query(HPWMI_ALS_QUERY, HPWMI_WRITE, &tmp, - sizeof(tmp), sizeof(tmp)); + sizeof(tmp), 0); if (ret) return ret < 0 ? ret : -EINVAL; @@ -625,9 +708,9 @@ static ssize_t postcode_store(struct device *dev, struct device_attribute *attr, if (clear == false) return -EINVAL; - /* Clear the POST error code. It is kept until until cleared. */ + /* Clear the POST error code. It is kept until cleared. */ ret = hp_wmi_perform_query(HPWMI_POSTCODEERROR_QUERY, HPWMI_WRITE, &tmp, - sizeof(tmp), sizeof(tmp)); + sizeof(tmp), 0); if (ret) return ret < 0 ? ret : -EINVAL; @@ -699,10 +782,10 @@ static void hp_wmi_notify(u32 value, void *context) case HPWMI_DOCK_EVENT: if (test_bit(SW_DOCK, hp_wmi_input_dev->swbit)) input_report_switch(hp_wmi_input_dev, SW_DOCK, - hp_wmi_hw_state(HPWMI_DOCK_MASK)); + hp_wmi_get_dock_state()); if (test_bit(SW_TABLET_MODE, hp_wmi_input_dev->swbit)) input_report_switch(hp_wmi_input_dev, SW_TABLET_MODE, - hp_wmi_hw_state(HPWMI_TABLET_MASK)); + hp_wmi_get_tablet_mode()); input_sync(hp_wmi_input_dev); break; case HPWMI_PARK_HDD: @@ -780,19 +863,17 @@ static int __init hp_wmi_input_setup(void) __set_bit(EV_SW, hp_wmi_input_dev->evbit); /* Dock */ - val = hp_wmi_hw_state(HPWMI_DOCK_MASK); + val = hp_wmi_get_dock_state(); if (!(val < 0)) { __set_bit(SW_DOCK, hp_wmi_input_dev->swbit); input_report_switch(hp_wmi_input_dev, SW_DOCK, val); } /* Tablet mode */ - if (enable_tablet_mode_sw > 0) { - val = hp_wmi_hw_state(HPWMI_TABLET_MASK); - if (val >= 0) { - __set_bit(SW_TABLET_MODE, hp_wmi_input_dev->swbit); - input_report_switch(hp_wmi_input_dev, SW_TABLET_MODE, val); - } + val = hp_wmi_get_tablet_mode(); + if (!(val < 0)) { + __set_bit(SW_TABLET_MODE, hp_wmi_input_dev->swbit); + input_report_switch(hp_wmi_input_dev, SW_TABLET_MODE, val); } err = sparse_keymap_setup(hp_wmi_input_dev, hp_wmi_keymap, NULL); @@ -919,7 +1000,7 @@ static int __init hp_wmi_rfkill2_setup(struct platform_device *device) int err, i; err = hp_wmi_perform_query(HPWMI_WIRELESS2_QUERY, HPWMI_READ, &state, - sizeof(state), sizeof(state)); + 0, sizeof(state)); if (err) return err < 0 ? err : -EINVAL; @@ -1008,13 +1089,16 @@ static int platform_profile_omen_get(struct platform_profile_handler *pprof, return tp; switch (tp) { - case HP_OMEN_THERMAL_PROFILE_PERFORMANCE: + case HP_OMEN_V0_THERMAL_PROFILE_PERFORMANCE: + case HP_OMEN_V1_THERMAL_PROFILE_PERFORMANCE: *profile = PLATFORM_PROFILE_PERFORMANCE; break; - case HP_OMEN_THERMAL_PROFILE_DEFAULT: + case HP_OMEN_V0_THERMAL_PROFILE_DEFAULT: + case HP_OMEN_V1_THERMAL_PROFILE_DEFAULT: *profile = PLATFORM_PROFILE_BALANCED; break; - case HP_OMEN_THERMAL_PROFILE_COOL: + case HP_OMEN_V0_THERMAL_PROFILE_COOL: + case HP_OMEN_V1_THERMAL_PROFILE_COOL: *profile = PLATFORM_PROFILE_COOL; break; default: @@ -1027,17 +1111,31 @@ static int platform_profile_omen_get(struct platform_profile_handler *pprof, static int platform_profile_omen_set(struct platform_profile_handler *pprof, enum platform_profile_option profile) { - int err, tp; + int err, tp, tp_version; + + tp_version = omen_get_thermal_policy_version(); + + if (tp_version < 0 || tp_version > 1) + return -EOPNOTSUPP; switch (profile) { case PLATFORM_PROFILE_PERFORMANCE: - tp = HP_OMEN_THERMAL_PROFILE_PERFORMANCE; + if (tp_version == 0) + tp = HP_OMEN_V0_THERMAL_PROFILE_PERFORMANCE; + else + tp = HP_OMEN_V1_THERMAL_PROFILE_PERFORMANCE; break; case PLATFORM_PROFILE_BALANCED: - tp = HP_OMEN_THERMAL_PROFILE_DEFAULT; + if (tp_version == 0) + tp = HP_OMEN_V0_THERMAL_PROFILE_DEFAULT; + else + tp = HP_OMEN_V1_THERMAL_PROFILE_DEFAULT; break; case PLATFORM_PROFILE_COOL: - tp = HP_OMEN_THERMAL_PROFILE_COOL; + if (tp_version == 0) + tp = HP_OMEN_V0_THERMAL_PROFILE_COOL; + else + tp = HP_OMEN_V1_THERMAL_PROFILE_COOL; break; default: return -EOPNOTSUPP; @@ -1227,10 +1325,10 @@ static int hp_wmi_resume_handler(struct device *device) if (hp_wmi_input_dev) { if (test_bit(SW_DOCK, hp_wmi_input_dev->swbit)) input_report_switch(hp_wmi_input_dev, SW_DOCK, - hp_wmi_hw_state(HPWMI_DOCK_MASK)); + hp_wmi_get_dock_state()); if (test_bit(SW_TABLET_MODE, hp_wmi_input_dev->swbit)) input_report_switch(hp_wmi_input_dev, SW_TABLET_MODE, - hp_wmi_hw_state(HPWMI_TABLET_MASK)); + hp_wmi_get_tablet_mode()); input_sync(hp_wmi_input_dev); } diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c index a2d846c4a7ee..eac3e6b4ea11 100644 --- a/drivers/platform/x86/huawei-wmi.c +++ b/drivers/platform/x86/huawei-wmi.c @@ -470,10 +470,17 @@ static DEVICE_ATTR_RW(charge_control_thresholds); static int huawei_wmi_battery_add(struct power_supply *battery) { - device_create_file(&battery->dev, &dev_attr_charge_control_start_threshold); - device_create_file(&battery->dev, &dev_attr_charge_control_end_threshold); + int err = 0; - return 0; + err = device_create_file(&battery->dev, &dev_attr_charge_control_start_threshold); + if (err) + return err; + + err = device_create_file(&battery->dev, &dev_attr_charge_control_end_threshold); + if (err) + device_remove_file(&battery->dev, &dev_attr_charge_control_start_threshold); + + return err; } static int huawei_wmi_battery_remove(struct power_supply *battery) diff --git a/drivers/platform/x86/i2c-multi-instantiate.c b/drivers/platform/x86/i2c-multi-instantiate.c deleted file mode 100644 index 4956a1df5b90..000000000000 --- a/drivers/platform/x86/i2c-multi-instantiate.c +++ /dev/null @@ -1,174 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0+ -/* - * I2C multi-instantiate driver, pseudo driver to instantiate multiple - * i2c-clients from a single fwnode. - * - * Copyright 2018 Hans de Goede <hdegoede@redhat.com> - */ - -#include <linux/acpi.h> -#include <linux/bits.h> -#include <linux/i2c.h> -#include <linux/interrupt.h> -#include <linux/kernel.h> -#include <linux/module.h> -#include <linux/platform_device.h> -#include <linux/property.h> -#include <linux/types.h> - -#define IRQ_RESOURCE_TYPE GENMASK(1, 0) -#define IRQ_RESOURCE_NONE 0 -#define IRQ_RESOURCE_GPIO 1 -#define IRQ_RESOURCE_APIC 2 - -struct i2c_inst_data { - const char *type; - unsigned int flags; - int irq_idx; -}; - -struct i2c_multi_inst_data { - int num_clients; - struct i2c_client *clients[]; -}; - -static int i2c_multi_inst_probe(struct platform_device *pdev) -{ - struct i2c_multi_inst_data *multi; - const struct i2c_inst_data *inst_data; - struct i2c_board_info board_info = {}; - struct device *dev = &pdev->dev; - struct acpi_device *adev; - char name[32]; - int i, ret; - - inst_data = device_get_match_data(dev); - if (!inst_data) { - dev_err(dev, "Error ACPI match data is missing\n"); - return -ENODEV; - } - - adev = ACPI_COMPANION(dev); - - /* Count number of clients to instantiate */ - ret = i2c_acpi_client_count(adev); - if (ret < 0) - return ret; - - multi = devm_kmalloc(dev, struct_size(multi, clients, ret), GFP_KERNEL); - if (!multi) - return -ENOMEM; - - multi->num_clients = ret; - - for (i = 0; i < multi->num_clients && inst_data[i].type; i++) { - memset(&board_info, 0, sizeof(board_info)); - strlcpy(board_info.type, inst_data[i].type, I2C_NAME_SIZE); - snprintf(name, sizeof(name), "%s-%s.%d", dev_name(dev), - inst_data[i].type, i); - board_info.dev_name = name; - switch (inst_data[i].flags & IRQ_RESOURCE_TYPE) { - case IRQ_RESOURCE_GPIO: - ret = acpi_dev_gpio_irq_get(adev, inst_data[i].irq_idx); - if (ret < 0) { - dev_err(dev, "Error requesting irq at index %d: %d\n", - inst_data[i].irq_idx, ret); - goto error; - } - board_info.irq = ret; - break; - case IRQ_RESOURCE_APIC: - ret = platform_get_irq(pdev, inst_data[i].irq_idx); - if (ret < 0) { - dev_dbg(dev, "Error requesting irq at index %d: %d\n", - inst_data[i].irq_idx, ret); - goto error; - } - board_info.irq = ret; - break; - default: - board_info.irq = 0; - break; - } - multi->clients[i] = i2c_acpi_new_device(dev, i, &board_info); - if (IS_ERR(multi->clients[i])) { - ret = dev_err_probe(dev, PTR_ERR(multi->clients[i]), - "Error creating i2c-client, idx %d\n", i); - goto error; - } - } - if (i < multi->num_clients) { - dev_err(dev, "Error finding driver, idx %d\n", i); - ret = -ENODEV; - goto error; - } - - platform_set_drvdata(pdev, multi); - return 0; - -error: - while (--i >= 0) - i2c_unregister_device(multi->clients[i]); - - return ret; -} - -static int i2c_multi_inst_remove(struct platform_device *pdev) -{ - struct i2c_multi_inst_data *multi = platform_get_drvdata(pdev); - int i; - - for (i = 0; i < multi->num_clients; i++) - i2c_unregister_device(multi->clients[i]); - - return 0; -} - -static const struct i2c_inst_data bsg1160_data[] = { - { "bmc150_accel", IRQ_RESOURCE_GPIO, 0 }, - { "bmc150_magn" }, - { "bmg160" }, - {} -}; - -static const struct i2c_inst_data bsg2150_data[] = { - { "bmc150_accel", IRQ_RESOURCE_GPIO, 0 }, - { "bmc150_magn" }, - /* The resources describe a 3th client, but it is not really there. */ - { "bsg2150_dummy_dev" }, - {} -}; - -static const struct i2c_inst_data int3515_data[] = { - { "tps6598x", IRQ_RESOURCE_APIC, 0 }, - { "tps6598x", IRQ_RESOURCE_APIC, 1 }, - { "tps6598x", IRQ_RESOURCE_APIC, 2 }, - { "tps6598x", IRQ_RESOURCE_APIC, 3 }, - {} -}; - -/* - * Note new device-ids must also be added to i2c_multi_instantiate_ids in - * drivers/acpi/scan.c: acpi_device_enumeration_by_parent(). - */ -static const struct acpi_device_id i2c_multi_inst_acpi_ids[] = { - { "BSG1160", (unsigned long)bsg1160_data }, - { "BSG2150", (unsigned long)bsg2150_data }, - { "INT3515", (unsigned long)int3515_data }, - { } -}; -MODULE_DEVICE_TABLE(acpi, i2c_multi_inst_acpi_ids); - -static struct platform_driver i2c_multi_inst_driver = { - .driver = { - .name = "I2C multi instantiate pseudo device driver", - .acpi_match_table = i2c_multi_inst_acpi_ids, - }, - .probe = i2c_multi_inst_probe, - .remove = i2c_multi_inst_remove, -}; -module_platform_driver(i2c_multi_inst_driver); - -MODULE_DESCRIPTION("I2C multi instantiate pseudo device driver"); -MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>"); -MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/intel/Kconfig b/drivers/platform/x86/intel/Kconfig index 8e65086bb6c8..1f01a8a23c57 100644 --- a/drivers/platform/x86/intel/Kconfig +++ b/drivers/platform/x86/intel/Kconfig @@ -5,13 +5,14 @@ source "drivers/platform/x86/intel/atomisp2/Kconfig" source "drivers/platform/x86/intel/int1092/Kconfig" -source "drivers/platform/x86/intel/int33fe/Kconfig" source "drivers/platform/x86/intel/int3472/Kconfig" source "drivers/platform/x86/intel/pmc/Kconfig" source "drivers/platform/x86/intel/pmt/Kconfig" source "drivers/platform/x86/intel/speed_select_if/Kconfig" source "drivers/platform/x86/intel/telemetry/Kconfig" source "drivers/platform/x86/intel/wmi/Kconfig" +source "drivers/platform/x86/intel/uncore-frequency/Kconfig" + config INTEL_HID_EVENT tristate "Intel HID Event" @@ -89,6 +90,26 @@ config INTEL_CHTDC_TI_PWRBTN To compile this driver as a module, choose M here: the module will be called intel_chtdc_ti_pwrbtn. +config INTEL_CHTWC_INT33FE + tristate "Intel Cherry Trail Whiskey Cove ACPI INT33FE Driver" + depends on X86 && ACPI && I2C && REGULATOR + depends on CHARGER_BQ24190=y || (CHARGER_BQ24190=m && m) + depends on USB_ROLES_INTEL_XHCI=y || (USB_ROLES_INTEL_XHCI=m && m) + depends on TYPEC_MUX_PI3USB30532=y || (TYPEC_MUX_PI3USB30532=m && m) + help + This driver add support for the Intel Cherry Trail Whiskey Cove + INT33FE ACPI device found on the GPD win and the GPD pocket. + + The INT33FE ACPI device on these mini laptops contains I2cSerialBusV2 + resources for a MAX17042 Fuel Gauge, FUSB302 USB Type-C Controller + and PI3USB30532 USB switch. + This driver instantiates i2c-clients for these, so that standard + i2c drivers for these chips can bind to the them. + + If you enable this driver it is advised to also select + CONFIG_TYPEC_FUSB302=m, CONFIG_TYPEC_MUX_PI3USB30532=m and + CONFIG_BATTERY_MAX17042=m. + config INTEL_ISHTP_ECLITE tristate "Intel ISHTP eclite controller Driver" depends on INTEL_ISH_HID @@ -134,6 +155,18 @@ config INTEL_RST firmware will copy the memory contents back to RAM and resume the OS as usual. +config INTEL_SDSI + tristate "Intel Software Defined Silicon Driver" + depends on INTEL_VSEC + depends on X86_64 + help + This driver enables access to the Intel Software Defined Silicon + interface used to provision silicon features with an authentication + certificate and capability license. + + To compile this driver as a module, choose M here: the module will + be called intel_sdsi. + config INTEL_SMARTCONNECT tristate "Intel Smart Connect disabling driver" depends on ACPI @@ -159,18 +192,6 @@ config INTEL_TURBO_MAX_3 This driver is only required when the system is not using Hardware P-States (HWP). In HWP mode, priority can be read from ACPI tables. -config INTEL_UNCORE_FREQ_CONTROL - tristate "Intel Uncore frequency control driver" - depends on X86_64 - help - This driver allows control of Uncore frequency limits on - supported server platforms. - - Uncore frequency controls RING/LLC (last-level cache) clocks. - - To compile this driver as a module, choose M here: the module - will be called intel-uncore-frequency. - config INTEL_VSEC tristate "Intel Vendor Specific Extended Capabilities Driver" depends on PCI diff --git a/drivers/platform/x86/intel/Makefile b/drivers/platform/x86/intel/Makefile index 35f2066578b2..c61bc3e97121 100644 --- a/drivers/platform/x86/intel/Makefile +++ b/drivers/platform/x86/intel/Makefile @@ -6,13 +6,14 @@ obj-$(CONFIG_INTEL_ATOMISP2_PDX86) += atomisp2/ obj-$(CONFIG_INTEL_SAR_INT1092) += int1092/ -obj-$(CONFIG_INTEL_CHT_INT33FE) += int33fe/ obj-$(CONFIG_INTEL_SKL_INT3472) += int3472/ obj-$(CONFIG_INTEL_PMC_CORE) += pmc/ obj-$(CONFIG_INTEL_PMT_CLASS) += pmt/ obj-$(CONFIG_INTEL_SPEED_SELECT_INTERFACE) += speed_select_if/ obj-$(CONFIG_INTEL_TELEMETRY) += telemetry/ obj-$(CONFIG_INTEL_WMI) += wmi/ +obj-$(CONFIG_INTEL_UNCORE_FREQ_CONTROL) += uncore-frequency/ + # Intel input drivers intel-hid-y := hid.o @@ -26,6 +27,8 @@ intel_int0002_vgpio-y := int0002_vgpio.o obj-$(CONFIG_INTEL_INT0002_VGPIO) += intel_int0002_vgpio.o intel_oaktrail-y := oaktrail.o obj-$(CONFIG_INTEL_OAKTRAIL) += intel_oaktrail.o +intel_sdsi-y := sdsi.o +obj-$(CONFIG_INTEL_SDSI) += intel_sdsi.o intel_vsec-y := vsec.o obj-$(CONFIG_INTEL_VSEC) += intel_vsec.o @@ -36,6 +39,8 @@ intel_crystal_cove_charger-y := crystal_cove_charger.o obj-$(CONFIG_X86_ANDROID_TABLETS) += intel_crystal_cove_charger.o intel_chtdc_ti_pwrbtn-y := chtdc_ti_pwrbtn.o obj-$(CONFIG_INTEL_CHTDC_TI_PWRBTN) += intel_chtdc_ti_pwrbtn.o +intel_chtwc_int33fe-y := chtwc_int33fe.o +obj-$(CONFIG_INTEL_CHTWC_INT33FE) += intel_chtwc_int33fe.o intel_mrfld_pwrbtn-y := mrfld_pwrbtn.o obj-$(CONFIG_INTEL_MRFLD_PWRBTN) += intel_mrfld_pwrbtn.o intel_punit_ipc-y := punit_ipc.o @@ -48,5 +53,3 @@ intel-smartconnect-y := smartconnect.o obj-$(CONFIG_INTEL_SMARTCONNECT) += intel-smartconnect.o intel_turbo_max_3-y := turbo_max_3.o obj-$(CONFIG_INTEL_TURBO_MAX_3) += intel_turbo_max_3.o -intel-uncore-frequency-y := uncore-frequency.o -obj-$(CONFIG_INTEL_UNCORE_FREQ_CONTROL) += intel-uncore-frequency.o diff --git a/drivers/platform/x86/intel/int33fe/intel_cht_int33fe_typec.c b/drivers/platform/x86/intel/chtwc_int33fe.c index d59544167430..0de509fbf020 100644 --- a/drivers/platform/x86/intel/int33fe/intel_cht_int33fe_typec.c +++ b/drivers/platform/x86/intel/chtwc_int33fe.c @@ -17,6 +17,7 @@ * for these chips can bind to the them. */ +#include <linux/dmi.h> #include <linux/i2c.h> #include <linux/interrupt.h> #include <linux/pci.h> @@ -26,7 +27,12 @@ #include <linux/slab.h> #include <linux/usb/pd.h> -#include "intel_cht_int33fe_common.h" +struct cht_int33fe_data { + struct i2c_client *battery_fg; + struct i2c_client *fusb302; + struct i2c_client *pi3usb30532; + struct fwnode_handle *dp; +}; /* * Grrr, I severely dislike buggy BIOS-es. At least one BIOS enumerates @@ -272,15 +278,44 @@ cht_int33fe_register_max17047(struct device *dev, struct cht_int33fe_data *data) return PTR_ERR_OR_ZERO(data->battery_fg); } -int cht_int33fe_typec_probe(struct cht_int33fe_data *data) +static const struct dmi_system_id cht_int33fe_typec_ids[] = { + { + /* + * GPD win / GPD pocket mini laptops + * + * This DMI match may not seem unique, but it is. In the 67000+ + * DMI decode dumps from linux-hardware.org only 116 have + * board_vendor set to "AMI Corporation" and of those 116 only + * the GPD win's and pocket's board_name is "Default string". + */ + .matches = { + DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "AMI Corporation"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "Default string"), + DMI_EXACT_MATCH(DMI_BOARD_SERIAL, "Default string"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Default string"), + }, + }, + { } +}; +MODULE_DEVICE_TABLE(dmi, cht_int33fe_typec_ids); + +static int cht_int33fe_typec_probe(struct platform_device *pdev) { - struct device *dev = data->dev; struct i2c_board_info board_info; + struct device *dev = &pdev->dev; + struct cht_int33fe_data *data; struct fwnode_handle *fwnode; struct regulator *regulator; int fusb302_irq; int ret; + if (!dmi_check_system(cht_int33fe_typec_ids)) + return -ENODEV; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + /* * We expect the WC PMIC to be paired with a TI bq24292i charger-IC. * We check for the bq24292i vbus regulator here, this has 2 purposes: @@ -368,8 +403,10 @@ out_remove_nodes: return ret; } -int cht_int33fe_typec_remove(struct cht_int33fe_data *data) +static int cht_int33fe_typec_remove(struct platform_device *pdev) { + struct cht_int33fe_data *data = platform_get_drvdata(pdev); + i2c_unregister_device(data->pi3usb30532); i2c_unregister_device(data->fusb302); i2c_unregister_device(data->battery_fg); @@ -378,3 +415,23 @@ int cht_int33fe_typec_remove(struct cht_int33fe_data *data) return 0; } + +static const struct acpi_device_id cht_int33fe_acpi_ids[] = { + { "INT33FE", }, + { } +}; + +static struct platform_driver cht_int33fe_typec_driver = { + .driver = { + .name = "Intel Cherry Trail ACPI INT33FE Type-C driver", + .acpi_match_table = ACPI_PTR(cht_int33fe_acpi_ids), + }, + .probe = cht_int33fe_typec_probe, + .remove = cht_int33fe_typec_remove, +}; + +module_platform_driver(cht_int33fe_typec_driver); + +MODULE_DESCRIPTION("Intel Cherry Trail ACPI INT33FE Type-C pseudo device driver"); +MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/platform/x86/intel/hid.c b/drivers/platform/x86/intel/hid.c index 13f8cf70b9ae..2def562c6e1d 100644 --- a/drivers/platform/x86/intel/hid.c +++ b/drivers/platform/x86/intel/hid.c @@ -726,12 +726,9 @@ static acpi_status __init check_acpi_dev(acpi_handle handle, u32 lvl, void *context, void **rv) { const struct acpi_device_id *ids = context; - struct acpi_device *dev; + struct acpi_device *dev = acpi_fetch_acpi_dev(handle); - if (acpi_bus_get_device(handle, &dev) != 0) - return AE_OK; - - if (acpi_match_device_ids(dev, ids) == 0) + if (dev && acpi_match_device_ids(dev, ids) == 0) if (!IS_ERR_OR_NULL(acpi_create_platform_device(dev, NULL))) dev_info(&dev->dev, "intel-hid: created platform device\n"); diff --git a/drivers/platform/x86/intel/int33fe/Kconfig b/drivers/platform/x86/intel/int33fe/Kconfig deleted file mode 100644 index 2f7329a2e399..000000000000 --- a/drivers/platform/x86/intel/int33fe/Kconfig +++ /dev/null @@ -1,24 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-only -config INTEL_CHT_INT33FE - tristate "Intel Cherry Trail ACPI INT33FE Driver" - depends on X86 && ACPI && I2C && REGULATOR - depends on CHARGER_BQ24190=y || (CHARGER_BQ24190=m && m) - depends on USB_ROLES_INTEL_XHCI=y || (USB_ROLES_INTEL_XHCI=m && m) - depends on TYPEC_MUX_PI3USB30532=y || (TYPEC_MUX_PI3USB30532=m && m) - help - This driver add support for the INT33FE ACPI device found on - some Intel Cherry Trail devices. - - There are two kinds of INT33FE ACPI device possible: for hardware - with USB Type-C and Micro-B connectors. This driver supports both. - - The INT33FE ACPI device has a CRS table with I2cSerialBusV2 - resources for Fuel Gauge Controller and (in the Type-C variant) - FUSB302 USB Type-C Controller and PI3USB30532 USB switch. - This driver instantiates i2c-clients for these, so that standard - i2c drivers for these chips can bind to the them. - - If you enable this driver it is advised to also select - CONFIG_BATTERY_BQ27XXX=m or CONFIG_BATTERY_BQ27XXX_I2C=m for Micro-B - device and CONFIG_TYPEC_FUSB302=m and CONFIG_BATTERY_MAX17042=m - for Type-C device. diff --git a/drivers/platform/x86/intel/int33fe/Makefile b/drivers/platform/x86/intel/int33fe/Makefile deleted file mode 100644 index 9456e3b37f6f..000000000000 --- a/drivers/platform/x86/intel/int33fe/Makefile +++ /dev/null @@ -1,5 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-only -obj-$(CONFIG_INTEL_CHT_INT33FE) += intel_cht_int33fe.o -intel_cht_int33fe-y := intel_cht_int33fe_common.o \ - intel_cht_int33fe_typec.o \ - intel_cht_int33fe_microb.o diff --git a/drivers/platform/x86/intel/int33fe/intel_cht_int33fe_common.c b/drivers/platform/x86/intel/int33fe/intel_cht_int33fe_common.c deleted file mode 100644 index 463222521e61..000000000000 --- a/drivers/platform/x86/intel/int33fe/intel_cht_int33fe_common.c +++ /dev/null @@ -1,118 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Common code for Intel Cherry Trail ACPI INT33FE pseudo device drivers - * (USB Micro-B and Type-C connector variants). - * - * Copyright (c) 2019 Yauhen Kharuzhy <jekhor@gmail.com> - */ - -#include <linux/acpi.h> -#include <linux/i2c.h> -#include <linux/module.h> -#include <linux/platform_device.h> -#include <linux/slab.h> - -#include "intel_cht_int33fe_common.h" - -#define EXPECTED_PTYPE 4 - -static int cht_int33fe_check_hw_type(struct device *dev) -{ - unsigned long long ptyp; - acpi_status status; - int ret; - - status = acpi_evaluate_integer(ACPI_HANDLE(dev), "PTYP", NULL, &ptyp); - if (ACPI_FAILURE(status)) { - dev_err(dev, "Error getting PTYPE\n"); - return -ENODEV; - } - - /* - * The same ACPI HID is used for different configurations check PTYP - * to ensure that we are dealing with the expected config. - */ - if (ptyp != EXPECTED_PTYPE) - return -ENODEV; - - /* Check presence of INT34D3 (hardware-rev 3) expected for ptype == 4 */ - if (!acpi_dev_present("INT34D3", "1", 3)) { - dev_err(dev, "Error PTYPE == %d, but no INT34D3 device\n", - EXPECTED_PTYPE); - return -ENODEV; - } - - ret = i2c_acpi_client_count(ACPI_COMPANION(dev)); - if (ret < 0) - return ret; - - switch (ret) { - case 2: - return INT33FE_HW_MICROB; - case 4: - return INT33FE_HW_TYPEC; - default: - return -ENODEV; - } -} - -static int cht_int33fe_probe(struct platform_device *pdev) -{ - struct cht_int33fe_data *data; - struct device *dev = &pdev->dev; - int ret; - - ret = cht_int33fe_check_hw_type(dev); - if (ret < 0) - return ret; - - data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); - if (!data) - return -ENOMEM; - - data->dev = dev; - - switch (ret) { - case INT33FE_HW_MICROB: - data->probe = cht_int33fe_microb_probe; - data->remove = cht_int33fe_microb_remove; - break; - - case INT33FE_HW_TYPEC: - data->probe = cht_int33fe_typec_probe; - data->remove = cht_int33fe_typec_remove; - break; - } - - platform_set_drvdata(pdev, data); - - return data->probe(data); -} - -static int cht_int33fe_remove(struct platform_device *pdev) -{ - struct cht_int33fe_data *data = platform_get_drvdata(pdev); - - return data->remove(data); -} - -static const struct acpi_device_id cht_int33fe_acpi_ids[] = { - { "INT33FE", }, - { } -}; -MODULE_DEVICE_TABLE(acpi, cht_int33fe_acpi_ids); - -static struct platform_driver cht_int33fe_driver = { - .driver = { - .name = "Intel Cherry Trail ACPI INT33FE driver", - .acpi_match_table = ACPI_PTR(cht_int33fe_acpi_ids), - }, - .probe = cht_int33fe_probe, - .remove = cht_int33fe_remove, -}; - -module_platform_driver(cht_int33fe_driver); - -MODULE_DESCRIPTION("Intel Cherry Trail ACPI INT33FE pseudo device driver"); -MODULE_AUTHOR("Yauhen Kharuzhy <jekhor@gmail.com>"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/platform/x86/intel/int33fe/intel_cht_int33fe_common.h b/drivers/platform/x86/intel/int33fe/intel_cht_int33fe_common.h deleted file mode 100644 index 03cd45f4e8cb..000000000000 --- a/drivers/platform/x86/intel/int33fe/intel_cht_int33fe_common.h +++ /dev/null @@ -1,41 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* - * Common code for Intel Cherry Trail ACPI INT33FE pseudo device drivers - * (USB Micro-B and Type-C connector variants), header file - * - * Copyright (c) 2019 Yauhen Kharuzhy <jekhor@gmail.com> - */ - -#ifndef _INTEL_CHT_INT33FE_COMMON_H -#define _INTEL_CHT_INT33FE_COMMON_H - -#include <linux/device.h> -#include <linux/fwnode.h> -#include <linux/i2c.h> - -enum int33fe_hw_type { - INT33FE_HW_MICROB, - INT33FE_HW_TYPEC, -}; - -struct cht_int33fe_data { - struct device *dev; - - int (*probe)(struct cht_int33fe_data *data); - int (*remove)(struct cht_int33fe_data *data); - - struct i2c_client *battery_fg; - - /* Type-C only */ - struct i2c_client *fusb302; - struct i2c_client *pi3usb30532; - - struct fwnode_handle *dp; -}; - -int cht_int33fe_microb_probe(struct cht_int33fe_data *data); -int cht_int33fe_microb_remove(struct cht_int33fe_data *data); -int cht_int33fe_typec_probe(struct cht_int33fe_data *data); -int cht_int33fe_typec_remove(struct cht_int33fe_data *data); - -#endif /* _INTEL_CHT_INT33FE_COMMON_H */ diff --git a/drivers/platform/x86/intel/int33fe/intel_cht_int33fe_microb.c b/drivers/platform/x86/intel/int33fe/intel_cht_int33fe_microb.c deleted file mode 100644 index 673f41cd14b5..000000000000 --- a/drivers/platform/x86/intel/int33fe/intel_cht_int33fe_microb.c +++ /dev/null @@ -1,61 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Intel Cherry Trail ACPI INT33FE pseudo device driver for devices with - * USB Micro-B connector (e.g. without of FUSB302 USB Type-C controller) - * - * Copyright (C) 2019 Yauhen Kharuzhy <jekhor@gmail.com> - * - * At least one Intel Cherry Trail based device which ship with Windows 10 - * (Lenovo YogaBook YB1-X91L/F tablet), have this weird INT33FE ACPI device - * with a CRS table with 2 I2cSerialBusV2 resources, for 2 different chips - * attached to various i2c busses: - * 1. The Whiskey Cove PMIC, which is also described by the INT34D3 ACPI device - * 2. TI BQ27542 Fuel Gauge Controller - * - * So this driver is a stub / pseudo driver whose only purpose is to - * instantiate i2c-client for battery fuel gauge, so that standard i2c driver - * for these chip can bind to the it. - */ - -#include <linux/acpi.h> -#include <linux/i2c.h> -#include <linux/module.h> -#include <linux/pci.h> -#include <linux/platform_device.h> -#include <linux/regulator/consumer.h> -#include <linux/slab.h> -#include <linux/usb/pd.h> - -#include "intel_cht_int33fe_common.h" - -static const char * const bq27xxx_suppliers[] = { "bq25890-charger" }; - -static const struct property_entry bq27xxx_props[] = { - PROPERTY_ENTRY_STRING_ARRAY("supplied-from", bq27xxx_suppliers), - { } -}; - -static const struct software_node bq27xxx_node = { - .properties = bq27xxx_props, -}; - -int cht_int33fe_microb_probe(struct cht_int33fe_data *data) -{ - struct device *dev = data->dev; - struct i2c_board_info board_info; - - memset(&board_info, 0, sizeof(board_info)); - strscpy(board_info.type, "bq27542", ARRAY_SIZE(board_info.type)); - board_info.dev_name = "bq27542"; - board_info.swnode = &bq27xxx_node; - data->battery_fg = i2c_acpi_new_device(dev, 1, &board_info); - - return PTR_ERR_OR_ZERO(data->battery_fg); -} - -int cht_int33fe_microb_remove(struct cht_int33fe_data *data) -{ - i2c_unregister_device(data->battery_fg); - - return 0; -} diff --git a/drivers/platform/x86/intel/int3472/discrete.c b/drivers/platform/x86/intel/int3472/discrete.c index 5b514fa01a97..ed4c9d760757 100644 --- a/drivers/platform/x86/intel/int3472/discrete.c +++ b/drivers/platform/x86/intel/int3472/discrete.c @@ -112,7 +112,6 @@ static int skl_int3472_map_gpio_to_sensor(struct int3472_discrete_device *int347 struct acpi_device *adev; acpi_handle handle; acpi_status status; - int ret; if (int3472->n_sensor_gpios >= INT3472_MAX_SENSOR_GPIOS) { dev_warn(int3472->dev, "Too many GPIOs mapped\n"); @@ -139,8 +138,8 @@ static int skl_int3472_map_gpio_to_sensor(struct int3472_discrete_device *int347 if (ACPI_FAILURE(status)) return -EINVAL; - ret = acpi_bus_get_device(handle, &adev); - if (ret) + adev = acpi_fetch_acpi_dev(handle); + if (!adev) return -ENODEV; table_entry = &int3472->gpios.table[int3472->n_sensor_gpios]; diff --git a/drivers/platform/x86/intel/sdsi.c b/drivers/platform/x86/intel/sdsi.c new file mode 100644 index 000000000000..11d14cc0ff0a --- /dev/null +++ b/drivers/platform/x86/intel/sdsi.c @@ -0,0 +1,574 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Intel Software Defined Silicon driver + * + * Copyright (c) 2022, Intel Corporation. + * All Rights Reserved. + * + * Author: "David E. Box" <david.e.box@linux.intel.com> + */ + +#include <linux/auxiliary_bus.h> +#include <linux/bits.h> +#include <linux/bitfield.h> +#include <linux/device.h> +#include <linux/iopoll.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/slab.h> +#include <linux/sysfs.h> +#include <linux/types.h> +#include <linux/uaccess.h> + +#include "vsec.h" + +#define ACCESS_TYPE_BARID 2 +#define ACCESS_TYPE_LOCAL 3 + +#define SDSI_MIN_SIZE_DWORDS 276 +#define SDSI_SIZE_CONTROL 8 +#define SDSI_SIZE_MAILBOX 1024 +#define SDSI_SIZE_REGS 72 +#define SDSI_SIZE_CMD sizeof(u64) + +/* + * Write messages are currently up to the size of the mailbox + * while read messages are up to 4 times the size of the + * mailbox, sent in packets + */ +#define SDSI_SIZE_WRITE_MSG SDSI_SIZE_MAILBOX +#define SDSI_SIZE_READ_MSG (SDSI_SIZE_MAILBOX * 4) + +#define SDSI_ENABLED_FEATURES_OFFSET 16 +#define SDSI_ENABLED BIT(3) +#define SDSI_SOCKET_ID_OFFSET 64 +#define SDSI_SOCKET_ID GENMASK(3, 0) + +#define SDSI_MBOX_CMD_SUCCESS 0x40 +#define SDSI_MBOX_CMD_TIMEOUT 0x80 + +#define MBOX_TIMEOUT_US 2000 +#define MBOX_TIMEOUT_ACQUIRE_US 1000 +#define MBOX_POLLING_PERIOD_US 100 +#define MBOX_MAX_PACKETS 4 + +#define MBOX_OWNER_NONE 0x00 +#define MBOX_OWNER_INBAND 0x01 + +#define CTRL_RUN_BUSY BIT(0) +#define CTRL_READ_WRITE BIT(1) +#define CTRL_SOM BIT(2) +#define CTRL_EOM BIT(3) +#define CTRL_OWNER GENMASK(5, 4) +#define CTRL_COMPLETE BIT(6) +#define CTRL_READY BIT(7) +#define CTRL_STATUS GENMASK(15, 8) +#define CTRL_PACKET_SIZE GENMASK(31, 16) +#define CTRL_MSG_SIZE GENMASK(63, 48) + +#define DISC_TABLE_SIZE 12 +#define DT_ACCESS_TYPE GENMASK(3, 0) +#define DT_SIZE GENMASK(27, 12) +#define DT_TBIR GENMASK(2, 0) +#define DT_OFFSET(v) ((v) & GENMASK(31, 3)) + +enum sdsi_command { + SDSI_CMD_PROVISION_AKC = 0x04, + SDSI_CMD_PROVISION_CAP = 0x08, + SDSI_CMD_READ_STATE = 0x10, +}; + +struct sdsi_mbox_info { + u64 *payload; + u64 *buffer; + int size; +}; + +struct disc_table { + u32 access_info; + u32 guid; + u32 offset; +}; + +struct sdsi_priv { + struct mutex mb_lock; /* Mailbox access lock */ + struct device *dev; + void __iomem *control_addr; + void __iomem *mbox_addr; + void __iomem *regs_addr; + u32 guid; + bool sdsi_enabled; +}; + +/* SDSi mailbox operations must be performed using 64bit mov instructions */ +static __always_inline void +sdsi_memcpy64_toio(u64 __iomem *to, const u64 *from, size_t count_bytes) +{ + size_t count = count_bytes / sizeof(*to); + int i; + + for (i = 0; i < count; i++) + writeq(from[i], &to[i]); +} + +static __always_inline void +sdsi_memcpy64_fromio(u64 *to, const u64 __iomem *from, size_t count_bytes) +{ + size_t count = count_bytes / sizeof(*to); + int i; + + for (i = 0; i < count; i++) + to[i] = readq(&from[i]); +} + +static inline void sdsi_complete_transaction(struct sdsi_priv *priv) +{ + u64 control = FIELD_PREP(CTRL_COMPLETE, 1); + + lockdep_assert_held(&priv->mb_lock); + writeq(control, priv->control_addr); +} + +static int sdsi_status_to_errno(u32 status) +{ + switch (status) { + case SDSI_MBOX_CMD_SUCCESS: + return 0; + case SDSI_MBOX_CMD_TIMEOUT: + return -ETIMEDOUT; + default: + return -EIO; + } +} + +static int sdsi_mbox_cmd_read(struct sdsi_priv *priv, struct sdsi_mbox_info *info, + size_t *data_size) +{ + struct device *dev = priv->dev; + u32 total, loop, eom, status, message_size; + u64 control; + int ret; + + lockdep_assert_held(&priv->mb_lock); + + /* Format and send the read command */ + control = FIELD_PREP(CTRL_EOM, 1) | + FIELD_PREP(CTRL_SOM, 1) | + FIELD_PREP(CTRL_RUN_BUSY, 1) | + FIELD_PREP(CTRL_PACKET_SIZE, info->size); + writeq(control, priv->control_addr); + + /* For reads, data sizes that are larger than the mailbox size are read in packets. */ + total = 0; + loop = 0; + do { + int offset = SDSI_SIZE_MAILBOX * loop; + void __iomem *addr = priv->mbox_addr + offset; + u64 *buf = info->buffer + offset / SDSI_SIZE_CMD; + u32 packet_size; + + /* Poll on ready bit */ + ret = readq_poll_timeout(priv->control_addr, control, control & CTRL_READY, + MBOX_POLLING_PERIOD_US, MBOX_TIMEOUT_US); + if (ret) + break; + + eom = FIELD_GET(CTRL_EOM, control); + status = FIELD_GET(CTRL_STATUS, control); + packet_size = FIELD_GET(CTRL_PACKET_SIZE, control); + message_size = FIELD_GET(CTRL_MSG_SIZE, control); + + ret = sdsi_status_to_errno(status); + if (ret) + break; + + /* Only the last packet can be less than the mailbox size. */ + if (!eom && packet_size != SDSI_SIZE_MAILBOX) { + dev_err(dev, "Invalid packet size\n"); + ret = -EPROTO; + break; + } + + if (packet_size > SDSI_SIZE_MAILBOX) { + dev_err(dev, "Packet size too large\n"); + ret = -EPROTO; + break; + } + + sdsi_memcpy64_fromio(buf, addr, round_up(packet_size, SDSI_SIZE_CMD)); + + total += packet_size; + + sdsi_complete_transaction(priv); + } while (!eom && ++loop < MBOX_MAX_PACKETS); + + if (ret) { + sdsi_complete_transaction(priv); + return ret; + } + + if (!eom) { + dev_err(dev, "Exceeded read attempts\n"); + return -EPROTO; + } + + /* Message size check is only valid for multi-packet transfers */ + if (loop && total != message_size) + dev_warn(dev, "Read count %u differs from expected count %u\n", + total, message_size); + + *data_size = total; + + return 0; +} + +static int sdsi_mbox_cmd_write(struct sdsi_priv *priv, struct sdsi_mbox_info *info) +{ + u64 control; + u32 status; + int ret; + + lockdep_assert_held(&priv->mb_lock); + + /* Write rest of the payload */ + sdsi_memcpy64_toio(priv->mbox_addr + SDSI_SIZE_CMD, info->payload + 1, + info->size - SDSI_SIZE_CMD); + + /* Format and send the write command */ + control = FIELD_PREP(CTRL_EOM, 1) | + FIELD_PREP(CTRL_SOM, 1) | + FIELD_PREP(CTRL_RUN_BUSY, 1) | + FIELD_PREP(CTRL_READ_WRITE, 1) | + FIELD_PREP(CTRL_PACKET_SIZE, info->size); + writeq(control, priv->control_addr); + + /* Poll on run_busy bit */ + ret = readq_poll_timeout(priv->control_addr, control, !(control & CTRL_RUN_BUSY), + MBOX_POLLING_PERIOD_US, MBOX_TIMEOUT_US); + + if (ret) + goto release_mbox; + + status = FIELD_GET(CTRL_STATUS, control); + ret = sdsi_status_to_errno(status); + +release_mbox: + sdsi_complete_transaction(priv); + + return ret; +} + +static int sdsi_mbox_acquire(struct sdsi_priv *priv, struct sdsi_mbox_info *info) +{ + u64 control; + u32 owner; + int ret; + + lockdep_assert_held(&priv->mb_lock); + + /* Check mailbox is available */ + control = readq(priv->control_addr); + owner = FIELD_GET(CTRL_OWNER, control); + if (owner != MBOX_OWNER_NONE) + return -EBUSY; + + /* Write first qword of payload */ + writeq(info->payload[0], priv->mbox_addr); + + /* Check for ownership */ + ret = readq_poll_timeout(priv->control_addr, control, + FIELD_GET(CTRL_OWNER, control) & MBOX_OWNER_INBAND, + MBOX_POLLING_PERIOD_US, MBOX_TIMEOUT_ACQUIRE_US); + + return ret; +} + +static int sdsi_mbox_write(struct sdsi_priv *priv, struct sdsi_mbox_info *info) +{ + int ret; + + lockdep_assert_held(&priv->mb_lock); + + ret = sdsi_mbox_acquire(priv, info); + if (ret) + return ret; + + return sdsi_mbox_cmd_write(priv, info); +} + +static int sdsi_mbox_read(struct sdsi_priv *priv, struct sdsi_mbox_info *info, size_t *data_size) +{ + int ret; + + lockdep_assert_held(&priv->mb_lock); + + ret = sdsi_mbox_acquire(priv, info); + if (ret) + return ret; + + return sdsi_mbox_cmd_read(priv, info, data_size); +} + +static ssize_t sdsi_provision(struct sdsi_priv *priv, char *buf, size_t count, + enum sdsi_command command) +{ + struct sdsi_mbox_info info; + int ret; + + if (!priv->sdsi_enabled) + return -EPERM; + + if (count > (SDSI_SIZE_WRITE_MSG - SDSI_SIZE_CMD)) + return -EOVERFLOW; + + /* Qword aligned message + command qword */ + info.size = round_up(count, SDSI_SIZE_CMD) + SDSI_SIZE_CMD; + + info.payload = kzalloc(info.size, GFP_KERNEL); + if (!info.payload) + return -ENOMEM; + + /* Copy message to payload buffer */ + memcpy(info.payload, buf, count); + + /* Command is last qword of payload buffer */ + info.payload[(info.size - SDSI_SIZE_CMD) / SDSI_SIZE_CMD] = command; + + ret = mutex_lock_interruptible(&priv->mb_lock); + if (ret) + goto free_payload; + ret = sdsi_mbox_write(priv, &info); + mutex_unlock(&priv->mb_lock); + +free_payload: + kfree(info.payload); + + if (ret) + return ret; + + return count; +} + +static ssize_t provision_akc_write(struct file *filp, struct kobject *kobj, + struct bin_attribute *attr, char *buf, loff_t off, + size_t count) +{ + struct device *dev = kobj_to_dev(kobj); + struct sdsi_priv *priv = dev_get_drvdata(dev); + + if (off) + return -ESPIPE; + + return sdsi_provision(priv, buf, count, SDSI_CMD_PROVISION_AKC); +} +static BIN_ATTR_WO(provision_akc, SDSI_SIZE_WRITE_MSG); + +static ssize_t provision_cap_write(struct file *filp, struct kobject *kobj, + struct bin_attribute *attr, char *buf, loff_t off, + size_t count) +{ + struct device *dev = kobj_to_dev(kobj); + struct sdsi_priv *priv = dev_get_drvdata(dev); + + if (off) + return -ESPIPE; + + return sdsi_provision(priv, buf, count, SDSI_CMD_PROVISION_CAP); +} +static BIN_ATTR_WO(provision_cap, SDSI_SIZE_WRITE_MSG); + +static long state_certificate_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *attr, char *buf, loff_t off, + size_t count) +{ + struct device *dev = kobj_to_dev(kobj); + struct sdsi_priv *priv = dev_get_drvdata(dev); + u64 command = SDSI_CMD_READ_STATE; + struct sdsi_mbox_info info; + size_t size; + int ret; + + if (!priv->sdsi_enabled) + return -EPERM; + + if (off) + return 0; + + /* Buffer for return data */ + info.buffer = kmalloc(SDSI_SIZE_READ_MSG, GFP_KERNEL); + if (!info.buffer) + return -ENOMEM; + + info.payload = &command; + info.size = sizeof(command); + + ret = mutex_lock_interruptible(&priv->mb_lock); + if (ret) + goto free_buffer; + ret = sdsi_mbox_read(priv, &info, &size); + mutex_unlock(&priv->mb_lock); + if (ret < 0) + goto free_buffer; + + if (size > count) + size = count; + + memcpy(buf, info.buffer, size); + +free_buffer: + kfree(info.buffer); + + if (ret) + return ret; + + return size; +} +static BIN_ATTR(state_certificate, 0400, state_certificate_read, NULL, SDSI_SIZE_READ_MSG); + +static ssize_t registers_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *attr, char *buf, loff_t off, + size_t count) +{ + struct device *dev = kobj_to_dev(kobj); + struct sdsi_priv *priv = dev_get_drvdata(dev); + void __iomem *addr = priv->regs_addr; + + memcpy_fromio(buf, addr + off, count); + + return count; +} +static BIN_ATTR(registers, 0400, registers_read, NULL, SDSI_SIZE_REGS); + +static struct bin_attribute *sdsi_bin_attrs[] = { + &bin_attr_registers, + &bin_attr_state_certificate, + &bin_attr_provision_akc, + &bin_attr_provision_cap, + NULL +}; + +static ssize_t guid_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct sdsi_priv *priv = dev_get_drvdata(dev); + + return sysfs_emit(buf, "0x%x\n", priv->guid); +} +static DEVICE_ATTR_RO(guid); + +static struct attribute *sdsi_attrs[] = { + &dev_attr_guid.attr, + NULL +}; + +static const struct attribute_group sdsi_group = { + .attrs = sdsi_attrs, + .bin_attrs = sdsi_bin_attrs, +}; +__ATTRIBUTE_GROUPS(sdsi); + +static int sdsi_map_mbox_registers(struct sdsi_priv *priv, struct pci_dev *parent, + struct disc_table *disc_table, struct resource *disc_res) +{ + u32 access_type = FIELD_GET(DT_ACCESS_TYPE, disc_table->access_info); + u32 size = FIELD_GET(DT_SIZE, disc_table->access_info); + u32 tbir = FIELD_GET(DT_TBIR, disc_table->offset); + u32 offset = DT_OFFSET(disc_table->offset); + u32 features_offset; + struct resource res = {}; + + /* Starting location of SDSi MMIO region based on access type */ + switch (access_type) { + case ACCESS_TYPE_LOCAL: + if (tbir) { + dev_err(priv->dev, "Unsupported BAR index %u for access type %u\n", + tbir, access_type); + return -EINVAL; + } + + /* + * For access_type LOCAL, the base address is as follows: + * base address = end of discovery region + base offset + 1 + */ + res.start = disc_res->end + offset + 1; + break; + + case ACCESS_TYPE_BARID: + res.start = pci_resource_start(parent, tbir) + offset; + break; + + default: + dev_err(priv->dev, "Unrecognized access_type %u\n", access_type); + return -EINVAL; + } + + res.end = res.start + size * sizeof(u32) - 1; + res.flags = IORESOURCE_MEM; + + priv->control_addr = devm_ioremap_resource(priv->dev, &res); + if (IS_ERR(priv->control_addr)) + return PTR_ERR(priv->control_addr); + + priv->mbox_addr = priv->control_addr + SDSI_SIZE_CONTROL; + priv->regs_addr = priv->mbox_addr + SDSI_SIZE_MAILBOX; + + features_offset = readq(priv->regs_addr + SDSI_ENABLED_FEATURES_OFFSET); + priv->sdsi_enabled = !!(features_offset & SDSI_ENABLED); + + return 0; +} + +static int sdsi_probe(struct auxiliary_device *auxdev, const struct auxiliary_device_id *id) +{ + struct intel_vsec_device *intel_cap_dev = auxdev_to_ivdev(auxdev); + struct disc_table disc_table; + struct resource *disc_res; + void __iomem *disc_addr; + struct sdsi_priv *priv; + int ret; + + priv = devm_kzalloc(&auxdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->dev = &auxdev->dev; + mutex_init(&priv->mb_lock); + auxiliary_set_drvdata(auxdev, priv); + + /* Get the SDSi discovery table */ + disc_res = &intel_cap_dev->resource[0]; + disc_addr = devm_ioremap_resource(&auxdev->dev, disc_res); + if (IS_ERR(disc_addr)) + return PTR_ERR(disc_addr); + + memcpy_fromio(&disc_table, disc_addr, DISC_TABLE_SIZE); + + priv->guid = disc_table.guid; + + /* Map the SDSi mailbox registers */ + ret = sdsi_map_mbox_registers(priv, intel_cap_dev->pcidev, &disc_table, disc_res); + if (ret) + return ret; + + return 0; +} + +static const struct auxiliary_device_id sdsi_aux_id_table[] = { + { .name = "intel_vsec.sdsi" }, + {} +}; +MODULE_DEVICE_TABLE(auxiliary, sdsi_aux_id_table); + +static struct auxiliary_driver sdsi_aux_driver = { + .driver = { + .dev_groups = sdsi_groups, + }, + .id_table = sdsi_aux_id_table, + .probe = sdsi_probe, + /* No remove. All resources are handled under devm */ +}; +module_auxiliary_driver(sdsi_aux_driver); + +MODULE_AUTHOR("David E. Box <david.e.box@linux.intel.com>"); +MODULE_DESCRIPTION("Intel Software Defined Silicon driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/intel/uncore-frequency.c b/drivers/platform/x86/intel/uncore-frequency.c deleted file mode 100644 index 4cd8254f2e40..000000000000 --- a/drivers/platform/x86/intel/uncore-frequency.c +++ /dev/null @@ -1,452 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Intel Uncore Frequency Setting - * Copyright (c) 2019, Intel Corporation. - * All rights reserved. - * - * Provide interface to set MSR 620 at a granularity of per die. On CPU online, - * one control CPU is identified per die to read/write limit. This control CPU - * is changed, if the CPU state is changed to offline. When the last CPU is - * offline in a die then remove the sysfs object for that die. - * The majority of actual code is related to sysfs create and read/write - * attributes. - * - * Author: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com> - */ - -#include <linux/cpu.h> -#include <linux/module.h> -#include <linux/slab.h> -#include <linux/suspend.h> -#include <asm/cpu_device_id.h> -#include <asm/intel-family.h> - -#define MSR_UNCORE_RATIO_LIMIT 0x620 -#define UNCORE_FREQ_KHZ_MULTIPLIER 100000 - -/** - * struct uncore_data - Encapsulate all uncore data - * @stored_uncore_data: Last user changed MSR 620 value, which will be restored - * on system resume. - * @initial_min_freq_khz: Sampled minimum uncore frequency at driver init - * @initial_max_freq_khz: Sampled maximum uncore frequency at driver init - * @control_cpu: Designated CPU for a die to read/write - * @valid: Mark the data valid/invalid - * - * This structure is used to encapsulate all data related to uncore sysfs - * settings for a die/package. - */ -struct uncore_data { - struct kobject kobj; - struct completion kobj_unregister; - u64 stored_uncore_data; - u32 initial_min_freq_khz; - u32 initial_max_freq_khz; - int control_cpu; - bool valid; -}; - -#define to_uncore_data(a) container_of(a, struct uncore_data, kobj) - -/* Max instances for uncore data, one for each die */ -static int uncore_max_entries __read_mostly; -/* Storage for uncore data for all instances */ -static struct uncore_data *uncore_instances; -/* Root of the all uncore sysfs kobjs */ -static struct kobject *uncore_root_kobj; -/* Stores the CPU mask of the target CPUs to use during uncore read/write */ -static cpumask_t uncore_cpu_mask; -/* CPU online callback register instance */ -static enum cpuhp_state uncore_hp_state __read_mostly; -/* Mutex to control all mutual exclusions */ -static DEFINE_MUTEX(uncore_lock); - -struct uncore_attr { - struct attribute attr; - ssize_t (*show)(struct kobject *kobj, - struct attribute *attr, char *buf); - ssize_t (*store)(struct kobject *kobj, - struct attribute *attr, const char *c, ssize_t count); -}; - -#define define_one_uncore_ro(_name) \ -static struct uncore_attr _name = \ -__ATTR(_name, 0444, show_##_name, NULL) - -#define define_one_uncore_rw(_name) \ -static struct uncore_attr _name = \ -__ATTR(_name, 0644, show_##_name, store_##_name) - -#define show_uncore_data(member_name) \ - static ssize_t show_##member_name(struct kobject *kobj, \ - struct attribute *attr, \ - char *buf) \ - { \ - struct uncore_data *data = to_uncore_data(kobj); \ - return scnprintf(buf, PAGE_SIZE, "%u\n", \ - data->member_name); \ - } \ - define_one_uncore_ro(member_name) - -show_uncore_data(initial_min_freq_khz); -show_uncore_data(initial_max_freq_khz); - -/* Common function to read MSR 0x620 and read min/max */ -static int uncore_read_ratio(struct uncore_data *data, unsigned int *min, - unsigned int *max) -{ - u64 cap; - int ret; - - if (data->control_cpu < 0) - return -ENXIO; - - ret = rdmsrl_on_cpu(data->control_cpu, MSR_UNCORE_RATIO_LIMIT, &cap); - if (ret) - return ret; - - *max = (cap & 0x7F) * UNCORE_FREQ_KHZ_MULTIPLIER; - *min = ((cap & GENMASK(14, 8)) >> 8) * UNCORE_FREQ_KHZ_MULTIPLIER; - - return 0; -} - -/* Common function to set min/max ratios to be used by sysfs callbacks */ -static int uncore_write_ratio(struct uncore_data *data, unsigned int input, - int set_max) -{ - int ret; - u64 cap; - - mutex_lock(&uncore_lock); - - if (data->control_cpu < 0) { - ret = -ENXIO; - goto finish_write; - } - - input /= UNCORE_FREQ_KHZ_MULTIPLIER; - if (!input || input > 0x7F) { - ret = -EINVAL; - goto finish_write; - } - - ret = rdmsrl_on_cpu(data->control_cpu, MSR_UNCORE_RATIO_LIMIT, &cap); - if (ret) - goto finish_write; - - if (set_max) { - cap &= ~0x7F; - cap |= input; - } else { - cap &= ~GENMASK(14, 8); - cap |= (input << 8); - } - - ret = wrmsrl_on_cpu(data->control_cpu, MSR_UNCORE_RATIO_LIMIT, cap); - if (ret) - goto finish_write; - - data->stored_uncore_data = cap; - -finish_write: - mutex_unlock(&uncore_lock); - - return ret; -} - -static ssize_t store_min_max_freq_khz(struct kobject *kobj, - struct attribute *attr, - const char *buf, ssize_t count, - int min_max) -{ - struct uncore_data *data = to_uncore_data(kobj); - unsigned int input; - - if (kstrtouint(buf, 10, &input)) - return -EINVAL; - - uncore_write_ratio(data, input, min_max); - - return count; -} - -static ssize_t show_min_max_freq_khz(struct kobject *kobj, - struct attribute *attr, - char *buf, int min_max) -{ - struct uncore_data *data = to_uncore_data(kobj); - unsigned int min, max; - int ret; - - mutex_lock(&uncore_lock); - ret = uncore_read_ratio(data, &min, &max); - mutex_unlock(&uncore_lock); - if (ret) - return ret; - - if (min_max) - return sprintf(buf, "%u\n", max); - - return sprintf(buf, "%u\n", min); -} - -#define store_uncore_min_max(name, min_max) \ - static ssize_t store_##name(struct kobject *kobj, \ - struct attribute *attr, \ - const char *buf, ssize_t count) \ - { \ - \ - return store_min_max_freq_khz(kobj, attr, buf, count, \ - min_max); \ - } - -#define show_uncore_min_max(name, min_max) \ - static ssize_t show_##name(struct kobject *kobj, \ - struct attribute *attr, char *buf) \ - { \ - \ - return show_min_max_freq_khz(kobj, attr, buf, min_max); \ - } - -store_uncore_min_max(min_freq_khz, 0); -store_uncore_min_max(max_freq_khz, 1); - -show_uncore_min_max(min_freq_khz, 0); -show_uncore_min_max(max_freq_khz, 1); - -define_one_uncore_rw(min_freq_khz); -define_one_uncore_rw(max_freq_khz); - -static struct attribute *uncore_attrs[] = { - &initial_min_freq_khz.attr, - &initial_max_freq_khz.attr, - &max_freq_khz.attr, - &min_freq_khz.attr, - NULL -}; -ATTRIBUTE_GROUPS(uncore); - -static void uncore_sysfs_entry_release(struct kobject *kobj) -{ - struct uncore_data *data = to_uncore_data(kobj); - - complete(&data->kobj_unregister); -} - -static struct kobj_type uncore_ktype = { - .release = uncore_sysfs_entry_release, - .sysfs_ops = &kobj_sysfs_ops, - .default_groups = uncore_groups, -}; - -/* Caller provides protection */ -static struct uncore_data *uncore_get_instance(unsigned int cpu) -{ - int id = topology_logical_die_id(cpu); - - if (id >= 0 && id < uncore_max_entries) - return &uncore_instances[id]; - - return NULL; -} - -static void uncore_add_die_entry(int cpu) -{ - struct uncore_data *data; - - mutex_lock(&uncore_lock); - data = uncore_get_instance(cpu); - if (!data) { - mutex_unlock(&uncore_lock); - return; - } - - if (data->valid) { - /* control cpu changed */ - data->control_cpu = cpu; - } else { - char str[64]; - int ret; - - memset(data, 0, sizeof(*data)); - sprintf(str, "package_%02d_die_%02d", - topology_physical_package_id(cpu), - topology_die_id(cpu)); - - uncore_read_ratio(data, &data->initial_min_freq_khz, - &data->initial_max_freq_khz); - - init_completion(&data->kobj_unregister); - - ret = kobject_init_and_add(&data->kobj, &uncore_ktype, - uncore_root_kobj, str); - if (!ret) { - data->control_cpu = cpu; - data->valid = true; - } - } - mutex_unlock(&uncore_lock); -} - -/* Last CPU in this die is offline, make control cpu invalid */ -static void uncore_remove_die_entry(int cpu) -{ - struct uncore_data *data; - - mutex_lock(&uncore_lock); - data = uncore_get_instance(cpu); - if (data) - data->control_cpu = -1; - mutex_unlock(&uncore_lock); -} - -static int uncore_event_cpu_online(unsigned int cpu) -{ - int target; - - /* Check if there is an online cpu in the package for uncore MSR */ - target = cpumask_any_and(&uncore_cpu_mask, topology_die_cpumask(cpu)); - if (target < nr_cpu_ids) - return 0; - - /* Use this CPU on this die as a control CPU */ - cpumask_set_cpu(cpu, &uncore_cpu_mask); - uncore_add_die_entry(cpu); - - return 0; -} - -static int uncore_event_cpu_offline(unsigned int cpu) -{ - int target; - - /* Check if existing cpu is used for uncore MSRs */ - if (!cpumask_test_and_clear_cpu(cpu, &uncore_cpu_mask)) - return 0; - - /* Find a new cpu to set uncore MSR */ - target = cpumask_any_but(topology_die_cpumask(cpu), cpu); - - if (target < nr_cpu_ids) { - cpumask_set_cpu(target, &uncore_cpu_mask); - uncore_add_die_entry(target); - } else { - uncore_remove_die_entry(cpu); - } - - return 0; -} - -static int uncore_pm_notify(struct notifier_block *nb, unsigned long mode, - void *_unused) -{ - int cpu; - - switch (mode) { - case PM_POST_HIBERNATION: - case PM_POST_RESTORE: - case PM_POST_SUSPEND: - for_each_cpu(cpu, &uncore_cpu_mask) { - struct uncore_data *data; - int ret; - - data = uncore_get_instance(cpu); - if (!data || !data->valid || !data->stored_uncore_data) - continue; - - ret = wrmsrl_on_cpu(cpu, MSR_UNCORE_RATIO_LIMIT, - data->stored_uncore_data); - if (ret) - return ret; - } - break; - default: - break; - } - return 0; -} - -static struct notifier_block uncore_pm_nb = { - .notifier_call = uncore_pm_notify, -}; - -static const struct x86_cpu_id intel_uncore_cpu_ids[] = { - X86_MATCH_INTEL_FAM6_MODEL(BROADWELL_G, NULL), - X86_MATCH_INTEL_FAM6_MODEL(BROADWELL_X, NULL), - X86_MATCH_INTEL_FAM6_MODEL(BROADWELL_D, NULL), - X86_MATCH_INTEL_FAM6_MODEL(SKYLAKE_X, NULL), - X86_MATCH_INTEL_FAM6_MODEL(ICELAKE_X, NULL), - X86_MATCH_INTEL_FAM6_MODEL(ICELAKE_D, NULL), - X86_MATCH_INTEL_FAM6_MODEL(SAPPHIRERAPIDS_X, NULL), - {} -}; - -static int __init intel_uncore_init(void) -{ - const struct x86_cpu_id *id; - int ret; - - id = x86_match_cpu(intel_uncore_cpu_ids); - if (!id) - return -ENODEV; - - uncore_max_entries = topology_max_packages() * - topology_max_die_per_package(); - uncore_instances = kcalloc(uncore_max_entries, - sizeof(*uncore_instances), GFP_KERNEL); - if (!uncore_instances) - return -ENOMEM; - - uncore_root_kobj = kobject_create_and_add("intel_uncore_frequency", - &cpu_subsys.dev_root->kobj); - if (!uncore_root_kobj) { - ret = -ENOMEM; - goto err_free; - } - - ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, - "platform/x86/uncore-freq:online", - uncore_event_cpu_online, - uncore_event_cpu_offline); - if (ret < 0) - goto err_rem_kobj; - - uncore_hp_state = ret; - - ret = register_pm_notifier(&uncore_pm_nb); - if (ret) - goto err_rem_state; - - return 0; - -err_rem_state: - cpuhp_remove_state(uncore_hp_state); -err_rem_kobj: - kobject_put(uncore_root_kobj); -err_free: - kfree(uncore_instances); - - return ret; -} -module_init(intel_uncore_init) - -static void __exit intel_uncore_exit(void) -{ - int i; - - unregister_pm_notifier(&uncore_pm_nb); - cpuhp_remove_state(uncore_hp_state); - for (i = 0; i < uncore_max_entries; ++i) { - if (uncore_instances[i].valid) { - kobject_put(&uncore_instances[i].kobj); - wait_for_completion(&uncore_instances[i].kobj_unregister); - } - } - kobject_put(uncore_root_kobj); - kfree(uncore_instances); -} -module_exit(intel_uncore_exit) - -MODULE_LICENSE("GPL v2"); -MODULE_DESCRIPTION("Intel Uncore Frequency Limits Driver"); diff --git a/drivers/platform/x86/intel/uncore-frequency/Kconfig b/drivers/platform/x86/intel/uncore-frequency/Kconfig new file mode 100644 index 000000000000..21b209124916 --- /dev/null +++ b/drivers/platform/x86/intel/uncore-frequency/Kconfig @@ -0,0 +1,21 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Uncore Frquency control drivers +# + +menu "Intel Uncore Frequency Control" + depends on X86_64 || COMPILE_TEST + +config INTEL_UNCORE_FREQ_CONTROL + tristate "Intel Uncore frequency control driver" + depends on X86_64 + help + This driver allows control of Uncore frequency limits on + supported server platforms. + + Uncore frequency controls RING/LLC (last-level cache) clocks. + + To compile this driver as a module, choose M here: the module + will be called intel-uncore-frequency. + +endmenu diff --git a/drivers/platform/x86/intel/uncore-frequency/Makefile b/drivers/platform/x86/intel/uncore-frequency/Makefile new file mode 100644 index 000000000000..e0f7968e8285 --- /dev/null +++ b/drivers/platform/x86/intel/uncore-frequency/Makefile @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for linux/drivers/platform/x86/intel/uncore-frequency +# + +obj-$(CONFIG_INTEL_UNCORE_FREQ_CONTROL) += intel-uncore-frequency.o +intel-uncore-frequency-y := uncore-frequency.o +obj-$(CONFIG_INTEL_UNCORE_FREQ_CONTROL) += intel-uncore-frequency-common.o +intel-uncore-frequency-common-y := uncore-frequency-common.o diff --git a/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-common.c b/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-common.c new file mode 100644 index 000000000000..84eabd6156bb --- /dev/null +++ b/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-common.c @@ -0,0 +1,252 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Intel Uncore Frequency Control: Common code implementation + * Copyright (c) 2022, Intel Corporation. + * All rights reserved. + * + */ +#include <linux/cpu.h> +#include <linux/module.h> +#include "uncore-frequency-common.h" + +/* Mutex to control all mutual exclusions */ +static DEFINE_MUTEX(uncore_lock); +/* Root of the all uncore sysfs kobjs */ +static struct kobject *uncore_root_kobj; +/* uncore instance count */ +static int uncore_instance_count; + +/* callbacks for actual HW read/write */ +static int (*uncore_read)(struct uncore_data *data, unsigned int *min, unsigned int *max); +static int (*uncore_write)(struct uncore_data *data, unsigned int input, unsigned int min_max); +static int (*uncore_read_freq)(struct uncore_data *data, unsigned int *freq); + +static ssize_t show_min_max_freq_khz(struct uncore_data *data, + char *buf, int min_max) +{ + unsigned int min, max; + int ret; + + mutex_lock(&uncore_lock); + ret = uncore_read(data, &min, &max); + mutex_unlock(&uncore_lock); + if (ret) + return ret; + + if (min_max) + return sprintf(buf, "%u\n", max); + + return sprintf(buf, "%u\n", min); +} + +static ssize_t store_min_max_freq_khz(struct uncore_data *data, + const char *buf, ssize_t count, + int min_max) +{ + unsigned int input; + + if (kstrtouint(buf, 10, &input)) + return -EINVAL; + + mutex_lock(&uncore_lock); + uncore_write(data, input, min_max); + mutex_unlock(&uncore_lock); + + return count; +} + +static ssize_t show_perf_status_freq_khz(struct uncore_data *data, char *buf) +{ + unsigned int freq; + int ret; + + mutex_lock(&uncore_lock); + ret = uncore_read_freq(data, &freq); + mutex_unlock(&uncore_lock); + if (ret) + return ret; + + return sprintf(buf, "%u\n", freq); +} + +#define store_uncore_min_max(name, min_max) \ + static ssize_t store_##name(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t count) \ + { \ + struct uncore_data *data = container_of(attr, struct uncore_data, name##_dev_attr);\ + \ + return store_min_max_freq_khz(data, buf, count, \ + min_max); \ + } + +#define show_uncore_min_max(name, min_max) \ + static ssize_t show_##name(struct device *dev, \ + struct device_attribute *attr, char *buf)\ + { \ + struct uncore_data *data = container_of(attr, struct uncore_data, name##_dev_attr);\ + \ + return show_min_max_freq_khz(data, buf, min_max); \ + } + +#define show_uncore_perf_status(name) \ + static ssize_t show_##name(struct device *dev, \ + struct device_attribute *attr, char *buf)\ + { \ + struct uncore_data *data = container_of(attr, struct uncore_data, name##_dev_attr);\ + \ + return show_perf_status_freq_khz(data, buf); \ + } + +store_uncore_min_max(min_freq_khz, 0); +store_uncore_min_max(max_freq_khz, 1); + +show_uncore_min_max(min_freq_khz, 0); +show_uncore_min_max(max_freq_khz, 1); + +show_uncore_perf_status(current_freq_khz); + +#define show_uncore_data(member_name) \ + static ssize_t show_##member_name(struct device *dev, \ + struct device_attribute *attr, char *buf)\ + { \ + struct uncore_data *data = container_of(attr, struct uncore_data,\ + member_name##_dev_attr);\ + \ + return scnprintf(buf, PAGE_SIZE, "%u\n", \ + data->member_name); \ + } \ + +show_uncore_data(initial_min_freq_khz); +show_uncore_data(initial_max_freq_khz); + +#define init_attribute_rw(_name) \ + do { \ + sysfs_attr_init(&data->_name##_dev_attr.attr); \ + data->_name##_dev_attr.show = show_##_name; \ + data->_name##_dev_attr.store = store_##_name; \ + data->_name##_dev_attr.attr.name = #_name; \ + data->_name##_dev_attr.attr.mode = 0644; \ + } while (0) + +#define init_attribute_ro(_name) \ + do { \ + sysfs_attr_init(&data->_name##_dev_attr.attr); \ + data->_name##_dev_attr.show = show_##_name; \ + data->_name##_dev_attr.store = NULL; \ + data->_name##_dev_attr.attr.name = #_name; \ + data->_name##_dev_attr.attr.mode = 0444; \ + } while (0) + +#define init_attribute_root_ro(_name) \ + do { \ + sysfs_attr_init(&data->_name##_dev_attr.attr); \ + data->_name##_dev_attr.show = show_##_name; \ + data->_name##_dev_attr.store = NULL; \ + data->_name##_dev_attr.attr.name = #_name; \ + data->_name##_dev_attr.attr.mode = 0400; \ + } while (0) + +static int create_attr_group(struct uncore_data *data, char *name) +{ + int ret, index = 0; + + init_attribute_rw(max_freq_khz); + init_attribute_rw(min_freq_khz); + init_attribute_ro(initial_min_freq_khz); + init_attribute_ro(initial_max_freq_khz); + init_attribute_root_ro(current_freq_khz); + + data->uncore_attrs[index++] = &data->max_freq_khz_dev_attr.attr; + data->uncore_attrs[index++] = &data->min_freq_khz_dev_attr.attr; + data->uncore_attrs[index++] = &data->initial_min_freq_khz_dev_attr.attr; + data->uncore_attrs[index++] = &data->initial_max_freq_khz_dev_attr.attr; + data->uncore_attrs[index++] = &data->current_freq_khz_dev_attr.attr; + data->uncore_attrs[index] = NULL; + + data->uncore_attr_group.name = name; + data->uncore_attr_group.attrs = data->uncore_attrs; + ret = sysfs_create_group(uncore_root_kobj, &data->uncore_attr_group); + + return ret; +} + +static void delete_attr_group(struct uncore_data *data, char *name) +{ + sysfs_remove_group(uncore_root_kobj, &data->uncore_attr_group); +} + +int uncore_freq_add_entry(struct uncore_data *data, int cpu) +{ + int ret = 0; + + mutex_lock(&uncore_lock); + if (data->valid) { + /* control cpu changed */ + data->control_cpu = cpu; + goto uncore_unlock; + } + + sprintf(data->name, "package_%02d_die_%02d", data->package_id, data->die_id); + + uncore_read(data, &data->initial_min_freq_khz, &data->initial_max_freq_khz); + + ret = create_attr_group(data, data->name); + if (!ret) { + data->control_cpu = cpu; + data->valid = true; + } + +uncore_unlock: + mutex_unlock(&uncore_lock); + + return ret; +} +EXPORT_SYMBOL_NS_GPL(uncore_freq_add_entry, INTEL_UNCORE_FREQUENCY); + +void uncore_freq_remove_die_entry(struct uncore_data *data) +{ + mutex_lock(&uncore_lock); + delete_attr_group(data, data->name); + data->control_cpu = -1; + data->valid = false; + mutex_unlock(&uncore_lock); +} +EXPORT_SYMBOL_NS_GPL(uncore_freq_remove_die_entry, INTEL_UNCORE_FREQUENCY); + +int uncore_freq_common_init(int (*read_control_freq)(struct uncore_data *data, unsigned int *min, unsigned int *max), + int (*write_control_freq)(struct uncore_data *data, unsigned int input, unsigned int set_max), + int (*read_freq)(struct uncore_data *data, unsigned int *freq)) +{ + mutex_lock(&uncore_lock); + + uncore_read = read_control_freq; + uncore_write = write_control_freq; + uncore_read_freq = read_freq; + + if (!uncore_root_kobj) + uncore_root_kobj = kobject_create_and_add("intel_uncore_frequency", + &cpu_subsys.dev_root->kobj); + if (uncore_root_kobj) + ++uncore_instance_count; + mutex_unlock(&uncore_lock); + + return uncore_root_kobj ? 0 : -ENOMEM; +} +EXPORT_SYMBOL_NS_GPL(uncore_freq_common_init, INTEL_UNCORE_FREQUENCY); + +void uncore_freq_common_exit(void) +{ + mutex_lock(&uncore_lock); + --uncore_instance_count; + if (!uncore_instance_count) { + kobject_put(uncore_root_kobj); + uncore_root_kobj = NULL; + } + mutex_unlock(&uncore_lock); +} +EXPORT_SYMBOL_NS_GPL(uncore_freq_common_exit, INTEL_UNCORE_FREQUENCY); + + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Intel Uncore Frequency Common Module"); diff --git a/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-common.h b/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-common.h new file mode 100644 index 000000000000..f5dcfa2fb285 --- /dev/null +++ b/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-common.h @@ -0,0 +1,62 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Intel Uncore Frequency Control: Common defines and prototypes + * Copyright (c) 2022, Intel Corporation. + * All rights reserved. + * + */ + +#ifndef __INTEL_UNCORE_FREQ_COMMON_H +#define __INTEL_UNCORE_FREQ_COMMON_H + +#include <linux/device.h> + +/** + * struct uncore_data - Encapsulate all uncore data + * @stored_uncore_data: Last user changed MSR 620 value, which will be restored + * on system resume. + * @initial_min_freq_khz: Sampled minimum uncore frequency at driver init + * @initial_max_freq_khz: Sampled maximum uncore frequency at driver init + * @control_cpu: Designated CPU for a die to read/write + * @valid: Mark the data valid/invalid + * @package_id: Package id for this instance + * @die_id: Die id for this instance + * @name: Sysfs entry name for this instance + * @uncore_attr_group: Attribute group storage + * @max_freq_khz_dev_attr: Storage for device attribute max_freq_khz + * @mix_freq_khz_dev_attr: Storage for device attribute min_freq_khz + * @initial_max_freq_khz_dev_attr: Storage for device attribute initial_max_freq_khz + * @initial_min_freq_khz_dev_attr: Storage for device attribute initial_min_freq_khz + * @current_freq_khz_dev_attr: Storage for device attribute current_freq_khz + * @uncore_attrs: Attribute storage for group creation + * + * This structure is used to encapsulate all data related to uncore sysfs + * settings for a die/package. + */ +struct uncore_data { + u64 stored_uncore_data; + u32 initial_min_freq_khz; + u32 initial_max_freq_khz; + int control_cpu; + bool valid; + int package_id; + int die_id; + char name[32]; + + struct attribute_group uncore_attr_group; + struct device_attribute max_freq_khz_dev_attr; + struct device_attribute min_freq_khz_dev_attr; + struct device_attribute initial_max_freq_khz_dev_attr; + struct device_attribute initial_min_freq_khz_dev_attr; + struct device_attribute current_freq_khz_dev_attr; + struct attribute *uncore_attrs[6]; +}; + +int uncore_freq_common_init(int (*read_control_freq)(struct uncore_data *data, unsigned int *min, unsigned int *max), + int (*write_control_freq)(struct uncore_data *data, unsigned int input, unsigned int min_max), + int (*uncore_read_freq)(struct uncore_data *data, unsigned int *freq)); +void uncore_freq_common_exit(void); +int uncore_freq_add_entry(struct uncore_data *data, int cpu); +void uncore_freq_remove_die_entry(struct uncore_data *data); + +#endif diff --git a/drivers/platform/x86/intel/uncore-frequency/uncore-frequency.c b/drivers/platform/x86/intel/uncore-frequency/uncore-frequency.c new file mode 100644 index 000000000000..c61f804dd44e --- /dev/null +++ b/drivers/platform/x86/intel/uncore-frequency/uncore-frequency.c @@ -0,0 +1,272 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Intel Uncore Frequency Setting + * Copyright (c) 2022, Intel Corporation. + * All rights reserved. + * + * Provide interface to set MSR 620 at a granularity of per die. On CPU online, + * one control CPU is identified per die to read/write limit. This control CPU + * is changed, if the CPU state is changed to offline. When the last CPU is + * offline in a die then remove the sysfs object for that die. + * The majority of actual code is related to sysfs create and read/write + * attributes. + * + * Author: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com> + */ + +#include <linux/cpu.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/suspend.h> +#include <asm/cpu_device_id.h> +#include <asm/intel-family.h> + +#include "uncore-frequency-common.h" + +/* Max instances for uncore data, one for each die */ +static int uncore_max_entries __read_mostly; +/* Storage for uncore data for all instances */ +static struct uncore_data *uncore_instances; +/* Stores the CPU mask of the target CPUs to use during uncore read/write */ +static cpumask_t uncore_cpu_mask; +/* CPU online callback register instance */ +static enum cpuhp_state uncore_hp_state __read_mostly; + +#define MSR_UNCORE_RATIO_LIMIT 0x620 +#define MSR_UNCORE_PERF_STATUS 0x621 +#define UNCORE_FREQ_KHZ_MULTIPLIER 100000 + +static int uncore_read_control_freq(struct uncore_data *data, unsigned int *min, + unsigned int *max) +{ + u64 cap; + int ret; + + if (data->control_cpu < 0) + return -ENXIO; + + ret = rdmsrl_on_cpu(data->control_cpu, MSR_UNCORE_RATIO_LIMIT, &cap); + if (ret) + return ret; + + *max = (cap & 0x7F) * UNCORE_FREQ_KHZ_MULTIPLIER; + *min = ((cap & GENMASK(14, 8)) >> 8) * UNCORE_FREQ_KHZ_MULTIPLIER; + + return 0; +} + +static int uncore_write_control_freq(struct uncore_data *data, unsigned int input, + unsigned int min_max) +{ + int ret; + u64 cap; + + input /= UNCORE_FREQ_KHZ_MULTIPLIER; + if (!input || input > 0x7F) + return -EINVAL; + + if (data->control_cpu < 0) + return -ENXIO; + + ret = rdmsrl_on_cpu(data->control_cpu, MSR_UNCORE_RATIO_LIMIT, &cap); + if (ret) + return ret; + + if (min_max) { + cap &= ~0x7F; + cap |= input; + } else { + cap &= ~GENMASK(14, 8); + cap |= (input << 8); + } + + ret = wrmsrl_on_cpu(data->control_cpu, MSR_UNCORE_RATIO_LIMIT, cap); + if (ret) + return ret; + + data->stored_uncore_data = cap; + + return 0; +} + +static int uncore_read_freq(struct uncore_data *data, unsigned int *freq) +{ + u64 ratio; + int ret; + + if (data->control_cpu < 0) + return -ENXIO; + + ret = rdmsrl_on_cpu(data->control_cpu, MSR_UNCORE_PERF_STATUS, &ratio); + if (ret) + return ret; + + *freq = (ratio & 0x7F) * UNCORE_FREQ_KHZ_MULTIPLIER; + + return 0; +} + +/* Caller provides protection */ +static struct uncore_data *uncore_get_instance(unsigned int cpu) +{ + int id = topology_logical_die_id(cpu); + + if (id >= 0 && id < uncore_max_entries) + return &uncore_instances[id]; + + return NULL; +} + +static int uncore_event_cpu_online(unsigned int cpu) +{ + struct uncore_data *data; + int target; + + /* Check if there is an online cpu in the package for uncore MSR */ + target = cpumask_any_and(&uncore_cpu_mask, topology_die_cpumask(cpu)); + if (target < nr_cpu_ids) + return 0; + + /* Use this CPU on this die as a control CPU */ + cpumask_set_cpu(cpu, &uncore_cpu_mask); + + data = uncore_get_instance(cpu); + if (!data) + return 0; + + data->package_id = topology_physical_package_id(cpu); + data->die_id = topology_die_id(cpu); + + return uncore_freq_add_entry(data, cpu); +} + +static int uncore_event_cpu_offline(unsigned int cpu) +{ + struct uncore_data *data; + int target; + + data = uncore_get_instance(cpu); + if (!data) + return 0; + + /* Check if existing cpu is used for uncore MSRs */ + if (!cpumask_test_and_clear_cpu(cpu, &uncore_cpu_mask)) + return 0; + + /* Find a new cpu to set uncore MSR */ + target = cpumask_any_but(topology_die_cpumask(cpu), cpu); + + if (target < nr_cpu_ids) { + cpumask_set_cpu(target, &uncore_cpu_mask); + uncore_freq_add_entry(data, target); + } else { + uncore_freq_remove_die_entry(data); + } + + return 0; +} + +static int uncore_pm_notify(struct notifier_block *nb, unsigned long mode, + void *_unused) +{ + int i; + + switch (mode) { + case PM_POST_HIBERNATION: + case PM_POST_RESTORE: + case PM_POST_SUSPEND: + for (i = 0; i < uncore_max_entries; ++i) { + struct uncore_data *data = &uncore_instances[i]; + + if (!data || !data->valid || !data->stored_uncore_data) + return 0; + + wrmsrl_on_cpu(data->control_cpu, MSR_UNCORE_RATIO_LIMIT, + data->stored_uncore_data); + } + break; + default: + break; + } + return 0; +} + +static struct notifier_block uncore_pm_nb = { + .notifier_call = uncore_pm_notify, +}; + +static const struct x86_cpu_id intel_uncore_cpu_ids[] = { + X86_MATCH_INTEL_FAM6_MODEL(BROADWELL_G, NULL), + X86_MATCH_INTEL_FAM6_MODEL(BROADWELL_X, NULL), + X86_MATCH_INTEL_FAM6_MODEL(BROADWELL_D, NULL), + X86_MATCH_INTEL_FAM6_MODEL(SKYLAKE_X, NULL), + X86_MATCH_INTEL_FAM6_MODEL(ICELAKE_X, NULL), + X86_MATCH_INTEL_FAM6_MODEL(ICELAKE_D, NULL), + X86_MATCH_INTEL_FAM6_MODEL(SAPPHIRERAPIDS_X, NULL), + {} +}; +MODULE_DEVICE_TABLE(x86cpu, intel_uncore_cpu_ids); + +static int __init intel_uncore_init(void) +{ + const struct x86_cpu_id *id; + int ret; + + id = x86_match_cpu(intel_uncore_cpu_ids); + if (!id) + return -ENODEV; + + uncore_max_entries = topology_max_packages() * + topology_max_die_per_package(); + uncore_instances = kcalloc(uncore_max_entries, + sizeof(*uncore_instances), GFP_KERNEL); + if (!uncore_instances) + return -ENOMEM; + + ret = uncore_freq_common_init(uncore_read_control_freq, uncore_write_control_freq, + uncore_read_freq); + if (ret) + goto err_free; + + ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, + "platform/x86/uncore-freq:online", + uncore_event_cpu_online, + uncore_event_cpu_offline); + if (ret < 0) + goto err_rem_kobj; + + uncore_hp_state = ret; + + ret = register_pm_notifier(&uncore_pm_nb); + if (ret) + goto err_rem_state; + + return 0; + +err_rem_state: + cpuhp_remove_state(uncore_hp_state); +err_rem_kobj: + uncore_freq_common_exit(); +err_free: + kfree(uncore_instances); + + return ret; +} +module_init(intel_uncore_init) + +static void __exit intel_uncore_exit(void) +{ + int i; + + unregister_pm_notifier(&uncore_pm_nb); + cpuhp_remove_state(uncore_hp_state); + for (i = 0; i < uncore_max_entries; ++i) + uncore_freq_remove_die_entry(&uncore_instances[i]); + uncore_freq_common_exit(); + kfree(uncore_instances); +} +module_exit(intel_uncore_exit) + +MODULE_IMPORT_NS(INTEL_UNCORE_FREQUENCY); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Intel Uncore Frequency Limits Driver"); diff --git a/drivers/platform/x86/intel/vbtn.c b/drivers/platform/x86/intel/vbtn.c index 15f013af9e62..c5e4e35c8d20 100644 --- a/drivers/platform/x86/intel/vbtn.c +++ b/drivers/platform/x86/intel/vbtn.c @@ -384,12 +384,9 @@ static acpi_status __init check_acpi_dev(acpi_handle handle, u32 lvl, void *context, void **rv) { const struct acpi_device_id *ids = context; - struct acpi_device *dev; + struct acpi_device *dev = acpi_fetch_acpi_dev(handle); - if (acpi_bus_get_device(handle, &dev) != 0) - return AE_OK; - - if (acpi_match_device_ids(dev, ids) == 0) + if (dev && acpi_match_device_ids(dev, ids) == 0) if (!IS_ERR_OR_NULL(acpi_create_platform_device(dev, NULL))) dev_info(&dev->dev, "intel-vbtn: created platform device\n"); diff --git a/drivers/platform/x86/intel/vsec.c b/drivers/platform/x86/intel/vsec.c index c3bdd75ed690..bed436bf181f 100644 --- a/drivers/platform/x86/intel/vsec.c +++ b/drivers/platform/x86/intel/vsec.c @@ -32,6 +32,7 @@ #define TABLE_OFFSET_SHIFT 3 static DEFINE_IDA(intel_vsec_ida); +static DEFINE_IDA(intel_vsec_sdsi_ida); /** * struct intel_vsec_header - Common fields of Intel VSEC and DVSEC registers. @@ -63,12 +64,14 @@ enum intel_vsec_id { VSEC_ID_TELEMETRY = 2, VSEC_ID_WATCHER = 3, VSEC_ID_CRASHLOG = 4, + VSEC_ID_SDSI = 65, }; static enum intel_vsec_id intel_vsec_allow_list[] = { VSEC_ID_TELEMETRY, VSEC_ID_WATCHER, VSEC_ID_CRASHLOG, + VSEC_ID_SDSI, }; static const char *intel_vsec_name(enum intel_vsec_id id) @@ -83,6 +86,9 @@ static const char *intel_vsec_name(enum intel_vsec_id id) case VSEC_ID_CRASHLOG: return "crashlog"; + case VSEC_ID_SDSI: + return "sdsi"; + default: return NULL; } @@ -211,7 +217,11 @@ static int intel_vsec_add_dev(struct pci_dev *pdev, struct intel_vsec_header *he intel_vsec_dev->resource = res; intel_vsec_dev->num_resources = header->num_entries; intel_vsec_dev->quirks = quirks; - intel_vsec_dev->ida = &intel_vsec_ida; + + if (header->id == VSEC_ID_SDSI) + intel_vsec_dev->ida = &intel_vsec_sdsi_ida; + else + intel_vsec_dev->ida = &intel_vsec_ida; return intel_vsec_add_aux(pdev, intel_vsec_dev, intel_vsec_name(header->id)); } diff --git a/drivers/platform/x86/lg-laptop.c b/drivers/platform/x86/lg-laptop.c index a91847a551a7..332868b140ed 100644 --- a/drivers/platform/x86/lg-laptop.c +++ b/drivers/platform/x86/lg-laptop.c @@ -17,6 +17,8 @@ #include <linux/platform_device.h> #include <linux/types.h> +#include <acpi/battery.h> + #define LED_DEVICE(_name, max, flag) struct led_classdev _name = { \ .name = __stringify(_name), \ .max_brightness = max, \ @@ -458,14 +460,14 @@ static ssize_t fn_lock_show(struct device *dev, return sysfs_emit(buffer, "%d\n", status); } -static ssize_t battery_care_limit_store(struct device *dev, - struct device_attribute *attr, - const char *buffer, size_t count) +static ssize_t charge_control_end_threshold_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) { unsigned long value; int ret; - ret = kstrtoul(buffer, 10, &value); + ret = kstrtoul(buf, 10, &value); if (ret) return ret; @@ -486,9 +488,9 @@ static ssize_t battery_care_limit_store(struct device *dev, return -EINVAL; } -static ssize_t battery_care_limit_show(struct device *dev, - struct device_attribute *attr, - char *buffer) +static ssize_t charge_control_end_threshold_show(struct device *device, + struct device_attribute *attr, + char *buf) { unsigned int status; union acpi_object *r; @@ -520,15 +522,52 @@ static ssize_t battery_care_limit_show(struct device *dev, if (status != 80 && status != 100) status = 0; - return sysfs_emit(buffer, "%d\n", status); + return sysfs_emit(buf, "%d\n", status); +} + +static ssize_t battery_care_limit_show(struct device *dev, + struct device_attribute *attr, + char *buffer) +{ + return charge_control_end_threshold_show(dev, attr, buffer); +} + +static ssize_t battery_care_limit_store(struct device *dev, + struct device_attribute *attr, + const char *buffer, size_t count) +{ + return charge_control_end_threshold_store(dev, attr, buffer, count); } static DEVICE_ATTR_RW(fan_mode); static DEVICE_ATTR_RW(usb_charge); static DEVICE_ATTR_RW(reader_mode); static DEVICE_ATTR_RW(fn_lock); +static DEVICE_ATTR_RW(charge_control_end_threshold); static DEVICE_ATTR_RW(battery_care_limit); +static int lg_battery_add(struct power_supply *battery) +{ + if (device_create_file(&battery->dev, + &dev_attr_charge_control_end_threshold)) + return -ENODEV; + + return 0; +} + +static int lg_battery_remove(struct power_supply *battery) +{ + device_remove_file(&battery->dev, + &dev_attr_charge_control_end_threshold); + return 0; +} + +static struct acpi_battery_hook battery_hook = { + .add_battery = lg_battery_add, + .remove_battery = lg_battery_remove, + .name = "LG Battery Extension", +}; + static struct attribute *dev_attributes[] = { &dev_attr_fan_mode.attr, &dev_attr_usb_charge.attr, @@ -711,6 +750,7 @@ static int acpi_add(struct acpi_device *device) led_classdev_register(&pf_device->dev, &tpad_led); wmi_input_setup(); + battery_hook_register(&battery_hook); return 0; @@ -728,6 +768,7 @@ static int acpi_remove(struct acpi_device *device) led_classdev_unregister(&tpad_led); led_classdev_unregister(&kbd_backlight); + battery_hook_unregister(&battery_hook); wmi_input_destroy(); platform_device_unregister(pf_device); pf_device = NULL; diff --git a/drivers/platform/x86/serial-multi-instantiate.c b/drivers/platform/x86/serial-multi-instantiate.c new file mode 100644 index 000000000000..1e8063b7c169 --- /dev/null +++ b/drivers/platform/x86/serial-multi-instantiate.c @@ -0,0 +1,348 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Serial multi-instantiate driver, pseudo driver to instantiate multiple + * client devices from a single fwnode. + * + * Copyright 2018 Hans de Goede <hdegoede@redhat.com> + */ + +#include <linux/acpi.h> +#include <linux/bits.h> +#include <linux/i2c.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/property.h> +#include <linux/spi/spi.h> +#include <linux/types.h> + +#define IRQ_RESOURCE_TYPE GENMASK(1, 0) +#define IRQ_RESOURCE_NONE 0 +#define IRQ_RESOURCE_GPIO 1 +#define IRQ_RESOURCE_APIC 2 + +enum smi_bus_type { + SMI_I2C, + SMI_SPI, + SMI_AUTO_DETECT, +}; + +struct smi_instance { + const char *type; + unsigned int flags; + int irq_idx; +}; + +struct smi_node { + enum smi_bus_type bus_type; + struct smi_instance instances[]; +}; + +struct smi { + int i2c_num; + int spi_num; + struct i2c_client **i2c_devs; + struct spi_device **spi_devs; +}; + +static int smi_get_irq(struct platform_device *pdev, struct acpi_device *adev, + const struct smi_instance *inst) +{ + int ret; + + switch (inst->flags & IRQ_RESOURCE_TYPE) { + case IRQ_RESOURCE_GPIO: + ret = acpi_dev_gpio_irq_get(adev, inst->irq_idx); + break; + case IRQ_RESOURCE_APIC: + ret = platform_get_irq(pdev, inst->irq_idx); + break; + default: + return 0; + } + + if (ret < 0) + dev_err_probe(&pdev->dev, ret, "Error requesting irq at index %d: %d\n", + inst->irq_idx, ret); + + return ret; +} + +static void smi_devs_unregister(struct smi *smi) +{ + while (smi->i2c_num > 0) + i2c_unregister_device(smi->i2c_devs[--smi->i2c_num]); + + while (smi->spi_num > 0) + spi_unregister_device(smi->spi_devs[--smi->spi_num]); +} + +/** + * smi_spi_probe - Instantiate multiple SPI devices from inst array + * @pdev: Platform device + * @adev: ACPI device + * @smi: Internal struct for Serial multi instantiate driver + * @inst_array: Array of instances to probe + * + * Returns the number of SPI devices instantiate, Zero if none is found or a negative error code. + */ +static int smi_spi_probe(struct platform_device *pdev, struct acpi_device *adev, struct smi *smi, + const struct smi_instance *inst_array) +{ + struct device *dev = &pdev->dev; + struct spi_controller *ctlr; + struct spi_device *spi_dev; + char name[50]; + int i, ret, count; + + ret = acpi_spi_count_resources(adev); + if (ret < 0) + return ret; + else if (!ret) + return -ENODEV; + + count = ret; + + smi->spi_devs = devm_kcalloc(dev, count, sizeof(*smi->spi_devs), GFP_KERNEL); + if (!smi->spi_devs) + return -ENOMEM; + + for (i = 0; i < count && inst_array[i].type; i++) { + + spi_dev = acpi_spi_device_alloc(NULL, adev, i); + if (IS_ERR(spi_dev)) { + ret = PTR_ERR(spi_dev); + dev_err_probe(dev, ret, "failed to allocate SPI device %s from ACPI: %d\n", + dev_name(&adev->dev), ret); + goto error; + } + + ctlr = spi_dev->controller; + + strscpy(spi_dev->modalias, inst_array[i].type, sizeof(spi_dev->modalias)); + + ret = smi_get_irq(pdev, adev, &inst_array[i]); + if (ret < 0) { + spi_dev_put(spi_dev); + goto error; + } + spi_dev->irq = ret; + + snprintf(name, sizeof(name), "%s-%s-%s.%d", dev_name(&ctlr->dev), dev_name(dev), + inst_array[i].type, i); + spi_dev->dev.init_name = name; + + ret = spi_add_device(spi_dev); + if (ret) { + dev_err_probe(&ctlr->dev, ret, + "failed to add SPI device %s from ACPI: %d\n", + dev_name(&adev->dev), ret); + spi_dev_put(spi_dev); + goto error; + } + + dev_dbg(dev, "SPI device %s using chip select %u", name, spi_dev->chip_select); + + smi->spi_devs[i] = spi_dev; + smi->spi_num++; + } + + if (smi->spi_num < count) { + dev_dbg(dev, "Error finding driver, idx %d\n", i); + ret = -ENODEV; + goto error; + } + + dev_info(dev, "Instantiated %d SPI devices.\n", smi->spi_num); + + return 0; +error: + smi_devs_unregister(smi); + + return ret; +} + +/** + * smi_i2c_probe - Instantiate multiple I2C devices from inst array + * @pdev: Platform device + * @adev: ACPI device + * @smi: Internal struct for Serial multi instantiate driver + * @inst_array: Array of instances to probe + * + * Returns the number of I2C devices instantiate, Zero if none is found or a negative error code. + */ +static int smi_i2c_probe(struct platform_device *pdev, struct acpi_device *adev, struct smi *smi, + const struct smi_instance *inst_array) +{ + struct i2c_board_info board_info = {}; + struct device *dev = &pdev->dev; + char name[32]; + int i, ret, count; + + ret = i2c_acpi_client_count(adev); + if (ret < 0) + return ret; + else if (!ret) + return -ENODEV; + + count = ret; + + smi->i2c_devs = devm_kcalloc(dev, count, sizeof(*smi->i2c_devs), GFP_KERNEL); + if (!smi->i2c_devs) + return -ENOMEM; + + for (i = 0; i < count && inst_array[i].type; i++) { + memset(&board_info, 0, sizeof(board_info)); + strscpy(board_info.type, inst_array[i].type, I2C_NAME_SIZE); + snprintf(name, sizeof(name), "%s-%s.%d", dev_name(dev), inst_array[i].type, i); + board_info.dev_name = name; + + ret = smi_get_irq(pdev, adev, &inst_array[i]); + if (ret < 0) + goto error; + board_info.irq = ret; + + smi->i2c_devs[i] = i2c_acpi_new_device(dev, i, &board_info); + if (IS_ERR(smi->i2c_devs[i])) { + ret = dev_err_probe(dev, PTR_ERR(smi->i2c_devs[i]), + "Error creating i2c-client, idx %d\n", i); + goto error; + } + smi->i2c_num++; + } + if (smi->i2c_num < count) { + dev_dbg(dev, "Error finding driver, idx %d\n", i); + ret = -ENODEV; + goto error; + } + + dev_info(dev, "Instantiated %d I2C devices.\n", smi->i2c_num); + + return 0; +error: + smi_devs_unregister(smi); + + return ret; +} + +static int smi_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + const struct smi_node *node; + struct acpi_device *adev; + struct smi *smi; + + adev = ACPI_COMPANION(dev); + if (!adev) + return -ENODEV; + + node = device_get_match_data(dev); + if (!node) { + dev_dbg(dev, "Error ACPI match data is missing\n"); + return -ENODEV; + } + + smi = devm_kzalloc(dev, sizeof(*smi), GFP_KERNEL); + if (!smi) + return -ENOMEM; + + platform_set_drvdata(pdev, smi); + + switch (node->bus_type) { + case SMI_I2C: + return smi_i2c_probe(pdev, adev, smi, node->instances); + case SMI_SPI: + return smi_spi_probe(pdev, adev, smi, node->instances); + case SMI_AUTO_DETECT: + if (i2c_acpi_client_count(adev) > 0) + return smi_i2c_probe(pdev, adev, smi, node->instances); + else + return smi_spi_probe(pdev, adev, smi, node->instances); + default: + return -EINVAL; + } + + return 0; /* never reached */ +} + +static int smi_remove(struct platform_device *pdev) +{ + struct smi *smi = platform_get_drvdata(pdev); + + smi_devs_unregister(smi); + + return 0; +} + +static const struct smi_node bsg1160_data = { + .instances = { + { "bmc150_accel", IRQ_RESOURCE_GPIO, 0 }, + { "bmc150_magn" }, + { "bmg160" }, + {} + }, + .bus_type = SMI_I2C, +}; + +static const struct smi_node bsg2150_data = { + .instances = { + { "bmc150_accel", IRQ_RESOURCE_GPIO, 0 }, + { "bmc150_magn" }, + /* The resources describe a 3th client, but it is not really there. */ + { "bsg2150_dummy_dev" }, + {} + }, + .bus_type = SMI_I2C, +}; + +static const struct smi_node int3515_data = { + .instances = { + { "tps6598x", IRQ_RESOURCE_APIC, 0 }, + { "tps6598x", IRQ_RESOURCE_APIC, 1 }, + { "tps6598x", IRQ_RESOURCE_APIC, 2 }, + { "tps6598x", IRQ_RESOURCE_APIC, 3 }, + {} + }, + .bus_type = SMI_I2C, +}; + +static const struct smi_node cs35l41_hda = { + .instances = { + { "cs35l41-hda", IRQ_RESOURCE_GPIO, 0 }, + { "cs35l41-hda", IRQ_RESOURCE_GPIO, 0 }, + { "cs35l41-hda", IRQ_RESOURCE_GPIO, 0 }, + { "cs35l41-hda", IRQ_RESOURCE_GPIO, 0 }, + {} + }, + .bus_type = SMI_AUTO_DETECT, +}; + +/* + * Note new device-ids must also be added to ignore_serial_bus_ids in + * drivers/acpi/scan.c: acpi_device_enumeration_by_parent(). + */ +static const struct acpi_device_id smi_acpi_ids[] = { + { "BSG1160", (unsigned long)&bsg1160_data }, + { "BSG2150", (unsigned long)&bsg2150_data }, + { "INT3515", (unsigned long)&int3515_data }, + { "CSC3551", (unsigned long)&cs35l41_hda }, + /* Non-conforming _HID for Cirrus Logic already released */ + { "CLSA0100", (unsigned long)&cs35l41_hda }, + { } +}; +MODULE_DEVICE_TABLE(acpi, smi_acpi_ids); + +static struct platform_driver smi_driver = { + .driver = { + .name = "Serial bus multi instantiate pseudo device driver", + .acpi_match_table = smi_acpi_ids, + }, + .probe = smi_probe, + .remove = smi_remove, +}; +module_platform_driver(smi_driver); + +MODULE_DESCRIPTION("Serial multi instantiate pseudo device driver"); +MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/think-lmi.c b/drivers/platform/x86/think-lmi.c index 0b73e16cccea..bce17ca97947 100644 --- a/drivers/platform/x86/think-lmi.c +++ b/drivers/platform/x86/think-lmi.c @@ -16,6 +16,7 @@ #include <linux/fs.h> #include <linux/string.h> #include <linux/types.h> +#include <linux/dmi.h> #include <linux/wmi.h> #include "firmware_attributes_class.h" #include "think-lmi.h" @@ -25,95 +26,66 @@ module_param(debug_support, bool, 0444); MODULE_PARM_DESC(debug_support, "Enable debug command support"); /* - * Name: - * Lenovo_BiosSetting - * Description: - * Get item name and settings for current LMI instance. - * Type: - * Query - * Returns: - * "Item,Value" - * Example: - * "WakeOnLAN,Enable" + * Name: BiosSetting + * Description: Get item name and settings for current LMI instance. + * Type: Query + * Returns: "Item,Value" + * Example: "WakeOnLAN,Enable" */ #define LENOVO_BIOS_SETTING_GUID "51F5230E-9677-46CD-A1CF-C0B23EE34DB7" /* - * Name: - * Lenovo_SetBiosSetting - * Description: - * Change the BIOS setting to the desired value using the Lenovo_SetBiosSetting - * class. To save the settings, use the Lenovo_SaveBiosSetting class. + * Name: SetBiosSetting + * Description: Change the BIOS setting to the desired value using the SetBiosSetting + * class. To save the settings, use the SaveBiosSetting class. * BIOS settings and values are case sensitive. * After making changes to the BIOS settings, you must reboot the computer * before the changes will take effect. - * Type: - * Method - * Arguments: - * "Item,Value,Password,Encoding,KbdLang;" - * Example: - * "WakeOnLAN,Disable,pa55w0rd,ascii,us;" + * Type: Method + * Arguments: "Item,Value,Password,Encoding,KbdLang;" + * Example: "WakeOnLAN,Disable,pa55w0rd,ascii,us;" */ #define LENOVO_SET_BIOS_SETTINGS_GUID "98479A64-33F5-4E33-A707-8E251EBBC3A1" /* - * Name: - * Lenovo_SaveBiosSettings - * Description: - * Save any pending changes in settings. - * Type: - * Method - * Arguments: - * "Password,Encoding,KbdLang;" - * Example: - * "pa55w0rd,ascii,us;" + * Name: SaveBiosSettings + * Description: Save any pending changes in settings. + * Type: Method + * Arguments: "Password,Encoding,KbdLang;" + * Example: "pa55w0rd,ascii,us;" */ #define LENOVO_SAVE_BIOS_SETTINGS_GUID "6A4B54EF-A5ED-4D33-9455-B0D9B48DF4B3" /* - * Name: - * Lenovo_BiosPasswordSettings - * Description: - * Return BIOS Password settings - * Type: - * Query - * Returns: - * PasswordMode, PasswordState, MinLength, MaxLength, + * Name: BiosPasswordSettings + * Description: Return BIOS Password settings + * Type: Query + * Returns: PasswordMode, PasswordState, MinLength, MaxLength, * SupportedEncoding, SupportedKeyboard */ #define LENOVO_BIOS_PASSWORD_SETTINGS_GUID "8ADB159E-1E32-455C-BC93-308A7ED98246" /* - * Name: - * Lenovo_SetBiosPassword - * Description: - * Change a specific password. + * Name: SetBiosPassword + * Description: Change a specific password. * - BIOS settings cannot be changed at the same boot as power-on * passwords (POP) and hard disk passwords (HDP). If you want to change * BIOS settings and POP or HDP, you must reboot the system after changing * one of them. * - A password cannot be set using this method when one does not already * exist. Passwords can only be updated or cleared. - * Type: - * Method - * Arguments: - * "PasswordType,CurrentPassword,NewPassword,Encoding,KbdLang;" - * Example: - * "pop,pa55w0rd,newpa55w0rd,ascii,us;” + * Type: Method + * Arguments: "PasswordType,CurrentPassword,NewPassword,Encoding,KbdLang;" + * Example: "pop,pa55w0rd,newpa55w0rd,ascii,us;” */ #define LENOVO_SET_BIOS_PASSWORD_GUID "2651D9FD-911C-4B69-B94E-D0DED5963BD7" /* - * Name: - * Lenovo_GetBiosSelections - * Description: - * Return a list of valid settings for a given item. - * Type: - * Method - * Arguments: - * "Item" - * Returns: - * "Value1,Value2,Value3,..." + * Name: GetBiosSelections + * Description: Return a list of valid settings for a given item. + * Type: Method + * Arguments: "Item" + * Returns: "Value1,Value2,Value3,..." * Example: * -> "FlashOverLAN" * <- "Enabled,Disabled" @@ -121,18 +93,14 @@ MODULE_PARM_DESC(debug_support, "Enable debug command support"); #define LENOVO_GET_BIOS_SELECTIONS_GUID "7364651A-132F-4FE7-ADAA-40C6C7EE2E3B" /* - * Name: - * Lenovo_DebugCmdGUID - * Description - * Debug entry GUID method for entering debug commands to the BIOS + * Name: DebugCmd + * Description: Debug entry method for entering debug commands to the BIOS */ #define LENOVO_DEBUG_CMD_GUID "7FF47003-3B6C-4E5E-A227-E979824A85D1" /* - * Name: - * Lenovo_OpcodeIF - * Description: - * Opcode interface which provides the ability to set multiple + * Name: OpcodeIF + * Description: Opcode interface which provides the ability to set multiple * parameters and then trigger an action with a final command. * This is particularly useful for simplifying setting passwords. * With this support comes the ability to set System, HDD and NVMe @@ -141,10 +109,71 @@ MODULE_PARM_DESC(debug_support, "Enable debug command support"); */ #define LENOVO_OPCODE_IF_GUID "DFDDEF2C-57D4-48ce-B196-0FB787D90836" +/* + * Name: SetBiosCert + * Description: Install BIOS certificate. + * Type: Method + * Arguments: "Certificate,Password" + * You must reboot the computer before the changes will take effect. + */ +#define LENOVO_SET_BIOS_CERT_GUID "26861C9F-47E9-44C4-BD8B-DFE7FA2610FE" + +/* + * Name: UpdateBiosCert + * Description: Update BIOS certificate. + * Type: Method + * Format: "Certificate,Signature" + * You must reboot the computer before the changes will take effect. + */ +#define LENOVO_UPDATE_BIOS_CERT_GUID "9AA3180A-9750-41F7-B9F7-D5D3B1BAC3CE" + +/* + * Name: ClearBiosCert + * Description: Uninstall BIOS certificate. + * Type: Method + * Format: "Serial,Signature" + * You must reboot the computer before the changes will take effect. + */ +#define LENOVO_CLEAR_BIOS_CERT_GUID "B2BC39A7-78DD-4D71-B059-A510DEC44890" +/* + * Name: CertToPassword + * Description: Switch from certificate to password authentication. + * Type: Method + * Format: "Password,Signature" + * You must reboot the computer before the changes will take effect. + */ +#define LENOVO_CERT_TO_PASSWORD_GUID "0DE8590D-5510-4044-9621-77C227F5A70D" + +/* + * Name: SetBiosSettingCert + * Description: Set attribute using certificate authentication. + * Type: Method + * Format: "Item,Value,Signature" + */ +#define LENOVO_SET_BIOS_SETTING_CERT_GUID "34A008CC-D205-4B62-9E67-31DFA8B90003" + +/* + * Name: SaveBiosSettingCert + * Description: Save any pending changes in settings. + * Type: Method + * Format: "Signature" + */ +#define LENOVO_SAVE_BIOS_SETTING_CERT_GUID "C050FB9D-DF5F-4606-B066-9EFC401B2551" + +/* + * Name: CertThumbprint + * Description: Display Certificate thumbprints + * Type: Query + * Returns: MD5, SHA1 & SHA256 thumbprints + */ +#define LENOVO_CERT_THUMBPRINT_GUID "C59119ED-1C0D-4806-A8E9-59AA318176C4" + #define TLMI_POP_PWD (1 << 0) #define TLMI_PAP_PWD (1 << 1) #define TLMI_HDD_PWD (1 << 2) #define TLMI_SYS_PWD (1 << 3) +#define TLMI_CERT (1 << 7) + #define to_tlmi_pwd_setting(kobj) container_of(kobj, struct tlmi_pwd_setting, kobj) #define to_tlmi_attr_setting(kobj) container_of(kobj, struct tlmi_attr_setting, kobj) @@ -168,6 +197,13 @@ static struct think_lmi tlmi_priv; static struct class *fw_attr_class; /* ------ Utility functions ------------*/ +/* Strip out CR if one is present */ +static void strip_cr(char *str) +{ + char *p = strchrnul(str, '\n'); + *p = '\0'; +} + /* Convert BIOS WMI error string to suitable error code */ static int tlmi_errstr_to_err(const char *errstr) { @@ -365,7 +401,6 @@ static ssize_t current_password_store(struct kobject *kobj, { struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj); size_t pwdlen; - char *p; pwdlen = strlen(buf); /* pwdlen == 0 is allowed to clear the password */ @@ -374,8 +409,7 @@ static ssize_t current_password_store(struct kobject *kobj, strscpy(setting->password, buf, setting->maxlen); /* Strip out CR if one is present, setting password won't work if it is present */ - p = strchrnul(setting->password, '\n'); - *p = '\0'; + strip_cr(setting->password); return count; } @@ -386,7 +420,7 @@ static ssize_t new_password_store(struct kobject *kobj, const char *buf, size_t count) { struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj); - char *auth_str, *new_pwd, *p; + char *auth_str, *new_pwd; size_t pwdlen; int ret; @@ -401,8 +435,7 @@ static ssize_t new_password_store(struct kobject *kobj, return -ENOMEM; /* Strip out CR if one is present, setting password won't work if it is present */ - p = strchrnul(new_pwd, '\n'); - *p = '\0'; + strip_cr(new_pwd); pwdlen = strlen(new_pwd); /* pwdlen == 0 is allowed to clear the password */ @@ -608,18 +641,258 @@ static ssize_t level_store(struct kobject *kobj, static struct kobj_attribute auth_level = __ATTR_RW(level); +static ssize_t cert_thumbprint(char *buf, const char *arg, int count) +{ + const struct acpi_buffer input = { strlen(arg), (char *)arg }; + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; + const union acpi_object *obj; + acpi_status status; + + status = wmi_evaluate_method(LENOVO_CERT_THUMBPRINT_GUID, 0, 0, &input, &output); + if (ACPI_FAILURE(status)) { + kfree(output.pointer); + return -EIO; + } + obj = output.pointer; + if (!obj) + return -ENOMEM; + if (obj->type != ACPI_TYPE_STRING || !obj->string.pointer) { + kfree(output.pointer); + return -EIO; + } + count += sysfs_emit_at(buf, count, "%s : %s\n", arg, (char *)obj->string.pointer); + kfree(output.pointer); + + return count; +} + +static ssize_t certificate_thumbprint_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj); + int count = 0; + + if (!tlmi_priv.certificate_support || !setting->cert_installed) + return -EOPNOTSUPP; + + count += cert_thumbprint(buf, "Md5", count); + count += cert_thumbprint(buf, "Sha1", count); + count += cert_thumbprint(buf, "Sha256", count); + return count; +} + +static struct kobj_attribute auth_cert_thumb = __ATTR_RO(certificate_thumbprint); + +static ssize_t cert_to_password_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count) +{ + struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj); + char *auth_str, *passwd; + int ret; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + if (!tlmi_priv.certificate_support) + return -EOPNOTSUPP; + + if (!setting->cert_installed) + return -EINVAL; + + if (!setting->signature || !setting->signature[0]) + return -EACCES; + + passwd = kstrdup(buf, GFP_KERNEL); + if (!passwd) + return -ENOMEM; + + /* Strip out CR if one is present */ + strip_cr(passwd); + + /* Format: 'Password,Signature' */ + auth_str = kasprintf(GFP_KERNEL, "%s,%s", passwd, setting->signature); + if (!auth_str) { + kfree(passwd); + return -ENOMEM; + } + ret = tlmi_simple_call(LENOVO_CERT_TO_PASSWORD_GUID, auth_str); + kfree(auth_str); + kfree(passwd); + + return ret ?: count; +} + +static struct kobj_attribute auth_cert_to_password = __ATTR_WO(cert_to_password); + +static ssize_t certificate_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count) +{ + struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj); + char *auth_str, *new_cert; + char *guid; + int ret; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + if (!tlmi_priv.certificate_support) + return -EOPNOTSUPP; + + new_cert = kstrdup(buf, GFP_KERNEL); + if (!new_cert) + return -ENOMEM; + /* Strip out CR if one is present */ + strip_cr(new_cert); + + /* If empty then clear installed certificate */ + if (new_cert[0] == '\0') { /* Clear installed certificate */ + kfree(new_cert); + + /* Check that signature is set */ + if (!setting->signature || !setting->signature[0]) + return -EACCES; + + /* Format: 'serial#, signature' */ + auth_str = kasprintf(GFP_KERNEL, "%s,%s", + dmi_get_system_info(DMI_PRODUCT_SERIAL), + setting->signature); + if (!auth_str) + return -ENOMEM; + + ret = tlmi_simple_call(LENOVO_CLEAR_BIOS_CERT_GUID, auth_str); + kfree(auth_str); + if (ret) + return ret; + + kfree(setting->certificate); + setting->certificate = NULL; + return count; + } + + if (setting->cert_installed) { + /* Certificate is installed so this is an update */ + if (!setting->signature || !setting->signature[0]) { + kfree(new_cert); + return -EACCES; + } + guid = LENOVO_UPDATE_BIOS_CERT_GUID; + /* Format: 'Certificate,Signature' */ + auth_str = kasprintf(GFP_KERNEL, "%s,%s", + new_cert, setting->signature); + } else { + /* This is a fresh install */ + if (!setting->valid || !setting->password[0]) { + kfree(new_cert); + return -EACCES; + } + guid = LENOVO_SET_BIOS_CERT_GUID; + /* Format: 'Certificate,Admin-password' */ + auth_str = kasprintf(GFP_KERNEL, "%s,%s", + new_cert, setting->password); + } + if (!auth_str) { + kfree(new_cert); + return -ENOMEM; + } + + ret = tlmi_simple_call(guid, auth_str); + kfree(auth_str); + if (ret) { + kfree(new_cert); + return ret; + } + + kfree(setting->certificate); + setting->certificate = new_cert; + return count; +} + +static struct kobj_attribute auth_certificate = __ATTR_WO(certificate); + +static ssize_t signature_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count) +{ + struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj); + char *new_signature; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + if (!tlmi_priv.certificate_support) + return -EOPNOTSUPP; + + new_signature = kstrdup(buf, GFP_KERNEL); + if (!new_signature) + return -ENOMEM; + + /* Strip out CR if one is present */ + strip_cr(new_signature); + + /* Free any previous signature */ + kfree(setting->signature); + setting->signature = new_signature; + + return count; +} + +static struct kobj_attribute auth_signature = __ATTR_WO(signature); + +static ssize_t save_signature_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count) +{ + struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj); + char *new_signature; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + if (!tlmi_priv.certificate_support) + return -EOPNOTSUPP; + + new_signature = kstrdup(buf, GFP_KERNEL); + if (!new_signature) + return -ENOMEM; + + /* Strip out CR if one is present */ + strip_cr(new_signature); + + /* Free any previous signature */ + kfree(setting->save_signature); + setting->save_signature = new_signature; + + return count; +} + +static struct kobj_attribute auth_save_signature = __ATTR_WO(save_signature); + static umode_t auth_attr_is_visible(struct kobject *kobj, struct attribute *attr, int n) { struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj); - /*We only want to display level and index settings on HDD/NVMe */ + /* We only want to display level and index settings on HDD/NVMe */ if ((attr == (struct attribute *)&auth_index) || (attr == (struct attribute *)&auth_level)) { if ((setting == tlmi_priv.pwd_hdd) || (setting == tlmi_priv.pwd_nvme)) return attr->mode; return 0; } + + /* We only display certificates on Admin account, if supported */ + if ((attr == (struct attribute *)&auth_certificate) || + (attr == (struct attribute *)&auth_signature) || + (attr == (struct attribute *)&auth_save_signature) || + (attr == (struct attribute *)&auth_cert_thumb) || + (attr == (struct attribute *)&auth_cert_to_password)) { + if ((setting == tlmi_priv.pwd_admin) && tlmi_priv.certificate_support) + return attr->mode; + return 0; + } + return attr->mode; } @@ -635,6 +908,11 @@ static struct attribute *auth_attrs[] = { &auth_kbdlang.attr, &auth_index.attr, &auth_level.attr, + &auth_certificate.attr, + &auth_signature.attr, + &auth_save_signature.attr, + &auth_cert_thumb.attr, + &auth_cert_to_password.attr, NULL }; @@ -689,7 +967,6 @@ static ssize_t current_value_store(struct kobject *kobj, struct tlmi_attr_setting *setting = to_tlmi_attr_setting(kobj); char *set_str = NULL, *new_setting = NULL; char *auth_str = NULL; - char *p; int ret; if (!tlmi_priv.can_set_bios_settings) @@ -700,40 +977,60 @@ static ssize_t current_value_store(struct kobject *kobj, return -ENOMEM; /* Strip out CR if one is present */ - p = strchrnul(new_setting, '\n'); - *p = '\0'; + strip_cr(new_setting); - if (tlmi_priv.pwd_admin->valid && tlmi_priv.pwd_admin->password[0]) { - auth_str = kasprintf(GFP_KERNEL, "%s,%s,%s;", - tlmi_priv.pwd_admin->password, - encoding_options[tlmi_priv.pwd_admin->encoding], - tlmi_priv.pwd_admin->kbdlang); - if (!auth_str) { + /* Check if certificate authentication is enabled and active */ + if (tlmi_priv.certificate_support && tlmi_priv.pwd_admin->cert_installed) { + if (!tlmi_priv.pwd_admin->signature || !tlmi_priv.pwd_admin->save_signature) { + ret = -EINVAL; + goto out; + } + set_str = kasprintf(GFP_KERNEL, "%s,%s,%s", setting->display_name, + new_setting, tlmi_priv.pwd_admin->signature); + if (!set_str) { ret = -ENOMEM; goto out; } - } - if (auth_str) - set_str = kasprintf(GFP_KERNEL, "%s,%s,%s", setting->display_name, - new_setting, auth_str); - else - set_str = kasprintf(GFP_KERNEL, "%s,%s;", setting->display_name, - new_setting); - if (!set_str) { - ret = -ENOMEM; - goto out; - } + ret = tlmi_simple_call(LENOVO_SET_BIOS_SETTING_CERT_GUID, set_str); + if (ret) + goto out; + ret = tlmi_simple_call(LENOVO_SAVE_BIOS_SETTING_CERT_GUID, + tlmi_priv.pwd_admin->save_signature); + if (ret) + goto out; + } else { /* Non certiifcate based authentication */ + if (tlmi_priv.pwd_admin->valid && tlmi_priv.pwd_admin->password[0]) { + auth_str = kasprintf(GFP_KERNEL, "%s,%s,%s;", + tlmi_priv.pwd_admin->password, + encoding_options[tlmi_priv.pwd_admin->encoding], + tlmi_priv.pwd_admin->kbdlang); + if (!auth_str) { + ret = -ENOMEM; + goto out; + } + } - ret = tlmi_simple_call(LENOVO_SET_BIOS_SETTINGS_GUID, set_str); - if (ret) - goto out; + if (auth_str) + set_str = kasprintf(GFP_KERNEL, "%s,%s,%s", setting->display_name, + new_setting, auth_str); + else + set_str = kasprintf(GFP_KERNEL, "%s,%s;", setting->display_name, + new_setting); + if (!set_str) { + ret = -ENOMEM; + goto out; + } - if (auth_str) - ret = tlmi_save_bios_settings(auth_str); - else - ret = tlmi_save_bios_settings(""); + ret = tlmi_simple_call(LENOVO_SET_BIOS_SETTINGS_GUID, set_str); + if (ret) + goto out; + if (auth_str) + ret = tlmi_save_bios_settings(auth_str); + else + ret = tlmi_save_bios_settings(""); + } if (!ret && !tlmi_priv.pending_changes) { tlmi_priv.pending_changes = true; /* let userland know it may need to check reboot pending again */ @@ -829,7 +1126,6 @@ static ssize_t debug_cmd_store(struct kobject *kobj, struct kobj_attribute *attr { char *set_str = NULL, *new_setting = NULL; char *auth_str = NULL; - char *p; int ret; if (!tlmi_priv.can_debug_cmd) @@ -840,8 +1136,7 @@ static ssize_t debug_cmd_store(struct kobject *kobj, struct kobj_attribute *attr return -ENOMEM; /* Strip out CR if one is present */ - p = strchrnul(new_setting, '\n'); - *p = '\0'; + strip_cr(new_setting); if (tlmi_priv.pwd_admin->valid && tlmi_priv.pwd_admin->password[0]) { auth_str = kasprintf(GFP_KERNEL, "%s,%s,%s;", @@ -896,6 +1191,7 @@ static void tlmi_release_attr(void) sysfs_remove_file(&tlmi_priv.attribute_kset->kobj, &pending_reboot.attr); if (tlmi_priv.can_debug_cmd && debug_support) sysfs_remove_file(&tlmi_priv.attribute_kset->kobj, &debug_cmd.attr); + kset_unregister(tlmi_priv.attribute_kset); /* Authentication structures */ @@ -914,6 +1210,11 @@ static void tlmi_release_attr(void) } kset_unregister(tlmi_priv.authentication_kset); + + /* Free up any saved certificates/signatures */ + kfree(tlmi_priv.pwd_admin->certificate); + kfree(tlmi_priv.pwd_admin->signature); + kfree(tlmi_priv.pwd_admin->save_signature); } static int tlmi_sysfs_init(void) @@ -975,6 +1276,7 @@ static int tlmi_sysfs_init(void) if (ret) goto fail_create_attr; } + /* Create authentication entries */ tlmi_priv.authentication_kset = kset_create_and_add("authentication", NULL, &tlmi_priv.class_dev->kobj); @@ -1087,6 +1389,11 @@ static int tlmi_analyze(void) if (wmi_has_guid(LENOVO_OPCODE_IF_GUID)) tlmi_priv.opcode_support = true; + if (wmi_has_guid(LENOVO_SET_BIOS_CERT_GUID) && + wmi_has_guid(LENOVO_SET_BIOS_SETTING_CERT_GUID) && + wmi_has_guid(LENOVO_SAVE_BIOS_SETTING_CERT_GUID)) + tlmi_priv.certificate_support = true; + /* * Try to find the number of valid settings of this machine * and use it to create sysfs attributes. @@ -1198,6 +1505,11 @@ static int tlmi_analyze(void) } } } + + if (tlmi_priv.certificate_support && + (tlmi_priv.pwdcfg.core.password_state & TLMI_CERT)) + tlmi_priv.pwd_admin->cert_installed = true; + return 0; fail_clear_attr: diff --git a/drivers/platform/x86/think-lmi.h b/drivers/platform/x86/think-lmi.h index e46c7f383353..4f69df6eed07 100644 --- a/drivers/platform/x86/think-lmi.h +++ b/drivers/platform/x86/think-lmi.h @@ -62,6 +62,10 @@ struct tlmi_pwd_setting { char kbdlang[TLMI_LANG_MAXLEN]; int index; /*Used for HDD and NVME auth */ enum level_option level; + bool cert_installed; + char *certificate; + char *signature; + char *save_signature; }; /* Attribute setting details */ @@ -82,6 +86,7 @@ struct think_lmi { bool pending_changes; bool can_debug_cmd; bool opcode_support; + bool certificate_support; struct tlmi_attr_setting *setting[TLMI_SETTINGS_COUNT]; struct device *class_dev; diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index 3424b080db77..c568fae56db2 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -728,11 +728,10 @@ static void __init drv_acpi_handle_init(const char *name, static acpi_status __init tpacpi_acpi_handle_locate_callback(acpi_handle handle, u32 level, void *context, void **return_value) { - struct acpi_device *dev; if (!strcmp(context, "video")) { - if (acpi_bus_get_device(handle, &dev)) - return AE_OK; - if (strcmp(ACPI_VIDEO_HID, acpi_device_hid(dev))) + struct acpi_device *dev = acpi_fetch_acpi_dev(handle); + + if (!dev || strcmp(ACPI_VIDEO_HID, acpi_device_hid(dev))) return AE_OK; } @@ -786,7 +785,6 @@ static void dispatch_acpi_notify(acpi_handle handle, u32 event, void *data) static int __init setup_acpi_notify(struct ibm_struct *ibm) { acpi_status status; - int rc; BUG_ON(!ibm->acpi); @@ -796,9 +794,9 @@ static int __init setup_acpi_notify(struct ibm_struct *ibm) vdbg_printk(TPACPI_DBG_INIT, "setting up ACPI notify for %s\n", ibm->name); - rc = acpi_bus_get_device(*ibm->acpi->handle, &ibm->acpi->device); - if (rc < 0) { - pr_err("acpi_bus_get_device(%s) failed: %d\n", ibm->name, rc); + ibm->acpi->device = acpi_fetch_acpi_dev(*ibm->acpi->handle); + if (!ibm->acpi->device) { + pr_err("acpi_fetch_acpi_dev(%s) failed\n", ibm->name); return -ENODEV; } @@ -6723,7 +6721,8 @@ static int __init tpacpi_query_bcl_levels(acpi_handle handle) struct acpi_device *device, *child; int rc; - if (acpi_bus_get_device(handle, &device)) + device = acpi_fetch_acpi_dev(handle); + if (!device) return 0; rc = 0; @@ -8286,7 +8285,7 @@ static int fan_set_enable(void) case TPACPI_FAN_WR_ACPI_FANS: case TPACPI_FAN_WR_TPEC: rc = fan_get_status(&s); - if (rc < 0) + if (rc) break; /* Don't go out of emergency fan mode */ @@ -8305,7 +8304,7 @@ static int fan_set_enable(void) case TPACPI_FAN_WR_ACPI_SFAN: rc = fan_get_status(&s); - if (rc < 0) + if (rc) break; s &= 0x07; @@ -8699,10 +8698,7 @@ static const struct tpacpi_quirk fan_quirk_table[] __initconst = { TPACPI_Q_LNV3('N', '2', 'N', TPACPI_FAN_2CTL), /* P53 / P73 */ TPACPI_Q_LNV3('N', '2', 'E', TPACPI_FAN_2CTL), /* P1 / X1 Extreme (1st gen) */ TPACPI_Q_LNV3('N', '2', 'O', TPACPI_FAN_2CTL), /* P1 / X1 Extreme (2nd gen) */ - TPACPI_Q_LNV3('N', '2', 'V', TPACPI_FAN_2CTL), /* P1 / X1 Extreme (3nd gen) */ - TPACPI_Q_LNV3('N', '4', '0', TPACPI_FAN_2CTL), /* P1 / X1 Extreme (4nd gen) */ TPACPI_Q_LNV3('N', '3', '0', TPACPI_FAN_2CTL), /* P15 (1st gen) / P15v (1st gen) */ - TPACPI_Q_LNV3('N', '3', '2', TPACPI_FAN_2CTL), /* X1 Carbon (9th gen) */ TPACPI_Q_LNV3('N', '3', '7', TPACPI_FAN_2CTL), /* T15g (2nd gen) */ TPACPI_Q_LNV3('N', '1', 'O', TPACPI_FAN_NOFAN), /* X1 Tablet (2nd gen) */ }; @@ -8746,6 +8742,9 @@ static int __init fan_init(struct ibm_init_struct *iibm) * ThinkPad ECs supports the fan control register */ if (likely(acpi_ec_read(fan_status_offset, &fan_control_initial_status))) { + int res; + unsigned int speed; + fan_status_access_mode = TPACPI_FAN_RD_TPEC; if (quirks & TPACPI_FAN_Q1) fan_quirk1_setup(); @@ -8758,6 +8757,15 @@ static int __init fan_init(struct ibm_init_struct *iibm) tp_features.second_fan_ctl = 1; pr_info("secondary fan control enabled\n"); } + /* Try and probe the 2nd fan */ + res = fan2_get_speed(&speed); + if (res >= 0) { + /* It responded - so let's assume it's there */ + tp_features.second_fan = 1; + tp_features.second_fan_ctl = 1; + pr_info("secondary fan control detected & enabled\n"); + } + } else { pr_err("ThinkPad ACPI EC access misbehaving, fan status and control unavailable\n"); return -ENODEV; @@ -8835,7 +8843,7 @@ static void fan_suspend(void) /* Store fan status in cache */ fan_control_resume_level = 0; rc = fan_get_status_safe(&fan_control_resume_level); - if (rc < 0) + if (rc) pr_notice("failed to read fan level for later restore during resume: %d\n", rc); @@ -8856,7 +8864,7 @@ static void fan_resume(void) if (!fan_control_allowed || !fan_control_resume_level || - (fan_get_status_safe(¤t_level) < 0)) + fan_get_status_safe(¤t_level)) return; switch (fan_control_access_mode) { @@ -8910,7 +8918,7 @@ static int fan_read(struct seq_file *m) case TPACPI_FAN_RD_ACPI_GFAN: /* 570, 600e/x, 770e, 770x */ rc = fan_get_status_safe(&status); - if (rc < 0) + if (rc) return rc; seq_printf(m, "status:\t\t%s\n" @@ -8921,7 +8929,7 @@ static int fan_read(struct seq_file *m) case TPACPI_FAN_RD_TPEC: /* all except 570, 600e/x, 770e, 770x */ rc = fan_get_status_safe(&status); - if (rc < 0) + if (rc) return rc; seq_printf(m, "status:\t\t%s\n", @@ -9877,7 +9885,7 @@ static int tpacpi_lcdshadow_init(struct ibm_init_struct *iibm) return 0; lcdshadow_dev = drm_privacy_screen_register(&tpacpi_pdev->dev, - &lcdshadow_ops); + &lcdshadow_ops, NULL); if (IS_ERR(lcdshadow_dev)) return PTR_ERR(lcdshadow_dev); @@ -10122,6 +10130,7 @@ static struct ibm_struct proxsensor_driver_data = { #define DYTC_CMD_FUNC_CAP 3 /* To get DYTC capabilities */ #define DYTC_FC_MMC 27 /* MMC Mode supported */ +#define DYTC_FC_PSC 29 /* PSC Mode supported */ #define DYTC_GET_FUNCTION_BIT 8 /* Bits 8-11 - function setting */ #define DYTC_GET_MODE_BIT 12 /* Bits 12-15 - mode setting */ @@ -10132,12 +10141,17 @@ static struct ibm_struct proxsensor_driver_data = { #define DYTC_FUNCTION_STD 0 /* Function = 0, standard mode */ #define DYTC_FUNCTION_CQL 1 /* Function = 1, lap mode */ -#define DYTC_FUNCTION_MMC 11 /* Function = 11, desk mode */ +#define DYTC_FUNCTION_MMC 11 /* Function = 11, MMC mode */ +#define DYTC_FUNCTION_PSC 13 /* Function = 13, PSC mode */ + +#define DYTC_MODE_MMC_PERFORM 2 /* High power mode aka performance */ +#define DYTC_MODE_MMC_LOWPOWER 3 /* Low power mode */ +#define DYTC_MODE_MMC_BALANCE 0xF /* Default mode aka balanced */ +#define DYTC_MODE_MMC_DEFAULT 0 /* Default mode from MMC_GET, aka balanced */ -#define DYTC_MODE_PERFORM 2 /* High power mode aka performance */ -#define DYTC_MODE_LOWPOWER 3 /* Low power mode */ -#define DYTC_MODE_BALANCE 0xF /* Default mode aka balanced */ -#define DYTC_MODE_MMC_BALANCE 0 /* Default mode from MMC_GET, aka balanced */ +#define DYTC_MODE_PSC_LOWPOWER 3 /* Low power mode */ +#define DYTC_MODE_PSC_BALANCE 5 /* Default mode aka balanced */ +#define DYTC_MODE_PSC_PERFORM 7 /* High power mode aka performance */ #define DYTC_ERR_MASK 0xF /* Bits 0-3 in cmd result are the error result */ #define DYTC_ERR_SUCCESS 1 /* CMD completed successful */ @@ -10147,10 +10161,16 @@ static struct ibm_struct proxsensor_driver_data = { (mode) << DYTC_SET_MODE_BIT | \ (on) << DYTC_SET_VALID_BIT) -#define DYTC_DISABLE_CQL DYTC_SET_COMMAND(DYTC_FUNCTION_CQL, DYTC_MODE_BALANCE, 0) +#define DYTC_DISABLE_CQL DYTC_SET_COMMAND(DYTC_FUNCTION_CQL, DYTC_MODE_MMC_BALANCE, 0) +#define DYTC_ENABLE_CQL DYTC_SET_COMMAND(DYTC_FUNCTION_CQL, DYTC_MODE_MMC_BALANCE, 1) -#define DYTC_ENABLE_CQL DYTC_SET_COMMAND(DYTC_FUNCTION_CQL, DYTC_MODE_BALANCE, 1) +enum dytc_profile_funcmode { + DYTC_FUNCMODE_NONE = 0, + DYTC_FUNCMODE_MMC, + DYTC_FUNCMODE_PSC, +}; +static enum dytc_profile_funcmode dytc_profile_available; static enum platform_profile_option dytc_current_profile; static atomic_t dytc_ignore_event = ATOMIC_INIT(0); static DEFINE_MUTEX(dytc_mutex); @@ -10158,19 +10178,37 @@ static bool dytc_mmc_get_available; static int convert_dytc_to_profile(int dytcmode, enum platform_profile_option *profile) { - switch (dytcmode) { - case DYTC_MODE_LOWPOWER: - *profile = PLATFORM_PROFILE_LOW_POWER; - break; - case DYTC_MODE_BALANCE: - case DYTC_MODE_MMC_BALANCE: - *profile = PLATFORM_PROFILE_BALANCED; - break; - case DYTC_MODE_PERFORM: - *profile = PLATFORM_PROFILE_PERFORMANCE; - break; - default: /* Unknown mode */ - return -EINVAL; + if (dytc_profile_available == DYTC_FUNCMODE_MMC) { + switch (dytcmode) { + case DYTC_MODE_MMC_LOWPOWER: + *profile = PLATFORM_PROFILE_LOW_POWER; + break; + case DYTC_MODE_MMC_DEFAULT: + case DYTC_MODE_MMC_BALANCE: + *profile = PLATFORM_PROFILE_BALANCED; + break; + case DYTC_MODE_MMC_PERFORM: + *profile = PLATFORM_PROFILE_PERFORMANCE; + break; + default: /* Unknown mode */ + return -EINVAL; + } + return 0; + } + if (dytc_profile_available == DYTC_FUNCMODE_PSC) { + switch (dytcmode) { + case DYTC_MODE_PSC_LOWPOWER: + *profile = PLATFORM_PROFILE_LOW_POWER; + break; + case DYTC_MODE_PSC_BALANCE: + *profile = PLATFORM_PROFILE_BALANCED; + break; + case DYTC_MODE_PSC_PERFORM: + *profile = PLATFORM_PROFILE_PERFORMANCE; + break; + default: /* Unknown mode */ + return -EINVAL; + } } return 0; } @@ -10179,13 +10217,22 @@ static int convert_profile_to_dytc(enum platform_profile_option profile, int *pe { switch (profile) { case PLATFORM_PROFILE_LOW_POWER: - *perfmode = DYTC_MODE_LOWPOWER; + if (dytc_profile_available == DYTC_FUNCMODE_MMC) + *perfmode = DYTC_MODE_MMC_LOWPOWER; + else if (dytc_profile_available == DYTC_FUNCMODE_PSC) + *perfmode = DYTC_MODE_PSC_LOWPOWER; break; case PLATFORM_PROFILE_BALANCED: - *perfmode = DYTC_MODE_BALANCE; + if (dytc_profile_available == DYTC_FUNCMODE_MMC) + *perfmode = DYTC_MODE_MMC_BALANCE; + else if (dytc_profile_available == DYTC_FUNCMODE_PSC) + *perfmode = DYTC_MODE_PSC_BALANCE; break; case PLATFORM_PROFILE_PERFORMANCE: - *perfmode = DYTC_MODE_PERFORM; + if (dytc_profile_available == DYTC_FUNCMODE_MMC) + *perfmode = DYTC_MODE_MMC_PERFORM; + else if (dytc_profile_available == DYTC_FUNCMODE_PSC) + *perfmode = DYTC_MODE_PSC_PERFORM; break; default: /* Unknown profile */ return -EOPNOTSUPP; @@ -10251,6 +10298,7 @@ static int dytc_cql_command(int command, int *output) static int dytc_profile_set(struct platform_profile_handler *pprof, enum platform_profile_option profile) { + int perfmode; int output; int err; @@ -10258,25 +10306,31 @@ static int dytc_profile_set(struct platform_profile_handler *pprof, if (err) return err; - if (profile == PLATFORM_PROFILE_BALANCED) { - /* - * To get back to balanced mode we need to issue a reset command. - * Note we still need to disable CQL mode before hand and re-enable - * it afterwards, otherwise dytc_lapmode gets reset to 0 and stays - * stuck at 0 for aprox. 30 minutes. - */ - err = dytc_cql_command(DYTC_CMD_RESET, &output); - if (err) - goto unlock; - } else { - int perfmode; - - err = convert_profile_to_dytc(profile, &perfmode); - if (err) - goto unlock; + err = convert_profile_to_dytc(profile, &perfmode); + if (err) + goto unlock; - /* Determine if we are in CQL mode. This alters the commands we do */ - err = dytc_cql_command(DYTC_SET_COMMAND(DYTC_FUNCTION_MMC, perfmode, 1), &output); + if (dytc_profile_available == DYTC_FUNCMODE_MMC) { + if (profile == PLATFORM_PROFILE_BALANCED) { + /* + * To get back to balanced mode we need to issue a reset command. + * Note we still need to disable CQL mode before hand and re-enable + * it afterwards, otherwise dytc_lapmode gets reset to 0 and stays + * stuck at 0 for aprox. 30 minutes. + */ + err = dytc_cql_command(DYTC_CMD_RESET, &output); + if (err) + goto unlock; + } else { + /* Determine if we are in CQL mode. This alters the commands we do */ + err = dytc_cql_command(DYTC_SET_COMMAND(DYTC_FUNCTION_MMC, perfmode, 1), + &output); + if (err) + goto unlock; + } + } + if (dytc_profile_available == DYTC_FUNCMODE_PSC) { + err = dytc_command(DYTC_SET_COMMAND(DYTC_FUNCTION_PSC, perfmode, 1), &output); if (err) goto unlock; } @@ -10290,14 +10344,18 @@ unlock: static void dytc_profile_refresh(void) { enum platform_profile_option profile; - int output, err; + int output, err = 0; int perfmode; mutex_lock(&dytc_mutex); - if (dytc_mmc_get_available) - err = dytc_command(DYTC_CMD_MMC_GET, &output); - else - err = dytc_cql_command(DYTC_CMD_GET, &output); + if (dytc_profile_available == DYTC_FUNCMODE_MMC) { + if (dytc_mmc_get_available) + err = dytc_command(DYTC_CMD_MMC_GET, &output); + else + err = dytc_cql_command(DYTC_CMD_GET, &output); + } else if (dytc_profile_available == DYTC_FUNCMODE_PSC) + err = dytc_command(DYTC_CMD_GET, &output); + mutex_unlock(&dytc_mutex); if (err) return; @@ -10324,6 +10382,7 @@ static int tpacpi_dytc_profile_init(struct ibm_init_struct *iibm) set_bit(PLATFORM_PROFILE_BALANCED, dytc_profile.choices); set_bit(PLATFORM_PROFILE_PERFORMANCE, dytc_profile.choices); + dytc_profile_available = DYTC_FUNCMODE_NONE; err = dytc_command(DYTC_CMD_QUERY, &output); if (err) return err; @@ -10335,27 +10394,34 @@ static int tpacpi_dytc_profile_init(struct ibm_init_struct *iibm) if (dytc_version < 5) return -ENODEV; - /* Check what capabilities are supported. Currently MMC is needed */ + /* Check what capabilities are supported */ err = dytc_command(DYTC_CMD_FUNC_CAP, &output); if (err) return err; - if (!(output & BIT(DYTC_FC_MMC))) { - dbg_printk(TPACPI_DBG_INIT, " DYTC MMC mode not supported\n"); + + if (output & BIT(DYTC_FC_MMC)) { /* MMC MODE */ + dytc_profile_available = DYTC_FUNCMODE_MMC; + + /* + * Check if MMC_GET functionality available + * Version > 6 and return success from MMC_GET command + */ + dytc_mmc_get_available = false; + if (dytc_version >= 6) { + err = dytc_command(DYTC_CMD_MMC_GET, &output); + if (!err && ((output & DYTC_ERR_MASK) == DYTC_ERR_SUCCESS)) + dytc_mmc_get_available = true; + } + } else if (output & BIT(DYTC_FC_PSC)) { /* PSC MODE */ + dytc_profile_available = DYTC_FUNCMODE_PSC; + } else { + dbg_printk(TPACPI_DBG_INIT, "No DYTC support available\n"); return -ENODEV; } dbg_printk(TPACPI_DBG_INIT, "DYTC version %d: thermal mode available\n", dytc_version); - /* - * Check if MMC_GET functionality available - * Version > 6 and return success from MMC_GET command - */ - dytc_mmc_get_available = false; - if (dytc_version >= 6) { - err = dytc_command(DYTC_CMD_MMC_GET, &output); - if (!err && ((output & DYTC_ERR_MASK) == DYTC_ERR_SUCCESS)) - dytc_mmc_get_available = true; - } + /* Create platform_profile structure and register */ err = platform_profile_register(&dytc_profile); /* @@ -10373,6 +10439,7 @@ static int tpacpi_dytc_profile_init(struct ibm_init_struct *iibm) static void dytc_profile_exit(void) { + dytc_profile_available = DYTC_FUNCMODE_NONE; platform_profile_remove(); } diff --git a/drivers/platform/x86/x86-android-tablets.c b/drivers/platform/x86/x86-android-tablets.c index 9360a8a92486..f446be72e539 100644 --- a/drivers/platform/x86/x86-android-tablets.c +++ b/drivers/platform/x86/x86-android-tablets.c @@ -12,17 +12,26 @@ #include <linux/acpi.h> #include <linux/dmi.h> +#include <linux/efi.h> +#include <linux/gpio_keys.h> #include <linux/gpio/consumer.h> #include <linux/gpio/driver.h> #include <linux/gpio/machine.h> #include <linux/i2c.h> +#include <linux/input.h> #include <linux/irq.h> #include <linux/irqdomain.h> #include <linux/module.h> #include <linux/mod_devicetable.h> +#include <linux/pinctrl/consumer.h> +#include <linux/pinctrl/machine.h> +#include <linux/platform_data/lp855x.h> #include <linux/platform_device.h> +#include <linux/pm.h> #include <linux/power/bq24190_charger.h> +#include <linux/rmi.h> #include <linux/serdev.h> +#include <linux/spi/spi.h> #include <linux/string.h> /* For gpio_get_desc() which is EXPORT_SYMBOL_GPL() */ #include "../../gpio/gpiolib.h" @@ -53,13 +62,33 @@ static int gpiochip_find_match_label(struct gpio_chip *gc, void *data) return gc->label && !strcmp(gc->label, data); } +static int x86_android_tablet_get_gpiod(char *label, int pin, struct gpio_desc **desc) +{ + struct gpio_desc *gpiod; + struct gpio_chip *chip; + + chip = gpiochip_find(label, gpiochip_find_match_label); + if (!chip) { + pr_err("error cannot find GPIO chip %s\n", label); + return -ENODEV; + } + + gpiod = gpiochip_get_desc(chip, pin); + if (IS_ERR(gpiod)) { + pr_err("error %ld getting GPIO %s %d\n", PTR_ERR(gpiod), label, pin); + return PTR_ERR(gpiod); + } + + *desc = gpiod; + return 0; +} + static int x86_acpi_irq_helper_get(const struct x86_acpi_irq_data *data) { struct irq_fwspec fwspec = { }; struct irq_domain *domain; struct acpi_device *adev; struct gpio_desc *gpiod; - struct gpio_chip *chip; unsigned int irq_type; acpi_handle handle; acpi_status status; @@ -67,6 +96,12 @@ static int x86_acpi_irq_helper_get(const struct x86_acpi_irq_data *data) switch (data->type) { case X86_ACPI_IRQ_TYPE_APIC: + /* + * The DSDT may already reference the GSI in a device skipped by + * acpi_quirk_skip_i2c_client_enumeration(). Unregister the GSI + * to avoid EBUSY errors in this case. + */ + acpi_unregister_gsi(data->index); irq = acpi_register_gsi(NULL, data->index, data->trigger, data->polarity); if (irq < 0) pr_err("error %d getting APIC IRQ %d\n", irq, data->index); @@ -74,18 +109,9 @@ static int x86_acpi_irq_helper_get(const struct x86_acpi_irq_data *data) return irq; case X86_ACPI_IRQ_TYPE_GPIOINT: /* Like acpi_dev_gpio_irq_get(), but without parsing ACPI resources */ - chip = gpiochip_find(data->chip, gpiochip_find_match_label); - if (!chip) { - pr_err("error cannot find GPIO chip %s\n", data->chip); - return -ENODEV; - } - - gpiod = gpiochip_get_desc(chip, data->index); - if (IS_ERR(gpiod)) { - ret = PTR_ERR(gpiod); - pr_err("error %d getting GPIO %s %d\n", ret, data->chip, data->index); + ret = x86_android_tablet_get_gpiod(data->chip, data->index, &gpiod); + if (ret) return ret; - } irq = gpiod_to_irq(gpiod); if (irq < 0) { @@ -105,7 +131,7 @@ static int x86_acpi_irq_helper_get(const struct x86_acpi_irq_data *data) return -ENODEV; } - acpi_bus_get_device(handle, &adev); + adev = acpi_fetch_acpi_dev(handle); if (!adev) { pr_err("error could not get %s adev\n", data->chip); return -ENODEV; @@ -146,6 +172,7 @@ struct x86_serdev_info { struct x86_dev_info { char *invalid_aei_gpiochip; const char * const *modules; + const struct software_node *bat_swnode; struct gpiod_lookup_table * const *gpiod_lookup_tables; const struct x86_i2c_client_info *i2c_client_info; const struct platform_device_info *pdev_info; @@ -157,21 +184,46 @@ struct x86_dev_info { void (*exit)(void); }; -/* Generic / shared bq24190 settings */ -static const char * const bq24190_suppliers[] = { "tusb1210-psy" }; +/* Generic / shared charger / battery settings */ +static const char * const tusb1211_chg_det_psy[] = { "tusb1211-charger-detect" }; +static const char * const bq24190_psy[] = { "bq24190-charger" }; +static const char * const bq25890_psy[] = { "bq25890-charger" }; -static const struct property_entry bq24190_props[] = { - PROPERTY_ENTRY_STRING_ARRAY("supplied-from", bq24190_suppliers), - PROPERTY_ENTRY_BOOL("omit-battery-class"), - PROPERTY_ENTRY_BOOL("disable-reset"), +static const struct property_entry fg_bq24190_supply_props[] = { + PROPERTY_ENTRY_STRING_ARRAY("supplied-from", bq24190_psy), { } }; -static const struct software_node bq24190_node = { - .properties = bq24190_props, +static const struct software_node fg_bq24190_supply_node = { + .properties = fg_bq24190_supply_props, +}; + +static const struct property_entry fg_bq25890_supply_props[] = { + PROPERTY_ENTRY_STRING_ARRAY("supplied-from", bq25890_psy), + { } }; -/* For enableing the bq24190 5V boost based on id-pin */ +static const struct software_node fg_bq25890_supply_node = { + .properties = fg_bq25890_supply_props, +}; + +/* LiPo HighVoltage (max 4.35V) settings used by most devs with a HV bat. */ +static const struct property_entry generic_lipo_hv_4v35_battery_props[] = { + PROPERTY_ENTRY_STRING("compatible", "simple-battery"), + PROPERTY_ENTRY_STRING("device-chemistry", "lithium-ion"), + PROPERTY_ENTRY_U32("precharge-current-microamp", 256000), + PROPERTY_ENTRY_U32("charge-term-current-microamp", 128000), + PROPERTY_ENTRY_U32("constant-charge-current-max-microamp", 1856000), + PROPERTY_ENTRY_U32("constant-charge-voltage-max-microvolt", 4352000), + PROPERTY_ENTRY_U32("factory-internal-resistance-micro-ohms", 150000), + { } +}; + +static const struct software_node generic_lipo_hv_4v35_battery_node = { + .properties = generic_lipo_hv_4v35_battery_props, +}; + +/* For enabling the bq24190 5V boost based on id-pin */ static struct regulator_consumer_supply intel_int3496_consumer = { .supply = "vbus", .dev_name = "intel-int3496", @@ -213,6 +265,51 @@ static struct gpiod_lookup_table int3496_gpo2_pin22_gpios = { }, }; +/* Asus ME176C and TF103C tablets shared data */ +static struct gpio_keys_button asus_me176c_tf103c_lid = { + .code = SW_LID, + /* .gpio gets filled in by asus_me176c_tf103c_init() */ + .active_low = true, + .desc = "lid_sw", + .type = EV_SW, + .wakeup = true, + .debounce_interval = 50, +}; + +static const struct gpio_keys_platform_data asus_me176c_tf103c_lid_pdata __initconst = { + .buttons = &asus_me176c_tf103c_lid, + .nbuttons = 1, + .name = "lid_sw", +}; + +static const struct platform_device_info asus_me176c_tf103c_pdevs[] __initconst = { + { + .name = "gpio-keys", + .id = PLATFORM_DEVID_AUTO, + .data = &asus_me176c_tf103c_lid_pdata, + .size_data = sizeof(asus_me176c_tf103c_lid_pdata), + }, + { + /* For micro USB ID pin handling */ + .name = "intel-int3496", + .id = PLATFORM_DEVID_NONE, + }, +}; + +static int __init asus_me176c_tf103c_init(void) +{ + struct gpio_desc *gpiod; + int ret; + + ret = x86_android_tablet_get_gpiod("INT33FC:02", 12, &gpiod); + if (ret < 0) + return ret; + asus_me176c_tf103c_lid.gpio = desc_to_gpio(gpiod); + + return 0; +} + + /* Asus ME176C tablets have an Android factory img with everything hardcoded */ static const char * const asus_me176c_accel_mount_matrix[] = { "-1", "0", "0", @@ -229,14 +326,38 @@ static const struct software_node asus_me176c_accel_node = { .properties = asus_me176c_accel_props, }; +static const struct property_entry asus_me176c_bq24190_props[] = { + PROPERTY_ENTRY_STRING_ARRAY("supplied-from", tusb1211_chg_det_psy), + PROPERTY_ENTRY_REF("monitored-battery", &generic_lipo_hv_4v35_battery_node), + PROPERTY_ENTRY_U32("ti,system-minimum-microvolt", 3600000), + PROPERTY_ENTRY_BOOL("omit-battery-class"), + PROPERTY_ENTRY_BOOL("disable-reset"), + { } +}; + +static const struct software_node asus_me176c_bq24190_node = { + .properties = asus_me176c_bq24190_props, +}; + +static const struct property_entry asus_me176c_ug3105_props[] = { + PROPERTY_ENTRY_STRING_ARRAY("supplied-from", bq24190_psy), + PROPERTY_ENTRY_REF("monitored-battery", &generic_lipo_hv_4v35_battery_node), + PROPERTY_ENTRY_U32("upisemi,rsns-microohm", 10000), + { } +}; + +static const struct software_node asus_me176c_ug3105_node = { + .properties = asus_me176c_ug3105_props, +}; + static const struct x86_i2c_client_info asus_me176c_i2c_clients[] __initconst = { { - /* bq24190 battery charger */ + /* bq24297 battery charger */ .board_info = { .type = "bq24190", .addr = 0x6b, - .dev_name = "bq24190", - .swnode = &bq24190_node, + .dev_name = "bq24297", + .swnode = &asus_me176c_bq24190_node, .platform_data = &bq24190_pdata, }, .adapter_path = "\\_SB_.I2C1", @@ -252,6 +373,7 @@ static const struct x86_i2c_client_info asus_me176c_i2c_clients[] __initconst = .type = "ug3105", .addr = 0x70, .dev_name = "ug3105", + .swnode = &asus_me176c_ug3105_node, }, .adapter_path = "\\_SB_.I2C1", }, { @@ -271,6 +393,12 @@ static const struct x86_i2c_client_info asus_me176c_i2c_clients[] __initconst = .swnode = &asus_me176c_accel_node, }, .adapter_path = "\\_SB_.I2C5", + .irq_data = { + .type = X86_ACPI_IRQ_TYPE_APIC, + .index = 0x44, + .trigger = ACPI_EDGE_SENSITIVE, + .polarity = ACPI_ACTIVE_LOW, + }, }, { /* goodix touchscreen */ .board_info = { @@ -315,13 +443,15 @@ static struct gpiod_lookup_table * const asus_me176c_gpios[] = { static const struct x86_dev_info asus_me176c_info __initconst = { .i2c_client_info = asus_me176c_i2c_clients, .i2c_client_count = ARRAY_SIZE(asus_me176c_i2c_clients), - .pdev_info = int3496_pdevs, - .pdev_count = ARRAY_SIZE(int3496_pdevs), + .pdev_info = asus_me176c_tf103c_pdevs, + .pdev_count = ARRAY_SIZE(asus_me176c_tf103c_pdevs), .serdev_info = asus_me176c_serdevs, .serdev_count = ARRAY_SIZE(asus_me176c_serdevs), .gpiod_lookup_tables = asus_me176c_gpios, + .bat_swnode = &generic_lipo_hv_4v35_battery_node, .modules = bq24190_modules, .invalid_aei_gpiochip = "INT33FC:02", + .init = asus_me176c_tf103c_init, }; /* Asus TF103C tablets have an Android factory img with everything hardcoded */ @@ -349,14 +479,53 @@ static const struct software_node asus_tf103c_touchscreen_node = { .properties = asus_tf103c_touchscreen_props, }; +static const struct property_entry asus_tf103c_battery_props[] = { + PROPERTY_ENTRY_STRING("compatible", "simple-battery"), + PROPERTY_ENTRY_STRING("device-chemistry", "lithium-ion-polymer"), + PROPERTY_ENTRY_U32("precharge-current-microamp", 256000), + PROPERTY_ENTRY_U32("charge-term-current-microamp", 128000), + PROPERTY_ENTRY_U32("constant-charge-current-max-microamp", 2048000), + PROPERTY_ENTRY_U32("constant-charge-voltage-max-microvolt", 4208000), + PROPERTY_ENTRY_U32("factory-internal-resistance-micro-ohms", 150000), + { } +}; + +static const struct software_node asus_tf103c_battery_node = { + .properties = asus_tf103c_battery_props, +}; + +static const struct property_entry asus_tf103c_bq24190_props[] = { + PROPERTY_ENTRY_STRING_ARRAY("supplied-from", tusb1211_chg_det_psy), + PROPERTY_ENTRY_REF("monitored-battery", &asus_tf103c_battery_node), + PROPERTY_ENTRY_U32("ti,system-minimum-microvolt", 3600000), + PROPERTY_ENTRY_BOOL("omit-battery-class"), + PROPERTY_ENTRY_BOOL("disable-reset"), + { } +}; + +static const struct software_node asus_tf103c_bq24190_node = { + .properties = asus_tf103c_bq24190_props, +}; + +static const struct property_entry asus_tf103c_ug3105_props[] = { + PROPERTY_ENTRY_STRING_ARRAY("supplied-from", bq24190_psy), + PROPERTY_ENTRY_REF("monitored-battery", &asus_tf103c_battery_node), + PROPERTY_ENTRY_U32("upisemi,rsns-microohm", 5000), + { } +}; + +static const struct software_node asus_tf103c_ug3105_node = { + .properties = asus_tf103c_ug3105_props, +}; + static const struct x86_i2c_client_info asus_tf103c_i2c_clients[] __initconst = { { - /* bq24190 battery charger */ + /* bq24297 battery charger */ .board_info = { .type = "bq24190", .addr = 0x6b, - .dev_name = "bq24190", - .swnode = &bq24190_node, + .dev_name = "bq24297", + .swnode = &asus_tf103c_bq24190_node, .platform_data = &bq24190_pdata, }, .adapter_path = "\\_SB_.I2C1", @@ -372,6 +541,7 @@ static const struct x86_i2c_client_info asus_tf103c_i2c_clients[] __initconst = .type = "ug3105", .addr = 0x70, .dev_name = "ug3105", + .swnode = &asus_tf103c_ug3105_node, }, .adapter_path = "\\_SB_.I2C1", }, { @@ -418,11 +588,13 @@ static struct gpiod_lookup_table * const asus_tf103c_gpios[] = { static const struct x86_dev_info asus_tf103c_info __initconst = { .i2c_client_info = asus_tf103c_i2c_clients, .i2c_client_count = ARRAY_SIZE(asus_tf103c_i2c_clients), - .pdev_info = int3496_pdevs, - .pdev_count = ARRAY_SIZE(int3496_pdevs), + .pdev_info = asus_me176c_tf103c_pdevs, + .pdev_count = ARRAY_SIZE(asus_me176c_tf103c_pdevs), .gpiod_lookup_tables = asus_tf103c_gpios, + .bat_swnode = &asus_tf103c_battery_node, .modules = bq24190_modules, .invalid_aei_gpiochip = "INT33FC:02", + .init = asus_me176c_tf103c_init, }; /* @@ -529,6 +701,347 @@ static const struct x86_dev_info czc_p10t __initconst = { .init = czc_p10t_init, }; +/* Lenovo Yoga Book X90F / X91F / X91L need manual instantiation of the fg client */ +static const struct x86_i2c_client_info lenovo_yogabook_x9x_i2c_clients[] __initconst = { + { + /* BQ27542 fuel-gauge */ + .board_info = { + .type = "bq27542", + .addr = 0x55, + .dev_name = "bq27542", + .swnode = &fg_bq25890_supply_node, + }, + .adapter_path = "\\_SB_.PCI0.I2C1", + }, +}; + +static const struct x86_dev_info lenovo_yogabook_x9x_info __initconst = { + .i2c_client_info = lenovo_yogabook_x9x_i2c_clients, + .i2c_client_count = ARRAY_SIZE(lenovo_yogabook_x9x_i2c_clients), +}; + +/* Lenovo Yoga Tablet 2 1050F/L's Android factory img has everything hardcoded */ +static const struct property_entry lenovo_yoga_tab2_830_1050_bq24190_props[] = { + PROPERTY_ENTRY_STRING_ARRAY("supplied-from", tusb1211_chg_det_psy), + PROPERTY_ENTRY_REF("monitored-battery", &generic_lipo_hv_4v35_battery_node), + PROPERTY_ENTRY_BOOL("omit-battery-class"), + PROPERTY_ENTRY_BOOL("disable-reset"), + { } +}; + +static const struct software_node lenovo_yoga_tab2_830_1050_bq24190_node = { + .properties = lenovo_yoga_tab2_830_1050_bq24190_props, +}; + +/* This gets filled by lenovo_yoga_tab2_830_1050_init() */ +static struct rmi_device_platform_data lenovo_yoga_tab2_830_1050_rmi_pdata = { }; + +static struct lp855x_platform_data lenovo_yoga_tab2_830_1050_lp8557_pdata = { + .device_control = 0x86, + .initial_brightness = 128, +}; + +static const struct x86_i2c_client_info lenovo_yoga_tab2_830_1050_i2c_clients[] __initconst = { + { + /* bq24292i battery charger */ + .board_info = { + .type = "bq24190", + .addr = 0x6b, + .dev_name = "bq24292i", + .swnode = &lenovo_yoga_tab2_830_1050_bq24190_node, + .platform_data = &bq24190_pdata, + }, + .adapter_path = "\\_SB_.I2C1", + .irq_data = { + .type = X86_ACPI_IRQ_TYPE_GPIOINT, + .chip = "INT33FC:02", + .index = 2, + .trigger = ACPI_EDGE_SENSITIVE, + .polarity = ACPI_ACTIVE_HIGH, + }, + }, { + /* BQ27541 fuel-gauge */ + .board_info = { + .type = "bq27541", + .addr = 0x55, + .dev_name = "bq27541", + .swnode = &fg_bq24190_supply_node, + }, + .adapter_path = "\\_SB_.I2C1", + }, { + /* Synaptics RMI touchscreen */ + .board_info = { + .type = "rmi4_i2c", + .addr = 0x38, + .dev_name = "rmi4_i2c", + .platform_data = &lenovo_yoga_tab2_830_1050_rmi_pdata, + }, + .adapter_path = "\\_SB_.I2C6", + .irq_data = { + .type = X86_ACPI_IRQ_TYPE_APIC, + .index = 0x45, + .trigger = ACPI_EDGE_SENSITIVE, + .polarity = ACPI_ACTIVE_HIGH, + }, + }, { + /* LP8557 Backlight controller */ + .board_info = { + .type = "lp8557", + .addr = 0x2c, + .dev_name = "lp8557", + .platform_data = &lenovo_yoga_tab2_830_1050_lp8557_pdata, + }, + .adapter_path = "\\_SB_.I2C3", + }, +}; + +static struct gpiod_lookup_table lenovo_yoga_tab2_830_1050_int3496_gpios = { + .dev_id = "intel-int3496", + .table = { + GPIO_LOOKUP("INT33FC:02", 1, "mux", GPIO_ACTIVE_LOW), + GPIO_LOOKUP("INT33FC:02", 24, "id", GPIO_ACTIVE_HIGH), + { } + }, +}; + +#define LENOVO_YOGA_TAB2_830_1050_CODEC_NAME "spi-10WM5102:00" + +static struct gpiod_lookup_table lenovo_yoga_tab2_830_1050_codec_gpios = { + .dev_id = LENOVO_YOGA_TAB2_830_1050_CODEC_NAME, + .table = { + GPIO_LOOKUP("gpio_crystalcove", 3, "reset", GPIO_ACTIVE_HIGH), + GPIO_LOOKUP("INT33FC:01", 23, "wlf,ldoena", GPIO_ACTIVE_HIGH), + GPIO_LOOKUP("arizona", 2, "wlf,spkvdd-ena", GPIO_ACTIVE_HIGH), + GPIO_LOOKUP("arizona", 4, "wlf,micd-pol", GPIO_ACTIVE_LOW), + { } + }, +}; + +static struct gpiod_lookup_table * const lenovo_yoga_tab2_830_1050_gpios[] = { + &lenovo_yoga_tab2_830_1050_int3496_gpios, + &lenovo_yoga_tab2_830_1050_codec_gpios, + NULL +}; + +static int __init lenovo_yoga_tab2_830_1050_init(void); +static void lenovo_yoga_tab2_830_1050_exit(void); + +static struct x86_dev_info lenovo_yoga_tab2_830_1050_info __initdata = { + .i2c_client_info = lenovo_yoga_tab2_830_1050_i2c_clients, + /* i2c_client_count gets set by lenovo_yoga_tab2_830_1050_init() */ + .pdev_info = int3496_pdevs, + .pdev_count = ARRAY_SIZE(int3496_pdevs), + .gpiod_lookup_tables = lenovo_yoga_tab2_830_1050_gpios, + .bat_swnode = &generic_lipo_hv_4v35_battery_node, + .modules = bq24190_modules, + .invalid_aei_gpiochip = "INT33FC:02", + .init = lenovo_yoga_tab2_830_1050_init, + .exit = lenovo_yoga_tab2_830_1050_exit, +}; + +/* + * The Lenovo Yoga Tablet 2 830 and 1050 (8" vs 10") versions use the same + * mainboard, but they need some different treatment related to the display: + * 1. The 830 uses a portrait LCD panel with a landscape touchscreen, requiring + * the touchscreen driver to adjust the touch-coords to match the LCD. + * 2. Both use an TI LP8557 LED backlight controller. On the 1050 the LP8557's + * PWM input is connected to the PMIC's PWM output and everything works fine + * with the defaults programmed into the LP8557 by the BIOS. + * But on the 830 the LP8557's PWM input is connected to a PWM output coming + * from the LCD panel's controller. The Android code has a hack in the i915 + * driver to write the non-standard DSI reg 0x9f with the desired backlight + * level to set the duty-cycle of the LCD's PWM output. + * + * To avoid having to have a similar hack in the mainline kernel the LP8557 + * entry in lenovo_yoga_tab2_830_1050_i2c_clients instead just programs the + * LP8557 to directly set the level, ignoring the PWM input. This means that + * the LP8557 i2c_client should only be instantiated on the 830. + */ +static int __init lenovo_yoga_tab2_830_1050_init_display(void) +{ + struct gpio_desc *gpiod; + int ret; + + /* Use PMIC GPIO 10 bootstrap pin to differentiate 830 vs 1050 */ + ret = x86_android_tablet_get_gpiod("gpio_crystalcove", 10, &gpiod); + if (ret) + return ret; + + ret = gpiod_get_value_cansleep(gpiod); + if (ret) { + pr_info("detected Lenovo Yoga Tablet 2 1050F/L\n"); + lenovo_yoga_tab2_830_1050_info.i2c_client_count = + ARRAY_SIZE(lenovo_yoga_tab2_830_1050_i2c_clients) - 1; + } else { + pr_info("detected Lenovo Yoga Tablet 2 830F/L\n"); + lenovo_yoga_tab2_830_1050_rmi_pdata.sensor_pdata.axis_align.swap_axes = true; + lenovo_yoga_tab2_830_1050_rmi_pdata.sensor_pdata.axis_align.flip_y = true; + lenovo_yoga_tab2_830_1050_info.i2c_client_count = + ARRAY_SIZE(lenovo_yoga_tab2_830_1050_i2c_clients); + } + + return 0; +} + +/* SUS (INT33FC:02) pin 6 needs to be configured as pmu_clk for the audio codec */ +static const struct pinctrl_map lenovo_yoga_tab2_830_1050_codec_pinctrl_map = + PIN_MAP_MUX_GROUP(LENOVO_YOGA_TAB2_830_1050_CODEC_NAME, "codec_32khz_clk", + "INT33FC:02", "pmu_clk2_grp", "pmu_clk"); + +static struct pinctrl *lenovo_yoga_tab2_830_1050_codec_pinctrl; + +static int __init lenovo_yoga_tab2_830_1050_init_codec(void) +{ + struct device *codec_dev; + struct pinctrl *pinctrl; + int ret; + + codec_dev = bus_find_device_by_name(&spi_bus_type, NULL, + LENOVO_YOGA_TAB2_830_1050_CODEC_NAME); + if (!codec_dev) { + pr_err("error cannot find %s device\n", LENOVO_YOGA_TAB2_830_1050_CODEC_NAME); + return -ENODEV; + } + + ret = pinctrl_register_mappings(&lenovo_yoga_tab2_830_1050_codec_pinctrl_map, 1); + if (ret) + goto err_put_device; + + pinctrl = pinctrl_get_select(codec_dev, "codec_32khz_clk"); + if (IS_ERR(pinctrl)) { + ret = dev_err_probe(codec_dev, PTR_ERR(pinctrl), "selecting codec_32khz_clk\n"); + goto err_unregister_mappings; + } + + /* We're done with the codec_dev now */ + put_device(codec_dev); + + lenovo_yoga_tab2_830_1050_codec_pinctrl = pinctrl; + return 0; + +err_unregister_mappings: + pinctrl_unregister_mappings(&lenovo_yoga_tab2_830_1050_codec_pinctrl_map); +err_put_device: + put_device(codec_dev); + return ret; +} + +/* + * These tablet's DSDT does not set acpi_gbl_reduced_hardware, so acpi_power_off + * gets used as pm_power_off handler. This causes "poweroff" on these tablets + * to hang hard. Requiring pressing the powerbutton for 30 seconds *twice* + * followed by a normal 3 second press to recover. Avoid this by doing an EFI + * poweroff instead. + */ +static void lenovo_yoga_tab2_830_1050_power_off(void) +{ + efi.reset_system(EFI_RESET_SHUTDOWN, EFI_SUCCESS, 0, NULL); +} + +static int __init lenovo_yoga_tab2_830_1050_init(void) +{ + int ret; + + ret = lenovo_yoga_tab2_830_1050_init_display(); + if (ret) + return ret; + + ret = lenovo_yoga_tab2_830_1050_init_codec(); + if (ret) + return ret; + + pm_power_off = lenovo_yoga_tab2_830_1050_power_off; + return 0; +} + +static void lenovo_yoga_tab2_830_1050_exit(void) +{ + pm_power_off = NULL; /* Just turn poweroff into halt on module unload */ + + if (lenovo_yoga_tab2_830_1050_codec_pinctrl) { + pinctrl_put(lenovo_yoga_tab2_830_1050_codec_pinctrl); + pinctrl_unregister_mappings(&lenovo_yoga_tab2_830_1050_codec_pinctrl_map); + } +} + +/* Nextbook Ares 8 tablets have an Android factory img with everything hardcoded */ +static const char * const nextbook_ares8_accel_mount_matrix[] = { + "0", "-1", "0", + "-1", "0", "0", + "0", "0", "1" +}; + +static const struct property_entry nextbook_ares8_accel_props[] = { + PROPERTY_ENTRY_STRING_ARRAY("mount-matrix", nextbook_ares8_accel_mount_matrix), + { } +}; + +static const struct software_node nextbook_ares8_accel_node = { + .properties = nextbook_ares8_accel_props, +}; + +static const struct property_entry nextbook_ares8_touchscreen_props[] = { + PROPERTY_ENTRY_U32("touchscreen-size-x", 800), + PROPERTY_ENTRY_U32("touchscreen-size-y", 1280), + { } +}; + +static const struct software_node nextbook_ares8_touchscreen_node = { + .properties = nextbook_ares8_touchscreen_props, +}; + +static const struct x86_i2c_client_info nextbook_ares8_i2c_clients[] __initconst = { + { + /* Freescale MMA8653FC accel */ + .board_info = { + .type = "mma8653", + .addr = 0x1d, + .dev_name = "mma8653", + .swnode = &nextbook_ares8_accel_node, + }, + .adapter_path = "\\_SB_.I2C3", + }, { + /* FT5416DQ9 touchscreen controller */ + .board_info = { + .type = "edt-ft5x06", + .addr = 0x38, + .dev_name = "ft5416", + .swnode = &nextbook_ares8_touchscreen_node, + }, + .adapter_path = "\\_SB_.I2C4", + .irq_data = { + .type = X86_ACPI_IRQ_TYPE_GPIOINT, + .chip = "INT33FC:02", + .index = 3, + .trigger = ACPI_EDGE_SENSITIVE, + .polarity = ACPI_ACTIVE_LOW, + }, + }, +}; + +static struct gpiod_lookup_table nextbook_ares8_int3496_gpios = { + .dev_id = "intel-int3496", + .table = { + GPIO_LOOKUP("INT33FC:02", 1, "mux", GPIO_ACTIVE_HIGH), + GPIO_LOOKUP("INT33FC:02", 18, "id", GPIO_ACTIVE_HIGH), + { } + }, +}; + +static struct gpiod_lookup_table * const nextbook_ares8_gpios[] = { + &nextbook_ares8_int3496_gpios, + NULL +}; + +static const struct x86_dev_info nextbook_ares8_info __initconst = { + .i2c_client_info = nextbook_ares8_i2c_clients, + .i2c_client_count = ARRAY_SIZE(nextbook_ares8_i2c_clients), + .pdev_info = int3496_pdevs, + .pdev_count = ARRAY_SIZE(int3496_pdevs), + .gpiod_lookup_tables = nextbook_ares8_gpios, + .invalid_aei_gpiochip = "INT33FC:02", +}; + /* * Whitelabel (sold as various brands) TM800A550L tablets. * These tablet's DSDT contains a whole bunch of bogus ACPI I2C devices @@ -616,17 +1129,6 @@ static const struct x86_dev_info whitelabel_tm800a550l_info __initconst = { * * This takes care of instantiating the hidden devices manually. */ -static const char * const bq27520_suppliers[] = { "bq25890-charger" }; - -static const struct property_entry bq27520_props[] = { - PROPERTY_ENTRY_STRING_ARRAY("supplied-from", bq27520_suppliers), - { } -}; - -static const struct software_node bq27520_node = { - .properties = bq27520_props, -}; - static const struct x86_i2c_client_info xiaomi_mipad2_i2c_clients[] __initconst = { { /* BQ27520 fuel-gauge */ @@ -634,7 +1136,7 @@ static const struct x86_i2c_client_info xiaomi_mipad2_i2c_clients[] __initconst .type = "bq27520", .addr = 0x55, .dev_name = "bq27520", - .swnode = &bq27520_node, + .swnode = &fg_bq25890_supply_node, }, .adapter_path = "\\_SB_.PCI0.I2C1", }, { @@ -690,7 +1192,7 @@ static const struct dmi_system_id x86_android_tablet_ids[] __initconst = { .driver_data = (void *)&czc_p10t, }, { - /* A variant of CZC P10T */ + /* CZC P10T variant */ .ident = "ViewSonic ViewPad 10", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "ViewSonic"), @@ -699,6 +1201,36 @@ static const struct dmi_system_id x86_android_tablet_ids[] __initconst = { .driver_data = (void *)&czc_p10t, }, { + /* Lenovo Yoga Book X90F / X91F / X91L */ + .matches = { + /* Non exact match to match all versions */ + DMI_MATCH(DMI_PRODUCT_NAME, "Lenovo YB1-X9"), + }, + .driver_data = (void *)&lenovo_yogabook_x9x_info, + }, + { + /* + * Lenovo Yoga Tablet 2 830F/L or 1050F/L (The 8" and 10" + * Lenovo Yoga Tablet 2 use the same mainboard) + */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Intel Corp."), + DMI_MATCH(DMI_PRODUCT_NAME, "VALLEYVIEW C0 PLATFORM"), + DMI_MATCH(DMI_BOARD_NAME, "BYT-T FFD8"), + /* Partial match on beginning of BIOS version */ + DMI_MATCH(DMI_BIOS_VERSION, "BLADE_21"), + }, + .driver_data = (void *)&lenovo_yoga_tab2_830_1050_info, + }, + { + /* Nextbook Ares 8 */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Insyde"), + DMI_MATCH(DMI_PRODUCT_NAME, "M890BAP"), + }, + .driver_data = (void *)&nextbook_ares8_info, + }, + { /* Whitelabel (sold as various brands) TM800A550L */ .matches = { DMI_MATCH(DMI_BOARD_VENDOR, "AMI Corporation"), @@ -727,6 +1259,7 @@ static struct i2c_client **i2c_clients; static struct platform_device **pdevs; static struct serdev_device **serdevs; static struct gpiod_lookup_table * const *gpiod_lookup_tables; +static const struct software_node *bat_swnode; static void (*exit_handler)(void); static __init int x86_instantiate_i2c_client(const struct x86_dev_info *dev_info, @@ -850,6 +1383,8 @@ static void x86_android_tablet_cleanup(void) for (i = 0; gpiod_lookup_tables && gpiod_lookup_tables[i]; i++) gpiod_remove_lookup_table(gpiod_lookup_tables[i]); + + software_node_unregister(bat_swnode); } static __init int x86_android_tablet_init(void) @@ -886,6 +1421,13 @@ static __init int x86_android_tablet_init(void) for (i = 0; dev_info->modules && dev_info->modules[i]; i++) request_module(dev_info->modules[i]); + bat_swnode = dev_info->bat_swnode; + if (bat_swnode) { + ret = software_node_register(bat_swnode); + if (ret) + return ret; + } + gpiod_lookup_tables = dev_info->gpiod_lookup_tables; for (i = 0; gpiod_lookup_tables && gpiod_lookup_tables[i]; i++) gpiod_add_lookup_table(gpiod_lookup_tables[i]); |