diff options
Diffstat (limited to 'drivers/platform')
145 files changed, 12797 insertions, 2732 deletions
diff --git a/drivers/platform/Kconfig b/drivers/platform/Kconfig index d4c2e424a700..971426bb4302 100644 --- a/drivers/platform/Kconfig +++ b/drivers/platform/Kconfig @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only if X86 source "drivers/platform/x86/Kconfig" endif @@ -10,3 +11,5 @@ source "drivers/platform/goldfish/Kconfig" source "drivers/platform/chrome/Kconfig" source "drivers/platform/mellanox/Kconfig" + +source "drivers/platform/olpc/Kconfig" diff --git a/drivers/platform/Makefile b/drivers/platform/Makefile index 4b2ce58bcd9c..6fda58c021ca 100644 --- a/drivers/platform/Makefile +++ b/drivers/platform/Makefile @@ -6,6 +6,6 @@ obj-$(CONFIG_X86) += x86/ obj-$(CONFIG_MELLANOX_PLATFORM) += mellanox/ obj-$(CONFIG_MIPS) += mips/ -obj-$(CONFIG_OLPC) += olpc/ +obj-$(CONFIG_OLPC_EC) += olpc/ obj-$(CONFIG_GOLDFISH) += goldfish/ obj-$(CONFIG_CHROME_PLATFORMS) += chrome/ diff --git a/drivers/platform/chrome/Kconfig b/drivers/platform/chrome/Kconfig index 9186d81a51cc..ee5f08ea57b6 100644 --- a/drivers/platform/chrome/Kconfig +++ b/drivers/platform/chrome/Kconfig @@ -1,7 +1,18 @@ +# SPDX-License-Identifier: GPL-2.0-only # # Platform support for Chrome OS hardware (Chromebooks and Chromeboxes) # +config MFD_CROS_EC + tristate "Platform support for Chrome hardware (transitional)" + select CHROME_PLATFORMS + select CROS_EC + select CONFIG_MFD_CROS_EC_DEV + depends on X86 || ARM || ARM64 || COMPILE_TEST + help + This is a transitional Kconfig option and will be removed after + everyone enables the parts individually. + menuconfig CHROME_PLATFORMS bool "Platform support for Chrome hardware" depends on X86 || ARM || ARM64 || COMPILE_TEST @@ -49,9 +60,22 @@ config CHROMEOS_TBMC To compile this driver as a module, choose M here: the module will be called chromeos_tbmc. +config CROS_EC + tristate "ChromeOS Embedded Controller" + select CROS_EC_PROTO + depends on X86 || ARM || ARM64 || COMPILE_TEST + help + If you say Y here you get support for the ChromeOS Embedded + Controller (EC) providing keyboard, battery and power services. + You also need to enable the driver for the bus you are using. The + protocol for talking to the EC is defined by the bus driver. + + To compile this driver as a module, choose M here: the + module will be called cros_ec. + config CROS_EC_I2C tristate "ChromeOS Embedded Controller (I2C)" - depends on MFD_CROS_EC && I2C + depends on CROS_EC && I2C help If you say Y here, you get support for talking to the ChromeOS @@ -59,9 +83,34 @@ config CROS_EC_I2C a checksum. Failing accesses will be retried three times to improve reliability. +config CROS_EC_RPMSG + tristate "ChromeOS Embedded Controller (rpmsg)" + depends on CROS_EC && RPMSG && OF + help + If you say Y here, you get support for talking to the ChromeOS EC + through rpmsg. This uses a simple byte-level protocol with a + checksum. Also since there's no addition EC-to-host interrupt, this + use a byte in message to distinguish host event from host command. + + To compile this driver as a module, choose M here: the + module will be called cros_ec_rpmsg. + +config CROS_EC_ISHTP + tristate "ChromeOS Embedded Controller (ISHTP)" + depends on CROS_EC + depends on INTEL_ISH_HID + help + If you say Y here, you get support for talking to the ChromeOS EC + firmware running on Intel Integrated Sensor Hub (ISH), using the + ISH Transport protocol (ISH-TP). This uses a simple byte-level + protocol with a checksum. + + To compile this driver as a module, choose M here: the + module will be called cros_ec_ishtp. + config CROS_EC_SPI tristate "ChromeOS Embedded Controller (SPI)" - depends on MFD_CROS_EC && SPI + depends on CROS_EC && SPI ---help--- If you say Y here, you get support for talking to the ChromeOS EC @@ -70,28 +119,17 @@ config CROS_EC_SPI 'pre-amble' bytes before the response actually starts. config CROS_EC_LPC - tristate "ChromeOS Embedded Controller (LPC)" - depends on MFD_CROS_EC && ACPI && (X86 || COMPILE_TEST) - help - If you say Y here, you get support for talking to the ChromeOS EC - over an LPC bus. This uses a simple byte-level protocol with a - checksum. This is used for userspace access only. The kernel - typically has its own communication methods. - - To compile this driver as a module, choose M here: the - module will be called cros_ec_lpc. - -config CROS_EC_LPC_MEC - bool "ChromeOS Embedded Controller LPC Microchip EC (MEC) variant" - depends on CROS_EC_LPC - default n + tristate "ChromeOS Embedded Controller (LPC)" + depends on CROS_EC && ACPI && (X86 || COMPILE_TEST) help - If you say Y here, a variant LPC protocol for the Microchip EC - will be used. Note that this variant is not backward compatible - with non-Microchip ECs. + If you say Y here, you get support for talking to the ChromeOS EC + over an LPC bus, including the LPC Microchip EC (MEC) variant. + This uses a simple byte-level protocol with a checksum. This is + used for userspace access only. The kernel typically has its own + communication methods. - If you have a ChromeOS Embedded Controller Microchip EC variant - choose Y here. + To compile this driver as a module, choose M here: the + module will be called cros_ec_lpcs. config CROS_EC_PROTO bool @@ -108,10 +146,21 @@ config CROS_KBD_LED_BACKLIGHT To compile this driver as a module, choose M here: the module will be called cros_kbd_led_backlight. +config CROS_EC_CHARDEV + tristate "ChromeOS EC miscdevice" + depends on MFD_CROS_EC_DEV + default MFD_CROS_EC_DEV + help + This driver adds file operations support to talk with the + ChromeOS EC from userspace via a character device. + + To compile this driver as a module, choose M here: the + module will be called cros_ec_chardev. + config CROS_EC_LIGHTBAR tristate "Chromebook Pixel's lightbar support" - depends on MFD_CROS_EC_CHARDEV - default MFD_CROS_EC_CHARDEV + depends on MFD_CROS_EC_DEV + default MFD_CROS_EC_DEV help This option exposes the Chromebook Pixel's lightbar to userspace. @@ -121,8 +170,8 @@ config CROS_EC_LIGHTBAR config CROS_EC_VBC tristate "ChromeOS EC vboot context support" - depends on MFD_CROS_EC_CHARDEV && OF - default MFD_CROS_EC_CHARDEV + depends on MFD_CROS_EC_DEV && OF + default MFD_CROS_EC_DEV help This option exposes the ChromeOS EC vboot context nvram to userspace. @@ -132,8 +181,8 @@ config CROS_EC_VBC config CROS_EC_DEBUGFS tristate "Export ChromeOS EC internals in DebugFS" - depends on MFD_CROS_EC_CHARDEV && DEBUG_FS - default MFD_CROS_EC_CHARDEV + depends on MFD_CROS_EC_DEV && DEBUG_FS + default MFD_CROS_EC_DEV help This option exposes the ChromeOS EC device internals to userspace. @@ -143,8 +192,8 @@ config CROS_EC_DEBUGFS config CROS_EC_SYSFS tristate "ChromeOS EC control and information through sysfs" - depends on MFD_CROS_EC_CHARDEV && SYSFS - default MFD_CROS_EC_CHARDEV + depends on MFD_CROS_EC_DEV && SYSFS + default MFD_CROS_EC_DEV help This option exposes some sysfs attributes to control and get information from ChromeOS EC. @@ -152,6 +201,18 @@ config CROS_EC_SYSFS To compile this driver as a module, choose M here: the module will be called cros_ec_sysfs. +config CROS_USBPD_LOGGER + tristate "Logging driver for USB PD charger" + depends on CHARGER_CROS_USBPD + default y + select RTC_LIB + help + This option enables support for logging event data for the USB PD charger + available in the Embedded Controller on ChromeOS systems. + + To compile this driver as a module, choose M here: the + module will be called cros_usbpd_logger. + source "drivers/platform/chrome/wilco_ec/Kconfig" endif # CHROMEOS_PLATFORMS diff --git a/drivers/platform/chrome/Makefile b/drivers/platform/chrome/Makefile index 1e2f0029b597..477ec3d1d1c9 100644 --- a/drivers/platform/chrome/Makefile +++ b/drivers/platform/chrome/Makefile @@ -1,18 +1,25 @@ # SPDX-License-Identifier: GPL-2.0 +# tell define_trace.h where to find the cros ec trace header +CFLAGS_cros_ec_trace.o:= -I$(src) + obj-$(CONFIG_CHROMEOS_LAPTOP) += chromeos_laptop.o obj-$(CONFIG_CHROMEOS_PSTORE) += chromeos_pstore.o obj-$(CONFIG_CHROMEOS_TBMC) += chromeos_tbmc.o +obj-$(CONFIG_CROS_EC) += cros_ec.o obj-$(CONFIG_CROS_EC_I2C) += cros_ec_i2c.o +obj-$(CONFIG_CROS_EC_ISHTP) += cros_ec_ishtp.o +obj-$(CONFIG_CROS_EC_RPMSG) += cros_ec_rpmsg.o obj-$(CONFIG_CROS_EC_SPI) += cros_ec_spi.o -cros_ec_lpcs-objs := cros_ec_lpc.o cros_ec_lpc_reg.o -cros_ec_lpcs-$(CONFIG_CROS_EC_LPC_MEC) += cros_ec_lpc_mec.o +cros_ec_lpcs-objs := cros_ec_lpc.o cros_ec_lpc_mec.o obj-$(CONFIG_CROS_EC_LPC) += cros_ec_lpcs.o -obj-$(CONFIG_CROS_EC_PROTO) += cros_ec_proto.o +obj-$(CONFIG_CROS_EC_PROTO) += cros_ec_proto.o cros_ec_trace.o obj-$(CONFIG_CROS_KBD_LED_BACKLIGHT) += cros_kbd_led_backlight.o +obj-$(CONFIG_CROS_EC_CHARDEV) += cros_ec_chardev.o obj-$(CONFIG_CROS_EC_LIGHTBAR) += cros_ec_lightbar.o obj-$(CONFIG_CROS_EC_VBC) += cros_ec_vbc.o obj-$(CONFIG_CROS_EC_DEBUGFS) += cros_ec_debugfs.o obj-$(CONFIG_CROS_EC_SYSFS) += cros_ec_sysfs.o +obj-$(CONFIG_CROS_USBPD_LOGGER) += cros_usbpd_logger.o obj-$(CONFIG_WILCO_EC) += wilco_ec/ diff --git a/drivers/platform/chrome/chromeos_laptop.c b/drivers/platform/chrome/chromeos_laptop.c index 24326eecd787..8723bcf10c93 100644 --- a/drivers/platform/chrome/chromeos_laptop.c +++ b/drivers/platform/chrome/chromeos_laptop.c @@ -125,7 +125,7 @@ static bool chromeos_laptop_match_adapter_devid(struct device *dev, u32 devid) return false; pdev = to_pci_dev(dev); - return devid == PCI_DEVID(pdev->bus->number, pdev->devfn); + return devid == pci_dev_id(pdev); } static void chromeos_laptop_check_adapter(struct i2c_adapter *adapter) @@ -838,18 +838,14 @@ static void chromeos_laptop_destroy(const struct chromeos_laptop *cros_laptop) i2c_dev = &cros_laptop->i2c_peripherals[i]; info = &i2c_dev->board_info; - if (i2c_dev->client) - i2c_unregister_device(i2c_dev->client); - - if (info->properties) - property_entries_free(info->properties); + i2c_unregister_device(i2c_dev->client); + property_entries_free(info->properties); } for (i = 0; i < cros_laptop->num_acpi_peripherals; i++) { acpi_dev = &cros_laptop->acpi_peripherals[i]; - if (acpi_dev->properties) - property_entries_free(acpi_dev->properties); + property_entries_free(acpi_dev->properties); } kfree(cros_laptop->i2c_peripherals); diff --git a/drivers/platform/chrome/chromeos_tbmc.c b/drivers/platform/chrome/chromeos_tbmc.c index ce259ec9f990..d1cf8f3463ce 100644 --- a/drivers/platform/chrome/chromeos_tbmc.c +++ b/drivers/platform/chrome/chromeos_tbmc.c @@ -47,6 +47,7 @@ static __maybe_unused int chromeos_tbmc_resume(struct device *dev) static void chromeos_tbmc_notify(struct acpi_device *adev, u32 event) { + acpi_pm_wakeup_event(&adev->dev); switch (event) { case 0x80: chromeos_tbmc_query_switch(adev, adev->driver_data); @@ -90,6 +91,7 @@ static int chromeos_tbmc_add(struct acpi_device *adev) dev_err(dev, "cannot register input device\n"); return ret; } + device_init_wakeup(dev, true); return 0; } diff --git a/drivers/platform/chrome/cros_ec.c b/drivers/platform/chrome/cros_ec.c new file mode 100644 index 000000000000..fd77e6fa74c2 --- /dev/null +++ b/drivers/platform/chrome/cros_ec.c @@ -0,0 +1,281 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ChromeOS EC multi-function device + * + * Copyright (C) 2012 Google, Inc + * + * The ChromeOS EC multi function device is used to mux all the requests + * to the EC device for its multiple features: keyboard controller, + * battery charging and regulator control, firmware update. + */ + +#include <linux/of_platform.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/platform_data/cros_ec_commands.h> +#include <linux/platform_data/cros_ec_proto.h> +#include <linux/suspend.h> +#include <asm/unaligned.h> + +#define CROS_EC_DEV_EC_INDEX 0 +#define CROS_EC_DEV_PD_INDEX 1 + +static struct cros_ec_platform ec_p = { + .ec_name = CROS_EC_DEV_NAME, + .cmd_offset = EC_CMD_PASSTHRU_OFFSET(CROS_EC_DEV_EC_INDEX), +}; + +static struct cros_ec_platform pd_p = { + .ec_name = CROS_EC_DEV_PD_NAME, + .cmd_offset = EC_CMD_PASSTHRU_OFFSET(CROS_EC_DEV_PD_INDEX), +}; + +static irqreturn_t ec_irq_thread(int irq, void *data) +{ + struct cros_ec_device *ec_dev = data; + bool wake_event = true; + int ret; + + ret = cros_ec_get_next_event(ec_dev, &wake_event); + + /* + * Signal only if wake host events or any interrupt if + * cros_ec_get_next_event() returned an error (default value for + * wake_event is true) + */ + if (wake_event && device_may_wakeup(ec_dev->dev)) + pm_wakeup_event(ec_dev->dev, 0); + + if (ret > 0) + blocking_notifier_call_chain(&ec_dev->event_notifier, + 0, ec_dev); + return IRQ_HANDLED; +} + +static int cros_ec_sleep_event(struct cros_ec_device *ec_dev, u8 sleep_event) +{ + int ret; + struct { + struct cros_ec_command msg; + union { + struct ec_params_host_sleep_event req0; + struct ec_params_host_sleep_event_v1 req1; + struct ec_response_host_sleep_event_v1 resp1; + } u; + } __packed buf; + + memset(&buf, 0, sizeof(buf)); + + if (ec_dev->host_sleep_v1) { + buf.u.req1.sleep_event = sleep_event; + buf.u.req1.suspend_params.sleep_timeout_ms = + EC_HOST_SLEEP_TIMEOUT_DEFAULT; + + buf.msg.outsize = sizeof(buf.u.req1); + if ((sleep_event == HOST_SLEEP_EVENT_S3_RESUME) || + (sleep_event == HOST_SLEEP_EVENT_S0IX_RESUME)) + buf.msg.insize = sizeof(buf.u.resp1); + + buf.msg.version = 1; + + } else { + buf.u.req0.sleep_event = sleep_event; + buf.msg.outsize = sizeof(buf.u.req0); + } + + buf.msg.command = EC_CMD_HOST_SLEEP_EVENT; + + ret = cros_ec_cmd_xfer(ec_dev, &buf.msg); + + /* For now, report failure to transition to S0ix with a warning. */ + if (ret >= 0 && ec_dev->host_sleep_v1 && + (sleep_event == HOST_SLEEP_EVENT_S0IX_RESUME)) { + ec_dev->last_resume_result = + buf.u.resp1.resume_response.sleep_transitions; + + WARN_ONCE(buf.u.resp1.resume_response.sleep_transitions & + EC_HOST_RESUME_SLEEP_TIMEOUT, + "EC detected sleep transition timeout. Total slp_s0 transitions: %d", + buf.u.resp1.resume_response.sleep_transitions & + EC_HOST_RESUME_SLEEP_TRANSITIONS_MASK); + } + + return ret; +} + +int cros_ec_register(struct cros_ec_device *ec_dev) +{ + struct device *dev = ec_dev->dev; + int err = 0; + + BLOCKING_INIT_NOTIFIER_HEAD(&ec_dev->event_notifier); + + ec_dev->max_request = sizeof(struct ec_params_hello); + ec_dev->max_response = sizeof(struct ec_response_get_protocol_info); + ec_dev->max_passthru = 0; + + ec_dev->din = devm_kzalloc(dev, ec_dev->din_size, GFP_KERNEL); + if (!ec_dev->din) + return -ENOMEM; + + ec_dev->dout = devm_kzalloc(dev, ec_dev->dout_size, GFP_KERNEL); + if (!ec_dev->dout) + return -ENOMEM; + + mutex_init(&ec_dev->lock); + + err = cros_ec_query_all(ec_dev); + if (err) { + dev_err(dev, "Cannot identify the EC: error %d\n", err); + return err; + } + + if (ec_dev->irq) { + err = devm_request_threaded_irq(dev, ec_dev->irq, NULL, + ec_irq_thread, IRQF_TRIGGER_LOW | IRQF_ONESHOT, + "chromeos-ec", ec_dev); + if (err) { + dev_err(dev, "Failed to request IRQ %d: %d", + ec_dev->irq, err); + return err; + } + } + + /* Register a platform device for the main EC instance */ + ec_dev->ec = platform_device_register_data(ec_dev->dev, "cros-ec-dev", + PLATFORM_DEVID_AUTO, &ec_p, + sizeof(struct cros_ec_platform)); + if (IS_ERR(ec_dev->ec)) { + dev_err(ec_dev->dev, + "Failed to create CrOS EC platform device\n"); + return PTR_ERR(ec_dev->ec); + } + + if (ec_dev->max_passthru) { + /* + * Register a platform device for the PD behind the main EC. + * We make the following assumptions: + * - behind an EC, we have a pd + * - only one device added. + * - the EC is responsive at init time (it is not true for a + * sensor hub). + */ + ec_dev->pd = platform_device_register_data(ec_dev->dev, + "cros-ec-dev", + PLATFORM_DEVID_AUTO, &pd_p, + sizeof(struct cros_ec_platform)); + if (IS_ERR(ec_dev->pd)) { + dev_err(ec_dev->dev, + "Failed to create CrOS PD platform device\n"); + platform_device_unregister(ec_dev->ec); + return PTR_ERR(ec_dev->pd); + } + } + + if (IS_ENABLED(CONFIG_OF) && dev->of_node) { + err = devm_of_platform_populate(dev); + if (err) { + platform_device_unregister(ec_dev->pd); + platform_device_unregister(ec_dev->ec); + dev_err(dev, "Failed to register sub-devices\n"); + return err; + } + } + + /* + * Clear sleep event - this will fail harmlessly on platforms that + * don't implement the sleep event host command. + */ + err = cros_ec_sleep_event(ec_dev, 0); + if (err < 0) + dev_dbg(ec_dev->dev, "Error %d clearing sleep event to ec", + err); + + dev_info(dev, "Chrome EC device registered\n"); + + return 0; +} +EXPORT_SYMBOL(cros_ec_register); + +int 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); + +#ifdef CONFIG_PM_SLEEP +int cros_ec_suspend(struct cros_ec_device *ec_dev) +{ + struct device *dev = ec_dev->dev; + int ret; + u8 sleep_event; + + sleep_event = (!IS_ENABLED(CONFIG_ACPI) || pm_suspend_via_firmware()) ? + HOST_SLEEP_EVENT_S3_SUSPEND : + HOST_SLEEP_EVENT_S0IX_SUSPEND; + + ret = cros_ec_sleep_event(ec_dev, sleep_event); + if (ret < 0) + dev_dbg(ec_dev->dev, "Error %d sending suspend event to ec", + ret); + + if (device_may_wakeup(dev)) + ec_dev->wake_enabled = !enable_irq_wake(ec_dev->irq); + + disable_irq(ec_dev->irq); + ec_dev->was_wake_device = ec_dev->wake_enabled; + ec_dev->suspended = true; + + return 0; +} +EXPORT_SYMBOL(cros_ec_suspend); + +static void cros_ec_report_events_during_suspend(struct cros_ec_device *ec_dev) +{ + while (ec_dev->mkbp_event_supported && + cros_ec_get_next_event(ec_dev, NULL) > 0) + blocking_notifier_call_chain(&ec_dev->event_notifier, + 1, ec_dev); +} + +int cros_ec_resume(struct cros_ec_device *ec_dev) +{ + int ret; + u8 sleep_event; + + ec_dev->suspended = false; + enable_irq(ec_dev->irq); + + sleep_event = (!IS_ENABLED(CONFIG_ACPI) || pm_suspend_via_firmware()) ? + HOST_SLEEP_EVENT_S3_RESUME : + HOST_SLEEP_EVENT_S0IX_RESUME; + + ret = cros_ec_sleep_event(ec_dev, sleep_event); + if (ret < 0) + dev_dbg(ec_dev->dev, "Error %d sending resume event to ec", + ret); + + if (ec_dev->wake_enabled) { + disable_irq_wake(ec_dev->irq); + ec_dev->wake_enabled = 0; + } + /* + * Let the mfd devices know about events that occur during + * suspend. This way the clients know what to do with them. + */ + cros_ec_report_events_during_suspend(ec_dev); + + + return 0; +} +EXPORT_SYMBOL(cros_ec_resume); + +#endif + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("ChromeOS EC core driver"); diff --git a/drivers/platform/chrome/cros_ec_chardev.c b/drivers/platform/chrome/cros_ec_chardev.c new file mode 100644 index 000000000000..74ded441bb50 --- /dev/null +++ b/drivers/platform/chrome/cros_ec_chardev.c @@ -0,0 +1,419 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Miscellaneous character driver for ChromeOS Embedded Controller + * + * Copyright 2014 Google, Inc. + * Copyright 2019 Google LLC + * + * This file is a rework and part of the code is ported from + * drivers/mfd/cros_ec_dev.c that was originally written by + * Bill Richardson. + */ + +#include <linux/init.h> +#include <linux/device.h> +#include <linux/fs.h> +#include <linux/mfd/cros_ec.h> +#include <linux/miscdevice.h> +#include <linux/module.h> +#include <linux/notifier.h> +#include <linux/platform_data/cros_ec_chardev.h> +#include <linux/platform_data/cros_ec_commands.h> +#include <linux/platform_data/cros_ec_proto.h> +#include <linux/platform_device.h> +#include <linux/poll.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <linux/uaccess.h> + +#define DRV_NAME "cros-ec-chardev" + +/* Arbitrary bounded size for the event queue */ +#define CROS_MAX_EVENT_LEN PAGE_SIZE + +struct chardev_data { + struct cros_ec_dev *ec_dev; + struct miscdevice misc; +}; + +struct chardev_priv { + struct cros_ec_dev *ec_dev; + struct notifier_block notifier; + wait_queue_head_t wait_event; + unsigned long event_mask; + struct list_head events; + size_t event_len; +}; + +struct ec_event { + struct list_head node; + size_t size; + u8 event_type; + u8 data[0]; +}; + +static int ec_get_version(struct cros_ec_dev *ec, char *str, int maxlen) +{ + static const char * const current_image_name[] = { + "unknown", "read-only", "read-write", "invalid", + }; + struct ec_response_get_version *resp; + struct cros_ec_command *msg; + int ret; + + msg = kzalloc(sizeof(*msg) + sizeof(*resp), GFP_KERNEL); + if (!msg) + return -ENOMEM; + + msg->command = EC_CMD_GET_VERSION + ec->cmd_offset; + msg->insize = sizeof(*resp); + + ret = cros_ec_cmd_xfer_status(ec->ec_dev, msg); + if (ret < 0) { + snprintf(str, maxlen, + "Unknown EC version, returned error: %d\n", + msg->result); + goto exit; + } + + resp = (struct ec_response_get_version *)msg->data; + if (resp->current_image >= ARRAY_SIZE(current_image_name)) + resp->current_image = 3; /* invalid */ + + snprintf(str, maxlen, "%s\n%s\n%s\n%s\n", CROS_EC_DEV_VERSION, + resp->version_string_ro, resp->version_string_rw, + current_image_name[resp->current_image]); + + ret = 0; +exit: + kfree(msg); + return ret; +} + +static int cros_ec_chardev_mkbp_event(struct notifier_block *nb, + unsigned long queued_during_suspend, + void *_notify) +{ + struct chardev_priv *priv = container_of(nb, struct chardev_priv, + notifier); + struct cros_ec_device *ec_dev = priv->ec_dev->ec_dev; + struct ec_event *event; + unsigned long event_bit = 1 << ec_dev->event_data.event_type; + int total_size = sizeof(*event) + ec_dev->event_size; + + if (!(event_bit & priv->event_mask) || + (priv->event_len + total_size) > CROS_MAX_EVENT_LEN) + return NOTIFY_DONE; + + event = kzalloc(total_size, GFP_KERNEL); + if (!event) + return NOTIFY_DONE; + + event->size = ec_dev->event_size; + event->event_type = ec_dev->event_data.event_type; + memcpy(event->data, &ec_dev->event_data.data, ec_dev->event_size); + + spin_lock(&priv->wait_event.lock); + list_add_tail(&event->node, &priv->events); + priv->event_len += total_size; + wake_up_locked(&priv->wait_event); + spin_unlock(&priv->wait_event.lock); + + return NOTIFY_OK; +} + +static struct ec_event *cros_ec_chardev_fetch_event(struct chardev_priv *priv, + bool fetch, bool block) +{ + struct ec_event *event; + int err; + + spin_lock(&priv->wait_event.lock); + if (!block && list_empty(&priv->events)) { + event = ERR_PTR(-EWOULDBLOCK); + goto out; + } + + if (!fetch) { + event = NULL; + goto out; + } + + err = wait_event_interruptible_locked(priv->wait_event, + !list_empty(&priv->events)); + if (err) { + event = ERR_PTR(err); + goto out; + } + + event = list_first_entry(&priv->events, struct ec_event, node); + list_del(&event->node); + priv->event_len -= sizeof(*event) + event->size; + +out: + spin_unlock(&priv->wait_event.lock); + return event; +} + +/* + * Device file ops + */ +static int cros_ec_chardev_open(struct inode *inode, struct file *filp) +{ + struct miscdevice *mdev = filp->private_data; + struct cros_ec_dev *ec_dev = dev_get_drvdata(mdev->parent); + struct chardev_priv *priv; + int ret; + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->ec_dev = ec_dev; + filp->private_data = priv; + INIT_LIST_HEAD(&priv->events); + init_waitqueue_head(&priv->wait_event); + nonseekable_open(inode, filp); + + priv->notifier.notifier_call = cros_ec_chardev_mkbp_event; + ret = blocking_notifier_chain_register(&ec_dev->ec_dev->event_notifier, + &priv->notifier); + if (ret) { + dev_err(ec_dev->dev, "failed to register event notifier\n"); + kfree(priv); + } + + return ret; +} + +static __poll_t cros_ec_chardev_poll(struct file *filp, poll_table *wait) +{ + struct chardev_priv *priv = filp->private_data; + + poll_wait(filp, &priv->wait_event, wait); + + if (list_empty(&priv->events)) + return 0; + + return EPOLLIN | EPOLLRDNORM; +} + +static ssize_t cros_ec_chardev_read(struct file *filp, char __user *buffer, + size_t length, loff_t *offset) +{ + char msg[sizeof(struct ec_response_get_version) + + sizeof(CROS_EC_DEV_VERSION)]; + struct chardev_priv *priv = filp->private_data; + struct cros_ec_dev *ec_dev = priv->ec_dev; + size_t count; + int ret; + + if (priv->event_mask) { /* queued MKBP event */ + struct ec_event *event; + + event = cros_ec_chardev_fetch_event(priv, length != 0, + !(filp->f_flags & O_NONBLOCK)); + if (IS_ERR(event)) + return PTR_ERR(event); + /* + * length == 0 is special - no IO is done but we check + * for error conditions. + */ + if (length == 0) + return 0; + + /* The event is 1 byte of type plus the payload */ + count = min(length, event->size + 1); + ret = copy_to_user(buffer, &event->event_type, count); + kfree(event); + if (ret) /* the copy failed */ + return -EFAULT; + *offset = count; + return count; + } + + /* + * Legacy behavior if no event mask is defined + */ + if (*offset != 0) + return 0; + + ret = ec_get_version(ec_dev, msg, sizeof(msg)); + if (ret) + return ret; + + count = min(length, strlen(msg)); + + if (copy_to_user(buffer, msg, count)) + return -EFAULT; + + *offset = count; + return count; +} + +static int cros_ec_chardev_release(struct inode *inode, struct file *filp) +{ + struct chardev_priv *priv = filp->private_data; + struct cros_ec_dev *ec_dev = priv->ec_dev; + struct ec_event *event, *e; + + blocking_notifier_chain_unregister(&ec_dev->ec_dev->event_notifier, + &priv->notifier); + + list_for_each_entry_safe(event, e, &priv->events, node) { + list_del(&event->node); + kfree(event); + } + kfree(priv); + + return 0; +} + +/* + * Ioctls + */ +static long cros_ec_chardev_ioctl_xcmd(struct cros_ec_dev *ec, void __user *arg) +{ + struct cros_ec_command *s_cmd; + struct cros_ec_command u_cmd; + long ret; + + if (copy_from_user(&u_cmd, arg, sizeof(u_cmd))) + return -EFAULT; + + if (u_cmd.outsize > EC_MAX_MSG_BYTES || + u_cmd.insize > EC_MAX_MSG_BYTES) + return -EINVAL; + + s_cmd = kmalloc(sizeof(*s_cmd) + max(u_cmd.outsize, u_cmd.insize), + GFP_KERNEL); + if (!s_cmd) + return -ENOMEM; + + if (copy_from_user(s_cmd, arg, sizeof(*s_cmd) + u_cmd.outsize)) { + ret = -EFAULT; + goto exit; + } + + if (u_cmd.outsize != s_cmd->outsize || + u_cmd.insize != s_cmd->insize) { + ret = -EINVAL; + goto exit; + } + + s_cmd->command += ec->cmd_offset; + ret = cros_ec_cmd_xfer(ec->ec_dev, s_cmd); + /* Only copy data to userland if data was received. */ + if (ret < 0) + goto exit; + + if (copy_to_user(arg, s_cmd, sizeof(*s_cmd) + s_cmd->insize)) + ret = -EFAULT; +exit: + kfree(s_cmd); + return ret; +} + +static long cros_ec_chardev_ioctl_readmem(struct cros_ec_dev *ec, + void __user *arg) +{ + struct cros_ec_device *ec_dev = ec->ec_dev; + struct cros_ec_readmem s_mem = { }; + long num; + + /* Not every platform supports direct reads */ + if (!ec_dev->cmd_readmem) + return -ENOTTY; + + if (copy_from_user(&s_mem, arg, sizeof(s_mem))) + return -EFAULT; + + num = ec_dev->cmd_readmem(ec_dev, s_mem.offset, s_mem.bytes, + s_mem.buffer); + if (num <= 0) + return num; + + if (copy_to_user((void __user *)arg, &s_mem, sizeof(s_mem))) + return -EFAULT; + + return num; +} + +static long cros_ec_chardev_ioctl(struct file *filp, unsigned int cmd, + unsigned long arg) +{ + struct chardev_priv *priv = filp->private_data; + struct cros_ec_dev *ec = priv->ec_dev; + + if (_IOC_TYPE(cmd) != CROS_EC_DEV_IOC) + return -ENOTTY; + + switch (cmd) { + case CROS_EC_DEV_IOCXCMD: + return cros_ec_chardev_ioctl_xcmd(ec, (void __user *)arg); + case CROS_EC_DEV_IOCRDMEM: + return cros_ec_chardev_ioctl_readmem(ec, (void __user *)arg); + case CROS_EC_DEV_IOCEVENTMASK: + priv->event_mask = arg; + return 0; + } + + return -ENOTTY; +} + +static const struct file_operations chardev_fops = { + .open = cros_ec_chardev_open, + .poll = cros_ec_chardev_poll, + .read = cros_ec_chardev_read, + .release = cros_ec_chardev_release, + .unlocked_ioctl = cros_ec_chardev_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = cros_ec_chardev_ioctl, +#endif +}; + +static int cros_ec_chardev_probe(struct platform_device *pdev) +{ + struct cros_ec_dev *ec_dev = dev_get_drvdata(pdev->dev.parent); + struct cros_ec_platform *ec_platform = dev_get_platdata(ec_dev->dev); + struct chardev_data *data; + + /* Create a char device: we want to create it anew */ + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->ec_dev = ec_dev; + data->misc.minor = MISC_DYNAMIC_MINOR; + data->misc.fops = &chardev_fops; + data->misc.name = ec_platform->ec_name; + data->misc.parent = pdev->dev.parent; + + dev_set_drvdata(&pdev->dev, data); + + return misc_register(&data->misc); +} + +static int cros_ec_chardev_remove(struct platform_device *pdev) +{ + struct chardev_data *data = dev_get_drvdata(&pdev->dev); + + misc_deregister(&data->misc); + + return 0; +} + +static struct platform_driver cros_ec_chardev_driver = { + .driver = { + .name = DRV_NAME, + }, + .probe = cros_ec_chardev_probe, + .remove = cros_ec_chardev_remove, +}; + +module_platform_driver(cros_ec_chardev_driver); + +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_AUTHOR("Enric Balletbo i Serra <enric.balletbo@collabora.com>"); +MODULE_DESCRIPTION("ChromeOS EC Miscellaneous Character Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/chrome/cros_ec_debugfs.c b/drivers/platform/chrome/cros_ec_debugfs.c index 71308766e891..6ae484989d1f 100644 --- a/drivers/platform/chrome/cros_ec_debugfs.c +++ b/drivers/platform/chrome/cros_ec_debugfs.c @@ -8,9 +8,10 @@ #include <linux/delay.h> #include <linux/fs.h> #include <linux/mfd/cros_ec.h> -#include <linux/mfd/cros_ec_commands.h> #include <linux/module.h> #include <linux/mutex.h> +#include <linux/platform_data/cros_ec_commands.h> +#include <linux/platform_data/cros_ec_proto.h> #include <linux/platform_device.h> #include <linux/poll.h> #include <linux/sched.h> @@ -25,7 +26,8 @@ #define CIRC_ADD(idx, size, value) (((idx) + (value)) & ((size) - 1)) -/* struct cros_ec_debugfs - ChromeOS EC debugging information +/** + * struct cros_ec_debugfs - EC debugging information. * * @ec: EC device this debugfs information belongs to * @dir: dentry for debugfs files @@ -72,15 +74,9 @@ static void cros_ec_console_log_work(struct work_struct *__work) int buf_space; int ret; - ret = cros_ec_cmd_xfer(ec->ec_dev, &snapshot_msg); - if (ret < 0) { - dev_err(ec->dev, "EC communication failed\n"); - goto resched; - } - if (snapshot_msg.result != EC_RES_SUCCESS) { - dev_err(ec->dev, "EC failed to snapshot the console log\n"); + ret = cros_ec_cmd_xfer_status(ec->ec_dev, &snapshot_msg); + if (ret < 0) goto resched; - } /* Loop until we have read everything, or there's an error. */ mutex_lock(&debug_info->log_mutex); @@ -95,16 +91,10 @@ static void cros_ec_console_log_work(struct work_struct *__work) memset(read_params, '\0', sizeof(*read_params)); read_params->subcmd = CONSOLE_READ_RECENT; - ret = cros_ec_cmd_xfer(ec->ec_dev, debug_info->read_msg); - if (ret < 0) { - dev_err(ec->dev, "EC communication failed\n"); - break; - } - if (debug_info->read_msg->result != EC_RES_SUCCESS) { - dev_err(ec->dev, - "EC failed to read the console log\n"); + ret = cros_ec_cmd_xfer_status(ec->ec_dev, + debug_info->read_msg); + if (ret < 0) break; - } /* If the buffer is empty, we're done here. */ if (ret == 0 || ec_buffer[0] == '\0') @@ -132,7 +122,7 @@ static int cros_ec_console_log_open(struct inode *inode, struct file *file) { file->private_data = inode->i_private; - return nonseekable_open(inode, file); + return stream_open(inode, file); } static ssize_t cros_ec_console_log_read(struct file *file, char __user *buf, @@ -253,7 +243,35 @@ static ssize_t cros_ec_pdinfo_read(struct file *file, read_buf, p - read_buf); } -const struct file_operations cros_ec_console_log_fops = { +static ssize_t cros_ec_uptime_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct cros_ec_debugfs *debug_info = file->private_data; + struct cros_ec_device *ec_dev = debug_info->ec->ec_dev; + struct { + struct cros_ec_command cmd; + struct ec_response_uptime_info resp; + } __packed msg = {}; + struct ec_response_uptime_info *resp; + char read_buf[32]; + int ret; + + resp = (struct ec_response_uptime_info *)&msg.resp; + + msg.cmd.command = EC_CMD_GET_UPTIME_INFO; + msg.cmd.insize = sizeof(*resp); + + ret = cros_ec_cmd_xfer_status(ec_dev, &msg.cmd); + if (ret < 0) + return ret; + + ret = scnprintf(read_buf, sizeof(read_buf), "%u\n", + resp->time_since_ec_boot_ms); + + return simple_read_from_buffer(user_buf, count, ppos, read_buf, ret); +} + +static const struct file_operations cros_ec_console_log_fops = { .owner = THIS_MODULE, .open = cros_ec_console_log_open, .read = cros_ec_console_log_read, @@ -262,13 +280,20 @@ const struct file_operations cros_ec_console_log_fops = { .release = cros_ec_console_log_release, }; -const struct file_operations cros_ec_pdinfo_fops = { +static const struct file_operations cros_ec_pdinfo_fops = { .owner = THIS_MODULE, .open = simple_open, .read = cros_ec_pdinfo_read, .llseek = default_llseek, }; +static const struct file_operations cros_ec_uptime_fops = { + .owner = THIS_MODULE, + .open = simple_open, + .read = cros_ec_uptime_read, + .llseek = default_llseek, +}; + static int ec_read_version_supported(struct cros_ec_dev *ec) { struct ec_params_get_cmd_versions_v1 *params; @@ -290,9 +315,8 @@ static int ec_read_version_supported(struct cros_ec_dev *ec) params->cmd = EC_CMD_CONSOLE_READ; response = (struct ec_response_get_cmd_versions *)msg->data; - ret = cros_ec_cmd_xfer(ec->ec_dev, msg) >= 0 && - msg->result == EC_RES_SUCCESS && - (response->version_mask & EC_VER_MASK(1)); + ret = cros_ec_cmd_xfer_status(ec->ec_dev, msg) >= 0 && + response->version_mask & EC_VER_MASK(1); kfree(msg); @@ -306,11 +330,12 @@ static int cros_ec_create_console_log(struct cros_ec_debugfs *debug_info) int read_params_size; int read_response_size; - if (!ec_read_version_supported(ec)) { - dev_warn(ec->dev, - "device does not support reading the console log\n"); + /* + * If the console log feature is not supported return silently and + * don't create the console_log entry. + */ + if (!ec_read_version_supported(ec)) return 0; - } buf = devm_kzalloc(ec->dev, LOG_SIZE, GFP_KERNEL); if (!buf) @@ -336,12 +361,8 @@ static int cros_ec_create_console_log(struct cros_ec_debugfs *debug_info) mutex_init(&debug_info->log_mutex); init_waitqueue_head(&debug_info->log_wq); - if (!debugfs_create_file("console_log", - S_IFREG | 0444, - debug_info->dir, - debug_info, - &cros_ec_console_log_fops)) - return -ENOMEM; + debugfs_create_file("console_log", S_IFREG | 0444, debug_info->dir, + debug_info, &cros_ec_console_log_fops); INIT_DELAYED_WORK(&debug_info->log_poll_work, cros_ec_console_log_work); @@ -375,9 +396,8 @@ static int cros_ec_create_panicinfo(struct cros_ec_debugfs *debug_info) msg->command = EC_CMD_GET_PANIC_INFO; msg->insize = insize; - ret = cros_ec_cmd_xfer(ec_dev, msg); + ret = cros_ec_cmd_xfer_status(ec_dev, msg); if (ret < 0) { - dev_warn(debug_info->ec->dev, "Cannot read panicinfo.\n"); ret = 0; goto free; } @@ -389,13 +409,8 @@ static int cros_ec_create_panicinfo(struct cros_ec_debugfs *debug_info) debug_info->panicinfo_blob.data = msg->data; debug_info->panicinfo_blob.size = ret; - if (!debugfs_create_blob("panicinfo", - S_IFREG | 0444, - debug_info->dir, - &debug_info->panicinfo_blob)) { - ret = -ENOMEM; - goto free; - } + debugfs_create_blob("panicinfo", S_IFREG | 0444, debug_info->dir, + &debug_info->panicinfo_blob); return 0; @@ -404,15 +419,6 @@ free: return ret; } -static int cros_ec_create_pdinfo(struct cros_ec_debugfs *debug_info) -{ - if (!debugfs_create_file("pdinfo", 0444, debug_info->dir, debug_info, - &cros_ec_pdinfo_fops)) - return -ENOMEM; - - return 0; -} - static int cros_ec_debugfs_probe(struct platform_device *pd) { struct cros_ec_dev *ec = dev_get_drvdata(pd->dev.parent); @@ -427,8 +433,6 @@ static int cros_ec_debugfs_probe(struct platform_device *pd) debug_info->ec = ec; debug_info->dir = debugfs_create_dir(name, NULL); - if (!debug_info->dir) - return -ENOMEM; ret = cros_ec_create_panicinfo(debug_info); if (ret) @@ -438,9 +442,14 @@ static int cros_ec_debugfs_probe(struct platform_device *pd) if (ret) goto remove_debugfs; - ret = cros_ec_create_pdinfo(debug_info); - if (ret) - goto remove_log; + debugfs_create_file("pdinfo", 0444, debug_info->dir, debug_info, + &cros_ec_pdinfo_fops); + + debugfs_create_file("uptime", 0444, debug_info->dir, debug_info, + &cros_ec_uptime_fops); + + debugfs_create_x32("last_resume_result", 0444, debug_info->dir, + &ec->ec_dev->last_resume_result); ec->debug_info = debug_info; @@ -448,8 +457,6 @@ static int cros_ec_debugfs_probe(struct platform_device *pd) return 0; -remove_log: - cros_ec_cleanup_console_log(debug_info); remove_debugfs: debugfs_remove_recursive(debug_info->dir); return ret; diff --git a/drivers/platform/chrome/cros_ec_i2c.c b/drivers/platform/chrome/cros_ec_i2c.c index 61d75395f86d..9bd97bc8454b 100644 --- a/drivers/platform/chrome/cros_ec_i2c.c +++ b/drivers/platform/chrome/cros_ec_i2c.c @@ -9,8 +9,8 @@ #include <linux/module.h> #include <linux/i2c.h> #include <linux/interrupt.h> -#include <linux/mfd/cros_ec.h> -#include <linux/mfd/cros_ec_commands.h> +#include <linux/platform_data/cros_ec_commands.h> +#include <linux/platform_data/cros_ec_proto.h> #include <linux/platform_device.h> #include <linux/slab.h> @@ -307,6 +307,13 @@ static int cros_ec_i2c_probe(struct i2c_client *client, return 0; } +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); +} + #ifdef CONFIG_PM_SLEEP static int cros_ec_i2c_suspend(struct device *dev) { @@ -357,6 +364,7 @@ static struct i2c_driver cros_ec_driver = { .pm = &cros_ec_i2c_pm_ops, }, .probe = cros_ec_i2c_probe, + .remove = cros_ec_i2c_remove, .id_table = cros_ec_i2c_id, }; diff --git a/drivers/platform/chrome/cros_ec_ishtp.c b/drivers/platform/chrome/cros_ec_ishtp.c new file mode 100644 index 000000000000..25ca2c894b4d --- /dev/null +++ b/drivers/platform/chrome/cros_ec_ishtp.c @@ -0,0 +1,762 @@ +// SPDX-License-Identifier: GPL-2.0 +// ISHTP interface for ChromeOS Embedded Controller +// +// Copyright (c) 2019, Intel Corporation. +// +// ISHTP client driver for talking to the Chrome OS EC firmware running +// on Intel Integrated Sensor Hub (ISH) using the ISH Transport protocol +// (ISH-TP). + +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/platform_data/cros_ec_commands.h> +#include <linux/platform_data/cros_ec_proto.h> +#include <linux/intel-ish-client-if.h> + +/* + * ISH TX/RX ring buffer pool size + * + * The AP->ISH messages and corresponding ISH->AP responses are + * serialized. We need 1 TX and 1 RX buffer for these. + * + * The MKBP ISH->AP events are serialized. We need one additional RX + * buffer for them. + */ +#define CROS_ISH_CL_TX_RING_SIZE 8 +#define CROS_ISH_CL_RX_RING_SIZE 8 + +/* ISH CrOS EC Host Commands */ +enum cros_ec_ish_channel { + CROS_EC_COMMAND = 1, /* AP->ISH message */ + CROS_MKBP_EVENT = 2, /* ISH->AP events */ +}; + +/* + * ISH firmware timeout for 1 message send failure is 1Hz, and the + * firmware will retry 2 times, so 3Hz is used for timeout. + */ +#define ISHTP_SEND_TIMEOUT (3 * HZ) + +/* ISH Transport CrOS EC ISH client unique GUID */ +static const guid_t cros_ish_guid = + GUID_INIT(0x7b7154d0, 0x56f4, 0x4bdc, + 0xb0, 0xd8, 0x9e, 0x7c, 0xda, 0xe0, 0xd6, 0xa0); + +struct header { + u8 channel; + u8 status; + u8 reserved[2]; +} __packed; + +struct cros_ish_out_msg { + struct header hdr; + struct ec_host_request ec_request; +} __packed; + +struct cros_ish_in_msg { + struct header hdr; + struct ec_host_response ec_response; +} __packed; + +#define IN_MSG_EC_RESPONSE_PREAMBLE \ + offsetof(struct cros_ish_in_msg, ec_response) + +#define OUT_MSG_EC_REQUEST_PREAMBLE \ + offsetof(struct cros_ish_out_msg, ec_request) + +#define cl_data_to_dev(client_data) ishtp_device((client_data)->cl_device) + +/* + * The Read-Write Semaphore is used to prevent message TX or RX while + * the ishtp client is being initialized or undergoing reset. + * + * The readers are the kernel function calls responsible for IA->ISH + * and ISH->AP messaging. + * + * The writers are .reset() and .probe() function. + */ +DECLARE_RWSEM(init_lock); + +/** + * struct response_info - Encapsulate firmware response related + * information for passing between function ish_send() and + * process_recv() callback. + * + * @data: Copy the data received from firmware here. + * @max_size: Max size allocated for the @data buffer. If the received + * data exceeds this value, we log an error. + * @size: Actual size of data received from firmware. + * @error: 0 for success, negative error code for a failure in process_recv(). + * @received: Set to true on receiving a valid firmware response to host command + * @wait_queue: Wait queue for host to wait for firmware response. + */ +struct response_info { + void *data; + size_t max_size; + size_t size; + int error; + bool received; + wait_queue_head_t wait_queue; +}; + +/** + * struct ishtp_cl_data - Encapsulate per ISH TP Client. + * + * @cros_ish_cl: ISHTP firmware client instance. + * @cl_device: ISHTP client device instance. + * @response: Response info passing between ish_send() and process_recv(). + * @work_ishtp_reset: Work queue reset handling. + * @work_ec_evt: Work queue for EC events. + * @ec_dev: CrOS EC MFD device. + * + * This structure is used to store per client data. + */ +struct ishtp_cl_data { + struct ishtp_cl *cros_ish_cl; + struct ishtp_cl_device *cl_device; + + /* + * Used for passing firmware response information between + * ish_send() and process_recv() callback. + */ + struct response_info response; + + struct work_struct work_ishtp_reset; + struct work_struct work_ec_evt; + struct cros_ec_device *ec_dev; +}; + +/** + * ish_evt_handler - ISH to AP event handler + * @work: Work struct + */ +static void ish_evt_handler(struct work_struct *work) +{ + struct ishtp_cl_data *client_data = + container_of(work, struct ishtp_cl_data, work_ec_evt); + struct cros_ec_device *ec_dev = client_data->ec_dev; + + if (cros_ec_get_next_event(ec_dev, NULL) > 0) { + blocking_notifier_call_chain(&ec_dev->event_notifier, + 0, ec_dev); + } +} + +/** + * ish_send() - Send message from host to firmware + * + * @client_data: Client data instance + * @out_msg: Message buffer to be sent to firmware + * @out_size: Size of out going message + * @in_msg: Message buffer where the incoming data is copied. This buffer + * is allocated by calling + * @in_size: Max size of incoming message + * + * Return: Number of bytes copied in the in_msg on success, negative + * error code on failure. + */ +static int ish_send(struct ishtp_cl_data *client_data, + u8 *out_msg, size_t out_size, + u8 *in_msg, size_t in_size) +{ + int rv; + struct header *out_hdr = (struct header *)out_msg; + struct ishtp_cl *cros_ish_cl = client_data->cros_ish_cl; + + dev_dbg(cl_data_to_dev(client_data), + "%s: channel=%02u status=%02u\n", + __func__, out_hdr->channel, out_hdr->status); + + /* Setup for incoming response */ + client_data->response.data = in_msg; + client_data->response.max_size = in_size; + client_data->response.error = 0; + client_data->response.received = false; + + rv = ishtp_cl_send(cros_ish_cl, out_msg, out_size); + if (rv) { + dev_err(cl_data_to_dev(client_data), + "ishtp_cl_send error %d\n", rv); + return rv; + } + + wait_event_interruptible_timeout(client_data->response.wait_queue, + client_data->response.received, + ISHTP_SEND_TIMEOUT); + if (!client_data->response.received) { + dev_err(cl_data_to_dev(client_data), + "Timed out for response to host message\n"); + return -ETIMEDOUT; + } + + if (client_data->response.error < 0) + return client_data->response.error; + + return client_data->response.size; +} + +/** + * process_recv() - Received and parse incoming packet + * @cros_ish_cl: Client instance to get stats + * @rb_in_proc: Host interface message buffer + * + * Parse the incoming packet. If it is a response packet then it will + * update per instance flags and wake up the caller waiting to for the + * response. If it is an event packet then it will schedule event work. + */ +static void process_recv(struct ishtp_cl *cros_ish_cl, + struct ishtp_cl_rb *rb_in_proc) +{ + size_t data_len = rb_in_proc->buf_idx; + struct ishtp_cl_data *client_data = + ishtp_get_client_data(cros_ish_cl); + struct device *dev = cl_data_to_dev(client_data); + struct cros_ish_in_msg *in_msg = + (struct cros_ish_in_msg *)rb_in_proc->buffer.data; + + /* Proceed only if reset or init is not in progress */ + if (!down_read_trylock(&init_lock)) { + /* Free the buffer */ + ishtp_cl_io_rb_recycle(rb_in_proc); + dev_warn(dev, + "Host is not ready to receive incoming messages\n"); + return; + } + + /* + * All firmware messages contain a header. Check the buffer size + * before accessing elements inside. + */ + if (!rb_in_proc->buffer.data) { + dev_warn(dev, "rb_in_proc->buffer.data returned null"); + client_data->response.error = -EBADMSG; + goto end_error; + } + + if (data_len < sizeof(struct header)) { + dev_err(dev, "data size %zu is less than header %zu\n", + data_len, sizeof(struct header)); + client_data->response.error = -EMSGSIZE; + goto end_error; + } + + dev_dbg(dev, "channel=%02u status=%02u\n", + in_msg->hdr.channel, in_msg->hdr.status); + + switch (in_msg->hdr.channel) { + case CROS_EC_COMMAND: + /* Sanity check */ + if (!client_data->response.data) { + dev_err(dev, + "Receiving buffer is null. Should be allocated by calling function\n"); + client_data->response.error = -EINVAL; + goto error_wake_up; + } + + if (client_data->response.received) { + dev_err(dev, + "Previous firmware message not yet processed\n"); + client_data->response.error = -EINVAL; + goto error_wake_up; + } + + if (data_len > client_data->response.max_size) { + dev_err(dev, + "Received buffer size %zu is larger than allocated buffer %zu\n", + data_len, client_data->response.max_size); + client_data->response.error = -EMSGSIZE; + goto error_wake_up; + } + + if (in_msg->hdr.status) { + dev_err(dev, "firmware returned status %d\n", + in_msg->hdr.status); + client_data->response.error = -EIO; + goto error_wake_up; + } + + /* Update the actual received buffer size */ + client_data->response.size = data_len; + + /* + * Copy the buffer received in firmware response for the + * calling thread. + */ + memcpy(client_data->response.data, + rb_in_proc->buffer.data, data_len); + + /* Set flag before waking up the caller */ + client_data->response.received = true; +error_wake_up: + /* Wake the calling thread */ + wake_up_interruptible(&client_data->response.wait_queue); + + break; + + case CROS_MKBP_EVENT: + /* The event system doesn't send any data in buffer */ + schedule_work(&client_data->work_ec_evt); + + break; + + default: + dev_err(dev, "Invalid channel=%02d\n", in_msg->hdr.channel); + } + +end_error: + /* Free the buffer */ + ishtp_cl_io_rb_recycle(rb_in_proc); + + up_read(&init_lock); +} + +/** + * ish_event_cb() - bus driver callback for incoming message + * @cl_device: ISHTP client device for which this message is targeted. + * + * Remove the packet from the list and process the message by calling + * process_recv. + */ +static void ish_event_cb(struct ishtp_cl_device *cl_device) +{ + struct ishtp_cl_rb *rb_in_proc; + struct ishtp_cl *cros_ish_cl = ishtp_get_drvdata(cl_device); + + while ((rb_in_proc = ishtp_cl_rx_get_rb(cros_ish_cl)) != NULL) { + /* Decide what to do with received data */ + process_recv(cros_ish_cl, rb_in_proc); + } +} + +/** + * cros_ish_init() - Init function for ISHTP client + * @cros_ish_cl: ISHTP client instance + * + * This function complete the initializtion of the client. + * + * Return: 0 for success, negative error code for failure. + */ +static int cros_ish_init(struct ishtp_cl *cros_ish_cl) +{ + int rv; + struct ishtp_device *dev; + struct ishtp_fw_client *fw_client; + struct ishtp_cl_data *client_data = ishtp_get_client_data(cros_ish_cl); + + rv = ishtp_cl_link(cros_ish_cl); + if (rv) { + dev_err(cl_data_to_dev(client_data), + "ishtp_cl_link failed\n"); + return rv; + } + + dev = ishtp_get_ishtp_device(cros_ish_cl); + + /* Connect to firmware client */ + ishtp_set_tx_ring_size(cros_ish_cl, CROS_ISH_CL_TX_RING_SIZE); + ishtp_set_rx_ring_size(cros_ish_cl, CROS_ISH_CL_RX_RING_SIZE); + + fw_client = ishtp_fw_cl_get_client(dev, &cros_ish_guid); + if (!fw_client) { + dev_err(cl_data_to_dev(client_data), + "ish client uuid not found\n"); + rv = -ENOENT; + goto err_cl_unlink; + } + + ishtp_cl_set_fw_client_id(cros_ish_cl, + ishtp_get_fw_client_id(fw_client)); + ishtp_set_connection_state(cros_ish_cl, ISHTP_CL_CONNECTING); + + rv = ishtp_cl_connect(cros_ish_cl); + if (rv) { + dev_err(cl_data_to_dev(client_data), + "client connect fail\n"); + goto err_cl_unlink; + } + + ishtp_register_event_cb(client_data->cl_device, ish_event_cb); + return 0; + +err_cl_unlink: + ishtp_cl_unlink(cros_ish_cl); + return rv; +} + +/** + * cros_ish_deinit() - Deinit function for ISHTP client + * @cros_ish_cl: ISHTP client instance + * + * Unlink and free cros_ec client + */ +static void cros_ish_deinit(struct ishtp_cl *cros_ish_cl) +{ + ishtp_set_connection_state(cros_ish_cl, ISHTP_CL_DISCONNECTING); + ishtp_cl_disconnect(cros_ish_cl); + ishtp_cl_unlink(cros_ish_cl); + ishtp_cl_flush_queues(cros_ish_cl); + + /* Disband and free all Tx and Rx client-level rings */ + ishtp_cl_free(cros_ish_cl); +} + +/** + * prepare_cros_ec_rx() - Check & prepare receive buffer + * @ec_dev: CrOS EC MFD device. + * @in_msg: Incoming message buffer + * @msg: cros_ec command used to send & receive data + * + * Return: 0 for success, negative error code for failure. + * + * Check the received buffer. Convert to cros_ec_command format. + */ +static int prepare_cros_ec_rx(struct cros_ec_device *ec_dev, + const struct cros_ish_in_msg *in_msg, + struct cros_ec_command *msg) +{ + u8 sum = 0; + int i, rv, offset; + + /* Check response error code */ + msg->result = in_msg->ec_response.result; + rv = cros_ec_check_result(ec_dev, msg); + if (rv < 0) + return rv; + + if (in_msg->ec_response.data_len > msg->insize) { + dev_err(ec_dev->dev, "Packet too long (%d bytes, expected %d)", + in_msg->ec_response.data_len, msg->insize); + return -ENOSPC; + } + + /* Copy response packet payload and compute checksum */ + for (i = 0; i < sizeof(struct ec_host_response); i++) + sum += ((u8 *)in_msg)[IN_MSG_EC_RESPONSE_PREAMBLE + i]; + + offset = sizeof(struct cros_ish_in_msg); + for (i = 0; i < in_msg->ec_response.data_len; i++) + sum += msg->data[i] = ((u8 *)in_msg)[offset + i]; + + if (sum) { + dev_dbg(ec_dev->dev, "Bad received packet checksum %d\n", sum); + return -EBADMSG; + } + + return 0; +} + +static int cros_ec_pkt_xfer_ish(struct cros_ec_device *ec_dev, + struct cros_ec_command *msg) +{ + int rv; + struct ishtp_cl *cros_ish_cl = ec_dev->priv; + struct ishtp_cl_data *client_data = ishtp_get_client_data(cros_ish_cl); + struct device *dev = cl_data_to_dev(client_data); + struct cros_ish_in_msg *in_msg = (struct cros_ish_in_msg *)ec_dev->din; + struct cros_ish_out_msg *out_msg = + (struct cros_ish_out_msg *)ec_dev->dout; + size_t in_size = sizeof(struct cros_ish_in_msg) + msg->insize; + size_t out_size = sizeof(struct cros_ish_out_msg) + msg->outsize; + + /* Sanity checks */ + if (in_size > ec_dev->din_size) { + dev_err(dev, + "Incoming payload size %zu is too large for ec_dev->din_size %d\n", + in_size, ec_dev->din_size); + return -EMSGSIZE; + } + + if (out_size > ec_dev->dout_size) { + dev_err(dev, + "Outgoing payload size %zu is too large for ec_dev->dout_size %d\n", + out_size, ec_dev->dout_size); + return -EMSGSIZE; + } + + /* Proceed only if reset-init is not in progress */ + if (!down_read_trylock(&init_lock)) { + dev_warn(dev, + "Host is not ready to send messages to ISH. Try again\n"); + return -EAGAIN; + } + + /* Prepare the package to be sent over ISH TP */ + out_msg->hdr.channel = CROS_EC_COMMAND; + out_msg->hdr.status = 0; + + ec_dev->dout += OUT_MSG_EC_REQUEST_PREAMBLE; + cros_ec_prepare_tx(ec_dev, msg); + ec_dev->dout -= OUT_MSG_EC_REQUEST_PREAMBLE; + + dev_dbg(dev, + "out_msg: struct_ver=0x%x checksum=0x%x command=0x%x command_ver=0x%x data_len=0x%x\n", + out_msg->ec_request.struct_version, + out_msg->ec_request.checksum, + out_msg->ec_request.command, + out_msg->ec_request.command_version, + out_msg->ec_request.data_len); + + /* Send command to ISH EC firmware and read response */ + rv = ish_send(client_data, + (u8 *)out_msg, out_size, + (u8 *)in_msg, in_size); + if (rv < 0) + goto end_error; + + rv = prepare_cros_ec_rx(ec_dev, in_msg, msg); + if (rv) + goto end_error; + + rv = in_msg->ec_response.data_len; + + dev_dbg(dev, + "in_msg: struct_ver=0x%x checksum=0x%x result=0x%x data_len=0x%x\n", + in_msg->ec_response.struct_version, + in_msg->ec_response.checksum, + in_msg->ec_response.result, + in_msg->ec_response.data_len); + +end_error: + if (msg->command == EC_CMD_REBOOT_EC) + msleep(EC_REBOOT_DELAY_MS); + + up_read(&init_lock); + + return rv; +} + +static int cros_ec_dev_init(struct ishtp_cl_data *client_data) +{ + struct cros_ec_device *ec_dev; + struct device *dev = cl_data_to_dev(client_data); + + ec_dev = devm_kzalloc(dev, sizeof(*ec_dev), GFP_KERNEL); + if (!ec_dev) + return -ENOMEM; + + client_data->ec_dev = ec_dev; + dev->driver_data = ec_dev; + + ec_dev->dev = dev; + ec_dev->priv = client_data->cros_ish_cl; + ec_dev->cmd_xfer = NULL; + ec_dev->pkt_xfer = cros_ec_pkt_xfer_ish; + ec_dev->phys_name = dev_name(dev); + ec_dev->din_size = sizeof(struct cros_ish_in_msg) + + sizeof(struct ec_response_get_protocol_info); + ec_dev->dout_size = sizeof(struct cros_ish_out_msg); + + return cros_ec_register(ec_dev); +} + +static void reset_handler(struct work_struct *work) +{ + int rv; + struct device *dev; + struct ishtp_cl *cros_ish_cl; + struct ishtp_cl_device *cl_device; + struct ishtp_cl_data *client_data = + container_of(work, struct ishtp_cl_data, work_ishtp_reset); + + /* Lock for reset to complete */ + down_write(&init_lock); + + cros_ish_cl = client_data->cros_ish_cl; + cl_device = client_data->cl_device; + + /* Unlink, flush queues & start again */ + ishtp_cl_unlink(cros_ish_cl); + ishtp_cl_flush_queues(cros_ish_cl); + ishtp_cl_free(cros_ish_cl); + + cros_ish_cl = ishtp_cl_allocate(cl_device); + if (!cros_ish_cl) { + up_write(&init_lock); + return; + } + + ishtp_set_drvdata(cl_device, cros_ish_cl); + ishtp_set_client_data(cros_ish_cl, client_data); + client_data->cros_ish_cl = cros_ish_cl; + + rv = cros_ish_init(cros_ish_cl); + if (rv) { + ishtp_cl_free(cros_ish_cl); + dev_err(cl_data_to_dev(client_data), "Reset Failed\n"); + up_write(&init_lock); + return; + } + + /* Refresh ec_dev device pointers */ + client_data->ec_dev->priv = client_data->cros_ish_cl; + dev = cl_data_to_dev(client_data); + dev->driver_data = client_data->ec_dev; + + dev_info(cl_data_to_dev(client_data), "Chrome EC ISH reset done\n"); + + up_write(&init_lock); +} + +/** + * cros_ec_ishtp_probe() - ISHTP client driver probe callback + * @cl_device: ISHTP client device instance + * + * Return: 0 for success, negative error code for failure. + */ +static int cros_ec_ishtp_probe(struct ishtp_cl_device *cl_device) +{ + int rv; + struct ishtp_cl *cros_ish_cl; + struct ishtp_cl_data *client_data = + devm_kzalloc(ishtp_device(cl_device), + sizeof(*client_data), GFP_KERNEL); + if (!client_data) + return -ENOMEM; + + /* Lock for initialization to complete */ + down_write(&init_lock); + + cros_ish_cl = ishtp_cl_allocate(cl_device); + if (!cros_ish_cl) { + rv = -ENOMEM; + goto end_ishtp_cl_alloc_error; + } + + ishtp_set_drvdata(cl_device, cros_ish_cl); + ishtp_set_client_data(cros_ish_cl, client_data); + client_data->cros_ish_cl = cros_ish_cl; + client_data->cl_device = cl_device; + + init_waitqueue_head(&client_data->response.wait_queue); + + INIT_WORK(&client_data->work_ishtp_reset, + reset_handler); + INIT_WORK(&client_data->work_ec_evt, + ish_evt_handler); + + rv = cros_ish_init(cros_ish_cl); + if (rv) + goto end_ishtp_cl_init_error; + + ishtp_get_device(cl_device); + + up_write(&init_lock); + + /* Register croc_ec_dev mfd */ + rv = cros_ec_dev_init(client_data); + if (rv) + goto end_cros_ec_dev_init_error; + + return 0; + +end_cros_ec_dev_init_error: + ishtp_set_connection_state(cros_ish_cl, ISHTP_CL_DISCONNECTING); + ishtp_cl_disconnect(cros_ish_cl); + ishtp_cl_unlink(cros_ish_cl); + ishtp_cl_flush_queues(cros_ish_cl); + ishtp_put_device(cl_device); +end_ishtp_cl_init_error: + ishtp_cl_free(cros_ish_cl); +end_ishtp_cl_alloc_error: + up_write(&init_lock); + return rv; +} + +/** + * cros_ec_ishtp_remove() - ISHTP client driver remove callback + * @cl_device: ISHTP client device instance + * + * Return: 0 + */ +static int cros_ec_ishtp_remove(struct ishtp_cl_device *cl_device) +{ + struct ishtp_cl *cros_ish_cl = ishtp_get_drvdata(cl_device); + struct ishtp_cl_data *client_data = ishtp_get_client_data(cros_ish_cl); + + cancel_work_sync(&client_data->work_ishtp_reset); + cancel_work_sync(&client_data->work_ec_evt); + cros_ish_deinit(cros_ish_cl); + ishtp_put_device(cl_device); + + return 0; +} + +/** + * cros_ec_ishtp_reset() - ISHTP client driver reset callback + * @cl_device: ISHTP client device instance + * + * Return: 0 + */ +static int cros_ec_ishtp_reset(struct ishtp_cl_device *cl_device) +{ + struct ishtp_cl *cros_ish_cl = ishtp_get_drvdata(cl_device); + struct ishtp_cl_data *client_data = ishtp_get_client_data(cros_ish_cl); + + schedule_work(&client_data->work_ishtp_reset); + + return 0; +} + +/** + * cros_ec_ishtp_suspend() - ISHTP client driver suspend callback + * @device: device instance + * + * Return: 0 for success, negative error code for failure. + */ +static int __maybe_unused cros_ec_ishtp_suspend(struct device *device) +{ + struct ishtp_cl_device *cl_device = ishtp_dev_to_cl_device(device); + struct ishtp_cl *cros_ish_cl = ishtp_get_drvdata(cl_device); + struct ishtp_cl_data *client_data = ishtp_get_client_data(cros_ish_cl); + + return cros_ec_suspend(client_data->ec_dev); +} + +/** + * cros_ec_ishtp_resume() - ISHTP client driver resume callback + * @device: device instance + * + * Return: 0 for success, negative error code for failure. + */ +static int __maybe_unused cros_ec_ishtp_resume(struct device *device) +{ + struct ishtp_cl_device *cl_device = ishtp_dev_to_cl_device(device); + struct ishtp_cl *cros_ish_cl = ishtp_get_drvdata(cl_device); + struct ishtp_cl_data *client_data = ishtp_get_client_data(cros_ish_cl); + + return cros_ec_resume(client_data->ec_dev); +} + +static SIMPLE_DEV_PM_OPS(cros_ec_ishtp_pm_ops, cros_ec_ishtp_suspend, + cros_ec_ishtp_resume); + +static struct ishtp_cl_driver cros_ec_ishtp_driver = { + .name = "cros_ec_ishtp", + .guid = &cros_ish_guid, + .probe = cros_ec_ishtp_probe, + .remove = cros_ec_ishtp_remove, + .reset = cros_ec_ishtp_reset, + .driver = { + .pm = &cros_ec_ishtp_pm_ops, + }, +}; + +static int __init cros_ec_ishtp_mod_init(void) +{ + return ishtp_cl_driver_register(&cros_ec_ishtp_driver, THIS_MODULE); +} + +static void __exit cros_ec_ishtp_mod_exit(void) +{ + ishtp_cl_driver_unregister(&cros_ec_ishtp_driver); +} + +module_init(cros_ec_ishtp_mod_init); +module_exit(cros_ec_ishtp_mod_exit); + +MODULE_DESCRIPTION("ChromeOS EC ISHTP Client Driver"); +MODULE_AUTHOR("Rushikesh S Kadam <rushikesh.s.kadam@intel.com>"); + +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("ishtp:*"); diff --git a/drivers/platform/chrome/cros_ec_lightbar.c b/drivers/platform/chrome/cros_ec_lightbar.c index d30a6650b0b5..c0f2eec35a48 100644 --- a/drivers/platform/chrome/cros_ec_lightbar.c +++ b/drivers/platform/chrome/cros_ec_lightbar.c @@ -9,8 +9,9 @@ #include <linux/fs.h> #include <linux/kobject.h> #include <linux/mfd/cros_ec.h> -#include <linux/mfd/cros_ec_commands.h> #include <linux/module.h> +#include <linux/platform_data/cros_ec_commands.h> +#include <linux/platform_data/cros_ec_proto.h> #include <linux/platform_device.h> #include <linux/sched.h> #include <linux/types.h> @@ -547,7 +548,7 @@ static struct attribute *__lb_cmds_attrs[] = { NULL, }; -struct attribute_group cros_ec_lightbar_attr_group = { +static struct attribute_group cros_ec_lightbar_attr_group = { .name = "lightbar", .attrs = __lb_cmds_attrs, }; @@ -600,7 +601,7 @@ static int cros_ec_lightbar_remove(struct platform_device *pd) static int __maybe_unused cros_ec_lightbar_resume(struct device *dev) { - struct cros_ec_dev *ec_dev = dev_get_drvdata(dev); + struct cros_ec_dev *ec_dev = dev_get_drvdata(dev->parent); if (userspace_control) return 0; @@ -610,7 +611,7 @@ static int __maybe_unused cros_ec_lightbar_resume(struct device *dev) static int __maybe_unused cros_ec_lightbar_suspend(struct device *dev) { - struct cros_ec_dev *ec_dev = dev_get_drvdata(dev); + struct cros_ec_dev *ec_dev = dev_get_drvdata(dev->parent); if (userspace_control) return 0; diff --git a/drivers/platform/chrome/cros_ec_lpc.c b/drivers/platform/chrome/cros_ec_lpc.c index c9c240fbe7c6..7d10d909435f 100644 --- a/drivers/platform/chrome/cros_ec_lpc.c +++ b/drivers/platform/chrome/cros_ec_lpc.c @@ -16,14 +16,14 @@ #include <linux/delay.h> #include <linux/io.h> #include <linux/interrupt.h> -#include <linux/mfd/cros_ec.h> -#include <linux/mfd/cros_ec_commands.h> #include <linux/module.h> +#include <linux/platform_data/cros_ec_commands.h> +#include <linux/platform_data/cros_ec_proto.h> #include <linux/platform_device.h> #include <linux/printk.h> #include <linux/suspend.h> -#include "cros_ec_lpc_reg.h" +#include "cros_ec_lpc_mec.h" #define DRV_NAME "cros_ec_lpcs" #define ACPI_DRV_NAME "GOOG0004" @@ -31,6 +31,96 @@ /* True if ACPI device is present */ static bool cros_ec_lpc_acpi_device_found; +/** + * struct lpc_driver_ops - LPC driver operations + * @read: Copy length bytes from EC address offset into buffer dest. Returns + * the 8-bit checksum of all bytes read. + * @write: Copy length bytes from buffer msg into EC address offset. Returns + * the 8-bit checksum of all bytes written. + */ +struct lpc_driver_ops { + u8 (*read)(unsigned int offset, unsigned int length, u8 *dest); + u8 (*write)(unsigned int offset, unsigned int length, const u8 *msg); +}; + +static struct lpc_driver_ops cros_ec_lpc_ops = { }; + +/* + * A generic instance of the read function of struct lpc_driver_ops, used for + * the LPC EC. + */ +static u8 cros_ec_lpc_read_bytes(unsigned int offset, unsigned int length, + u8 *dest) +{ + int sum = 0; + int i; + + for (i = 0; i < length; ++i) { + dest[i] = inb(offset + i); + sum += dest[i]; + } + + /* Return checksum of all bytes read */ + return sum; +} + +/* + * A generic instance of the write function of struct lpc_driver_ops, used for + * the LPC EC. + */ +static u8 cros_ec_lpc_write_bytes(unsigned int offset, unsigned int length, + const u8 *msg) +{ + int sum = 0; + int i; + + for (i = 0; i < length; ++i) { + outb(msg[i], offset + i); + sum += msg[i]; + } + + /* Return checksum of all bytes written */ + return sum; +} + +/* + * An instance of the read function of struct lpc_driver_ops, used for the + * MEC variant of LPC EC. + */ +static u8 cros_ec_lpc_mec_read_bytes(unsigned int offset, unsigned int length, + u8 *dest) +{ + int in_range = cros_ec_lpc_mec_in_range(offset, length); + + if (in_range < 0) + return 0; + + return in_range ? + cros_ec_lpc_io_bytes_mec(MEC_IO_READ, + offset - EC_HOST_CMD_REGION0, + length, dest) : + cros_ec_lpc_read_bytes(offset, length, dest); +} + +/* + * An instance of the write function of struct lpc_driver_ops, used for the + * MEC variant of LPC EC. + */ +static u8 cros_ec_lpc_mec_write_bytes(unsigned int offset, unsigned int length, + const u8 *msg) +{ + int in_range = cros_ec_lpc_mec_in_range(offset, length); + + if (in_range < 0) + return 0; + + return in_range ? + cros_ec_lpc_io_bytes_mec(MEC_IO_WRITE, + offset - EC_HOST_CMD_REGION0, + length, (u8 *)msg) : + cros_ec_lpc_write_bytes(offset, length, msg); +} + static int ec_response_timed_out(void) { unsigned long one_second = jiffies + HZ; @@ -38,7 +128,7 @@ static int ec_response_timed_out(void) usleep_range(200, 300); do { - if (!(cros_ec_lpc_read_bytes(EC_LPC_ADDR_HOST_CMD, 1, &data) & + if (!(cros_ec_lpc_ops.read(EC_LPC_ADDR_HOST_CMD, 1, &data) & EC_LPC_STATUS_BUSY_MASK)) return 0; usleep_range(100, 200); @@ -58,11 +148,11 @@ static int cros_ec_pkt_xfer_lpc(struct cros_ec_device *ec, ret = cros_ec_prepare_tx(ec, msg); /* Write buffer */ - cros_ec_lpc_write_bytes(EC_LPC_ADDR_HOST_PACKET, ret, ec->dout); + cros_ec_lpc_ops.write(EC_LPC_ADDR_HOST_PACKET, ret, ec->dout); /* Here we go */ sum = EC_COMMAND_PROTOCOL_3; - cros_ec_lpc_write_bytes(EC_LPC_ADDR_HOST_CMD, 1, &sum); + cros_ec_lpc_ops.write(EC_LPC_ADDR_HOST_CMD, 1, &sum); if (ec_response_timed_out()) { dev_warn(ec->dev, "EC responsed timed out\n"); @@ -71,15 +161,15 @@ static int cros_ec_pkt_xfer_lpc(struct cros_ec_device *ec, } /* Check result */ - msg->result = cros_ec_lpc_read_bytes(EC_LPC_ADDR_HOST_DATA, 1, &sum); + msg->result = cros_ec_lpc_ops.read(EC_LPC_ADDR_HOST_DATA, 1, &sum); ret = cros_ec_check_result(ec, msg); if (ret) goto done; /* Read back response */ dout = (u8 *)&response; - sum = cros_ec_lpc_read_bytes(EC_LPC_ADDR_HOST_PACKET, sizeof(response), - dout); + sum = cros_ec_lpc_ops.read(EC_LPC_ADDR_HOST_PACKET, sizeof(response), + dout); msg->result = response.result; @@ -92,9 +182,9 @@ static int cros_ec_pkt_xfer_lpc(struct cros_ec_device *ec, } /* Read response and process checksum */ - sum += cros_ec_lpc_read_bytes(EC_LPC_ADDR_HOST_PACKET + - sizeof(response), response.data_len, - msg->data); + sum += cros_ec_lpc_ops.read(EC_LPC_ADDR_HOST_PACKET + + sizeof(response), response.data_len, + msg->data); if (sum) { dev_err(ec->dev, @@ -134,17 +224,17 @@ static int cros_ec_cmd_xfer_lpc(struct cros_ec_device *ec, sum = msg->command + args.flags + args.command_version + args.data_size; /* Copy data and update checksum */ - sum += cros_ec_lpc_write_bytes(EC_LPC_ADDR_HOST_PARAM, msg->outsize, - msg->data); + sum += cros_ec_lpc_ops.write(EC_LPC_ADDR_HOST_PARAM, msg->outsize, + msg->data); /* Finalize checksum and write args */ args.checksum = sum; - cros_ec_lpc_write_bytes(EC_LPC_ADDR_HOST_ARGS, sizeof(args), - (u8 *)&args); + cros_ec_lpc_ops.write(EC_LPC_ADDR_HOST_ARGS, sizeof(args), + (u8 *)&args); /* Here we go */ sum = msg->command; - cros_ec_lpc_write_bytes(EC_LPC_ADDR_HOST_CMD, 1, &sum); + cros_ec_lpc_ops.write(EC_LPC_ADDR_HOST_CMD, 1, &sum); if (ec_response_timed_out()) { dev_warn(ec->dev, "EC responsed timed out\n"); @@ -153,14 +243,13 @@ static int cros_ec_cmd_xfer_lpc(struct cros_ec_device *ec, } /* Check result */ - msg->result = cros_ec_lpc_read_bytes(EC_LPC_ADDR_HOST_DATA, 1, &sum); + msg->result = cros_ec_lpc_ops.read(EC_LPC_ADDR_HOST_DATA, 1, &sum); ret = cros_ec_check_result(ec, msg); if (ret) goto done; /* Read back args */ - cros_ec_lpc_read_bytes(EC_LPC_ADDR_HOST_ARGS, sizeof(args), - (u8 *)&args); + cros_ec_lpc_ops.read(EC_LPC_ADDR_HOST_ARGS, sizeof(args), (u8 *)&args); if (args.data_size > msg->insize) { dev_err(ec->dev, @@ -174,8 +263,8 @@ static int cros_ec_cmd_xfer_lpc(struct cros_ec_device *ec, sum = msg->command + args.flags + args.command_version + args.data_size; /* Read response and update checksum */ - sum += cros_ec_lpc_read_bytes(EC_LPC_ADDR_HOST_PARAM, args.data_size, - msg->data); + sum += cros_ec_lpc_ops.read(EC_LPC_ADDR_HOST_PARAM, args.data_size, + msg->data); /* Verify checksum */ if (args.checksum != sum) { @@ -205,13 +294,13 @@ static int cros_ec_lpc_readmem(struct cros_ec_device *ec, unsigned int offset, /* fixed length */ if (bytes) { - cros_ec_lpc_read_bytes(EC_LPC_ADDR_MEMMAP + offset, bytes, s); + cros_ec_lpc_ops.read(EC_LPC_ADDR_MEMMAP + offset, bytes, s); return bytes; } /* string */ for (; i < EC_MEMMAP_SIZE; i++, s++) { - cros_ec_lpc_read_bytes(EC_LPC_ADDR_MEMMAP + i, 1, s); + cros_ec_lpc_ops.read(EC_LPC_ADDR_MEMMAP + i, 1, s); cnt++; if (!*s) break; @@ -248,10 +337,25 @@ static int cros_ec_lpc_probe(struct platform_device *pdev) return -EBUSY; } - cros_ec_lpc_read_bytes(EC_LPC_ADDR_MEMMAP + EC_MEMMAP_ID, 2, buf); + /* + * Read the mapped ID twice, the first one is assuming the + * EC is a Microchip Embedded Controller (MEC) variant, if the + * protocol fails, fallback to the non MEC variant and try to + * read again the ID. + */ + cros_ec_lpc_ops.read = cros_ec_lpc_mec_read_bytes; + cros_ec_lpc_ops.write = cros_ec_lpc_mec_write_bytes; + cros_ec_lpc_ops.read(EC_LPC_ADDR_MEMMAP + EC_MEMMAP_ID, 2, buf); if (buf[0] != 'E' || buf[1] != 'C') { - dev_err(dev, "EC ID not detected\n"); - return -ENODEV; + /* Re-assign read/write operations for the non MEC variant */ + cros_ec_lpc_ops.read = cros_ec_lpc_read_bytes; + cros_ec_lpc_ops.write = cros_ec_lpc_write_bytes; + cros_ec_lpc_ops.read(EC_LPC_ADDR_MEMMAP + EC_MEMMAP_ID, 2, + buf); + if (buf[0] != 'E' || buf[1] != 'C') { + dev_err(dev, "EC ID not detected\n"); + return -ENODEV; + } } if (!devm_request_region(dev, EC_HOST_CMD_REGION0, @@ -317,6 +421,7 @@ static int cros_ec_lpc_probe(struct platform_device *pdev) static int cros_ec_lpc_remove(struct platform_device *pdev) { + struct cros_ec_device *ec_dev = platform_get_drvdata(pdev); struct acpi_device *adev; adev = ACPI_COMPANION(&pdev->dev); @@ -324,7 +429,7 @@ 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 0; + return cros_ec_unregister(ec_dev); } static const struct acpi_device_id cros_ec_lpc_acpi_device_ids[] = { @@ -405,7 +510,7 @@ static int cros_ec_lpc_resume(struct device *dev) } #endif -const struct dev_pm_ops cros_ec_lpc_pm_ops = { +static const struct dev_pm_ops cros_ec_lpc_pm_ops = { SET_LATE_SYSTEM_SLEEP_PM_OPS(cros_ec_lpc_suspend, cros_ec_lpc_resume) }; @@ -446,13 +551,14 @@ static int __init cros_ec_lpc_init(void) return -ENODEV; } - cros_ec_lpc_reg_init(); + cros_ec_lpc_mec_init(EC_HOST_CMD_REGION0, + EC_LPC_ADDR_MEMMAP + EC_MEMMAP_SIZE); /* Register the driver */ ret = platform_driver_register(&cros_ec_lpc_driver); if (ret) { pr_err(DRV_NAME ": can't register driver: %d\n", ret); - cros_ec_lpc_reg_destroy(); + cros_ec_lpc_mec_destroy(); return ret; } @@ -462,7 +568,7 @@ static int __init cros_ec_lpc_init(void) if (ret) { pr_err(DRV_NAME ": can't register device: %d\n", ret); platform_driver_unregister(&cros_ec_lpc_driver); - cros_ec_lpc_reg_destroy(); + cros_ec_lpc_mec_destroy(); } } @@ -474,7 +580,7 @@ static void __exit cros_ec_lpc_exit(void) if (!cros_ec_lpc_acpi_device_found) platform_device_unregister(&cros_ec_lpc_device); platform_driver_unregister(&cros_ec_lpc_driver); - cros_ec_lpc_reg_destroy(); + cros_ec_lpc_mec_destroy(); } module_init(cros_ec_lpc_init); diff --git a/drivers/platform/chrome/cros_ec_lpc_mec.c b/drivers/platform/chrome/cros_ec_lpc_mec.c index d8890bafb55d..9035b17e8c86 100644 --- a/drivers/platform/chrome/cros_ec_lpc_mec.c +++ b/drivers/platform/chrome/cros_ec_lpc_mec.c @@ -17,12 +17,10 @@ static struct mutex io_mutex; static u16 mec_emi_base, mec_emi_end; -/* - * cros_ec_lpc_mec_emi_write_address - * - * Initialize EMI read / write at a given address. +/** + * cros_ec_lpc_mec_emi_write_address() - Initialize EMI at a given address. * - * @addr: Starting read / write address + * @addr: Starting read / write address * @access_type: Type of access, typically 32-bit auto-increment */ static void cros_ec_lpc_mec_emi_write_address(u16 addr, @@ -61,15 +59,15 @@ int cros_ec_lpc_mec_in_range(unsigned int offset, unsigned int length) return 0; } -/* - * cros_ec_lpc_io_bytes_mec - Read / write bytes to MEC EMI port +/** + * cros_ec_lpc_io_bytes_mec() - Read / write bytes to MEC EMI port. * * @io_type: MEC_IO_READ or MEC_IO_WRITE, depending on request * @offset: Base read / write address * @length: Number of bytes to read / write * @buf: Destination / source buffer * - * @return 8-bit checksum of all bytes read / written + * Return: 8-bit checksum of all bytes read / written */ u8 cros_ec_lpc_io_bytes_mec(enum cros_ec_lpc_mec_io_type io_type, unsigned int offset, unsigned int length, diff --git a/drivers/platform/chrome/cros_ec_lpc_reg.c b/drivers/platform/chrome/cros_ec_lpc_reg.c deleted file mode 100644 index 0f5cd0ac8b49..000000000000 --- a/drivers/platform/chrome/cros_ec_lpc_reg.c +++ /dev/null @@ -1,101 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -// LPC interface for ChromeOS Embedded Controller -// -// Copyright (C) 2016 Google, Inc - -#include <linux/io.h> -#include <linux/mfd/cros_ec.h> -#include <linux/mfd/cros_ec_commands.h> - -#include "cros_ec_lpc_mec.h" - -static u8 lpc_read_bytes(unsigned int offset, unsigned int length, u8 *dest) -{ - int i; - int sum = 0; - - for (i = 0; i < length; ++i) { - dest[i] = inb(offset + i); - sum += dest[i]; - } - - /* Return checksum of all bytes read */ - return sum; -} - -static u8 lpc_write_bytes(unsigned int offset, unsigned int length, u8 *msg) -{ - int i; - int sum = 0; - - for (i = 0; i < length; ++i) { - outb(msg[i], offset + i); - sum += msg[i]; - } - - /* Return checksum of all bytes written */ - return sum; -} - -#ifdef CONFIG_CROS_EC_LPC_MEC - -u8 cros_ec_lpc_read_bytes(unsigned int offset, unsigned int length, u8 *dest) -{ - int in_range = cros_ec_lpc_mec_in_range(offset, length); - - if (in_range < 0) - return 0; - - return in_range ? - cros_ec_lpc_io_bytes_mec(MEC_IO_READ, - offset - EC_HOST_CMD_REGION0, - length, dest) : - lpc_read_bytes(offset, length, dest); -} - -u8 cros_ec_lpc_write_bytes(unsigned int offset, unsigned int length, u8 *msg) -{ - int in_range = cros_ec_lpc_mec_in_range(offset, length); - - if (in_range < 0) - return 0; - - return in_range ? - cros_ec_lpc_io_bytes_mec(MEC_IO_WRITE, - offset - EC_HOST_CMD_REGION0, - length, msg) : - lpc_write_bytes(offset, length, msg); -} - -void cros_ec_lpc_reg_init(void) -{ - cros_ec_lpc_mec_init(EC_HOST_CMD_REGION0, - EC_LPC_ADDR_MEMMAP + EC_MEMMAP_SIZE); -} - -void cros_ec_lpc_reg_destroy(void) -{ - cros_ec_lpc_mec_destroy(); -} - -#else /* CONFIG_CROS_EC_LPC_MEC */ - -u8 cros_ec_lpc_read_bytes(unsigned int offset, unsigned int length, u8 *dest) -{ - return lpc_read_bytes(offset, length, dest); -} - -u8 cros_ec_lpc_write_bytes(unsigned int offset, unsigned int length, u8 *msg) -{ - return lpc_write_bytes(offset, length, msg); -} - -void cros_ec_lpc_reg_init(void) -{ -} - -void cros_ec_lpc_reg_destroy(void) -{ -} - -#endif /* CONFIG_CROS_EC_LPC_MEC */ diff --git a/drivers/platform/chrome/cros_ec_lpc_reg.h b/drivers/platform/chrome/cros_ec_lpc_reg.h deleted file mode 100644 index 416fd2572182..000000000000 --- a/drivers/platform/chrome/cros_ec_lpc_reg.h +++ /dev/null @@ -1,45 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* - * LPC interface for ChromeOS Embedded Controller - * - * Copyright (C) 2016 Google, Inc - */ - -#ifndef __CROS_EC_LPC_REG_H -#define __CROS_EC_LPC_REG_H - -/** - * cros_ec_lpc_read_bytes - Read bytes from a given LPC-mapped address. - * Returns 8-bit checksum of all bytes read. - * - * @offset: Base read address - * @length: Number of bytes to read - * @dest: Destination buffer - */ -u8 cros_ec_lpc_read_bytes(unsigned int offset, unsigned int length, u8 *dest); - -/** - * cros_ec_lpc_write_bytes - Write bytes to a given LPC-mapped address. - * Returns 8-bit checksum of all bytes written. - * - * @offset: Base write address - * @length: Number of bytes to write - * @msg: Write data buffer - */ -u8 cros_ec_lpc_write_bytes(unsigned int offset, unsigned int length, u8 *msg); - -/** - * cros_ec_lpc_reg_init - * - * Initialize register I/O. - */ -void cros_ec_lpc_reg_init(void); - -/** - * cros_ec_lpc_reg_destroy - * - * Cleanup reg I/O. - */ -void cros_ec_lpc_reg_destroy(void); - -#endif /* __CROS_EC_LPC_REG_H */ diff --git a/drivers/platform/chrome/cros_ec_proto.c b/drivers/platform/chrome/cros_ec_proto.c index 97a068dff192..f659f96bda12 100644 --- a/drivers/platform/chrome/cros_ec_proto.c +++ b/drivers/platform/chrome/cros_ec_proto.c @@ -3,13 +3,16 @@ // // Copyright (C) 2015 Google, Inc -#include <linux/mfd/cros_ec.h> #include <linux/delay.h> #include <linux/device.h> #include <linux/module.h> +#include <linux/platform_data/cros_ec_commands.h> +#include <linux/platform_data/cros_ec_proto.h> #include <linux/slab.h> #include <asm/unaligned.h> +#include "cros_ec_trace.h" + #define EC_COMMAND_RETRIES 50 static int prepare_packet(struct cros_ec_device *ec_dev, @@ -51,11 +54,24 @@ static int send_command(struct cros_ec_device *ec_dev, int ret; int (*xfer_fxn)(struct cros_ec_device *ec, struct cros_ec_command *msg); + trace_cros_ec_cmd(msg); + if (ec_dev->proto_version > 2) xfer_fxn = ec_dev->pkt_xfer; else xfer_fxn = ec_dev->cmd_xfer; + if (!xfer_fxn) { + /* + * This error can happen if a communication error happened and + * the EC is trying to use protocol v2, on an underlying + * communication mechanism that does not support v2. + */ + dev_err_once(ec_dev->dev, + "missing EC transfer API, cannot send command\n"); + return -EIO; + } + ret = (*xfer_fxn)(ec_dev, msg); if (msg->result == EC_RES_IN_PROGRESS) { int i; @@ -414,6 +430,12 @@ int cros_ec_query_all(struct cros_ec_device *ec_dev) else ec_dev->mkbp_event_supported = 1; + /* Probe if host sleep v1 is supported for S0ix failure detection. */ + ret = cros_ec_get_host_command_version_mask(ec_dev, + EC_CMD_HOST_SLEEP_EVENT, + &ver_mask); + ec_dev->host_sleep_v1 = (ret >= 0 && (ver_mask & EC_VER_MASK(1))); + /* * Get host event wake mask, assume all events are wake events * if unavailable. diff --git a/drivers/platform/chrome/cros_ec_rpmsg.c b/drivers/platform/chrome/cros_ec_rpmsg.c new file mode 100644 index 000000000000..0c3738c3244d --- /dev/null +++ b/drivers/platform/chrome/cros_ec_rpmsg.c @@ -0,0 +1,303 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Copyright 2018 Google LLC. + +#include <linux/completion.h> +#include <linux/delay.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_data/cros_ec_commands.h> +#include <linux/platform_data/cros_ec_proto.h> +#include <linux/platform_device.h> +#include <linux/rpmsg.h> +#include <linux/slab.h> + +#define EC_MSG_TIMEOUT_MS 200 +#define HOST_COMMAND_MARK 1 +#define HOST_EVENT_MARK 2 + +/** + * struct cros_ec_rpmsg_response - rpmsg message format from from EC. + * + * @type: The type of message, should be either HOST_COMMAND_MARK or + * HOST_EVENT_MARK, representing that the message is a response to + * host command, or a host event. + * @data: ec_host_response for host command. + */ +struct cros_ec_rpmsg_response { + u8 type; + u8 data[] __aligned(4); +}; + +/** + * struct cros_ec_rpmsg - information about a EC over rpmsg. + * + * @rpdev: rpmsg device we are connected to + * @xfer_ack: completion for host command transfer. + * @host_event_work: Work struct for pending host event. + */ +struct cros_ec_rpmsg { + struct rpmsg_device *rpdev; + struct completion xfer_ack; + struct work_struct host_event_work; + struct rpmsg_endpoint *ept; +}; + +/** + * cros_ec_cmd_xfer_rpmsg - Transfer a message over rpmsg and receive the reply + * + * @ec_dev: ChromeOS EC device + * @ec_msg: Message to transfer + * + * This is only used for old EC proto version, and is not supported for this + * driver. + * + * Return: -EINVAL + */ +static int cros_ec_cmd_xfer_rpmsg(struct cros_ec_device *ec_dev, + struct cros_ec_command *ec_msg) +{ + return -EINVAL; +} + +/** + * cros_ec_pkt_xfer_rpmsg - Transfer a packet over rpmsg and receive the reply + * + * @ec_dev: ChromeOS EC device + * @ec_msg: Message to transfer + * + * Return: number of bytes of the reply on success or negative error code. + */ +static int cros_ec_pkt_xfer_rpmsg(struct cros_ec_device *ec_dev, + struct cros_ec_command *ec_msg) +{ + struct cros_ec_rpmsg *ec_rpmsg = ec_dev->priv; + struct ec_host_response *response; + unsigned long timeout; + int len; + int ret; + u8 sum; + int i; + + ec_msg->result = 0; + len = cros_ec_prepare_tx(ec_dev, ec_msg); + dev_dbg(ec_dev->dev, "prepared, len=%d\n", len); + + reinit_completion(&ec_rpmsg->xfer_ack); + ret = rpmsg_send(ec_rpmsg->ept, ec_dev->dout, len); + if (ret) { + dev_err(ec_dev->dev, "rpmsg send failed\n"); + return ret; + } + + timeout = msecs_to_jiffies(EC_MSG_TIMEOUT_MS); + ret = wait_for_completion_timeout(&ec_rpmsg->xfer_ack, timeout); + if (!ret) { + dev_err(ec_dev->dev, "rpmsg send timeout\n"); + return -EIO; + } + + /* check response error code */ + response = (struct ec_host_response *)ec_dev->din; + ec_msg->result = response->result; + + ret = cros_ec_check_result(ec_dev, ec_msg); + if (ret) + goto exit; + + if (response->data_len > ec_msg->insize) { + dev_err(ec_dev->dev, "packet too long (%d bytes, expected %d)", + response->data_len, ec_msg->insize); + ret = -EMSGSIZE; + goto exit; + } + + /* copy response packet payload and compute checksum */ + memcpy(ec_msg->data, ec_dev->din + sizeof(*response), + response->data_len); + + sum = 0; + for (i = 0; i < sizeof(*response) + response->data_len; i++) + sum += ec_dev->din[i]; + + if (sum) { + dev_err(ec_dev->dev, "bad packet checksum, calculated %x\n", + sum); + ret = -EBADMSG; + goto exit; + } + + ret = response->data_len; +exit: + if (ec_msg->command == EC_CMD_REBOOT_EC) + msleep(EC_REBOOT_DELAY_MS); + + return ret; +} + +static void +cros_ec_rpmsg_host_event_function(struct work_struct *host_event_work) +{ + struct cros_ec_rpmsg *ec_rpmsg = container_of(host_event_work, + struct cros_ec_rpmsg, + host_event_work); + struct cros_ec_device *ec_dev = dev_get_drvdata(&ec_rpmsg->rpdev->dev); + bool wake_event = true; + int ret; + + ret = cros_ec_get_next_event(ec_dev, &wake_event); + + /* + * Signal only if wake host events or any interrupt if + * cros_ec_get_next_event() returned an error (default value for + * wake_event is true) + */ + if (wake_event && device_may_wakeup(ec_dev->dev)) + pm_wakeup_event(ec_dev->dev, 0); + + if (ret > 0) + blocking_notifier_call_chain(&ec_dev->event_notifier, + 0, ec_dev); +} + +static int cros_ec_rpmsg_callback(struct rpmsg_device *rpdev, void *data, + int len, void *priv, u32 src) +{ + struct cros_ec_device *ec_dev = dev_get_drvdata(&rpdev->dev); + struct cros_ec_rpmsg *ec_rpmsg = ec_dev->priv; + struct cros_ec_rpmsg_response *resp; + + if (!len) { + dev_warn(ec_dev->dev, "rpmsg received empty response"); + return -EINVAL; + } + + resp = data; + len -= offsetof(struct cros_ec_rpmsg_response, data); + if (resp->type == HOST_COMMAND_MARK) { + if (len > ec_dev->din_size) { + dev_warn(ec_dev->dev, + "received length %d > din_size %d, truncating", + len, ec_dev->din_size); + len = ec_dev->din_size; + } + + memcpy(ec_dev->din, resp->data, len); + complete(&ec_rpmsg->xfer_ack); + } else if (resp->type == HOST_EVENT_MARK) { + schedule_work(&ec_rpmsg->host_event_work); + } else { + dev_warn(ec_dev->dev, "rpmsg received invalid type = %d", + resp->type); + return -EINVAL; + } + + return 0; +} + +static struct rpmsg_endpoint * +cros_ec_rpmsg_create_ept(struct rpmsg_device *rpdev) +{ + struct rpmsg_channel_info chinfo = {}; + + strscpy(chinfo.name, rpdev->id.name, RPMSG_NAME_SIZE); + chinfo.src = rpdev->src; + chinfo.dst = RPMSG_ADDR_ANY; + + return rpmsg_create_ept(rpdev, cros_ec_rpmsg_callback, NULL, chinfo); +} + +static int cros_ec_rpmsg_probe(struct rpmsg_device *rpdev) +{ + struct device *dev = &rpdev->dev; + struct cros_ec_rpmsg *ec_rpmsg; + struct cros_ec_device *ec_dev; + int ret; + + ec_dev = devm_kzalloc(dev, sizeof(*ec_dev), GFP_KERNEL); + if (!ec_dev) + return -ENOMEM; + + ec_rpmsg = devm_kzalloc(dev, sizeof(*ec_rpmsg), GFP_KERNEL); + if (!ec_rpmsg) + return -ENOMEM; + + ec_dev->dev = dev; + ec_dev->priv = ec_rpmsg; + ec_dev->cmd_xfer = cros_ec_cmd_xfer_rpmsg; + ec_dev->pkt_xfer = cros_ec_pkt_xfer_rpmsg; + ec_dev->phys_name = dev_name(&rpdev->dev); + ec_dev->din_size = sizeof(struct ec_host_response) + + sizeof(struct ec_response_get_protocol_info); + ec_dev->dout_size = sizeof(struct ec_host_request); + dev_set_drvdata(dev, ec_dev); + + ec_rpmsg->rpdev = rpdev; + init_completion(&ec_rpmsg->xfer_ack); + INIT_WORK(&ec_rpmsg->host_event_work, + cros_ec_rpmsg_host_event_function); + + ec_rpmsg->ept = cros_ec_rpmsg_create_ept(rpdev); + if (!ec_rpmsg->ept) + return -ENOMEM; + + ret = cros_ec_register(ec_dev); + if (ret < 0) { + rpmsg_destroy_ept(ec_rpmsg->ept); + cancel_work_sync(&ec_rpmsg->host_event_work); + return ret; + } + + return 0; +} + +static void cros_ec_rpmsg_remove(struct rpmsg_device *rpdev) +{ + struct cros_ec_device *ec_dev = dev_get_drvdata(&rpdev->dev); + struct cros_ec_rpmsg *ec_rpmsg = ec_dev->priv; + + cros_ec_unregister(ec_dev); + rpmsg_destroy_ept(ec_rpmsg->ept); + cancel_work_sync(&ec_rpmsg->host_event_work); +} + +#ifdef CONFIG_PM_SLEEP +static int cros_ec_rpmsg_suspend(struct device *dev) +{ + struct cros_ec_device *ec_dev = dev_get_drvdata(dev); + + return cros_ec_suspend(ec_dev); +} + +static int cros_ec_rpmsg_resume(struct device *dev) +{ + struct cros_ec_device *ec_dev = dev_get_drvdata(dev); + + return cros_ec_resume(ec_dev); +} +#endif + +static SIMPLE_DEV_PM_OPS(cros_ec_rpmsg_pm_ops, cros_ec_rpmsg_suspend, + cros_ec_rpmsg_resume); + +static const struct of_device_id cros_ec_rpmsg_of_match[] = { + { .compatible = "google,cros-ec-rpmsg", }, + { } +}; +MODULE_DEVICE_TABLE(of, cros_ec_rpmsg_of_match); + +static struct rpmsg_driver cros_ec_driver_rpmsg = { + .drv = { + .name = "cros-ec-rpmsg", + .of_match_table = cros_ec_rpmsg_of_match, + .pm = &cros_ec_rpmsg_pm_ops, + }, + .probe = cros_ec_rpmsg_probe, + .remove = cros_ec_rpmsg_remove, +}; + +module_rpmsg_driver(cros_ec_driver_rpmsg); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("ChromeOS EC multi function device (rpmsg)"); diff --git a/drivers/platform/chrome/cros_ec_spi.c b/drivers/platform/chrome/cros_ec_spi.c index ffc38f9d4829..a831bd5a5b2f 100644 --- a/drivers/platform/chrome/cros_ec_spi.c +++ b/drivers/platform/chrome/cros_ec_spi.c @@ -6,13 +6,13 @@ #include <linux/delay.h> #include <linux/kernel.h> #include <linux/module.h> -#include <linux/mfd/cros_ec.h> -#include <linux/mfd/cros_ec_commands.h> #include <linux/of.h> +#include <linux/platform_data/cros_ec_commands.h> +#include <linux/platform_data/cros_ec_proto.h> #include <linux/platform_device.h> #include <linux/slab.h> #include <linux/spi/spi.h> - +#include <uapi/linux/sched/types.h> /* The header byte, which follows the preamble */ #define EC_MSG_HEADER 0xec @@ -67,12 +67,35 @@ * is sent when we want to turn on CS at the start of a transaction. * @end_of_msg_delay: used to set the delay_usecs on the spi_transfer that * is sent when we want to turn off CS at the end of a transaction. + * @high_pri_worker: Used to schedule high priority work. */ struct cros_ec_spi { struct spi_device *spi; s64 last_transfer_ns; unsigned int start_of_msg_delay; unsigned int end_of_msg_delay; + struct kthread_worker *high_pri_worker; +}; + +typedef int (*cros_ec_xfer_fn_t) (struct cros_ec_device *ec_dev, + struct cros_ec_command *ec_msg); + +/** + * struct cros_ec_xfer_work_params - params for our high priority workers + * + * @work: The work_struct needed to queue work + * @fn: The function to use to transfer + * @ec_dev: ChromeOS EC device + * @ec_msg: Message to transfer + * @ret: The return value of the function + */ + +struct cros_ec_xfer_work_params { + struct kthread_work work; + cros_ec_xfer_fn_t fn; + struct cros_ec_device *ec_dev; + struct cros_ec_command *ec_msg; + int ret; }; static void debug_packet(struct device *dev, const char *name, u8 *ptr, @@ -350,13 +373,13 @@ static int cros_ec_spi_receive_response(struct cros_ec_device *ec_dev, } /** - * cros_ec_pkt_xfer_spi - Transfer a packet over SPI and receive the reply + * do_cros_ec_pkt_xfer_spi - Transfer a packet over SPI and receive the reply * * @ec_dev: ChromeOS EC device * @ec_msg: Message to transfer */ -static int cros_ec_pkt_xfer_spi(struct cros_ec_device *ec_dev, - struct cros_ec_command *ec_msg) +static int do_cros_ec_pkt_xfer_spi(struct cros_ec_device *ec_dev, + struct cros_ec_command *ec_msg) { struct ec_host_response *response; struct cros_ec_spi *ec_spi = ec_dev->priv; @@ -493,13 +516,13 @@ exit: } /** - * cros_ec_cmd_xfer_spi - Transfer a message over SPI and receive the reply + * do_cros_ec_cmd_xfer_spi - Transfer a message over SPI and receive the reply * * @ec_dev: ChromeOS EC device * @ec_msg: Message to transfer */ -static int cros_ec_cmd_xfer_spi(struct cros_ec_device *ec_dev, - struct cros_ec_command *ec_msg) +static int do_cros_ec_cmd_xfer_spi(struct cros_ec_device *ec_dev, + struct cros_ec_command *ec_msg) { struct cros_ec_spi *ec_spi = ec_dev->priv; struct spi_transfer trans; @@ -611,6 +634,54 @@ exit: return ret; } +static void cros_ec_xfer_high_pri_work(struct kthread_work *work) +{ + struct cros_ec_xfer_work_params *params; + + params = container_of(work, struct cros_ec_xfer_work_params, work); + params->ret = params->fn(params->ec_dev, params->ec_msg); +} + +static int cros_ec_xfer_high_pri(struct cros_ec_device *ec_dev, + struct cros_ec_command *ec_msg, + cros_ec_xfer_fn_t fn) +{ + struct cros_ec_spi *ec_spi = ec_dev->priv; + struct cros_ec_xfer_work_params params = { + .work = KTHREAD_WORK_INIT(params.work, + cros_ec_xfer_high_pri_work), + .ec_dev = ec_dev, + .ec_msg = ec_msg, + .fn = fn, + }; + + /* + * This looks a bit ridiculous. Why do the work on a + * different thread if we're just going to block waiting for + * the thread to finish? The key here is that the thread is + * running at high priority but the calling context might not + * be. We need to be at high priority to avoid getting + * context switched out for too long and the EC giving up on + * the transfer. + */ + kthread_queue_work(ec_spi->high_pri_worker, ¶ms.work); + kthread_flush_work(¶ms.work); + + return params.ret; +} + +static int cros_ec_pkt_xfer_spi(struct cros_ec_device *ec_dev, + struct cros_ec_command *ec_msg) +{ + return cros_ec_xfer_high_pri(ec_dev, ec_msg, do_cros_ec_pkt_xfer_spi); +} + +static int cros_ec_cmd_xfer_spi(struct cros_ec_device *ec_dev, + struct cros_ec_command *ec_msg) +{ + return cros_ec_xfer_high_pri(ec_dev, ec_msg, do_cros_ec_cmd_xfer_spi); +} + static void cros_ec_spi_dt_probe(struct cros_ec_spi *ec_spi, struct device *dev) { struct device_node *np = dev->of_node; @@ -626,6 +697,40 @@ static void cros_ec_spi_dt_probe(struct cros_ec_spi *ec_spi, struct device *dev) ec_spi->end_of_msg_delay = val; } +static void cros_ec_spi_high_pri_release(void *worker) +{ + kthread_destroy_worker(worker); +} + +static int cros_ec_spi_devm_high_pri_alloc(struct device *dev, + struct cros_ec_spi *ec_spi) +{ + struct sched_param sched_priority = { + .sched_priority = MAX_RT_PRIO / 2, + }; + int err; + + ec_spi->high_pri_worker = + kthread_create_worker(0, "cros_ec_spi_high_pri"); + + if (IS_ERR(ec_spi->high_pri_worker)) { + err = PTR_ERR(ec_spi->high_pri_worker); + dev_err(dev, "Can't create cros_ec high pri worker: %d\n", err); + return err; + } + + err = devm_add_action_or_reset(dev, cros_ec_spi_high_pri_release, + ec_spi->high_pri_worker); + if (err) + return err; + + err = sched_setscheduler_nocheck(ec_spi->high_pri_worker->task, + SCHED_FIFO, &sched_priority); + if (err) + dev_err(dev, "Can't set cros_ec high pri priority: %d\n", err); + return err; +} + static int cros_ec_spi_probe(struct spi_device *spi) { struct device *dev = &spi->dev; @@ -635,6 +740,7 @@ static int cros_ec_spi_probe(struct spi_device *spi) spi->bits_per_word = 8; spi->mode = SPI_MODE_0; + spi->rt = true; err = spi_setup(spi); if (err < 0) return err; @@ -664,6 +770,10 @@ static int cros_ec_spi_probe(struct spi_device *spi) ec_spi->last_transfer_ns = ktime_get_ns(); + err = cros_ec_spi_devm_high_pri_alloc(dev, ec_spi); + if (err) + return err; + err = cros_ec_register(ec_dev); if (err) { dev_err(dev, "cannot register EC\n"); @@ -675,6 +785,13 @@ static int cros_ec_spi_probe(struct spi_device *spi) return 0; } +static int cros_ec_spi_remove(struct spi_device *spi) +{ + struct cros_ec_device *ec_dev = spi_get_drvdata(spi); + + return cros_ec_unregister(ec_dev); +} + #ifdef CONFIG_PM_SLEEP static int cros_ec_spi_suspend(struct device *dev) { @@ -709,10 +826,11 @@ MODULE_DEVICE_TABLE(spi, cros_ec_spi_id); static struct spi_driver cros_ec_driver_spi = { .driver = { .name = "cros-ec-spi", - .of_match_table = of_match_ptr(cros_ec_spi_of_match), + .of_match_table = cros_ec_spi_of_match, .pm = &cros_ec_spi_pm_ops, }, .probe = cros_ec_spi_probe, + .remove = cros_ec_spi_remove, .id_table = cros_ec_spi_id, }; diff --git a/drivers/platform/chrome/cros_ec_sysfs.c b/drivers/platform/chrome/cros_ec_sysfs.c index fe0b7614ae1b..74d36b8d4f46 100644 --- a/drivers/platform/chrome/cros_ec_sysfs.c +++ b/drivers/platform/chrome/cros_ec_sysfs.c @@ -9,8 +9,9 @@ #include <linux/fs.h> #include <linux/kobject.h> #include <linux/mfd/cros_ec.h> -#include <linux/mfd/cros_ec_commands.h> #include <linux/module.h> +#include <linux/platform_data/cros_ec_commands.h> +#include <linux/platform_data/cros_ec_proto.h> #include <linux/platform_device.h> #include <linux/printk.h> #include <linux/slab.h> @@ -335,7 +336,7 @@ static umode_t cros_ec_ctrl_visible(struct kobject *kobj, return a->mode; } -struct attribute_group cros_ec_attr_group = { +static struct attribute_group cros_ec_attr_group = { .attrs = __ec_attrs, .is_visible = cros_ec_ctrl_visible, }; diff --git a/drivers/platform/chrome/cros_ec_trace.c b/drivers/platform/chrome/cros_ec_trace.c new file mode 100644 index 000000000000..5af1d66d9eca --- /dev/null +++ b/drivers/platform/chrome/cros_ec_trace.c @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: GPL-2.0 +// Trace events for the ChromeOS Embedded Controller +// +// Copyright 2019 Google LLC. + +#define TRACE_SYMBOL(a) {a, #a} + +// Generate the list using the following script: +// sed -n 's/^#define \(EC_CMD_[[:alnum:]_]*\)\s.*/\tTRACE_SYMBOL(\1), \\/p' include/linux/platform_data/cros_ec_commands.h +#define EC_CMDS \ + TRACE_SYMBOL(EC_CMD_PROTO_VERSION), \ + TRACE_SYMBOL(EC_CMD_HELLO), \ + TRACE_SYMBOL(EC_CMD_GET_VERSION), \ + TRACE_SYMBOL(EC_CMD_READ_TEST), \ + TRACE_SYMBOL(EC_CMD_GET_BUILD_INFO), \ + TRACE_SYMBOL(EC_CMD_GET_CHIP_INFO), \ + TRACE_SYMBOL(EC_CMD_GET_BOARD_VERSION), \ + TRACE_SYMBOL(EC_CMD_READ_MEMMAP), \ + TRACE_SYMBOL(EC_CMD_GET_CMD_VERSIONS), \ + TRACE_SYMBOL(EC_CMD_GET_COMMS_STATUS), \ + TRACE_SYMBOL(EC_CMD_TEST_PROTOCOL), \ + TRACE_SYMBOL(EC_CMD_GET_PROTOCOL_INFO), \ + TRACE_SYMBOL(EC_CMD_GSV_PAUSE_IN_S5), \ + TRACE_SYMBOL(EC_CMD_GET_FEATURES), \ + TRACE_SYMBOL(EC_CMD_FLASH_INFO), \ + TRACE_SYMBOL(EC_CMD_FLASH_READ), \ + TRACE_SYMBOL(EC_CMD_FLASH_WRITE), \ + TRACE_SYMBOL(EC_CMD_FLASH_ERASE), \ + TRACE_SYMBOL(EC_CMD_FLASH_PROTECT), \ + TRACE_SYMBOL(EC_CMD_FLASH_REGION_INFO), \ + TRACE_SYMBOL(EC_CMD_VBNV_CONTEXT), \ + TRACE_SYMBOL(EC_CMD_PWM_GET_FAN_TARGET_RPM), \ + TRACE_SYMBOL(EC_CMD_PWM_SET_FAN_TARGET_RPM), \ + TRACE_SYMBOL(EC_CMD_PWM_GET_KEYBOARD_BACKLIGHT), \ + TRACE_SYMBOL(EC_CMD_PWM_SET_KEYBOARD_BACKLIGHT), \ + TRACE_SYMBOL(EC_CMD_PWM_SET_FAN_DUTY), \ + TRACE_SYMBOL(EC_CMD_PWM_SET_DUTY), \ + TRACE_SYMBOL(EC_CMD_PWM_GET_DUTY), \ + TRACE_SYMBOL(EC_CMD_LIGHTBAR_CMD), \ + TRACE_SYMBOL(EC_CMD_LED_CONTROL), \ + TRACE_SYMBOL(EC_CMD_VBOOT_HASH), \ + TRACE_SYMBOL(EC_CMD_MOTION_SENSE_CMD), \ + TRACE_SYMBOL(EC_CMD_USB_CHARGE_SET_MODE), \ + TRACE_SYMBOL(EC_CMD_PSTORE_INFO), \ + TRACE_SYMBOL(EC_CMD_PSTORE_READ), \ + TRACE_SYMBOL(EC_CMD_PSTORE_WRITE), \ + TRACE_SYMBOL(EC_CMD_RTC_GET_VALUE), \ + TRACE_SYMBOL(EC_CMD_RTC_GET_ALARM), \ + TRACE_SYMBOL(EC_CMD_RTC_SET_VALUE), \ + TRACE_SYMBOL(EC_CMD_RTC_SET_ALARM), \ + TRACE_SYMBOL(EC_CMD_PORT80_LAST_BOOT), \ + TRACE_SYMBOL(EC_CMD_PORT80_READ), \ + TRACE_SYMBOL(EC_CMD_THERMAL_SET_THRESHOLD), \ + TRACE_SYMBOL(EC_CMD_THERMAL_GET_THRESHOLD), \ + TRACE_SYMBOL(EC_CMD_THERMAL_AUTO_FAN_CTRL), \ + TRACE_SYMBOL(EC_CMD_TMP006_GET_CALIBRATION), \ + TRACE_SYMBOL(EC_CMD_TMP006_SET_CALIBRATION), \ + TRACE_SYMBOL(EC_CMD_TMP006_GET_RAW), \ + TRACE_SYMBOL(EC_CMD_MKBP_STATE), \ + TRACE_SYMBOL(EC_CMD_MKBP_INFO), \ + TRACE_SYMBOL(EC_CMD_MKBP_SIMULATE_KEY), \ + TRACE_SYMBOL(EC_CMD_MKBP_SET_CONFIG), \ + TRACE_SYMBOL(EC_CMD_MKBP_GET_CONFIG), \ + TRACE_SYMBOL(EC_CMD_KEYSCAN_SEQ_CTRL), \ + TRACE_SYMBOL(EC_CMD_GET_NEXT_EVENT), \ + TRACE_SYMBOL(EC_CMD_TEMP_SENSOR_GET_INFO), \ + TRACE_SYMBOL(EC_CMD_HOST_EVENT_GET_B), \ + TRACE_SYMBOL(EC_CMD_HOST_EVENT_GET_SMI_MASK), \ + TRACE_SYMBOL(EC_CMD_HOST_EVENT_GET_SCI_MASK), \ + TRACE_SYMBOL(EC_CMD_HOST_EVENT_GET_WAKE_MASK), \ + TRACE_SYMBOL(EC_CMD_HOST_EVENT_SET_SMI_MASK), \ + TRACE_SYMBOL(EC_CMD_HOST_EVENT_SET_SCI_MASK), \ + TRACE_SYMBOL(EC_CMD_HOST_EVENT_CLEAR), \ + TRACE_SYMBOL(EC_CMD_HOST_EVENT_SET_WAKE_MASK), \ + TRACE_SYMBOL(EC_CMD_HOST_EVENT_CLEAR_B), \ + TRACE_SYMBOL(EC_CMD_SWITCH_ENABLE_BKLIGHT), \ + TRACE_SYMBOL(EC_CMD_SWITCH_ENABLE_WIRELESS), \ + TRACE_SYMBOL(EC_CMD_GPIO_SET), \ + TRACE_SYMBOL(EC_CMD_GPIO_GET), \ + TRACE_SYMBOL(EC_CMD_I2C_READ), \ + TRACE_SYMBOL(EC_CMD_I2C_WRITE), \ + TRACE_SYMBOL(EC_CMD_CHARGE_CONTROL), \ + TRACE_SYMBOL(EC_CMD_CONSOLE_SNAPSHOT), \ + TRACE_SYMBOL(EC_CMD_CONSOLE_READ), \ + TRACE_SYMBOL(EC_CMD_BATTERY_CUT_OFF), \ + TRACE_SYMBOL(EC_CMD_USB_MUX), \ + TRACE_SYMBOL(EC_CMD_LDO_SET), \ + TRACE_SYMBOL(EC_CMD_LDO_GET), \ + TRACE_SYMBOL(EC_CMD_POWER_INFO), \ + TRACE_SYMBOL(EC_CMD_I2C_PASSTHRU), \ + TRACE_SYMBOL(EC_CMD_HANG_DETECT), \ + TRACE_SYMBOL(EC_CMD_CHARGE_STATE), \ + TRACE_SYMBOL(EC_CMD_CHARGE_CURRENT_LIMIT), \ + TRACE_SYMBOL(EC_CMD_EXTERNAL_POWER_LIMIT), \ + TRACE_SYMBOL(EC_CMD_HOST_SLEEP_EVENT), \ + TRACE_SYMBOL(EC_CMD_SB_READ_WORD), \ + TRACE_SYMBOL(EC_CMD_SB_WRITE_WORD), \ + TRACE_SYMBOL(EC_CMD_SB_READ_BLOCK), \ + TRACE_SYMBOL(EC_CMD_SB_WRITE_BLOCK), \ + TRACE_SYMBOL(EC_CMD_BATTERY_VENDOR_PARAM), \ + TRACE_SYMBOL(EC_CMD_EC_CODEC), \ + TRACE_SYMBOL(EC_CMD_EC_CODEC_DMIC), \ + TRACE_SYMBOL(EC_CMD_EC_CODEC_I2S_RX), \ + TRACE_SYMBOL(EC_CMD_EC_CODEC_WOV), \ + TRACE_SYMBOL(EC_CMD_REBOOT_EC), \ + TRACE_SYMBOL(EC_CMD_GET_PANIC_INFO), \ + TRACE_SYMBOL(EC_CMD_ACPI_READ), \ + TRACE_SYMBOL(EC_CMD_ACPI_WRITE), \ + TRACE_SYMBOL(EC_CMD_ACPI_QUERY_EVENT), \ + TRACE_SYMBOL(EC_CMD_CEC_WRITE_MSG), \ + TRACE_SYMBOL(EC_CMD_CEC_SET), \ + TRACE_SYMBOL(EC_CMD_CEC_GET), \ + TRACE_SYMBOL(EC_CMD_REBOOT), \ + TRACE_SYMBOL(EC_CMD_RESEND_RESPONSE), \ + TRACE_SYMBOL(EC_CMD_VERSION0), \ + TRACE_SYMBOL(EC_CMD_PD_EXCHANGE_STATUS), \ + TRACE_SYMBOL(EC_CMD_USB_PD_CONTROL), \ + TRACE_SYMBOL(EC_CMD_USB_PD_PORTS), \ + TRACE_SYMBOL(EC_CMD_USB_PD_POWER_INFO), \ + TRACE_SYMBOL(EC_CMD_CHARGE_PORT_COUNT), \ + TRACE_SYMBOL(EC_CMD_USB_PD_DISCOVERY), \ + TRACE_SYMBOL(EC_CMD_PD_CHARGE_PORT_OVERRIDE), \ + TRACE_SYMBOL(EC_CMD_PD_GET_LOG_ENTRY), \ + TRACE_SYMBOL(EC_CMD_USB_PD_MUX_INFO) + +#define CREATE_TRACE_POINTS +#include "cros_ec_trace.h" diff --git a/drivers/platform/chrome/cros_ec_trace.h b/drivers/platform/chrome/cros_ec_trace.h new file mode 100644 index 000000000000..0dd4df30fa89 --- /dev/null +++ b/drivers/platform/chrome/cros_ec_trace.h @@ -0,0 +1,53 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Trace events for the ChromeOS Embedded Controller + * + * Copyright 2019 Google LLC. + */ + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM cros_ec + +#if !defined(_CROS_EC_TRACE_H_) || defined(TRACE_HEADER_MULTI_READ) +#define _CROS_EC_TRACE_H_ + +#include <linux/bits.h> +#include <linux/types.h> +#include <linux/platform_data/cros_ec_commands.h> +#include <linux/platform_data/cros_ec_proto.h> + +#include <linux/tracepoint.h> + +DECLARE_EVENT_CLASS(cros_ec_cmd_class, + TP_PROTO(struct cros_ec_command *cmd), + TP_ARGS(cmd), + TP_STRUCT__entry( + __field(uint32_t, version) + __field(uint32_t, command) + ), + TP_fast_assign( + __entry->version = cmd->version; + __entry->command = cmd->command; + ), + TP_printk("version: %u, command: %s", __entry->version, + __print_symbolic(__entry->command, EC_CMDS)) +); + + +DEFINE_EVENT(cros_ec_cmd_class, cros_ec_cmd, + TP_PROTO(struct cros_ec_command *cmd), + TP_ARGS(cmd) +); + + +#endif /* _CROS_EC_TRACE_H_ */ + +/* this part must be outside header guard */ + +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . + +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE cros_ec_trace + +#include <trace/define_trace.h> diff --git a/drivers/platform/chrome/cros_ec_vbc.c b/drivers/platform/chrome/cros_ec_vbc.c index 8392a1ec33a7..f11a1283e5c8 100644 --- a/drivers/platform/chrome/cros_ec_vbc.c +++ b/drivers/platform/chrome/cros_ec_vbc.c @@ -7,8 +7,9 @@ #include <linux/of.h> #include <linux/platform_device.h> #include <linux/mfd/cros_ec.h> -#include <linux/mfd/cros_ec_commands.h> #include <linux/module.h> +#include <linux/platform_data/cros_ec_commands.h> +#include <linux/platform_data/cros_ec_proto.h> #include <linux/slab.h> #define DRV_NAME "cros-ec-vbc" @@ -101,7 +102,7 @@ static struct bin_attribute *cros_ec_vbc_bin_attrs[] = { NULL }; -struct attribute_group cros_ec_vbc_attr_group = { +static struct attribute_group cros_ec_vbc_attr_group = { .name = "vbc", .bin_attrs = cros_ec_vbc_bin_attrs, }; diff --git a/drivers/platform/chrome/cros_usbpd_logger.c b/drivers/platform/chrome/cros_usbpd_logger.c new file mode 100644 index 000000000000..2430e8b82810 --- /dev/null +++ b/drivers/platform/chrome/cros_usbpd_logger.c @@ -0,0 +1,266 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Logging driver for ChromeOS EC based USBPD Charger. + * + * Copyright 2018 Google LLC. + */ + +#include <linux/ktime.h> +#include <linux/mfd/cros_ec.h> +#include <linux/math64.h> +#include <linux/module.h> +#include <linux/platform_data/cros_ec_commands.h> +#include <linux/platform_data/cros_ec_proto.h> +#include <linux/platform_device.h> +#include <linux/rtc.h> + +#define DRV_NAME "cros-usbpd-logger" + +#define CROS_USBPD_MAX_LOG_ENTRIES 30 +#define CROS_USBPD_LOG_UPDATE_DELAY msecs_to_jiffies(60000) +#define CROS_USBPD_DATA_SIZE 16 +#define CROS_USBPD_LOG_RESP_SIZE (sizeof(struct ec_response_pd_log) + \ + CROS_USBPD_DATA_SIZE) +#define CROS_USBPD_BUFFER_SIZE (sizeof(struct cros_ec_command) + \ + CROS_USBPD_LOG_RESP_SIZE) +/* Buffer for building the PDLOG string */ +#define BUF_SIZE 80 + +struct logger_data { + struct device *dev; + struct cros_ec_dev *ec_dev; + u8 ec_buffer[CROS_USBPD_BUFFER_SIZE]; + struct delayed_work log_work; + struct workqueue_struct *log_workqueue; +}; + +static const char * const chg_type_names[] = { + "None", "PD", "Type-C", "Proprietary", "DCP", "CDP", "SDP", + "Other", "VBUS" +}; + +static const char * const role_names[] = { + "Disconnected", "SRC", "SNK", "SNK (not charging)" +}; + +static const char * const fault_names[] = { + "---", "OCP", "fast OCP", "OVP", "Discharge" +}; + +static int append_str(char *buf, int pos, const char *fmt, ...) +{ + va_list args; + int i; + + va_start(args, fmt); + i = vsnprintf(buf + pos, BUF_SIZE - pos, fmt, args); + va_end(args); + + return i; +} + +static struct ec_response_pd_log *ec_get_log_entry(struct logger_data *logger) +{ + struct cros_ec_dev *ec_dev = logger->ec_dev; + struct cros_ec_command *msg; + int ret; + + msg = (struct cros_ec_command *)logger->ec_buffer; + + msg->command = ec_dev->cmd_offset + EC_CMD_PD_GET_LOG_ENTRY; + msg->insize = CROS_USBPD_LOG_RESP_SIZE; + + ret = cros_ec_cmd_xfer_status(ec_dev->ec_dev, msg); + if (ret < 0) + return ERR_PTR(ret); + + return (struct ec_response_pd_log *)msg->data; +} + +static void cros_usbpd_print_log_entry(struct ec_response_pd_log *r, + ktime_t tstamp) +{ + const char *fault, *role, *chg_type; + struct usb_chg_measures *meas; + struct mcdp_info *minfo; + int role_idx, type_idx; + char buf[BUF_SIZE + 1]; + struct rtc_time rt; + int len = 0; + s32 rem; + int i; + + /* The timestamp is the number of 1024th of seconds in the past */ + tstamp = ktime_sub_us(tstamp, r->timestamp << PD_LOG_TIMESTAMP_SHIFT); + rt = rtc_ktime_to_tm(tstamp); + + switch (r->type) { + case PD_EVENT_MCU_CHARGE: + if (r->data & CHARGE_FLAGS_OVERRIDE) + len += append_str(buf, len, "override "); + + if (r->data & CHARGE_FLAGS_DELAYED_OVERRIDE) + len += append_str(buf, len, "pending_override "); + + role_idx = r->data & CHARGE_FLAGS_ROLE_MASK; + role = role_idx < ARRAY_SIZE(role_names) ? + role_names[role_idx] : "Unknown"; + + type_idx = (r->data & CHARGE_FLAGS_TYPE_MASK) + >> CHARGE_FLAGS_TYPE_SHIFT; + + chg_type = type_idx < ARRAY_SIZE(chg_type_names) ? + chg_type_names[type_idx] : "???"; + + if (role_idx == USB_PD_PORT_POWER_DISCONNECTED || + role_idx == USB_PD_PORT_POWER_SOURCE) { + len += append_str(buf, len, "%s", role); + break; + } + + meas = (struct usb_chg_measures *)r->payload; + len += append_str(buf, len, "%s %s %s %dmV max %dmV / %dmA", + role, r->data & CHARGE_FLAGS_DUAL_ROLE ? + "DRP" : "Charger", + chg_type, meas->voltage_now, + meas->voltage_max, meas->current_max); + break; + case PD_EVENT_ACC_RW_FAIL: + len += append_str(buf, len, "RW signature check failed"); + break; + case PD_EVENT_PS_FAULT: + fault = r->data < ARRAY_SIZE(fault_names) ? fault_names[r->data] + : "???"; + len += append_str(buf, len, "Power supply fault: %s", fault); + break; + case PD_EVENT_VIDEO_DP_MODE: + len += append_str(buf, len, "DP mode %sabled", r->data == 1 ? + "en" : "dis"); + break; + case PD_EVENT_VIDEO_CODEC: + minfo = (struct mcdp_info *)r->payload; + len += append_str(buf, len, "HDMI info: family:%04x chipid:%04x ", + MCDP_FAMILY(minfo->family), + MCDP_CHIPID(minfo->chipid)); + len += append_str(buf, len, "irom:%d.%d.%d fw:%d.%d.%d", + minfo->irom.major, minfo->irom.minor, + minfo->irom.build, minfo->fw.major, + minfo->fw.minor, minfo->fw.build); + break; + default: + len += append_str(buf, len, "Event %02x (%04x) [", r->type, + r->data); + + for (i = 0; i < PD_LOG_SIZE(r->size_port); i++) + len += append_str(buf, len, "%02x ", r->payload[i]); + + len += append_str(buf, len, "]"); + break; + } + + div_s64_rem(ktime_to_ms(tstamp), MSEC_PER_SEC, &rem); + pr_info("PDLOG %d/%02d/%02d %02d:%02d:%02d.%03d P%d %s\n", + rt.tm_year + 1900, rt.tm_mon + 1, rt.tm_mday, + rt.tm_hour, rt.tm_min, rt.tm_sec, rem, + PD_LOG_PORT(r->size_port), buf); +} + +static void cros_usbpd_log_check(struct work_struct *work) +{ + struct logger_data *logger = container_of(to_delayed_work(work), + struct logger_data, + log_work); + struct device *dev = logger->dev; + struct ec_response_pd_log *r; + int entries = 0; + ktime_t now; + + while (entries++ < CROS_USBPD_MAX_LOG_ENTRIES) { + r = ec_get_log_entry(logger); + now = ktime_get_real(); + if (IS_ERR(r)) { + dev_dbg(dev, "Cannot get PD log %ld\n", PTR_ERR(r)); + break; + } + if (r->type == PD_EVENT_NO_ENTRY) + break; + + cros_usbpd_print_log_entry(r, now); + } + + queue_delayed_work(logger->log_workqueue, &logger->log_work, + CROS_USBPD_LOG_UPDATE_DELAY); +} + +static int cros_usbpd_logger_probe(struct platform_device *pd) +{ + struct cros_ec_dev *ec_dev = dev_get_drvdata(pd->dev.parent); + struct device *dev = &pd->dev; + struct logger_data *logger; + + logger = devm_kzalloc(dev, sizeof(*logger), GFP_KERNEL); + if (!logger) + return -ENOMEM; + + logger->dev = dev; + logger->ec_dev = ec_dev; + + platform_set_drvdata(pd, logger); + + /* Retrieve PD event logs periodically */ + INIT_DELAYED_WORK(&logger->log_work, cros_usbpd_log_check); + logger->log_workqueue = create_singlethread_workqueue("cros_usbpd_log"); + if (!logger->log_workqueue) + return -ENOMEM; + + queue_delayed_work(logger->log_workqueue, &logger->log_work, + CROS_USBPD_LOG_UPDATE_DELAY); + + return 0; +} + +static int cros_usbpd_logger_remove(struct platform_device *pd) +{ + struct logger_data *logger = platform_get_drvdata(pd); + + cancel_delayed_work_sync(&logger->log_work); + + return 0; +} + +static int __maybe_unused cros_usbpd_logger_resume(struct device *dev) +{ + struct logger_data *logger = dev_get_drvdata(dev); + + queue_delayed_work(logger->log_workqueue, &logger->log_work, + CROS_USBPD_LOG_UPDATE_DELAY); + + return 0; +} + +static int __maybe_unused cros_usbpd_logger_suspend(struct device *dev) +{ + struct logger_data *logger = dev_get_drvdata(dev); + + cancel_delayed_work_sync(&logger->log_work); + + return 0; +} + +static SIMPLE_DEV_PM_OPS(cros_usbpd_logger_pm_ops, cros_usbpd_logger_suspend, + cros_usbpd_logger_resume); + +static struct platform_driver cros_usbpd_logger_driver = { + .driver = { + .name = DRV_NAME, + .pm = &cros_usbpd_logger_pm_ops, + }, + .probe = cros_usbpd_logger_probe, + .remove = cros_usbpd_logger_remove, +}; + +module_platform_driver(cros_usbpd_logger_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Logging driver for ChromeOS EC USBPD Charger."); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/drivers/platform/chrome/wilco_ec/Kconfig b/drivers/platform/chrome/wilco_ec/Kconfig index e09e4cebe9b4..89007b0bc743 100644 --- a/drivers/platform/chrome/wilco_ec/Kconfig +++ b/drivers/platform/chrome/wilco_ec/Kconfig @@ -1,6 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0-only config WILCO_EC tristate "ChromeOS Wilco Embedded Controller" - depends on ACPI && X86 && CROS_EC_LPC && CROS_EC_LPC_MEC + depends on ACPI && X86 && CROS_EC_LPC help If you say Y here, you get support for talking to the ChromeOS Wilco EC over an eSPI bus. This uses a simple byte-level protocol @@ -18,3 +19,19 @@ config WILCO_EC_DEBUGFS manipulation and allow for testing arbitrary commands. This interface is intended for debug only and will not be present on production devices. + +config WILCO_EC_EVENTS + tristate "Enable event forwarding from EC to userspace" + depends on WILCO_EC + help + If you say Y here, you get support for the EC to send events + (such as power state changes) to userspace. The EC sends the events + over ACPI, and a driver queues up the events to be read by a + userspace daemon from /dev/wilco_event using read() and poll(). + +config WILCO_EC_TELEMETRY + tristate "Enable querying telemetry data from EC" + depends on WILCO_EC + help + If you say Y here, you get support to query EC telemetry data from + /dev/wilco_telem0 using write() and then read(). diff --git a/drivers/platform/chrome/wilco_ec/Makefile b/drivers/platform/chrome/wilco_ec/Makefile index 063e7fb4ea17..bc817164596e 100644 --- a/drivers/platform/chrome/wilco_ec/Makefile +++ b/drivers/platform/chrome/wilco_ec/Makefile @@ -1,6 +1,10 @@ # SPDX-License-Identifier: GPL-2.0 -wilco_ec-objs := core.o mailbox.o +wilco_ec-objs := core.o mailbox.o properties.o sysfs.o obj-$(CONFIG_WILCO_EC) += wilco_ec.o wilco_ec_debugfs-objs := debugfs.o obj-$(CONFIG_WILCO_EC_DEBUGFS) += wilco_ec_debugfs.o +wilco_ec_events-objs := event.o +obj-$(CONFIG_WILCO_EC_EVENTS) += wilco_ec_events.o +wilco_ec_telem-objs := telemetry.o +obj-$(CONFIG_WILCO_EC_TELEMETRY) += wilco_ec_telem.o diff --git a/drivers/platform/chrome/wilco_ec/core.c b/drivers/platform/chrome/wilco_ec/core.c index 05e1e2be1c91..3724bf4b77c6 100644 --- a/drivers/platform/chrome/wilco_ec/core.c +++ b/drivers/platform/chrome/wilco_ec/core.c @@ -52,9 +52,7 @@ static int wilco_ec_probe(struct platform_device *pdev) ec->dev = dev; mutex_init(&ec->mailbox_lock); - /* Largest data buffer size requirement is extended data response */ - ec->data_size = sizeof(struct wilco_ec_response) + - EC_MAILBOX_DATA_SIZE_EXTENDED; + ec->data_size = sizeof(struct wilco_ec_response) + EC_MAILBOX_DATA_SIZE; ec->data_buffer = devm_kzalloc(dev, ec->data_size, GFP_KERNEL); if (!ec->data_buffer) return -ENOMEM; @@ -89,8 +87,28 @@ static int wilco_ec_probe(struct platform_device *pdev) goto unregister_debugfs; } + ret = wilco_ec_add_sysfs(ec); + if (ret < 0) { + dev_err(dev, "Failed to create sysfs entries: %d", ret); + goto unregister_rtc; + } + + /* Register child device that will be found by the telemetry driver. */ + ec->telem_pdev = platform_device_register_data(dev, "wilco_telem", + PLATFORM_DEVID_AUTO, + ec, sizeof(*ec)); + if (IS_ERR(ec->telem_pdev)) { + dev_err(dev, "Failed to create telemetry platform device\n"); + ret = PTR_ERR(ec->telem_pdev); + goto remove_sysfs; + } + return 0; +remove_sysfs: + wilco_ec_remove_sysfs(ec); +unregister_rtc: + platform_device_unregister(ec->rtc_pdev); unregister_debugfs: if (ec->debugfs_pdev) platform_device_unregister(ec->debugfs_pdev); @@ -102,6 +120,8 @@ static int wilco_ec_remove(struct platform_device *pdev) { struct wilco_ec_device *ec = platform_get_drvdata(pdev); + wilco_ec_remove_sysfs(ec); + platform_device_unregister(ec->telem_pdev); platform_device_unregister(ec->rtc_pdev); if (ec->debugfs_pdev) platform_device_unregister(ec->debugfs_pdev); diff --git a/drivers/platform/chrome/wilco_ec/debugfs.c b/drivers/platform/chrome/wilco_ec/debugfs.c index c090db2cd5be..8d65a1e2f1a3 100644 --- a/drivers/platform/chrome/wilco_ec/debugfs.c +++ b/drivers/platform/chrome/wilco_ec/debugfs.c @@ -4,31 +4,7 @@ * * Copyright 2019 Google LLC * - * There is only one attribute used for debugging, called raw. - * You can write a hexadecimal sentence to raw, and that series of bytes - * will be sent to the EC. Then, you can read the bytes of response - * by reading from raw. - * - * For writing: - * Bytes 0-1 indicate the message type: - * 00 F0 = Execute Legacy Command - * 00 F2 = Read/Write NVRAM Property - * Byte 2 provides the command code - * Bytes 3+ consist of the data passed in the request - * - * When referencing the EC interface spec, byte 2 corresponds to MBOX[0], - * byte 3 corresponds to MBOX[1], etc. - * - * At least three bytes are required, for the msg type and command, - * with additional bytes optional for additional data. - * - * Example: - * // Request EC info type 3 (EC firmware build date) - * $ echo 00 f0 38 00 03 00 > raw - * // View the result. The decoded ASCII result "12/21/18" is - * // included after the raw hex. - * $ cat raw - * 00 31 32 2f 32 31 2f 31 38 00 38 00 01 00 2f 00 .12/21/18.8... + * See Documentation/ABI/testing/debugfs-wilco-ec for usage. */ #include <linux/ctype.h> @@ -40,14 +16,14 @@ #define DRV_NAME "wilco-ec-debugfs" -/* The 256 raw bytes will take up more space when represented as a hex string */ -#define FORMATTED_BUFFER_SIZE (EC_MAILBOX_DATA_SIZE_EXTENDED * 4) +/* The raw bytes will take up more space when represented as a hex string */ +#define FORMATTED_BUFFER_SIZE (EC_MAILBOX_DATA_SIZE * 4) struct wilco_ec_debugfs { struct wilco_ec_device *ec; struct dentry *dir; size_t response_size; - u8 raw_data[EC_MAILBOX_DATA_SIZE_EXTENDED]; + u8 raw_data[EC_MAILBOX_DATA_SIZE]; u8 formatted_data[FORMATTED_BUFFER_SIZE]; }; static struct wilco_ec_debugfs *debug_info; @@ -136,27 +112,18 @@ static ssize_t raw_write(struct file *file, const char __user *user_buf, ret = parse_hex_sentence(buf, kcount, request_data, TYPE_AND_DATA_SIZE); if (ret < 0) return ret; - /* Need at least two bytes for message type and one for command */ + /* Need at least two bytes for message type and one byte of data */ if (ret < 3) return -EINVAL; - /* Clear response data buffer */ - memset(debug_info->raw_data, '\0', EC_MAILBOX_DATA_SIZE_EXTENDED); - msg.type = request_data[0] << 8 | request_data[1]; - msg.flags = WILCO_EC_FLAG_RAW; - msg.command = request_data[2]; - msg.request_data = ret > 3 ? request_data + 3 : 0; - msg.request_size = ret - 3; + msg.flags = 0; + msg.request_data = request_data + 2; + msg.request_size = ret - 2; + memset(debug_info->raw_data, 0, sizeof(debug_info->raw_data)); msg.response_data = debug_info->raw_data; msg.response_size = EC_MAILBOX_DATA_SIZE; - /* Telemetry commands use extended response data */ - if (msg.type == WILCO_EC_MSG_TELEMETRY_LONG) { - msg.flags |= WILCO_EC_FLAG_EXTENDED_DATA; - msg.response_size = EC_MAILBOX_DATA_SIZE_EXTENDED; - } - ret = wilco_ec_mailbox(debug_info->ec, &msg); if (ret < 0) return ret; @@ -174,7 +141,8 @@ static ssize_t raw_read(struct file *file, char __user *user_buf, size_t count, fmt_len = hex_dump_to_buffer(debug_info->raw_data, debug_info->response_size, 16, 1, debug_info->formatted_data, - FORMATTED_BUFFER_SIZE, true); + sizeof(debug_info->formatted_data), + true); /* Only return response the first time it is read */ debug_info->response_size = 0; } @@ -190,6 +158,51 @@ static const struct file_operations fops_raw = { .llseek = no_llseek, }; +#define CMD_KB_CHROME 0x88 +#define SUB_CMD_H1_GPIO 0x0A + +struct h1_gpio_status_request { + u8 cmd; /* Always CMD_KB_CHROME */ + u8 reserved; + u8 sub_cmd; /* Always SUB_CMD_H1_GPIO */ +} __packed; + +struct hi_gpio_status_response { + u8 status; /* 0 if allowed */ + u8 val; /* BIT(0)=ENTRY_TO_FACT_MODE, BIT(1)=SPI_CHROME_SEL */ +} __packed; + +static int h1_gpio_get(void *arg, u64 *val) +{ + struct wilco_ec_device *ec = arg; + struct h1_gpio_status_request rq; + struct hi_gpio_status_response rs; + struct wilco_ec_message msg; + int ret; + + memset(&rq, 0, sizeof(rq)); + rq.cmd = CMD_KB_CHROME; + rq.sub_cmd = SUB_CMD_H1_GPIO; + + memset(&msg, 0, sizeof(msg)); + msg.type = WILCO_EC_MSG_LEGACY; + msg.request_data = &rq; + msg.request_size = sizeof(rq); + msg.response_data = &rs; + msg.response_size = sizeof(rs); + ret = wilco_ec_mailbox(ec, &msg); + if (ret < 0) + return ret; + if (rs.status) + return -EIO; + + *val = rs.val; + + return 0; +} + +DEFINE_DEBUGFS_ATTRIBUTE(fops_h1_gpio, h1_gpio_get, NULL, "0x%02llx\n"); + /** * wilco_ec_debugfs_probe() - Create the debugfs node * @pdev: The platform device, probably created in core.c @@ -211,6 +224,8 @@ static int wilco_ec_debugfs_probe(struct platform_device *pdev) if (!debug_info->dir) return 0; debugfs_create_file("raw", 0644, debug_info->dir, NULL, &fops_raw); + debugfs_create_file("h1_gpio", 0444, debug_info->dir, ec, + &fops_h1_gpio); return 0; } diff --git a/drivers/platform/chrome/wilco_ec/event.c b/drivers/platform/chrome/wilco_ec/event.c new file mode 100644 index 000000000000..dba3d445623f --- /dev/null +++ b/drivers/platform/chrome/wilco_ec/event.c @@ -0,0 +1,581 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ACPI event handling for Wilco Embedded Controller + * + * Copyright 2019 Google LLC + * + * The Wilco Embedded Controller can create custom events that + * are not handled as standard ACPI objects. These events can + * contain information about changes in EC controlled features, + * such as errors and events in the dock or display. For example, + * an event is triggered if the dock is plugged into a display + * incorrectly. These events are needed for telemetry and + * diagnostics reasons, and for possibly alerting the user. + + * These events are triggered by the EC with an ACPI Notify(0x90), + * and then the BIOS reads the event buffer from EC RAM via an + * ACPI method. When the OS receives these events via ACPI, + * it passes them along to this driver. The events are put into + * a queue which can be read by a userspace daemon via a char device + * that implements read() and poll(). The event queue acts as a + * circular buffer of size 64, so if there are no userspace consumers + * the kernel will not run out of memory. The char device will appear at + * /dev/wilco_event{n}, where n is some small non-negative integer, + * starting from 0. Standard ACPI events such as the battery getting + * plugged/unplugged can also come through this path, but they are + * dealt with via other paths, and are ignored here. + + * To test, you can tail the binary data with + * $ cat /dev/wilco_event0 | hexdump -ve '1/1 "%x\n"' + * and then create an event by plugging/unplugging the battery. + */ + +#include <linux/acpi.h> +#include <linux/cdev.h> +#include <linux/device.h> +#include <linux/fs.h> +#include <linux/idr.h> +#include <linux/io.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/poll.h> +#include <linux/spinlock.h> +#include <linux/uaccess.h> +#include <linux/wait.h> + +/* ACPI Notify event code indicating event data is available. */ +#define EC_ACPI_NOTIFY_EVENT 0x90 +/* ACPI Method to execute to retrieve event data buffer from the EC. */ +#define EC_ACPI_GET_EVENT "QSET" +/* Maximum number of words in event data returned by the EC. */ +#define EC_ACPI_MAX_EVENT_WORDS 6 +#define EC_ACPI_MAX_EVENT_SIZE \ + (sizeof(struct ec_event) + (EC_ACPI_MAX_EVENT_WORDS) * sizeof(u16)) + +/* Node will appear in /dev/EVENT_DEV_NAME */ +#define EVENT_DEV_NAME "wilco_event" +#define EVENT_CLASS_NAME EVENT_DEV_NAME +#define DRV_NAME EVENT_DEV_NAME +#define EVENT_DEV_NAME_FMT (EVENT_DEV_NAME "%d") +static struct class event_class = { + .owner = THIS_MODULE, + .name = EVENT_CLASS_NAME, +}; + +/* Keep track of all the device numbers used. */ +#define EVENT_MAX_DEV 128 +static int event_major; +static DEFINE_IDA(event_ida); + +/* Size of circular queue of events. */ +#define MAX_NUM_EVENTS 64 + +/** + * struct ec_event - Extended event returned by the EC. + * @size: Number of 16bit words in structure after the size word. + * @type: Extended event type, meaningless for us. + * @event: Event data words. Max count is %EC_ACPI_MAX_EVENT_WORDS. + */ +struct ec_event { + u16 size; + u16 type; + u16 event[0]; +} __packed; + +#define ec_event_num_words(ev) (ev->size - 1) +#define ec_event_size(ev) (sizeof(*ev) + (ec_event_num_words(ev) * sizeof(u16))) + +/** + * struct ec_event_queue - Circular queue for events. + * @capacity: Number of elements the queue can hold. + * @head: Next index to write to. + * @tail: Next index to read from. + * @entries: Array of events. + */ +struct ec_event_queue { + int capacity; + int head; + int tail; + struct ec_event *entries[0]; +}; + +/* Maximum number of events to store in ec_event_queue */ +static int queue_size = 64; +module_param(queue_size, int, 0644); + +static struct ec_event_queue *event_queue_new(int capacity) +{ + struct ec_event_queue *q; + + q = kzalloc(struct_size(q, entries, capacity), GFP_KERNEL); + if (!q) + return NULL; + + q->capacity = capacity; + + return q; +} + +static inline bool event_queue_empty(struct ec_event_queue *q) +{ + /* head==tail when both full and empty, but head==NULL when empty */ + return q->head == q->tail && !q->entries[q->head]; +} + +static inline bool event_queue_full(struct ec_event_queue *q) +{ + /* head==tail when both full and empty, but head!=NULL when full */ + return q->head == q->tail && q->entries[q->head]; +} + +static struct ec_event *event_queue_pop(struct ec_event_queue *q) +{ + struct ec_event *ev; + + if (event_queue_empty(q)) + return NULL; + + ev = q->entries[q->tail]; + q->entries[q->tail] = NULL; + q->tail = (q->tail + 1) % q->capacity; + + return ev; +} + +/* + * If full, overwrite the oldest event and return it so the caller + * can kfree it. If not full, return NULL. + */ +static struct ec_event *event_queue_push(struct ec_event_queue *q, + struct ec_event *ev) +{ + struct ec_event *popped = NULL; + + if (event_queue_full(q)) + popped = event_queue_pop(q); + q->entries[q->head] = ev; + q->head = (q->head + 1) % q->capacity; + + return popped; +} + +static void event_queue_free(struct ec_event_queue *q) +{ + struct ec_event *event; + + while ((event = event_queue_pop(q)) != NULL) + kfree(event); + + kfree(q); +} + +/** + * struct event_device_data - Data for a Wilco EC device that responds to ACPI. + * @events: Circular queue of EC events to be provided to userspace. + * @queue_lock: Protect the queue from simultaneous read/writes. + * @wq: Wait queue to notify processes when events are available or the + * device has been removed. + * @cdev: Char dev that userspace reads() and polls() from. + * @dev: Device associated with the %cdev. + * @exist: Has the device been not been removed? Once a device has been removed, + * writes, reads, and new opens will fail. + * @available: Guarantee only one client can open() file and read from queue. + * + * There will be one of these structs for each ACPI device registered. This data + * is the queue of events received from ACPI that still need to be read from + * userspace, the device and char device that userspace is using, a wait queue + * used to notify different threads when something has changed, plus a flag + * on whether the ACPI device has been removed. + */ +struct event_device_data { + struct ec_event_queue *events; + spinlock_t queue_lock; + wait_queue_head_t wq; + struct device dev; + struct cdev cdev; + bool exist; + atomic_t available; +}; + +/** + * enqueue_events() - Place EC events in queue to be read by userspace. + * @adev: Device the events came from. + * @buf: Buffer of event data. + * @length: Length of event data buffer. + * + * %buf contains a number of ec_event's, packed one after the other. + * Each ec_event is of variable length. Start with the first event, copy it + * into a persistent ec_event, store that entry in the queue, move on + * to the next ec_event in buf, and repeat. + * + * Return: 0 on success or negative error code on failure. + */ +static int enqueue_events(struct acpi_device *adev, const u8 *buf, u32 length) +{ + struct event_device_data *dev_data = adev->driver_data; + struct ec_event *event, *queue_event, *old_event; + size_t num_words, event_size; + u32 offset = 0; + + while (offset < length) { + event = (struct ec_event *)(buf + offset); + + num_words = ec_event_num_words(event); + event_size = ec_event_size(event); + if (num_words > EC_ACPI_MAX_EVENT_WORDS) { + dev_err(&adev->dev, "Too many event words: %zu > %d\n", + num_words, EC_ACPI_MAX_EVENT_WORDS); + return -EOVERFLOW; + } + + /* Ensure event does not overflow the available buffer */ + if ((offset + event_size) > length) { + dev_err(&adev->dev, "Event exceeds buffer: %zu > %d\n", + offset + event_size, length); + return -EOVERFLOW; + } + + /* Point to the next event in the buffer */ + offset += event_size; + + /* Copy event into the queue */ + queue_event = kmemdup(event, event_size, GFP_KERNEL); + if (!queue_event) + return -ENOMEM; + spin_lock(&dev_data->queue_lock); + old_event = event_queue_push(dev_data->events, queue_event); + spin_unlock(&dev_data->queue_lock); + kfree(old_event); + wake_up_interruptible(&dev_data->wq); + } + + return 0; +} + +/** + * event_device_notify() - Callback when EC generates an event over ACPI. + * @adev: The device that the event is coming from. + * @value: Value passed to Notify() in ACPI. + * + * This function will read the events from the device and enqueue them. + */ +static void event_device_notify(struct acpi_device *adev, u32 value) +{ + struct acpi_buffer event_buffer = { ACPI_ALLOCATE_BUFFER, NULL }; + union acpi_object *obj; + acpi_status status; + + if (value != EC_ACPI_NOTIFY_EVENT) { + dev_err(&adev->dev, "Invalid event: 0x%08x\n", value); + return; + } + + /* Execute ACPI method to get event data buffer. */ + status = acpi_evaluate_object(adev->handle, EC_ACPI_GET_EVENT, + NULL, &event_buffer); + if (ACPI_FAILURE(status)) { + dev_err(&adev->dev, "Error executing ACPI method %s()\n", + EC_ACPI_GET_EVENT); + return; + } + + obj = (union acpi_object *)event_buffer.pointer; + if (!obj) { + dev_err(&adev->dev, "Nothing returned from %s()\n", + EC_ACPI_GET_EVENT); + return; + } + if (obj->type != ACPI_TYPE_BUFFER) { + dev_err(&adev->dev, "Invalid object returned from %s()\n", + EC_ACPI_GET_EVENT); + kfree(obj); + return; + } + if (obj->buffer.length < sizeof(struct ec_event)) { + dev_err(&adev->dev, "Invalid buffer length %d from %s()\n", + obj->buffer.length, EC_ACPI_GET_EVENT); + kfree(obj); + return; + } + + enqueue_events(adev, obj->buffer.pointer, obj->buffer.length); + kfree(obj); +} + +static int event_open(struct inode *inode, struct file *filp) +{ + struct event_device_data *dev_data; + + dev_data = container_of(inode->i_cdev, struct event_device_data, cdev); + if (!dev_data->exist) + return -ENODEV; + + if (atomic_cmpxchg(&dev_data->available, 1, 0) == 0) + return -EBUSY; + + /* Increase refcount on device so dev_data is not freed */ + get_device(&dev_data->dev); + stream_open(inode, filp); + filp->private_data = dev_data; + + return 0; +} + +static __poll_t event_poll(struct file *filp, poll_table *wait) +{ + struct event_device_data *dev_data = filp->private_data; + __poll_t mask = 0; + + poll_wait(filp, &dev_data->wq, wait); + if (!dev_data->exist) + return EPOLLHUP; + if (!event_queue_empty(dev_data->events)) + mask |= EPOLLIN | EPOLLRDNORM | EPOLLPRI; + return mask; +} + +/** + * event_read() - Callback for passing event data to userspace via read(). + * @filp: The file we are reading from. + * @buf: Pointer to userspace buffer to fill with one event. + * @count: Number of bytes requested. Must be at least EC_ACPI_MAX_EVENT_SIZE. + * @pos: File position pointer, irrelevant since we don't support seeking. + * + * Removes the first event from the queue, places it in the passed buffer. + * + * If there are no events in the the queue, then one of two things happens, + * depending on if the file was opened in nonblocking mode: If in nonblocking + * mode, then return -EAGAIN to say there's no data. If in blocking mode, then + * block until an event is available. + * + * Return: Number of bytes placed in buffer, negative error code on failure. + */ +static ssize_t event_read(struct file *filp, char __user *buf, size_t count, + loff_t *pos) +{ + struct event_device_data *dev_data = filp->private_data; + struct ec_event *event; + ssize_t n_bytes_written = 0; + int err; + + /* We only will give them the entire event at once */ + if (count != 0 && count < EC_ACPI_MAX_EVENT_SIZE) + return -EINVAL; + + spin_lock(&dev_data->queue_lock); + while (event_queue_empty(dev_data->events)) { + spin_unlock(&dev_data->queue_lock); + if (filp->f_flags & O_NONBLOCK) + return -EAGAIN; + + err = wait_event_interruptible(dev_data->wq, + !event_queue_empty(dev_data->events) || + !dev_data->exist); + if (err) + return err; + + /* Device was removed as we waited? */ + if (!dev_data->exist) + return -ENODEV; + spin_lock(&dev_data->queue_lock); + } + event = event_queue_pop(dev_data->events); + spin_unlock(&dev_data->queue_lock); + n_bytes_written = ec_event_size(event); + if (copy_to_user(buf, event, n_bytes_written)) + n_bytes_written = -EFAULT; + kfree(event); + + return n_bytes_written; +} + +static int event_release(struct inode *inode, struct file *filp) +{ + struct event_device_data *dev_data = filp->private_data; + + atomic_set(&dev_data->available, 1); + put_device(&dev_data->dev); + + return 0; +} + +static const struct file_operations event_fops = { + .open = event_open, + .poll = event_poll, + .read = event_read, + .release = event_release, + .llseek = no_llseek, + .owner = THIS_MODULE, +}; + +/** + * free_device_data() - Callback to free the event_device_data structure. + * @d: The device embedded in our device data, which we have been ref counting. + * + * This is called only after event_device_remove() has been called and all + * userspace programs have called event_release() on all the open file + * descriptors. + */ +static void free_device_data(struct device *d) +{ + struct event_device_data *dev_data; + + dev_data = container_of(d, struct event_device_data, dev); + event_queue_free(dev_data->events); + kfree(dev_data); +} + +static void hangup_device(struct event_device_data *dev_data) +{ + dev_data->exist = false; + /* Wake up the waiting processes so they can close. */ + wake_up_interruptible(&dev_data->wq); + put_device(&dev_data->dev); +} + +/** + * event_device_add() - Callback when creating a new device. + * @adev: ACPI device that we will be receiving events from. + * + * This finds a free minor number for the device, allocates and initializes + * some device data, and creates a new device and char dev node. + * + * The device data is freed in free_device_data(), which is called when + * %dev_data->dev is release()ed. This happens after all references to + * %dev_data->dev are dropped, which happens once both event_device_remove() + * has been called and every open()ed file descriptor has been release()ed. + * + * Return: 0 on success, negative error code on failure. + */ +static int event_device_add(struct acpi_device *adev) +{ + struct event_device_data *dev_data; + int error, minor; + + minor = ida_alloc_max(&event_ida, EVENT_MAX_DEV-1, GFP_KERNEL); + if (minor < 0) { + error = minor; + dev_err(&adev->dev, "Failed to find minor number: %d\n", error); + return error; + } + + dev_data = kzalloc(sizeof(*dev_data), GFP_KERNEL); + if (!dev_data) { + error = -ENOMEM; + goto free_minor; + } + + /* Initialize the device data. */ + adev->driver_data = dev_data; + dev_data->events = event_queue_new(queue_size); + if (!dev_data->events) { + kfree(dev_data); + error = -ENOMEM; + goto free_minor; + } + spin_lock_init(&dev_data->queue_lock); + init_waitqueue_head(&dev_data->wq); + dev_data->exist = true; + atomic_set(&dev_data->available, 1); + + /* Initialize the device. */ + dev_data->dev.devt = MKDEV(event_major, minor); + dev_data->dev.class = &event_class; + dev_data->dev.release = free_device_data; + dev_set_name(&dev_data->dev, EVENT_DEV_NAME_FMT, minor); + device_initialize(&dev_data->dev); + + /* Initialize the character device, and add it to userspace. */ + cdev_init(&dev_data->cdev, &event_fops); + error = cdev_device_add(&dev_data->cdev, &dev_data->dev); + if (error) + goto free_dev_data; + + return 0; + +free_dev_data: + hangup_device(dev_data); +free_minor: + ida_simple_remove(&event_ida, minor); + return error; +} + +static int event_device_remove(struct acpi_device *adev) +{ + struct event_device_data *dev_data = adev->driver_data; + + cdev_device_del(&dev_data->cdev, &dev_data->dev); + ida_simple_remove(&event_ida, MINOR(dev_data->dev.devt)); + hangup_device(dev_data); + + return 0; +} + +static const struct acpi_device_id event_acpi_ids[] = { + { "GOOG000D", 0 }, + { } +}; +MODULE_DEVICE_TABLE(acpi, event_acpi_ids); + +static struct acpi_driver event_driver = { + .name = DRV_NAME, + .class = DRV_NAME, + .ids = event_acpi_ids, + .ops = { + .add = event_device_add, + .notify = event_device_notify, + .remove = event_device_remove, + }, + .owner = THIS_MODULE, +}; + +static int __init event_module_init(void) +{ + dev_t dev_num = 0; + int ret; + + ret = class_register(&event_class); + if (ret) { + pr_err(DRV_NAME ": Failed registering class: %d\n", ret); + return ret; + } + + /* Request device numbers, starting with minor=0. Save the major num. */ + ret = alloc_chrdev_region(&dev_num, 0, EVENT_MAX_DEV, EVENT_DEV_NAME); + if (ret) { + pr_err(DRV_NAME ": Failed allocating dev numbers: %d\n", ret); + goto destroy_class; + } + event_major = MAJOR(dev_num); + + ret = acpi_bus_register_driver(&event_driver); + if (ret < 0) { + pr_err(DRV_NAME ": Failed registering driver: %d\n", ret); + goto unregister_region; + } + + return 0; + +unregister_region: + unregister_chrdev_region(MKDEV(event_major, 0), EVENT_MAX_DEV); +destroy_class: + class_unregister(&event_class); + ida_destroy(&event_ida); + return ret; +} + +static void __exit event_module_exit(void) +{ + acpi_bus_unregister_driver(&event_driver); + unregister_chrdev_region(MKDEV(event_major, 0), EVENT_MAX_DEV); + class_unregister(&event_class); + ida_destroy(&event_ida); +} + +module_init(event_module_init); +module_exit(event_module_exit); + +MODULE_AUTHOR("Nick Crews <ncrews@chromium.org>"); +MODULE_DESCRIPTION("Wilco EC ACPI event driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/drivers/platform/chrome/wilco_ec/mailbox.c b/drivers/platform/chrome/wilco_ec/mailbox.c index 14355668ddfa..ced1f9f3dcee 100644 --- a/drivers/platform/chrome/wilco_ec/mailbox.c +++ b/drivers/platform/chrome/wilco_ec/mailbox.c @@ -92,21 +92,10 @@ static void wilco_ec_prepare(struct wilco_ec_message *msg, struct wilco_ec_request *rq) { memset(rq, 0, sizeof(*rq)); - - /* Handle messages without trimming bytes from the request */ - if (msg->request_size && msg->flags & WILCO_EC_FLAG_RAW_REQUEST) { - rq->reserved_raw = *(u8 *)msg->request_data; - msg->request_size--; - memmove(msg->request_data, msg->request_data + 1, - msg->request_size); - } - - /* Fill in request packet */ rq->struct_version = EC_MAILBOX_PROTO_VERSION; rq->mailbox_id = msg->type; rq->mailbox_version = EC_MAILBOX_VERSION; - rq->data_size = msg->request_size + EC_MAILBOX_DATA_EXTRA; - rq->command = msg->command; + rq->data_size = msg->request_size; /* Checksum header and data */ rq->checksum = wilco_ec_checksum(rq, sizeof(*rq)); @@ -130,7 +119,6 @@ static int wilco_ec_transfer(struct wilco_ec_device *ec, struct wilco_ec_response *rs; u8 checksum; u8 flag; - size_t size; /* Write request header, then data */ cros_ec_lpc_io_bytes_mec(MEC_IO_WRITE, 0, sizeof(*rq), (u8 *)rq); @@ -159,47 +147,36 @@ static int wilco_ec_transfer(struct wilco_ec_device *ec, return -EIO; } - if (msg->flags & WILCO_EC_FLAG_EXTENDED_DATA) - size = EC_MAILBOX_DATA_SIZE_EXTENDED; - else - size = EC_MAILBOX_DATA_SIZE; - /* Read back response */ rs = ec->data_buffer; checksum = cros_ec_lpc_io_bytes_mec(MEC_IO_READ, 0, - sizeof(*rs) + size, (u8 *)rs); + sizeof(*rs) + EC_MAILBOX_DATA_SIZE, + (u8 *)rs); if (checksum) { dev_dbg(ec->dev, "bad packet checksum 0x%02x\n", rs->checksum); return -EBADMSG; } - /* Check that the EC reported success */ - msg->result = rs->result; - if (msg->result) { - dev_dbg(ec->dev, "bad response: 0x%02x\n", msg->result); + if (rs->result) { + dev_dbg(ec->dev, "EC reported failure: 0x%02x\n", rs->result); return -EBADMSG; } - /* Check the returned data size, skipping the header */ - if (rs->data_size != size) { - dev_dbg(ec->dev, "unexpected packet size (%u != %zu)", - rs->data_size, size); + if (rs->data_size != EC_MAILBOX_DATA_SIZE) { + dev_dbg(ec->dev, "unexpected packet size (%u != %u)", + rs->data_size, EC_MAILBOX_DATA_SIZE); return -EMSGSIZE; } - /* Skip 1 response data byte unless specified */ - size = (msg->flags & WILCO_EC_FLAG_RAW_RESPONSE) ? 0 : 1; - if ((ssize_t) rs->data_size - size < msg->response_size) { - dev_dbg(ec->dev, "response data too short (%zd < %zu)", - (ssize_t) rs->data_size - size, msg->response_size); + if (rs->data_size < msg->response_size) { + dev_dbg(ec->dev, "EC didn't return enough data (%u < %zu)", + rs->data_size, msg->response_size); return -EMSGSIZE; } - /* Ignore response data bytes as requested */ - memcpy(msg->response_data, rs->data + size, msg->response_size); + memcpy(msg->response_data, rs->data, msg->response_size); - /* Return actual amount of data received */ - return msg->response_size; + return rs->data_size; } /** @@ -207,10 +184,12 @@ static int wilco_ec_transfer(struct wilco_ec_device *ec, * @ec: EC device. * @msg: EC message data for request and response. * - * On entry msg->type, msg->flags, msg->command, msg->request_size, - * msg->response_size, and msg->request_data should all be filled in. + * On entry msg->type, msg->request_size, and msg->request_data should all be + * filled in. If desired, msg->flags can be set. * - * On exit msg->result and msg->response_data will be filled. + * If a response is expected, msg->response_size should be set, and + * msg->response_data should point to a buffer with enough space. On exit + * msg->response_data will be filled. * * Return: number of bytes received or negative error code on failure. */ @@ -219,9 +198,8 @@ int wilco_ec_mailbox(struct wilco_ec_device *ec, struct wilco_ec_message *msg) struct wilco_ec_request *rq; int ret; - dev_dbg(ec->dev, "cmd=%02x type=%04x flags=%02x rslen=%zu rqlen=%zu\n", - msg->command, msg->type, msg->flags, msg->response_size, - msg->request_size); + dev_dbg(ec->dev, "type=%04x flags=%02x rslen=%zu rqlen=%zu\n", + msg->type, msg->flags, msg->response_size, msg->request_size); mutex_lock(&ec->mailbox_lock); /* Prepare request packet */ diff --git a/drivers/platform/chrome/wilco_ec/properties.c b/drivers/platform/chrome/wilco_ec/properties.c new file mode 100644 index 000000000000..e69682c95ea2 --- /dev/null +++ b/drivers/platform/chrome/wilco_ec/properties.c @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2019 Google LLC + */ + +#include <linux/platform_data/wilco-ec.h> +#include <linux/string.h> +#include <linux/unaligned/le_memmove.h> + +/* Operation code; what the EC should do with the property */ +enum ec_property_op { + EC_OP_GET = 0, + EC_OP_SET = 1, +}; + +struct ec_property_request { + u8 op; /* One of enum ec_property_op */ + u8 property_id[4]; /* The 32 bit PID is stored Little Endian */ + u8 length; + u8 data[WILCO_EC_PROPERTY_MAX_SIZE]; +} __packed; + +struct ec_property_response { + u8 reserved[2]; + u8 op; /* One of enum ec_property_op */ + u8 property_id[4]; /* The 32 bit PID is stored Little Endian */ + u8 length; + u8 data[WILCO_EC_PROPERTY_MAX_SIZE]; +} __packed; + +static int send_property_msg(struct wilco_ec_device *ec, + struct ec_property_request *rq, + struct ec_property_response *rs) +{ + struct wilco_ec_message ec_msg; + int ret; + + memset(&ec_msg, 0, sizeof(ec_msg)); + ec_msg.type = WILCO_EC_MSG_PROPERTY; + ec_msg.request_data = rq; + ec_msg.request_size = sizeof(*rq); + ec_msg.response_data = rs; + ec_msg.response_size = sizeof(*rs); + + ret = wilco_ec_mailbox(ec, &ec_msg); + if (ret < 0) + return ret; + if (rs->op != rq->op) + return -EBADMSG; + if (memcmp(rq->property_id, rs->property_id, sizeof(rs->property_id))) + return -EBADMSG; + + return 0; +} + +int wilco_ec_get_property(struct wilco_ec_device *ec, + struct wilco_ec_property_msg *prop_msg) +{ + struct ec_property_request rq; + struct ec_property_response rs; + int ret; + + memset(&rq, 0, sizeof(rq)); + rq.op = EC_OP_GET; + put_unaligned_le32(prop_msg->property_id, rq.property_id); + + ret = send_property_msg(ec, &rq, &rs); + if (ret < 0) + return ret; + + prop_msg->length = rs.length; + memcpy(prop_msg->data, rs.data, rs.length); + + return 0; +} +EXPORT_SYMBOL_GPL(wilco_ec_get_property); + +int wilco_ec_set_property(struct wilco_ec_device *ec, + struct wilco_ec_property_msg *prop_msg) +{ + struct ec_property_request rq; + struct ec_property_response rs; + int ret; + + memset(&rq, 0, sizeof(rq)); + rq.op = EC_OP_SET; + put_unaligned_le32(prop_msg->property_id, rq.property_id); + rq.length = prop_msg->length; + memcpy(rq.data, prop_msg->data, prop_msg->length); + + ret = send_property_msg(ec, &rq, &rs); + if (ret < 0) + return ret; + if (rs.length != prop_msg->length) + return -EBADMSG; + + return 0; +} +EXPORT_SYMBOL_GPL(wilco_ec_set_property); + +int wilco_ec_get_byte_property(struct wilco_ec_device *ec, u32 property_id, + u8 *val) +{ + struct wilco_ec_property_msg msg; + int ret; + + msg.property_id = property_id; + + ret = wilco_ec_get_property(ec, &msg); + if (ret < 0) + return ret; + if (msg.length != 1) + return -EBADMSG; + + *val = msg.data[0]; + + return 0; +} +EXPORT_SYMBOL_GPL(wilco_ec_get_byte_property); + +int wilco_ec_set_byte_property(struct wilco_ec_device *ec, u32 property_id, + u8 val) +{ + struct wilco_ec_property_msg msg; + + msg.property_id = property_id; + msg.data[0] = val; + msg.length = 1; + + return wilco_ec_set_property(ec, &msg); +} +EXPORT_SYMBOL_GPL(wilco_ec_set_byte_property); diff --git a/drivers/platform/chrome/wilco_ec/sysfs.c b/drivers/platform/chrome/wilco_ec/sysfs.c new file mode 100644 index 000000000000..3b86a21005d3 --- /dev/null +++ b/drivers/platform/chrome/wilco_ec/sysfs.c @@ -0,0 +1,156 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2019 Google LLC + * + * Sysfs properties to view and modify EC-controlled features on Wilco devices. + * The entries will appear under /sys/bus/platform/devices/GOOG000C:00/ + * + * See Documentation/ABI/testing/sysfs-platform-wilco-ec for more information. + */ + +#include <linux/platform_data/wilco-ec.h> +#include <linux/sysfs.h> + +#define CMD_KB_CMOS 0x7C +#define SUB_CMD_KB_CMOS_AUTO_ON 0x03 + +struct boot_on_ac_request { + u8 cmd; /* Always CMD_KB_CMOS */ + u8 reserved1; + u8 sub_cmd; /* Always SUB_CMD_KB_CMOS_AUTO_ON */ + u8 reserved3to5[3]; + u8 val; /* Either 0 or 1 */ + u8 reserved7; +} __packed; + +#define CMD_EC_INFO 0x38 +enum get_ec_info_op { + CMD_GET_EC_LABEL = 0, + CMD_GET_EC_REV = 1, + CMD_GET_EC_MODEL = 2, + CMD_GET_EC_BUILD_DATE = 3, +}; + +struct get_ec_info_req { + u8 cmd; /* Always CMD_EC_INFO */ + u8 reserved; + u8 op; /* One of enum get_ec_info_op */ +} __packed; + +struct get_ec_info_resp { + u8 reserved[2]; + char value[9]; /* __nonstring: might not be null terminated */ +} __packed; + +static ssize_t boot_on_ac_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct wilco_ec_device *ec = dev_get_drvdata(dev); + struct boot_on_ac_request rq; + struct wilco_ec_message msg; + int ret; + u8 val; + + ret = kstrtou8(buf, 10, &val); + if (ret < 0) + return ret; + if (val > 1) + return -EINVAL; + + memset(&rq, 0, sizeof(rq)); + rq.cmd = CMD_KB_CMOS; + rq.sub_cmd = SUB_CMD_KB_CMOS_AUTO_ON; + rq.val = val; + + memset(&msg, 0, sizeof(msg)); + msg.type = WILCO_EC_MSG_LEGACY; + msg.request_data = &rq; + msg.request_size = sizeof(rq); + ret = wilco_ec_mailbox(ec, &msg); + if (ret < 0) + return ret; + + return count; +} + +static DEVICE_ATTR_WO(boot_on_ac); + +static ssize_t get_info(struct device *dev, char *buf, enum get_ec_info_op op) +{ + struct wilco_ec_device *ec = dev_get_drvdata(dev); + struct get_ec_info_req req = { .cmd = CMD_EC_INFO, .op = op }; + struct get_ec_info_resp resp; + int ret; + + struct wilco_ec_message msg = { + .type = WILCO_EC_MSG_LEGACY, + .request_data = &req, + .request_size = sizeof(req), + .response_data = &resp, + .response_size = sizeof(resp), + }; + + ret = wilco_ec_mailbox(ec, &msg); + if (ret < 0) + return ret; + + return scnprintf(buf, PAGE_SIZE, "%.*s\n", (int)sizeof(resp.value), + (char *)&resp.value); +} + +static ssize_t version_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return get_info(dev, buf, CMD_GET_EC_LABEL); +} + +static DEVICE_ATTR_RO(version); + +static ssize_t build_revision_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return get_info(dev, buf, CMD_GET_EC_REV); +} + +static DEVICE_ATTR_RO(build_revision); + +static ssize_t build_date_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return get_info(dev, buf, CMD_GET_EC_BUILD_DATE); +} + +static DEVICE_ATTR_RO(build_date); + +static ssize_t model_number_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return get_info(dev, buf, CMD_GET_EC_MODEL); +} + +static DEVICE_ATTR_RO(model_number); + + +static struct attribute *wilco_dev_attrs[] = { + &dev_attr_boot_on_ac.attr, + &dev_attr_build_date.attr, + &dev_attr_build_revision.attr, + &dev_attr_model_number.attr, + &dev_attr_version.attr, + NULL, +}; + +static struct attribute_group wilco_dev_attr_group = { + .attrs = wilco_dev_attrs, +}; + +int wilco_ec_add_sysfs(struct wilco_ec_device *ec) +{ + return sysfs_create_group(&ec->dev->kobj, &wilco_dev_attr_group); +} + +void wilco_ec_remove_sysfs(struct wilco_ec_device *ec) +{ + sysfs_remove_group(&ec->dev->kobj, &wilco_dev_attr_group); +} diff --git a/drivers/platform/chrome/wilco_ec/telemetry.c b/drivers/platform/chrome/wilco_ec/telemetry.c new file mode 100644 index 000000000000..b9d03c33d8dc --- /dev/null +++ b/drivers/platform/chrome/wilco_ec/telemetry.c @@ -0,0 +1,472 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Telemetry communication for Wilco EC + * + * Copyright 2019 Google LLC + * + * The Wilco Embedded Controller is able to send telemetry data + * which is useful for enterprise applications. A daemon running on + * the OS sends a command to the EC via a write() to a char device, + * and can read the response with a read(). The write() request is + * verified by the driver to ensure that it is performing only one + * of the allowlisted commands, and that no extraneous data is + * being transmitted to the EC. The response is passed directly + * back to the reader with no modification. + * + * The character device will appear as /dev/wilco_telemN, where N + * is some small non-negative integer, starting with 0. Only one + * process may have the file descriptor open at a time. The calling + * userspace program needs to keep the device file descriptor open + * between the calls to write() and read() in order to preserve the + * response. Up to 32 bytes will be available for reading. + * + * For testing purposes, try requesting the EC's firmware build + * date, by sending the WILCO_EC_TELEM_GET_VERSION command with + * argument index=3. i.e. write [0x38, 0x00, 0x03] + * to the device node. An ASCII string of the build date is + * returned. + */ + +#include <linux/cdev.h> +#include <linux/device.h> +#include <linux/fs.h> +#include <linux/module.h> +#include <linux/platform_data/wilco-ec.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <linux/uaccess.h> + +#define TELEM_DEV_NAME "wilco_telem" +#define TELEM_CLASS_NAME TELEM_DEV_NAME +#define DRV_NAME TELEM_DEV_NAME +#define TELEM_DEV_NAME_FMT (TELEM_DEV_NAME "%d") +static struct class telem_class = { + .owner = THIS_MODULE, + .name = TELEM_CLASS_NAME, +}; + +/* Keep track of all the device numbers used. */ +#define TELEM_MAX_DEV 128 +static int telem_major; +static DEFINE_IDA(telem_ida); + +/* EC telemetry command codes */ +#define WILCO_EC_TELEM_GET_LOG 0x99 +#define WILCO_EC_TELEM_GET_VERSION 0x38 +#define WILCO_EC_TELEM_GET_FAN_INFO 0x2E +#define WILCO_EC_TELEM_GET_DIAG_INFO 0xFA +#define WILCO_EC_TELEM_GET_TEMP_INFO 0x95 +#define WILCO_EC_TELEM_GET_TEMP_READ 0x2C +#define WILCO_EC_TELEM_GET_BATT_EXT_INFO 0x07 +#define WILCO_EC_TELEM_GET_BATT_PPID_INFO 0x8A + +#define TELEM_ARGS_SIZE_MAX 30 + +/* + * The following telem_args_get_* structs are embedded within the |args| field + * of wilco_ec_telem_request. + */ + +struct telem_args_get_log { + u8 log_type; + u8 log_index; +} __packed; + +/* + * Get a piece of info about the EC firmware version: + * 0 = label + * 1 = svn_rev + * 2 = model_no + * 3 = build_date + * 4 = frio_version + */ +struct telem_args_get_version { + u8 index; +} __packed; + +struct telem_args_get_fan_info { + u8 command; + u8 fan_number; + u8 arg; +} __packed; + +struct telem_args_get_diag_info { + u8 type; + u8 sub_type; +} __packed; + +struct telem_args_get_temp_info { + u8 command; + u8 index; + u8 field; + u8 zone; +} __packed; + +struct telem_args_get_temp_read { + u8 sensor_index; +} __packed; + +struct telem_args_get_batt_ext_info { + u8 var_args[5]; +} __packed; + +struct telem_args_get_batt_ppid_info { + u8 always1; /* Should always be 1 */ +} __packed; + +/** + * struct wilco_ec_telem_request - Telemetry command and arguments sent to EC. + * @command: One of WILCO_EC_TELEM_GET_* command codes. + * @reserved: Must be 0. + * @args: The first N bytes are one of telem_args_get_* structs, the rest is 0. + */ +struct wilco_ec_telem_request { + u8 command; + u8 reserved; + union { + u8 buf[TELEM_ARGS_SIZE_MAX]; + struct telem_args_get_log get_log; + struct telem_args_get_version get_version; + struct telem_args_get_fan_info get_fan_info; + struct telem_args_get_diag_info get_diag_info; + struct telem_args_get_temp_info get_temp_info; + struct telem_args_get_temp_read get_temp_read; + struct telem_args_get_batt_ext_info get_batt_ext_info; + struct telem_args_get_batt_ppid_info get_batt_ppid_info; + } args; +} __packed; + +/** + * check_telem_request() - Ensure that a request from userspace is valid. + * @rq: Request buffer copied from userspace. + * @size: Number of bytes copied from userspace. + * + * Return: 0 if valid, -EINVAL if bad command or reserved byte is non-zero, + * -EMSGSIZE if the request is too long. + * + * We do not want to allow userspace to send arbitrary telemetry commands to + * the EC. Therefore we check to ensure that + * 1. The request follows the format of struct wilco_ec_telem_request. + * 2. The supplied command code is one of the allowlisted commands. + * 3. The request only contains the necessary data for the header and arguments. + */ +static int check_telem_request(struct wilco_ec_telem_request *rq, + size_t size) +{ + size_t max_size = offsetof(struct wilco_ec_telem_request, args); + + if (rq->reserved) + return -EINVAL; + + switch (rq->command) { + case WILCO_EC_TELEM_GET_LOG: + max_size += sizeof(rq->args.get_log); + break; + case WILCO_EC_TELEM_GET_VERSION: + max_size += sizeof(rq->args.get_version); + break; + case WILCO_EC_TELEM_GET_FAN_INFO: + max_size += sizeof(rq->args.get_fan_info); + break; + case WILCO_EC_TELEM_GET_DIAG_INFO: + max_size += sizeof(rq->args.get_diag_info); + break; + case WILCO_EC_TELEM_GET_TEMP_INFO: + max_size += sizeof(rq->args.get_temp_info); + break; + case WILCO_EC_TELEM_GET_TEMP_READ: + max_size += sizeof(rq->args.get_temp_read); + break; + case WILCO_EC_TELEM_GET_BATT_EXT_INFO: + max_size += sizeof(rq->args.get_batt_ext_info); + break; + case WILCO_EC_TELEM_GET_BATT_PPID_INFO: + if (rq->args.get_batt_ppid_info.always1 != 1) + return -EINVAL; + + max_size += sizeof(rq->args.get_batt_ppid_info); + break; + default: + return -EINVAL; + } + + return (size <= max_size) ? 0 : -EMSGSIZE; +} + +/** + * struct telem_device_data - Data for a Wilco EC device that queries telemetry. + * @cdev: Char dev that userspace reads and polls from. + * @dev: Device associated with the %cdev. + * @ec: Wilco EC that we will be communicating with using the mailbox interface. + * @available: Boolean of if the device can be opened. + */ +struct telem_device_data { + struct device dev; + struct cdev cdev; + struct wilco_ec_device *ec; + atomic_t available; +}; + +#define TELEM_RESPONSE_SIZE EC_MAILBOX_DATA_SIZE + +/** + * struct telem_session_data - Data that exists between open() and release(). + * @dev_data: Pointer to get back to the device data and EC. + * @request: Command and arguments sent to EC. + * @response: Response buffer of data from EC. + * @has_msg: Is there data available to read from a previous write? + */ +struct telem_session_data { + struct telem_device_data *dev_data; + struct wilco_ec_telem_request request; + u8 response[TELEM_RESPONSE_SIZE]; + bool has_msg; +}; + +/** + * telem_open() - Callback for when the device node is opened. + * @inode: inode for this char device node. + * @filp: file for this char device node. + * + * We need to ensure that after writing a command to the device, + * the same userspace process reads the corresponding result. + * Therefore, we increment a refcount on opening the device, so that + * only one process can communicate with the EC at a time. + * + * Return: 0 on success, or negative error code on failure. + */ +static int telem_open(struct inode *inode, struct file *filp) +{ + struct telem_device_data *dev_data; + struct telem_session_data *sess_data; + + /* Ensure device isn't already open */ + dev_data = container_of(inode->i_cdev, struct telem_device_data, cdev); + if (atomic_cmpxchg(&dev_data->available, 1, 0) == 0) + return -EBUSY; + + get_device(&dev_data->dev); + + sess_data = kzalloc(sizeof(*sess_data), GFP_KERNEL); + if (!sess_data) { + atomic_set(&dev_data->available, 1); + return -ENOMEM; + } + sess_data->dev_data = dev_data; + sess_data->has_msg = false; + + nonseekable_open(inode, filp); + filp->private_data = sess_data; + + return 0; +} + +static ssize_t telem_write(struct file *filp, const char __user *buf, + size_t count, loff_t *pos) +{ + struct telem_session_data *sess_data = filp->private_data; + struct wilco_ec_message msg = {}; + int ret; + + if (count > sizeof(sess_data->request)) + return -EMSGSIZE; + memset(&sess_data->request, 0, sizeof(sess_data->request)); + if (copy_from_user(&sess_data->request, buf, count)) + return -EFAULT; + ret = check_telem_request(&sess_data->request, count); + if (ret < 0) + return ret; + + memset(sess_data->response, 0, sizeof(sess_data->response)); + msg.type = WILCO_EC_MSG_TELEMETRY; + msg.request_data = &sess_data->request; + msg.request_size = sizeof(sess_data->request); + msg.response_data = sess_data->response; + msg.response_size = sizeof(sess_data->response); + + ret = wilco_ec_mailbox(sess_data->dev_data->ec, &msg); + if (ret < 0) + return ret; + if (ret != sizeof(sess_data->response)) + return -EMSGSIZE; + + sess_data->has_msg = true; + + return count; +} + +static ssize_t telem_read(struct file *filp, char __user *buf, size_t count, + loff_t *pos) +{ + struct telem_session_data *sess_data = filp->private_data; + + if (!sess_data->has_msg) + return -ENODATA; + if (count > sizeof(sess_data->response)) + return -EINVAL; + + if (copy_to_user(buf, sess_data->response, count)) + return -EFAULT; + + sess_data->has_msg = false; + + return count; +} + +static int telem_release(struct inode *inode, struct file *filp) +{ + struct telem_session_data *sess_data = filp->private_data; + + atomic_set(&sess_data->dev_data->available, 1); + put_device(&sess_data->dev_data->dev); + kfree(sess_data); + + return 0; +} + +static const struct file_operations telem_fops = { + .open = telem_open, + .write = telem_write, + .read = telem_read, + .release = telem_release, + .llseek = no_llseek, + .owner = THIS_MODULE, +}; + +/** + * telem_device_free() - Callback to free the telem_device_data structure. + * @d: The device embedded in our device data, which we have been ref counting. + * + * Once all open file descriptors are closed and the device has been removed, + * the refcount of the device will fall to 0 and this will be called. + */ +static void telem_device_free(struct device *d) +{ + struct telem_device_data *dev_data; + + dev_data = container_of(d, struct telem_device_data, dev); + kfree(dev_data); +} + +/** + * telem_device_probe() - Callback when creating a new device. + * @pdev: platform device that we will be receiving telems from. + * + * This finds a free minor number for the device, allocates and initializes + * some device data, and creates a new device and char dev node. + * + * Return: 0 on success, negative error code on failure. + */ +static int telem_device_probe(struct platform_device *pdev) +{ + struct telem_device_data *dev_data; + int error, minor; + + /* Get the next available device number */ + minor = ida_alloc_max(&telem_ida, TELEM_MAX_DEV-1, GFP_KERNEL); + if (minor < 0) { + error = minor; + dev_err(&pdev->dev, "Failed to find minor number: %d", error); + return error; + } + + dev_data = kzalloc(sizeof(*dev_data), GFP_KERNEL); + if (!dev_data) { + ida_simple_remove(&telem_ida, minor); + return -ENOMEM; + } + + /* Initialize the device data */ + dev_data->ec = dev_get_platdata(&pdev->dev); + atomic_set(&dev_data->available, 1); + platform_set_drvdata(pdev, dev_data); + + /* Initialize the device */ + dev_data->dev.devt = MKDEV(telem_major, minor); + dev_data->dev.class = &telem_class; + dev_data->dev.release = telem_device_free; + dev_set_name(&dev_data->dev, TELEM_DEV_NAME_FMT, minor); + device_initialize(&dev_data->dev); + + /* Initialize the character device and add it to userspace */; + cdev_init(&dev_data->cdev, &telem_fops); + error = cdev_device_add(&dev_data->cdev, &dev_data->dev); + if (error) { + put_device(&dev_data->dev); + ida_simple_remove(&telem_ida, minor); + return error; + } + + return 0; +} + +static int telem_device_remove(struct platform_device *pdev) +{ + struct telem_device_data *dev_data = platform_get_drvdata(pdev); + + cdev_device_del(&dev_data->cdev, &dev_data->dev); + put_device(&dev_data->dev); + ida_simple_remove(&telem_ida, MINOR(dev_data->dev.devt)); + + return 0; +} + +static struct platform_driver telem_driver = { + .probe = telem_device_probe, + .remove = telem_device_remove, + .driver = { + .name = DRV_NAME, + }, +}; + +static int __init telem_module_init(void) +{ + dev_t dev_num = 0; + int ret; + + ret = class_register(&telem_class); + if (ret) { + pr_err(DRV_NAME ": Failed registering class: %d", ret); + return ret; + } + + /* Request the kernel for device numbers, starting with minor=0 */ + ret = alloc_chrdev_region(&dev_num, 0, TELEM_MAX_DEV, TELEM_DEV_NAME); + if (ret) { + pr_err(DRV_NAME ": Failed allocating dev numbers: %d", ret); + goto destroy_class; + } + telem_major = MAJOR(dev_num); + + ret = platform_driver_register(&telem_driver); + if (ret < 0) { + pr_err(DRV_NAME ": Failed registering driver: %d\n", ret); + goto unregister_region; + } + + return 0; + +unregister_region: + unregister_chrdev_region(MKDEV(telem_major, 0), TELEM_MAX_DEV); +destroy_class: + class_unregister(&telem_class); + ida_destroy(&telem_ida); + return ret; +} + +static void __exit telem_module_exit(void) +{ + platform_driver_unregister(&telem_driver); + unregister_chrdev_region(MKDEV(telem_major, 0), TELEM_MAX_DEV); + class_unregister(&telem_class); + ida_destroy(&telem_ida); +} + +module_init(telem_module_init); +module_exit(telem_module_exit); + +MODULE_AUTHOR("Nick Crews <ncrews@chromium.org>"); +MODULE_DESCRIPTION("Wilco EC telemetry driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/drivers/platform/goldfish/Kconfig b/drivers/platform/goldfish/Kconfig index 74fdfa68d1f2..f3d09b1631e3 100644 --- a/drivers/platform/goldfish/Kconfig +++ b/drivers/platform/goldfish/Kconfig @@ -1,7 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0-only menuconfig GOLDFISH bool "Platform support for Goldfish virtual devices" - depends on X86_32 || X86_64 || ARM || ARM64 || MIPS - depends on HAS_IOMEM + depends on HAS_IOMEM && HAS_DMA help Say Y here to get to see options for the Goldfish virtual platform. This option alone does not add any kernel code. diff --git a/drivers/platform/goldfish/Makefile b/drivers/platform/goldfish/Makefile index e0c202df9674..76ba1d571896 100644 --- a/drivers/platform/goldfish/Makefile +++ b/drivers/platform/goldfish/Makefile @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only # # Makefile for Goldfish platform specific drivers # diff --git a/drivers/platform/goldfish/goldfish_pipe.c b/drivers/platform/goldfish/goldfish_pipe.c index 321bc673c417..cef0133aa47a 100644 --- a/drivers/platform/goldfish/goldfish_pipe.c +++ b/drivers/platform/goldfish/goldfish_pipe.c @@ -274,7 +274,8 @@ static int pin_user_pages(unsigned long first_page, *iter_last_page_size = last_page_size; } - ret = get_user_pages_fast(first_page, requested_pages, !is_write, + ret = get_user_pages_fast(first_page, requested_pages, + !is_write ? FOLL_WRITE : 0, pages); if (ret <= 0) return -EFAULT; diff --git a/drivers/platform/mellanox/Kconfig b/drivers/platform/mellanox/Kconfig index cd8a90846063..56e037623b29 100644 --- a/drivers/platform/mellanox/Kconfig +++ b/drivers/platform/mellanox/Kconfig @@ -5,7 +5,7 @@ menuconfig MELLANOX_PLATFORM bool "Platform support for Mellanox hardware" - depends on X86 || ARM || COMPILE_TEST + depends on X86 || ARM || ARM64 || COMPILE_TEST ---help--- Say Y here to get to see options for platform support for Mellanox systems. This option alone does not add any kernel code. @@ -34,4 +34,26 @@ config MLXREG_IO to system resets operation, system reset causes monitoring and some kinds of mux selection. +config MLXBF_TMFIFO + tristate "Mellanox BlueField SoC TmFifo platform driver" + depends on ARM64 + depends on ACPI + depends on VIRTIO_CONSOLE && VIRTIO_NET + help + Say y here to enable TmFifo support. The TmFifo driver provides + platform driver support for the TmFifo which supports console + and networking based on the virtio framework. + +config MLXBF_BOOTCTL + tristate "Mellanox BlueField Firmware Boot Control driver" + depends on ARM64 + depends on ACPI + help + The Mellanox BlueField firmware implements functionality to + request swapping the primary and alternate eMMC boot partition, + and to set up a watchdog that can undo that swap if the system + does not boot up correctly. This driver provides sysfs access + to the userspace tools, to be used in conjunction with the eMMC + device driver to do necessary initial swap of the boot partition. + endif # MELLANOX_PLATFORM diff --git a/drivers/platform/mellanox/Makefile b/drivers/platform/mellanox/Makefile index 57074d9c722c..499623ccf2fe 100644 --- a/drivers/platform/mellanox/Makefile +++ b/drivers/platform/mellanox/Makefile @@ -3,5 +3,7 @@ # Makefile for linux/drivers/platform/mellanox # Mellanox Platform-Specific Drivers # +obj-$(CONFIG_MLXBF_BOOTCTL) += mlxbf-bootctl.o +obj-$(CONFIG_MLXBF_TMFIFO) += mlxbf-tmfifo.o obj-$(CONFIG_MLXREG_HOTPLUG) += mlxreg-hotplug.o obj-$(CONFIG_MLXREG_IO) += mlxreg-io.o diff --git a/drivers/platform/mellanox/mlxbf-bootctl.c b/drivers/platform/mellanox/mlxbf-bootctl.c new file mode 100644 index 000000000000..61753b648506 --- /dev/null +++ b/drivers/platform/mellanox/mlxbf-bootctl.c @@ -0,0 +1,321 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Mellanox boot control driver + * + * This driver provides a sysfs interface for systems management + * software to manage reset-time actions. + * + * Copyright (C) 2019 Mellanox Technologies + */ + +#include <linux/acpi.h> +#include <linux/arm-smccc.h> +#include <linux/module.h> +#include <linux/platform_device.h> + +#include "mlxbf-bootctl.h" + +#define MLXBF_BOOTCTL_SB_SECURE_MASK 0x03 +#define MLXBF_BOOTCTL_SB_TEST_MASK 0x0c + +#define MLXBF_SB_KEY_NUM 4 + +/* UUID used to probe ATF service. */ +static const char *mlxbf_bootctl_svc_uuid_str = + "89c036b4-e7d7-11e6-8797-001aca00bfc4"; + +struct mlxbf_bootctl_name { + u32 value; + const char *name; +}; + +static struct mlxbf_bootctl_name boot_names[] = { + { MLXBF_BOOTCTL_EXTERNAL, "external" }, + { MLXBF_BOOTCTL_EMMC, "emmc" }, + { MLNX_BOOTCTL_SWAP_EMMC, "swap_emmc" }, + { MLXBF_BOOTCTL_EMMC_LEGACY, "emmc_legacy" }, + { MLXBF_BOOTCTL_NONE, "none" }, +}; + +static const char * const mlxbf_bootctl_lifecycle_states[] = { + [0] = "Production", + [1] = "GA Secured", + [2] = "GA Non-Secured", + [3] = "RMA", +}; + +/* ARM SMC call which is atomic and no need for lock. */ +static int mlxbf_bootctl_smc(unsigned int smc_op, int smc_arg) +{ + struct arm_smccc_res res; + + arm_smccc_smc(smc_op, smc_arg, 0, 0, 0, 0, 0, 0, &res); + + return res.a0; +} + +/* Return the action in integer or an error code. */ +static int mlxbf_bootctl_reset_action_to_val(const char *action) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(boot_names); i++) + if (sysfs_streq(boot_names[i].name, action)) + return boot_names[i].value; + + return -EINVAL; +} + +/* Return the action in string. */ +static const char *mlxbf_bootctl_action_to_string(int action) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(boot_names); i++) + if (boot_names[i].value == action) + return boot_names[i].name; + + return "invalid action"; +} + +static ssize_t post_reset_wdog_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int ret; + + ret = mlxbf_bootctl_smc(MLXBF_BOOTCTL_GET_POST_RESET_WDOG, 0); + if (ret < 0) + return ret; + + return sprintf(buf, "%d\n", ret); +} + +static ssize_t post_reset_wdog_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long value; + int ret; + + ret = kstrtoul(buf, 10, &value); + if (ret) + return ret; + + ret = mlxbf_bootctl_smc(MLXBF_BOOTCTL_SET_POST_RESET_WDOG, value); + if (ret < 0) + return ret; + + return count; +} + +static ssize_t mlxbf_bootctl_show(int smc_op, char *buf) +{ + int action; + + action = mlxbf_bootctl_smc(smc_op, 0); + if (action < 0) + return action; + + return sprintf(buf, "%s\n", mlxbf_bootctl_action_to_string(action)); +} + +static int mlxbf_bootctl_store(int smc_op, const char *buf, size_t count) +{ + int ret, action; + + action = mlxbf_bootctl_reset_action_to_val(buf); + if (action < 0) + return action; + + ret = mlxbf_bootctl_smc(smc_op, action); + if (ret < 0) + return ret; + + return count; +} + +static ssize_t reset_action_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return mlxbf_bootctl_show(MLXBF_BOOTCTL_GET_RESET_ACTION, buf); +} + +static ssize_t reset_action_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + return mlxbf_bootctl_store(MLXBF_BOOTCTL_SET_RESET_ACTION, buf, count); +} + +static ssize_t second_reset_action_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return mlxbf_bootctl_show(MLXBF_BOOTCTL_GET_SECOND_RESET_ACTION, buf); +} + +static ssize_t second_reset_action_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + return mlxbf_bootctl_store(MLXBF_BOOTCTL_SET_SECOND_RESET_ACTION, buf, + count); +} + +static ssize_t lifecycle_state_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int lc_state; + + lc_state = mlxbf_bootctl_smc(MLXBF_BOOTCTL_GET_TBB_FUSE_STATUS, + MLXBF_BOOTCTL_FUSE_STATUS_LIFECYCLE); + if (lc_state < 0) + return lc_state; + + lc_state &= + MLXBF_BOOTCTL_SB_TEST_MASK | MLXBF_BOOTCTL_SB_SECURE_MASK; + + /* + * If the test bits are set, we specify that the current state may be + * due to using the test bits. + */ + if (lc_state & MLXBF_BOOTCTL_SB_TEST_MASK) { + lc_state &= MLXBF_BOOTCTL_SB_SECURE_MASK; + + return sprintf(buf, "%s(test)\n", + mlxbf_bootctl_lifecycle_states[lc_state]); + } + + return sprintf(buf, "%s\n", mlxbf_bootctl_lifecycle_states[lc_state]); +} + +static ssize_t secure_boot_fuse_state_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int burnt, valid, key, key_state, buf_len = 0, upper_key_used = 0; + const char *status; + + key_state = mlxbf_bootctl_smc(MLXBF_BOOTCTL_GET_TBB_FUSE_STATUS, + MLXBF_BOOTCTL_FUSE_STATUS_KEYS); + if (key_state < 0) + return key_state; + + /* + * key_state contains the bits for 4 Key versions, loaded from eFuses + * after a hard reset. Lower 4 bits are a thermometer code indicating + * key programming has started for key n (0000 = none, 0001 = version 0, + * 0011 = version 1, 0111 = version 2, 1111 = version 3). Upper 4 bits + * are a thermometer code indicating key programming has completed for + * key n (same encodings as the start bits). This allows for detection + * of an interruption in the progamming process which has left the key + * partially programmed (and thus invalid). The process is to burn the + * eFuse for the new key start bit, burn the key eFuses, then burn the + * eFuse for the new key complete bit. + * + * For example 0000_0000: no key valid, 0001_0001: key version 0 valid, + * 0011_0011: key 1 version valid, 0011_0111: key version 2 started + * programming but did not complete, etc. The most recent key for which + * both start and complete bit is set is loaded. On soft reset, this + * register is not modified. + */ + for (key = MLXBF_SB_KEY_NUM - 1; key >= 0; key--) { + burnt = key_state & BIT(key); + valid = key_state & BIT(key + MLXBF_SB_KEY_NUM); + + if (burnt && valid) + upper_key_used = 1; + + if (upper_key_used) { + if (burnt) + status = valid ? "Used" : "Wasted"; + else + status = valid ? "Invalid" : "Skipped"; + } else { + if (burnt) + status = valid ? "InUse" : "Incomplete"; + else + status = valid ? "Invalid" : "Free"; + } + buf_len += sprintf(buf + buf_len, "%d:%s ", key, status); + } + buf_len += sprintf(buf + buf_len, "\n"); + + return buf_len; +} + +static DEVICE_ATTR_RW(post_reset_wdog); +static DEVICE_ATTR_RW(reset_action); +static DEVICE_ATTR_RW(second_reset_action); +static DEVICE_ATTR_RO(lifecycle_state); +static DEVICE_ATTR_RO(secure_boot_fuse_state); + +static struct attribute *mlxbf_bootctl_attrs[] = { + &dev_attr_post_reset_wdog.attr, + &dev_attr_reset_action.attr, + &dev_attr_second_reset_action.attr, + &dev_attr_lifecycle_state.attr, + &dev_attr_secure_boot_fuse_state.attr, + NULL +}; + +ATTRIBUTE_GROUPS(mlxbf_bootctl); + +static const struct acpi_device_id mlxbf_bootctl_acpi_ids[] = { + {"MLNXBF04", 0}, + {} +}; + +MODULE_DEVICE_TABLE(acpi, mlxbf_bootctl_acpi_ids); + +static bool mlxbf_bootctl_guid_match(const guid_t *guid, + const struct arm_smccc_res *res) +{ + guid_t id = GUID_INIT(res->a0, res->a1, res->a1 >> 16, + res->a2, res->a2 >> 8, res->a2 >> 16, + res->a2 >> 24, res->a3, res->a3 >> 8, + res->a3 >> 16, res->a3 >> 24); + + return guid_equal(guid, &id); +} + +static int mlxbf_bootctl_probe(struct platform_device *pdev) +{ + struct arm_smccc_res res = { 0 }; + guid_t guid; + int ret; + + /* Ensure we have the UUID we expect for this service. */ + arm_smccc_smc(MLXBF_BOOTCTL_SIP_SVC_UID, 0, 0, 0, 0, 0, 0, 0, &res); + guid_parse(mlxbf_bootctl_svc_uuid_str, &guid); + if (!mlxbf_bootctl_guid_match(&guid, &res)) + return -ENODEV; + + /* + * When watchdog is used, it sets boot mode to MLXBF_BOOTCTL_SWAP_EMMC + * in case of boot failures. However it doesn't clear the state if there + * is no failure. Restore the default boot mode here to avoid any + * unnecessary boot partition swapping. + */ + ret = mlxbf_bootctl_smc(MLXBF_BOOTCTL_SET_RESET_ACTION, + MLXBF_BOOTCTL_EMMC); + if (ret < 0) + dev_warn(&pdev->dev, "Unable to reset the EMMC boot mode\n"); + + return 0; +} + +static struct platform_driver mlxbf_bootctl_driver = { + .probe = mlxbf_bootctl_probe, + .driver = { + .name = "mlxbf-bootctl", + .groups = mlxbf_bootctl_groups, + .acpi_match_table = mlxbf_bootctl_acpi_ids, + } +}; + +module_platform_driver(mlxbf_bootctl_driver); + +MODULE_DESCRIPTION("Mellanox boot control driver"); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Mellanox Technologies"); diff --git a/drivers/platform/mellanox/mlxbf-bootctl.h b/drivers/platform/mellanox/mlxbf-bootctl.h new file mode 100644 index 000000000000..148fdb43b435 --- /dev/null +++ b/drivers/platform/mellanox/mlxbf-bootctl.h @@ -0,0 +1,103 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2019, Mellanox Technologies. All rights reserved. + */ + +#ifndef __MLXBF_BOOTCTL_H__ +#define __MLXBF_BOOTCTL_H__ + +/* + * Request that the on-chip watchdog be enabled, or disabled, after + * the next chip soft reset. This call does not affect the current + * status of the on-chip watchdog. If non-zero, the argument + * specifies the watchdog interval in seconds. If zero, the watchdog + * will not be enabled after the next soft reset. Non-zero errors are + * returned as documented below. + */ +#define MLXBF_BOOTCTL_SET_POST_RESET_WDOG 0x82000000 + +/* + * Query the status which has been requested for the on-chip watchdog + * after the next chip soft reset. Returns the interval as set by + * MLXBF_BOOTCTL_SET_POST_RESET_WDOG. + */ +#define MLXBF_BOOTCTL_GET_POST_RESET_WDOG 0x82000001 + +/* + * Request that a specific boot action be taken at the next soft + * reset. By default, the boot action is set by external chip pins, + * which are sampled on hard reset. Note that the boot action + * requested by this call will persist on subsequent resets unless + * this service, or the MLNX_SET_SECOND_RESET_ACTION service, is + * invoked. See below for the available MLNX_BOOT_xxx parameter + * values. Non-zero errors are returned as documented below. + */ +#define MLXBF_BOOTCTL_SET_RESET_ACTION 0x82000002 + +/* + * Return the specific boot action which will be taken at the next + * soft reset. Returns the reset action (see below for the parameter + * values for MLXBF_BOOTCTL_SET_RESET_ACTION). + */ +#define MLXBF_BOOTCTL_GET_RESET_ACTION 0x82000003 + +/* + * Request that a specific boot action be taken at the soft reset + * after the next soft reset. For a specified valid boot mode, the + * effect of this call is identical to that of invoking + * MLXBF_BOOTCTL_SET_RESET_ACTION after the next chip soft reset; in + * particular, after that reset, the action for the now next reset can + * be queried with MLXBF_BOOTCTL_GET_RESET_ACTION and modified with + * MLXBF_BOOTCTL_SET_RESET_ACTION. You may also specify the parameter as + * MLNX_BOOT_NONE, which is equivalent to specifying that no call to + * MLXBF_BOOTCTL_SET_RESET_ACTION be taken after the next chip soft reset. + * This call does not affect the action to be taken at the next soft + * reset. Non-zero errors are returned as documented below. + */ +#define MLXBF_BOOTCTL_SET_SECOND_RESET_ACTION 0x82000004 + +/* + * Return the specific boot action which will be taken at the soft + * reset after the next soft reset; this will be one of the valid + * actions for MLXBF_BOOTCTL_SET_SECOND_RESET_ACTION. + */ +#define MLXBF_BOOTCTL_GET_SECOND_RESET_ACTION 0x82000005 + +/* + * Return the fuse status of the current chip. The caller should specify + * with the second argument if the state of the lifecycle fuses or the + * version of secure boot fuse keys left should be returned. + */ +#define MLXBF_BOOTCTL_GET_TBB_FUSE_STATUS 0x82000006 + +/* Reset eMMC by programming the RST_N register. */ +#define MLXBF_BOOTCTL_SET_EMMC_RST_N 0x82000007 + +#define MLXBF_BOOTCTL_GET_DIMM_INFO 0x82000008 + +/* SMC function IDs for SiP Service queries */ +#define MLXBF_BOOTCTL_SIP_SVC_CALL_COUNT 0x8200ff00 +#define MLXBF_BOOTCTL_SIP_SVC_UID 0x8200ff01 +#define MLXBF_BOOTCTL_SIP_SVC_VERSION 0x8200ff03 + +/* ARM Standard Service Calls version numbers */ +#define MLXBF_BOOTCTL_SVC_VERSION_MAJOR 0x0 +#define MLXBF_BOOTCTL_SVC_VERSION_MINOR 0x2 + +/* Number of svc calls defined. */ +#define MLXBF_BOOTCTL_NUM_SVC_CALLS 12 + +/* Valid reset actions for MLXBF_BOOTCTL_SET_RESET_ACTION. */ +#define MLXBF_BOOTCTL_EXTERNAL 0 /* Not boot from eMMC */ +#define MLXBF_BOOTCTL_EMMC 1 /* From primary eMMC boot partition */ +#define MLNX_BOOTCTL_SWAP_EMMC 2 /* Swap eMMC boot partitions and reboot */ +#define MLXBF_BOOTCTL_EMMC_LEGACY 3 /* From primary eMMC in legacy mode */ + +/* Valid arguments for requesting the fuse status. */ +#define MLXBF_BOOTCTL_FUSE_STATUS_LIFECYCLE 0 /* Return lifecycle status. */ +#define MLXBF_BOOTCTL_FUSE_STATUS_KEYS 1 /* Return secure boot key status */ + +/* Additional value to disable the MLXBF_BOOTCTL_SET_SECOND_RESET_ACTION. */ +#define MLXBF_BOOTCTL_NONE 0x7fffffff /* Don't change next boot action */ + +#endif /* __MLXBF_BOOTCTL_H__ */ diff --git a/drivers/platform/mellanox/mlxbf-tmfifo-regs.h b/drivers/platform/mellanox/mlxbf-tmfifo-regs.h new file mode 100644 index 000000000000..e4f0d2eda714 --- /dev/null +++ b/drivers/platform/mellanox/mlxbf-tmfifo-regs.h @@ -0,0 +1,63 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2019, Mellanox Technologies. All rights reserved. + */ + +#ifndef __MLXBF_TMFIFO_REGS_H__ +#define __MLXBF_TMFIFO_REGS_H__ + +#include <linux/types.h> +#include <linux/bits.h> + +#define MLXBF_TMFIFO_TX_DATA 0x00 +#define MLXBF_TMFIFO_TX_STS 0x08 +#define MLXBF_TMFIFO_TX_STS__LENGTH 0x0001 +#define MLXBF_TMFIFO_TX_STS__COUNT_SHIFT 0 +#define MLXBF_TMFIFO_TX_STS__COUNT_WIDTH 9 +#define MLXBF_TMFIFO_TX_STS__COUNT_RESET_VAL 0 +#define MLXBF_TMFIFO_TX_STS__COUNT_RMASK GENMASK_ULL(8, 0) +#define MLXBF_TMFIFO_TX_STS__COUNT_MASK GENMASK_ULL(8, 0) +#define MLXBF_TMFIFO_TX_CTL 0x10 +#define MLXBF_TMFIFO_TX_CTL__LENGTH 0x0001 +#define MLXBF_TMFIFO_TX_CTL__LWM_SHIFT 0 +#define MLXBF_TMFIFO_TX_CTL__LWM_WIDTH 8 +#define MLXBF_TMFIFO_TX_CTL__LWM_RESET_VAL 128 +#define MLXBF_TMFIFO_TX_CTL__LWM_RMASK GENMASK_ULL(7, 0) +#define MLXBF_TMFIFO_TX_CTL__LWM_MASK GENMASK_ULL(7, 0) +#define MLXBF_TMFIFO_TX_CTL__HWM_SHIFT 8 +#define MLXBF_TMFIFO_TX_CTL__HWM_WIDTH 8 +#define MLXBF_TMFIFO_TX_CTL__HWM_RESET_VAL 128 +#define MLXBF_TMFIFO_TX_CTL__HWM_RMASK GENMASK_ULL(7, 0) +#define MLXBF_TMFIFO_TX_CTL__HWM_MASK GENMASK_ULL(15, 8) +#define MLXBF_TMFIFO_TX_CTL__MAX_ENTRIES_SHIFT 32 +#define MLXBF_TMFIFO_TX_CTL__MAX_ENTRIES_WIDTH 9 +#define MLXBF_TMFIFO_TX_CTL__MAX_ENTRIES_RESET_VAL 256 +#define MLXBF_TMFIFO_TX_CTL__MAX_ENTRIES_RMASK GENMASK_ULL(8, 0) +#define MLXBF_TMFIFO_TX_CTL__MAX_ENTRIES_MASK GENMASK_ULL(40, 32) +#define MLXBF_TMFIFO_RX_DATA 0x00 +#define MLXBF_TMFIFO_RX_STS 0x08 +#define MLXBF_TMFIFO_RX_STS__LENGTH 0x0001 +#define MLXBF_TMFIFO_RX_STS__COUNT_SHIFT 0 +#define MLXBF_TMFIFO_RX_STS__COUNT_WIDTH 9 +#define MLXBF_TMFIFO_RX_STS__COUNT_RESET_VAL 0 +#define MLXBF_TMFIFO_RX_STS__COUNT_RMASK GENMASK_ULL(8, 0) +#define MLXBF_TMFIFO_RX_STS__COUNT_MASK GENMASK_ULL(8, 0) +#define MLXBF_TMFIFO_RX_CTL 0x10 +#define MLXBF_TMFIFO_RX_CTL__LENGTH 0x0001 +#define MLXBF_TMFIFO_RX_CTL__LWM_SHIFT 0 +#define MLXBF_TMFIFO_RX_CTL__LWM_WIDTH 8 +#define MLXBF_TMFIFO_RX_CTL__LWM_RESET_VAL 128 +#define MLXBF_TMFIFO_RX_CTL__LWM_RMASK GENMASK_ULL(7, 0) +#define MLXBF_TMFIFO_RX_CTL__LWM_MASK GENMASK_ULL(7, 0) +#define MLXBF_TMFIFO_RX_CTL__HWM_SHIFT 8 +#define MLXBF_TMFIFO_RX_CTL__HWM_WIDTH 8 +#define MLXBF_TMFIFO_RX_CTL__HWM_RESET_VAL 128 +#define MLXBF_TMFIFO_RX_CTL__HWM_RMASK GENMASK_ULL(7, 0) +#define MLXBF_TMFIFO_RX_CTL__HWM_MASK GENMASK_ULL(15, 8) +#define MLXBF_TMFIFO_RX_CTL__MAX_ENTRIES_SHIFT 32 +#define MLXBF_TMFIFO_RX_CTL__MAX_ENTRIES_WIDTH 9 +#define MLXBF_TMFIFO_RX_CTL__MAX_ENTRIES_RESET_VAL 256 +#define MLXBF_TMFIFO_RX_CTL__MAX_ENTRIES_RMASK GENMASK_ULL(8, 0) +#define MLXBF_TMFIFO_RX_CTL__MAX_ENTRIES_MASK GENMASK_ULL(40, 32) + +#endif /* !defined(__MLXBF_TMFIFO_REGS_H__) */ diff --git a/drivers/platform/mellanox/mlxbf-tmfifo.c b/drivers/platform/mellanox/mlxbf-tmfifo.c new file mode 100644 index 000000000000..9a5c9fd2dbc6 --- /dev/null +++ b/drivers/platform/mellanox/mlxbf-tmfifo.c @@ -0,0 +1,1281 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Mellanox BlueField SoC TmFifo driver + * + * Copyright (C) 2019 Mellanox Technologies + */ + +#include <linux/acpi.h> +#include <linux/bitfield.h> +#include <linux/circ_buf.h> +#include <linux/efi.h> +#include <linux/irq.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/types.h> + +#include <linux/virtio_config.h> +#include <linux/virtio_console.h> +#include <linux/virtio_ids.h> +#include <linux/virtio_net.h> +#include <linux/virtio_ring.h> + +#include "mlxbf-tmfifo-regs.h" + +/* Vring size. */ +#define MLXBF_TMFIFO_VRING_SIZE SZ_1K + +/* Console Tx buffer size. */ +#define MLXBF_TMFIFO_CON_TX_BUF_SIZE SZ_32K + +/* Console Tx buffer reserved space. */ +#define MLXBF_TMFIFO_CON_TX_BUF_RSV_SIZE 8 + +/* House-keeping timer interval. */ +#define MLXBF_TMFIFO_TIMER_INTERVAL (HZ / 10) + +/* Virtual devices sharing the TM FIFO. */ +#define MLXBF_TMFIFO_VDEV_MAX (VIRTIO_ID_CONSOLE + 1) + +/* + * Reserve 1/16 of TmFifo space, so console messages are not starved by + * the networking traffic. + */ +#define MLXBF_TMFIFO_RESERVE_RATIO 16 + +/* Message with data needs at least two words (for header & data). */ +#define MLXBF_TMFIFO_DATA_MIN_WORDS 2 + +struct mlxbf_tmfifo; + +/** + * mlxbf_tmfifo_vring - Structure of the TmFifo virtual ring + * @va: virtual address of the ring + * @dma: dma address of the ring + * @vq: pointer to the virtio virtqueue + * @desc: current descriptor of the pending packet + * @desc_head: head descriptor of the pending packet + * @cur_len: processed length of the current descriptor + * @rem_len: remaining length of the pending packet + * @pkt_len: total length of the pending packet + * @next_avail: next avail descriptor id + * @num: vring size (number of descriptors) + * @align: vring alignment size + * @index: vring index + * @vdev_id: vring virtio id (VIRTIO_ID_xxx) + * @fifo: pointer to the tmfifo structure + */ +struct mlxbf_tmfifo_vring { + void *va; + dma_addr_t dma; + struct virtqueue *vq; + struct vring_desc *desc; + struct vring_desc *desc_head; + int cur_len; + int rem_len; + u32 pkt_len; + u16 next_avail; + int num; + int align; + int index; + int vdev_id; + struct mlxbf_tmfifo *fifo; +}; + +/* Interrupt types. */ +enum { + MLXBF_TM_RX_LWM_IRQ, + MLXBF_TM_RX_HWM_IRQ, + MLXBF_TM_TX_LWM_IRQ, + MLXBF_TM_TX_HWM_IRQ, + MLXBF_TM_MAX_IRQ +}; + +/* Ring types (Rx & Tx). */ +enum { + MLXBF_TMFIFO_VRING_RX, + MLXBF_TMFIFO_VRING_TX, + MLXBF_TMFIFO_VRING_MAX +}; + +/** + * mlxbf_tmfifo_vdev - Structure of the TmFifo virtual device + * @vdev: virtio device, in which the vdev.id.device field has the + * VIRTIO_ID_xxx id to distinguish the virtual device. + * @status: status of the device + * @features: supported features of the device + * @vrings: array of tmfifo vrings of this device + * @config.cons: virtual console config - + * select if vdev.id.device is VIRTIO_ID_CONSOLE + * @config.net: virtual network config - + * select if vdev.id.device is VIRTIO_ID_NET + * @tx_buf: tx buffer used to buffer data before writing into the FIFO + */ +struct mlxbf_tmfifo_vdev { + struct virtio_device vdev; + u8 status; + u64 features; + struct mlxbf_tmfifo_vring vrings[MLXBF_TMFIFO_VRING_MAX]; + union { + struct virtio_console_config cons; + struct virtio_net_config net; + } config; + struct circ_buf tx_buf; +}; + +/** + * mlxbf_tmfifo_irq_info - Structure of the interrupt information + * @fifo: pointer to the tmfifo structure + * @irq: interrupt number + * @index: index into the interrupt array + */ +struct mlxbf_tmfifo_irq_info { + struct mlxbf_tmfifo *fifo; + int irq; + int index; +}; + +/** + * mlxbf_tmfifo - Structure of the TmFifo + * @vdev: array of the virtual devices running over the TmFifo + * @lock: lock to protect the TmFifo access + * @rx_base: mapped register base address for the Rx FIFO + * @tx_base: mapped register base address for the Tx FIFO + * @rx_fifo_size: number of entries of the Rx FIFO + * @tx_fifo_size: number of entries of the Tx FIFO + * @pend_events: pending bits for deferred events + * @irq_info: interrupt information + * @work: work struct for deferred process + * @timer: background timer + * @vring: Tx/Rx ring + * @spin_lock: spin lock + * @is_ready: ready flag + */ +struct mlxbf_tmfifo { + struct mlxbf_tmfifo_vdev *vdev[MLXBF_TMFIFO_VDEV_MAX]; + struct mutex lock; /* TmFifo lock */ + void __iomem *rx_base; + void __iomem *tx_base; + int rx_fifo_size; + int tx_fifo_size; + unsigned long pend_events; + struct mlxbf_tmfifo_irq_info irq_info[MLXBF_TM_MAX_IRQ]; + struct work_struct work; + struct timer_list timer; + struct mlxbf_tmfifo_vring *vring[2]; + spinlock_t spin_lock; /* spin lock */ + bool is_ready; +}; + +/** + * mlxbf_tmfifo_msg_hdr - Structure of the TmFifo message header + * @type: message type + * @len: payload length in network byte order. Messages sent into the FIFO + * will be read by the other side as data stream in the same byte order. + * The length needs to be encoded into network order so both sides + * could understand it. + */ +struct mlxbf_tmfifo_msg_hdr { + u8 type; + __be16 len; + u8 unused[5]; +} __packed __aligned(sizeof(u64)); + +/* + * Default MAC. + * This MAC address will be read from EFI persistent variable if configured. + * It can also be reconfigured with standard Linux tools. + */ +static u8 mlxbf_tmfifo_net_default_mac[ETH_ALEN] = { + 0x00, 0x1A, 0xCA, 0xFF, 0xFF, 0x01 +}; + +/* EFI variable name of the MAC address. */ +static efi_char16_t mlxbf_tmfifo_efi_name[] = L"RshimMacAddr"; + +/* Maximum L2 header length. */ +#define MLXBF_TMFIFO_NET_L2_OVERHEAD 36 + +/* Supported virtio-net features. */ +#define MLXBF_TMFIFO_NET_FEATURES \ + (BIT_ULL(VIRTIO_NET_F_MTU) | BIT_ULL(VIRTIO_NET_F_STATUS) | \ + BIT_ULL(VIRTIO_NET_F_MAC)) + +#define mlxbf_vdev_to_tmfifo(d) container_of(d, struct mlxbf_tmfifo_vdev, vdev) + +/* Free vrings of the FIFO device. */ +static void mlxbf_tmfifo_free_vrings(struct mlxbf_tmfifo *fifo, + struct mlxbf_tmfifo_vdev *tm_vdev) +{ + struct mlxbf_tmfifo_vring *vring; + int i, size; + + for (i = 0; i < ARRAY_SIZE(tm_vdev->vrings); i++) { + vring = &tm_vdev->vrings[i]; + if (vring->va) { + size = vring_size(vring->num, vring->align); + dma_free_coherent(tm_vdev->vdev.dev.parent, size, + vring->va, vring->dma); + vring->va = NULL; + if (vring->vq) { + vring_del_virtqueue(vring->vq); + vring->vq = NULL; + } + } + } +} + +/* Allocate vrings for the FIFO. */ +static int mlxbf_tmfifo_alloc_vrings(struct mlxbf_tmfifo *fifo, + struct mlxbf_tmfifo_vdev *tm_vdev) +{ + struct mlxbf_tmfifo_vring *vring; + struct device *dev; + dma_addr_t dma; + int i, size; + void *va; + + for (i = 0; i < ARRAY_SIZE(tm_vdev->vrings); i++) { + vring = &tm_vdev->vrings[i]; + vring->fifo = fifo; + vring->num = MLXBF_TMFIFO_VRING_SIZE; + vring->align = SMP_CACHE_BYTES; + vring->index = i; + vring->vdev_id = tm_vdev->vdev.id.device; + dev = &tm_vdev->vdev.dev; + + size = vring_size(vring->num, vring->align); + va = dma_alloc_coherent(dev->parent, size, &dma, GFP_KERNEL); + if (!va) { + mlxbf_tmfifo_free_vrings(fifo, tm_vdev); + dev_err(dev->parent, "dma_alloc_coherent failed\n"); + return -ENOMEM; + } + + vring->va = va; + vring->dma = dma; + } + + return 0; +} + +/* Disable interrupts of the FIFO device. */ +static void mlxbf_tmfifo_disable_irqs(struct mlxbf_tmfifo *fifo) +{ + int i, irq; + + for (i = 0; i < MLXBF_TM_MAX_IRQ; i++) { + irq = fifo->irq_info[i].irq; + fifo->irq_info[i].irq = 0; + disable_irq(irq); + } +} + +/* Interrupt handler. */ +static irqreturn_t mlxbf_tmfifo_irq_handler(int irq, void *arg) +{ + struct mlxbf_tmfifo_irq_info *irq_info = arg; + + if (!test_and_set_bit(irq_info->index, &irq_info->fifo->pend_events)) + schedule_work(&irq_info->fifo->work); + + return IRQ_HANDLED; +} + +/* Get the next packet descriptor from the vring. */ +static struct vring_desc * +mlxbf_tmfifo_get_next_desc(struct mlxbf_tmfifo_vring *vring) +{ + const struct vring *vr = virtqueue_get_vring(vring->vq); + struct virtio_device *vdev = vring->vq->vdev; + unsigned int idx, head; + + if (vring->next_avail == virtio16_to_cpu(vdev, vr->avail->idx)) + return NULL; + + idx = vring->next_avail % vr->num; + head = virtio16_to_cpu(vdev, vr->avail->ring[idx]); + if (WARN_ON(head >= vr->num)) + return NULL; + + vring->next_avail++; + + return &vr->desc[head]; +} + +/* Release virtio descriptor. */ +static void mlxbf_tmfifo_release_desc(struct mlxbf_tmfifo_vring *vring, + struct vring_desc *desc, u32 len) +{ + const struct vring *vr = virtqueue_get_vring(vring->vq); + struct virtio_device *vdev = vring->vq->vdev; + u16 idx, vr_idx; + + vr_idx = virtio16_to_cpu(vdev, vr->used->idx); + idx = vr_idx % vr->num; + vr->used->ring[idx].id = cpu_to_virtio32(vdev, desc - vr->desc); + vr->used->ring[idx].len = cpu_to_virtio32(vdev, len); + + /* + * Virtio could poll and check the 'idx' to decide whether the desc is + * done or not. Add a memory barrier here to make sure the update above + * completes before updating the idx. + */ + mb(); + vr->used->idx = cpu_to_virtio16(vdev, vr_idx + 1); +} + +/* Get the total length of the descriptor chain. */ +static u32 mlxbf_tmfifo_get_pkt_len(struct mlxbf_tmfifo_vring *vring, + struct vring_desc *desc) +{ + const struct vring *vr = virtqueue_get_vring(vring->vq); + struct virtio_device *vdev = vring->vq->vdev; + u32 len = 0, idx; + + while (desc) { + len += virtio32_to_cpu(vdev, desc->len); + if (!(virtio16_to_cpu(vdev, desc->flags) & VRING_DESC_F_NEXT)) + break; + idx = virtio16_to_cpu(vdev, desc->next); + desc = &vr->desc[idx]; + } + + return len; +} + +static void mlxbf_tmfifo_release_pending_pkt(struct mlxbf_tmfifo_vring *vring) +{ + struct vring_desc *desc_head; + u32 len = 0; + + if (vring->desc_head) { + desc_head = vring->desc_head; + len = vring->pkt_len; + } else { + desc_head = mlxbf_tmfifo_get_next_desc(vring); + len = mlxbf_tmfifo_get_pkt_len(vring, desc_head); + } + + if (desc_head) + mlxbf_tmfifo_release_desc(vring, desc_head, len); + + vring->pkt_len = 0; + vring->desc = NULL; + vring->desc_head = NULL; +} + +static void mlxbf_tmfifo_init_net_desc(struct mlxbf_tmfifo_vring *vring, + struct vring_desc *desc, bool is_rx) +{ + struct virtio_device *vdev = vring->vq->vdev; + struct virtio_net_hdr *net_hdr; + + net_hdr = phys_to_virt(virtio64_to_cpu(vdev, desc->addr)); + memset(net_hdr, 0, sizeof(*net_hdr)); +} + +/* Get and initialize the next packet. */ +static struct vring_desc * +mlxbf_tmfifo_get_next_pkt(struct mlxbf_tmfifo_vring *vring, bool is_rx) +{ + struct vring_desc *desc; + + desc = mlxbf_tmfifo_get_next_desc(vring); + if (desc && is_rx && vring->vdev_id == VIRTIO_ID_NET) + mlxbf_tmfifo_init_net_desc(vring, desc, is_rx); + + vring->desc_head = desc; + vring->desc = desc; + + return desc; +} + +/* House-keeping timer. */ +static void mlxbf_tmfifo_timer(struct timer_list *t) +{ + struct mlxbf_tmfifo *fifo = container_of(t, struct mlxbf_tmfifo, timer); + int rx, tx; + + rx = !test_and_set_bit(MLXBF_TM_RX_HWM_IRQ, &fifo->pend_events); + tx = !test_and_set_bit(MLXBF_TM_TX_LWM_IRQ, &fifo->pend_events); + + if (rx || tx) + schedule_work(&fifo->work); + + mod_timer(&fifo->timer, jiffies + MLXBF_TMFIFO_TIMER_INTERVAL); +} + +/* Copy one console packet into the output buffer. */ +static void mlxbf_tmfifo_console_output_one(struct mlxbf_tmfifo_vdev *cons, + struct mlxbf_tmfifo_vring *vring, + struct vring_desc *desc) +{ + const struct vring *vr = virtqueue_get_vring(vring->vq); + struct virtio_device *vdev = &cons->vdev; + u32 len, idx, seg; + void *addr; + + while (desc) { + addr = phys_to_virt(virtio64_to_cpu(vdev, desc->addr)); + len = virtio32_to_cpu(vdev, desc->len); + + seg = CIRC_SPACE_TO_END(cons->tx_buf.head, cons->tx_buf.tail, + MLXBF_TMFIFO_CON_TX_BUF_SIZE); + if (len <= seg) { + memcpy(cons->tx_buf.buf + cons->tx_buf.head, addr, len); + } else { + memcpy(cons->tx_buf.buf + cons->tx_buf.head, addr, seg); + addr += seg; + memcpy(cons->tx_buf.buf, addr, len - seg); + } + cons->tx_buf.head = (cons->tx_buf.head + len) % + MLXBF_TMFIFO_CON_TX_BUF_SIZE; + + if (!(virtio16_to_cpu(vdev, desc->flags) & VRING_DESC_F_NEXT)) + break; + idx = virtio16_to_cpu(vdev, desc->next); + desc = &vr->desc[idx]; + } +} + +/* Copy console data into the output buffer. */ +static void mlxbf_tmfifo_console_output(struct mlxbf_tmfifo_vdev *cons, + struct mlxbf_tmfifo_vring *vring) +{ + struct vring_desc *desc; + u32 len, avail; + + desc = mlxbf_tmfifo_get_next_desc(vring); + while (desc) { + /* Release the packet if not enough space. */ + len = mlxbf_tmfifo_get_pkt_len(vring, desc); + avail = CIRC_SPACE(cons->tx_buf.head, cons->tx_buf.tail, + MLXBF_TMFIFO_CON_TX_BUF_SIZE); + if (len + MLXBF_TMFIFO_CON_TX_BUF_RSV_SIZE > avail) { + mlxbf_tmfifo_release_desc(vring, desc, len); + break; + } + + mlxbf_tmfifo_console_output_one(cons, vring, desc); + mlxbf_tmfifo_release_desc(vring, desc, len); + desc = mlxbf_tmfifo_get_next_desc(vring); + } +} + +/* Get the number of available words in Rx FIFO for receiving. */ +static int mlxbf_tmfifo_get_rx_avail(struct mlxbf_tmfifo *fifo) +{ + u64 sts; + + sts = readq(fifo->rx_base + MLXBF_TMFIFO_RX_STS); + return FIELD_GET(MLXBF_TMFIFO_RX_STS__COUNT_MASK, sts); +} + +/* Get the number of available words in the TmFifo for sending. */ +static int mlxbf_tmfifo_get_tx_avail(struct mlxbf_tmfifo *fifo, int vdev_id) +{ + int tx_reserve; + u32 count; + u64 sts; + + /* Reserve some room in FIFO for console messages. */ + if (vdev_id == VIRTIO_ID_NET) + tx_reserve = fifo->tx_fifo_size / MLXBF_TMFIFO_RESERVE_RATIO; + else + tx_reserve = 1; + + sts = readq(fifo->tx_base + MLXBF_TMFIFO_TX_STS); + count = FIELD_GET(MLXBF_TMFIFO_TX_STS__COUNT_MASK, sts); + return fifo->tx_fifo_size - tx_reserve - count; +} + +/* Console Tx (move data from the output buffer into the TmFifo). */ +static void mlxbf_tmfifo_console_tx(struct mlxbf_tmfifo *fifo, int avail) +{ + struct mlxbf_tmfifo_msg_hdr hdr; + struct mlxbf_tmfifo_vdev *cons; + unsigned long flags; + int size, seg; + void *addr; + u64 data; + + /* Return if not enough space available. */ + if (avail < MLXBF_TMFIFO_DATA_MIN_WORDS) + return; + + cons = fifo->vdev[VIRTIO_ID_CONSOLE]; + if (!cons || !cons->tx_buf.buf) + return; + + /* Return if no data to send. */ + size = CIRC_CNT(cons->tx_buf.head, cons->tx_buf.tail, + MLXBF_TMFIFO_CON_TX_BUF_SIZE); + if (size == 0) + return; + + /* Adjust the size to available space. */ + if (size + sizeof(hdr) > avail * sizeof(u64)) + size = avail * sizeof(u64) - sizeof(hdr); + + /* Write header. */ + hdr.type = VIRTIO_ID_CONSOLE; + hdr.len = htons(size); + writeq(*(u64 *)&hdr, fifo->tx_base + MLXBF_TMFIFO_TX_DATA); + + /* Use spin-lock to protect the 'cons->tx_buf'. */ + spin_lock_irqsave(&fifo->spin_lock, flags); + + while (size > 0) { + addr = cons->tx_buf.buf + cons->tx_buf.tail; + + seg = CIRC_CNT_TO_END(cons->tx_buf.head, cons->tx_buf.tail, + MLXBF_TMFIFO_CON_TX_BUF_SIZE); + if (seg >= sizeof(u64)) { + memcpy(&data, addr, sizeof(u64)); + } else { + memcpy(&data, addr, seg); + memcpy((u8 *)&data + seg, cons->tx_buf.buf, + sizeof(u64) - seg); + } + writeq(data, fifo->tx_base + MLXBF_TMFIFO_TX_DATA); + + if (size >= sizeof(u64)) { + cons->tx_buf.tail = (cons->tx_buf.tail + sizeof(u64)) % + MLXBF_TMFIFO_CON_TX_BUF_SIZE; + size -= sizeof(u64); + } else { + cons->tx_buf.tail = (cons->tx_buf.tail + size) % + MLXBF_TMFIFO_CON_TX_BUF_SIZE; + size = 0; + } + } + + spin_unlock_irqrestore(&fifo->spin_lock, flags); +} + +/* Rx/Tx one word in the descriptor buffer. */ +static void mlxbf_tmfifo_rxtx_word(struct mlxbf_tmfifo_vring *vring, + struct vring_desc *desc, + bool is_rx, int len) +{ + struct virtio_device *vdev = vring->vq->vdev; + struct mlxbf_tmfifo *fifo = vring->fifo; + void *addr; + u64 data; + + /* Get the buffer address of this desc. */ + addr = phys_to_virt(virtio64_to_cpu(vdev, desc->addr)); + + /* Read a word from FIFO for Rx. */ + if (is_rx) + data = readq(fifo->rx_base + MLXBF_TMFIFO_RX_DATA); + + if (vring->cur_len + sizeof(u64) <= len) { + /* The whole word. */ + if (is_rx) + memcpy(addr + vring->cur_len, &data, sizeof(u64)); + else + memcpy(&data, addr + vring->cur_len, sizeof(u64)); + vring->cur_len += sizeof(u64); + } else { + /* Leftover bytes. */ + if (is_rx) + memcpy(addr + vring->cur_len, &data, + len - vring->cur_len); + else + memcpy(&data, addr + vring->cur_len, + len - vring->cur_len); + vring->cur_len = len; + } + + /* Write the word into FIFO for Tx. */ + if (!is_rx) + writeq(data, fifo->tx_base + MLXBF_TMFIFO_TX_DATA); +} + +/* + * Rx/Tx packet header. + * + * In Rx case, the packet might be found to belong to a different vring since + * the TmFifo is shared by different services. In such case, the 'vring_change' + * flag is set. + */ +static void mlxbf_tmfifo_rxtx_header(struct mlxbf_tmfifo_vring *vring, + struct vring_desc *desc, + bool is_rx, bool *vring_change) +{ + struct mlxbf_tmfifo *fifo = vring->fifo; + struct virtio_net_config *config; + struct mlxbf_tmfifo_msg_hdr hdr; + int vdev_id, hdr_len; + + /* Read/Write packet header. */ + if (is_rx) { + /* Drain one word from the FIFO. */ + *(u64 *)&hdr = readq(fifo->rx_base + MLXBF_TMFIFO_RX_DATA); + + /* Skip the length 0 packets (keepalive). */ + if (hdr.len == 0) + return; + + /* Check packet type. */ + if (hdr.type == VIRTIO_ID_NET) { + vdev_id = VIRTIO_ID_NET; + hdr_len = sizeof(struct virtio_net_hdr); + config = &fifo->vdev[vdev_id]->config.net; + if (ntohs(hdr.len) > config->mtu + + MLXBF_TMFIFO_NET_L2_OVERHEAD) + return; + } else { + vdev_id = VIRTIO_ID_CONSOLE; + hdr_len = 0; + } + + /* + * Check whether the new packet still belongs to this vring. + * If not, update the pkt_len of the new vring. + */ + if (vdev_id != vring->vdev_id) { + struct mlxbf_tmfifo_vdev *tm_dev2 = fifo->vdev[vdev_id]; + + if (!tm_dev2) + return; + vring->desc = desc; + vring = &tm_dev2->vrings[MLXBF_TMFIFO_VRING_RX]; + *vring_change = true; + } + vring->pkt_len = ntohs(hdr.len) + hdr_len; + } else { + /* Network virtio has an extra header. */ + hdr_len = (vring->vdev_id == VIRTIO_ID_NET) ? + sizeof(struct virtio_net_hdr) : 0; + vring->pkt_len = mlxbf_tmfifo_get_pkt_len(vring, desc); + hdr.type = (vring->vdev_id == VIRTIO_ID_NET) ? + VIRTIO_ID_NET : VIRTIO_ID_CONSOLE; + hdr.len = htons(vring->pkt_len - hdr_len); + writeq(*(u64 *)&hdr, fifo->tx_base + MLXBF_TMFIFO_TX_DATA); + } + + vring->cur_len = hdr_len; + vring->rem_len = vring->pkt_len; + fifo->vring[is_rx] = vring; +} + +/* + * Rx/Tx one descriptor. + * + * Return true to indicate more data available. + */ +static bool mlxbf_tmfifo_rxtx_one_desc(struct mlxbf_tmfifo_vring *vring, + bool is_rx, int *avail) +{ + const struct vring *vr = virtqueue_get_vring(vring->vq); + struct mlxbf_tmfifo *fifo = vring->fifo; + struct virtio_device *vdev; + bool vring_change = false; + struct vring_desc *desc; + unsigned long flags; + u32 len, idx; + + vdev = &fifo->vdev[vring->vdev_id]->vdev; + + /* Get the descriptor of the next packet. */ + if (!vring->desc) { + desc = mlxbf_tmfifo_get_next_pkt(vring, is_rx); + if (!desc) + return false; + } else { + desc = vring->desc; + } + + /* Beginning of a packet. Start to Rx/Tx packet header. */ + if (vring->pkt_len == 0) { + mlxbf_tmfifo_rxtx_header(vring, desc, is_rx, &vring_change); + (*avail)--; + + /* Return if new packet is for another ring. */ + if (vring_change) + return false; + goto mlxbf_tmfifo_desc_done; + } + + /* Get the length of this desc. */ + len = virtio32_to_cpu(vdev, desc->len); + if (len > vring->rem_len) + len = vring->rem_len; + + /* Rx/Tx one word (8 bytes) if not done. */ + if (vring->cur_len < len) { + mlxbf_tmfifo_rxtx_word(vring, desc, is_rx, len); + (*avail)--; + } + + /* Check again whether it's done. */ + if (vring->cur_len == len) { + vring->cur_len = 0; + vring->rem_len -= len; + + /* Get the next desc on the chain. */ + if (vring->rem_len > 0 && + (virtio16_to_cpu(vdev, desc->flags) & VRING_DESC_F_NEXT)) { + idx = virtio16_to_cpu(vdev, desc->next); + desc = &vr->desc[idx]; + goto mlxbf_tmfifo_desc_done; + } + + /* Done and release the pending packet. */ + mlxbf_tmfifo_release_pending_pkt(vring); + desc = NULL; + fifo->vring[is_rx] = NULL; + + /* Notify upper layer that packet is done. */ + spin_lock_irqsave(&fifo->spin_lock, flags); + vring_interrupt(0, vring->vq); + spin_unlock_irqrestore(&fifo->spin_lock, flags); + } + +mlxbf_tmfifo_desc_done: + /* Save the current desc. */ + vring->desc = desc; + + return true; +} + +/* Rx & Tx processing of a queue. */ +static void mlxbf_tmfifo_rxtx(struct mlxbf_tmfifo_vring *vring, bool is_rx) +{ + int avail = 0, devid = vring->vdev_id; + struct mlxbf_tmfifo *fifo; + bool more; + + fifo = vring->fifo; + + /* Return if vdev is not ready. */ + if (!fifo->vdev[devid]) + return; + + /* Return if another vring is running. */ + if (fifo->vring[is_rx] && fifo->vring[is_rx] != vring) + return; + + /* Only handle console and network for now. */ + if (WARN_ON(devid != VIRTIO_ID_NET && devid != VIRTIO_ID_CONSOLE)) + return; + + do { + /* Get available FIFO space. */ + if (avail == 0) { + if (is_rx) + avail = mlxbf_tmfifo_get_rx_avail(fifo); + else + avail = mlxbf_tmfifo_get_tx_avail(fifo, devid); + if (avail <= 0) + break; + } + + /* Console output always comes from the Tx buffer. */ + if (!is_rx && devid == VIRTIO_ID_CONSOLE) { + mlxbf_tmfifo_console_tx(fifo, avail); + break; + } + + /* Handle one descriptor. */ + more = mlxbf_tmfifo_rxtx_one_desc(vring, is_rx, &avail); + } while (more); +} + +/* Handle Rx or Tx queues. */ +static void mlxbf_tmfifo_work_rxtx(struct mlxbf_tmfifo *fifo, int queue_id, + int irq_id, bool is_rx) +{ + struct mlxbf_tmfifo_vdev *tm_vdev; + struct mlxbf_tmfifo_vring *vring; + int i; + + if (!test_and_clear_bit(irq_id, &fifo->pend_events) || + !fifo->irq_info[irq_id].irq) + return; + + for (i = 0; i < MLXBF_TMFIFO_VDEV_MAX; i++) { + tm_vdev = fifo->vdev[i]; + if (tm_vdev) { + vring = &tm_vdev->vrings[queue_id]; + if (vring->vq) + mlxbf_tmfifo_rxtx(vring, is_rx); + } + } +} + +/* Work handler for Rx and Tx case. */ +static void mlxbf_tmfifo_work_handler(struct work_struct *work) +{ + struct mlxbf_tmfifo *fifo; + + fifo = container_of(work, struct mlxbf_tmfifo, work); + if (!fifo->is_ready) + return; + + mutex_lock(&fifo->lock); + + /* Tx (Send data to the TmFifo). */ + mlxbf_tmfifo_work_rxtx(fifo, MLXBF_TMFIFO_VRING_TX, + MLXBF_TM_TX_LWM_IRQ, false); + + /* Rx (Receive data from the TmFifo). */ + mlxbf_tmfifo_work_rxtx(fifo, MLXBF_TMFIFO_VRING_RX, + MLXBF_TM_RX_HWM_IRQ, true); + + mutex_unlock(&fifo->lock); +} + +/* The notify function is called when new buffers are posted. */ +static bool mlxbf_tmfifo_virtio_notify(struct virtqueue *vq) +{ + struct mlxbf_tmfifo_vring *vring = vq->priv; + struct mlxbf_tmfifo_vdev *tm_vdev; + struct mlxbf_tmfifo *fifo; + unsigned long flags; + + fifo = vring->fifo; + + /* + * Virtio maintains vrings in pairs, even number ring for Rx + * and odd number ring for Tx. + */ + if (vring->index & BIT(0)) { + /* + * Console could make blocking call with interrupts disabled. + * In such case, the vring needs to be served right away. For + * other cases, just set the TX LWM bit to start Tx in the + * worker handler. + */ + if (vring->vdev_id == VIRTIO_ID_CONSOLE) { + spin_lock_irqsave(&fifo->spin_lock, flags); + tm_vdev = fifo->vdev[VIRTIO_ID_CONSOLE]; + mlxbf_tmfifo_console_output(tm_vdev, vring); + spin_unlock_irqrestore(&fifo->spin_lock, flags); + } else if (test_and_set_bit(MLXBF_TM_TX_LWM_IRQ, + &fifo->pend_events)) { + return true; + } + } else { + if (test_and_set_bit(MLXBF_TM_RX_HWM_IRQ, &fifo->pend_events)) + return true; + } + + schedule_work(&fifo->work); + + return true; +} + +/* Get the array of feature bits for this device. */ +static u64 mlxbf_tmfifo_virtio_get_features(struct virtio_device *vdev) +{ + struct mlxbf_tmfifo_vdev *tm_vdev = mlxbf_vdev_to_tmfifo(vdev); + + return tm_vdev->features; +} + +/* Confirm device features to use. */ +static int mlxbf_tmfifo_virtio_finalize_features(struct virtio_device *vdev) +{ + struct mlxbf_tmfifo_vdev *tm_vdev = mlxbf_vdev_to_tmfifo(vdev); + + tm_vdev->features = vdev->features; + + return 0; +} + +/* Free virtqueues found by find_vqs(). */ +static void mlxbf_tmfifo_virtio_del_vqs(struct virtio_device *vdev) +{ + struct mlxbf_tmfifo_vdev *tm_vdev = mlxbf_vdev_to_tmfifo(vdev); + struct mlxbf_tmfifo_vring *vring; + struct virtqueue *vq; + int i; + + for (i = 0; i < ARRAY_SIZE(tm_vdev->vrings); i++) { + vring = &tm_vdev->vrings[i]; + + /* Release the pending packet. */ + if (vring->desc) + mlxbf_tmfifo_release_pending_pkt(vring); + vq = vring->vq; + if (vq) { + vring->vq = NULL; + vring_del_virtqueue(vq); + } + } +} + +/* Create and initialize the virtual queues. */ +static int mlxbf_tmfifo_virtio_find_vqs(struct virtio_device *vdev, + unsigned int nvqs, + struct virtqueue *vqs[], + vq_callback_t *callbacks[], + const char * const names[], + const bool *ctx, + struct irq_affinity *desc) +{ + struct mlxbf_tmfifo_vdev *tm_vdev = mlxbf_vdev_to_tmfifo(vdev); + struct mlxbf_tmfifo_vring *vring; + struct virtqueue *vq; + int i, ret, size; + + if (nvqs > ARRAY_SIZE(tm_vdev->vrings)) + return -EINVAL; + + for (i = 0; i < nvqs; ++i) { + if (!names[i]) { + ret = -EINVAL; + goto error; + } + vring = &tm_vdev->vrings[i]; + + /* zero vring */ + size = vring_size(vring->num, vring->align); + memset(vring->va, 0, size); + vq = vring_new_virtqueue(i, vring->num, vring->align, vdev, + false, false, vring->va, + mlxbf_tmfifo_virtio_notify, + callbacks[i], names[i]); + if (!vq) { + dev_err(&vdev->dev, "vring_new_virtqueue failed\n"); + ret = -ENOMEM; + goto error; + } + + vqs[i] = vq; + vring->vq = vq; + vq->priv = vring; + } + + return 0; + +error: + mlxbf_tmfifo_virtio_del_vqs(vdev); + return ret; +} + +/* Read the status byte. */ +static u8 mlxbf_tmfifo_virtio_get_status(struct virtio_device *vdev) +{ + struct mlxbf_tmfifo_vdev *tm_vdev = mlxbf_vdev_to_tmfifo(vdev); + + return tm_vdev->status; +} + +/* Write the status byte. */ +static void mlxbf_tmfifo_virtio_set_status(struct virtio_device *vdev, + u8 status) +{ + struct mlxbf_tmfifo_vdev *tm_vdev = mlxbf_vdev_to_tmfifo(vdev); + + tm_vdev->status = status; +} + +/* Reset the device. Not much here for now. */ +static void mlxbf_tmfifo_virtio_reset(struct virtio_device *vdev) +{ + struct mlxbf_tmfifo_vdev *tm_vdev = mlxbf_vdev_to_tmfifo(vdev); + + tm_vdev->status = 0; +} + +/* Read the value of a configuration field. */ +static void mlxbf_tmfifo_virtio_get(struct virtio_device *vdev, + unsigned int offset, + void *buf, + unsigned int len) +{ + struct mlxbf_tmfifo_vdev *tm_vdev = mlxbf_vdev_to_tmfifo(vdev); + + if ((u64)offset + len > sizeof(tm_vdev->config)) + return; + + memcpy(buf, (u8 *)&tm_vdev->config + offset, len); +} + +/* Write the value of a configuration field. */ +static void mlxbf_tmfifo_virtio_set(struct virtio_device *vdev, + unsigned int offset, + const void *buf, + unsigned int len) +{ + struct mlxbf_tmfifo_vdev *tm_vdev = mlxbf_vdev_to_tmfifo(vdev); + + if ((u64)offset + len > sizeof(tm_vdev->config)) + return; + + memcpy((u8 *)&tm_vdev->config + offset, buf, len); +} + +static void tmfifo_virtio_dev_release(struct device *device) +{ + struct virtio_device *vdev = + container_of(device, struct virtio_device, dev); + struct mlxbf_tmfifo_vdev *tm_vdev = mlxbf_vdev_to_tmfifo(vdev); + + kfree(tm_vdev); +} + +/* Virtio config operations. */ +static const struct virtio_config_ops mlxbf_tmfifo_virtio_config_ops = { + .get_features = mlxbf_tmfifo_virtio_get_features, + .finalize_features = mlxbf_tmfifo_virtio_finalize_features, + .find_vqs = mlxbf_tmfifo_virtio_find_vqs, + .del_vqs = mlxbf_tmfifo_virtio_del_vqs, + .reset = mlxbf_tmfifo_virtio_reset, + .set_status = mlxbf_tmfifo_virtio_set_status, + .get_status = mlxbf_tmfifo_virtio_get_status, + .get = mlxbf_tmfifo_virtio_get, + .set = mlxbf_tmfifo_virtio_set, +}; + +/* Create vdev for the FIFO. */ +static int mlxbf_tmfifo_create_vdev(struct device *dev, + struct mlxbf_tmfifo *fifo, + int vdev_id, u64 features, + void *config, u32 size) +{ + struct mlxbf_tmfifo_vdev *tm_vdev, *reg_dev = NULL; + int ret; + + mutex_lock(&fifo->lock); + + tm_vdev = fifo->vdev[vdev_id]; + if (tm_vdev) { + dev_err(dev, "vdev %d already exists\n", vdev_id); + ret = -EEXIST; + goto fail; + } + + tm_vdev = kzalloc(sizeof(*tm_vdev), GFP_KERNEL); + if (!tm_vdev) { + ret = -ENOMEM; + goto fail; + } + + tm_vdev->vdev.id.device = vdev_id; + tm_vdev->vdev.config = &mlxbf_tmfifo_virtio_config_ops; + tm_vdev->vdev.dev.parent = dev; + tm_vdev->vdev.dev.release = tmfifo_virtio_dev_release; + tm_vdev->features = features; + if (config) + memcpy(&tm_vdev->config, config, size); + + if (mlxbf_tmfifo_alloc_vrings(fifo, tm_vdev)) { + dev_err(dev, "unable to allocate vring\n"); + ret = -ENOMEM; + goto vdev_fail; + } + + /* Allocate an output buffer for the console device. */ + if (vdev_id == VIRTIO_ID_CONSOLE) + tm_vdev->tx_buf.buf = devm_kmalloc(dev, + MLXBF_TMFIFO_CON_TX_BUF_SIZE, + GFP_KERNEL); + fifo->vdev[vdev_id] = tm_vdev; + + /* Register the virtio device. */ + ret = register_virtio_device(&tm_vdev->vdev); + reg_dev = tm_vdev; + if (ret) { + dev_err(dev, "register_virtio_device failed\n"); + goto vdev_fail; + } + + mutex_unlock(&fifo->lock); + return 0; + +vdev_fail: + mlxbf_tmfifo_free_vrings(fifo, tm_vdev); + fifo->vdev[vdev_id] = NULL; + if (reg_dev) + put_device(&tm_vdev->vdev.dev); + else + kfree(tm_vdev); +fail: + mutex_unlock(&fifo->lock); + return ret; +} + +/* Delete vdev for the FIFO. */ +static int mlxbf_tmfifo_delete_vdev(struct mlxbf_tmfifo *fifo, int vdev_id) +{ + struct mlxbf_tmfifo_vdev *tm_vdev; + + mutex_lock(&fifo->lock); + + /* Unregister vdev. */ + tm_vdev = fifo->vdev[vdev_id]; + if (tm_vdev) { + unregister_virtio_device(&tm_vdev->vdev); + mlxbf_tmfifo_free_vrings(fifo, tm_vdev); + fifo->vdev[vdev_id] = NULL; + } + + mutex_unlock(&fifo->lock); + + return 0; +} + +/* Read the configured network MAC address from efi variable. */ +static void mlxbf_tmfifo_get_cfg_mac(u8 *mac) +{ + efi_guid_t guid = EFI_GLOBAL_VARIABLE_GUID; + unsigned long size = ETH_ALEN; + u8 buf[ETH_ALEN]; + efi_status_t rc; + + rc = efi.get_variable(mlxbf_tmfifo_efi_name, &guid, NULL, &size, buf); + if (rc == EFI_SUCCESS && size == ETH_ALEN) + ether_addr_copy(mac, buf); + else + ether_addr_copy(mac, mlxbf_tmfifo_net_default_mac); +} + +/* Set TmFifo thresolds which is used to trigger interrupts. */ +static void mlxbf_tmfifo_set_threshold(struct mlxbf_tmfifo *fifo) +{ + u64 ctl; + + /* Get Tx FIFO size and set the low/high watermark. */ + ctl = readq(fifo->tx_base + MLXBF_TMFIFO_TX_CTL); + fifo->tx_fifo_size = + FIELD_GET(MLXBF_TMFIFO_TX_CTL__MAX_ENTRIES_MASK, ctl); + ctl = (ctl & ~MLXBF_TMFIFO_TX_CTL__LWM_MASK) | + FIELD_PREP(MLXBF_TMFIFO_TX_CTL__LWM_MASK, + fifo->tx_fifo_size / 2); + ctl = (ctl & ~MLXBF_TMFIFO_TX_CTL__HWM_MASK) | + FIELD_PREP(MLXBF_TMFIFO_TX_CTL__HWM_MASK, + fifo->tx_fifo_size - 1); + writeq(ctl, fifo->tx_base + MLXBF_TMFIFO_TX_CTL); + + /* Get Rx FIFO size and set the low/high watermark. */ + ctl = readq(fifo->rx_base + MLXBF_TMFIFO_RX_CTL); + fifo->rx_fifo_size = + FIELD_GET(MLXBF_TMFIFO_RX_CTL__MAX_ENTRIES_MASK, ctl); + ctl = (ctl & ~MLXBF_TMFIFO_RX_CTL__LWM_MASK) | + FIELD_PREP(MLXBF_TMFIFO_RX_CTL__LWM_MASK, 0); + ctl = (ctl & ~MLXBF_TMFIFO_RX_CTL__HWM_MASK) | + FIELD_PREP(MLXBF_TMFIFO_RX_CTL__HWM_MASK, 1); + writeq(ctl, fifo->rx_base + MLXBF_TMFIFO_RX_CTL); +} + +static void mlxbf_tmfifo_cleanup(struct mlxbf_tmfifo *fifo) +{ + int i; + + fifo->is_ready = false; + del_timer_sync(&fifo->timer); + mlxbf_tmfifo_disable_irqs(fifo); + cancel_work_sync(&fifo->work); + for (i = 0; i < MLXBF_TMFIFO_VDEV_MAX; i++) + mlxbf_tmfifo_delete_vdev(fifo, i); +} + +/* Probe the TMFIFO. */ +static int mlxbf_tmfifo_probe(struct platform_device *pdev) +{ + struct virtio_net_config net_config; + struct device *dev = &pdev->dev; + struct mlxbf_tmfifo *fifo; + int i, rc; + + fifo = devm_kzalloc(dev, sizeof(*fifo), GFP_KERNEL); + if (!fifo) + return -ENOMEM; + + spin_lock_init(&fifo->spin_lock); + INIT_WORK(&fifo->work, mlxbf_tmfifo_work_handler); + mutex_init(&fifo->lock); + + /* Get the resource of the Rx FIFO. */ + fifo->rx_base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(fifo->rx_base)) + return PTR_ERR(fifo->rx_base); + + /* Get the resource of the Tx FIFO. */ + fifo->tx_base = devm_platform_ioremap_resource(pdev, 1); + if (IS_ERR(fifo->tx_base)) + return PTR_ERR(fifo->tx_base); + + platform_set_drvdata(pdev, fifo); + + timer_setup(&fifo->timer, mlxbf_tmfifo_timer, 0); + + for (i = 0; i < MLXBF_TM_MAX_IRQ; i++) { + fifo->irq_info[i].index = i; + fifo->irq_info[i].fifo = fifo; + fifo->irq_info[i].irq = platform_get_irq(pdev, i); + rc = devm_request_irq(dev, fifo->irq_info[i].irq, + mlxbf_tmfifo_irq_handler, 0, + "tmfifo", &fifo->irq_info[i]); + if (rc) { + dev_err(dev, "devm_request_irq failed\n"); + fifo->irq_info[i].irq = 0; + return rc; + } + } + + mlxbf_tmfifo_set_threshold(fifo); + + /* Create the console vdev. */ + rc = mlxbf_tmfifo_create_vdev(dev, fifo, VIRTIO_ID_CONSOLE, 0, NULL, 0); + if (rc) + goto fail; + + /* Create the network vdev. */ + memset(&net_config, 0, sizeof(net_config)); + net_config.mtu = ETH_DATA_LEN; + net_config.status = VIRTIO_NET_S_LINK_UP; + mlxbf_tmfifo_get_cfg_mac(net_config.mac); + rc = mlxbf_tmfifo_create_vdev(dev, fifo, VIRTIO_ID_NET, + MLXBF_TMFIFO_NET_FEATURES, &net_config, + sizeof(net_config)); + if (rc) + goto fail; + + mod_timer(&fifo->timer, jiffies + MLXBF_TMFIFO_TIMER_INTERVAL); + + fifo->is_ready = true; + return 0; + +fail: + mlxbf_tmfifo_cleanup(fifo); + return rc; +} + +/* Device remove function. */ +static int mlxbf_tmfifo_remove(struct platform_device *pdev) +{ + struct mlxbf_tmfifo *fifo = platform_get_drvdata(pdev); + + mlxbf_tmfifo_cleanup(fifo); + + return 0; +} + +static const struct acpi_device_id mlxbf_tmfifo_acpi_match[] = { + { "MLNXBF01", 0 }, + {} +}; +MODULE_DEVICE_TABLE(acpi, mlxbf_tmfifo_acpi_match); + +static struct platform_driver mlxbf_tmfifo_driver = { + .probe = mlxbf_tmfifo_probe, + .remove = mlxbf_tmfifo_remove, + .driver = { + .name = "bf-tmfifo", + .acpi_match_table = mlxbf_tmfifo_acpi_match, + }, +}; + +module_platform_driver(mlxbf_tmfifo_driver); + +MODULE_DESCRIPTION("Mellanox BlueField SoC TmFifo Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Mellanox Technologies"); diff --git a/drivers/platform/mellanox/mlxreg-hotplug.c b/drivers/platform/mellanox/mlxreg-hotplug.c index 687ce6817d0d..706207d192ae 100644 --- a/drivers/platform/mellanox/mlxreg-hotplug.c +++ b/drivers/platform/mellanox/mlxreg-hotplug.c @@ -642,11 +642,8 @@ static int mlxreg_hotplug_probe(struct platform_device *pdev) priv->irq = pdata->irq; } else { priv->irq = platform_get_irq(pdev, 0); - if (priv->irq < 0) { - dev_err(&pdev->dev, "Failed to get platform irq: %d\n", - priv->irq); + if (priv->irq < 0) return priv->irq; - } } priv->regmap = pdata->regmap; @@ -694,6 +691,7 @@ static int mlxreg_hotplug_remove(struct platform_device *pdev) /* Clean interrupts setup. */ mlxreg_hotplug_unset_irq(priv); + devm_free_irq(&pdev->dev, priv->irq, priv); return 0; } diff --git a/drivers/platform/mips/Kconfig b/drivers/platform/mips/Kconfig index b3ae30a4c67b..f4d0a86c00d0 100644 --- a/drivers/platform/mips/Kconfig +++ b/drivers/platform/mips/Kconfig @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only # # MIPS Platform Specific Drivers # @@ -16,8 +17,8 @@ menuconfig MIPS_PLATFORM_DEVICES if MIPS_PLATFORM_DEVICES config CPU_HWMON - tristate "Loongson CPU HWMon Driver" - depends on LOONGSON_MACH3X + tristate "Loongson-3 CPU HWMon Driver" + depends on CONFIG_MACH_LOONGSON64 select HWMON default y help diff --git a/drivers/platform/mips/Makefile b/drivers/platform/mips/Makefile index 8dfd03924c37..be8146c20dc8 100644 --- a/drivers/platform/mips/Makefile +++ b/drivers/platform/mips/Makefile @@ -1 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0-only obj-$(CONFIG_CPU_HWMON) += cpu_hwmon.o diff --git a/drivers/platform/mips/cpu_hwmon.c b/drivers/platform/mips/cpu_hwmon.c index 42efcb850722..0d27cb7a9e3c 100644 --- a/drivers/platform/mips/cpu_hwmon.c +++ b/drivers/platform/mips/cpu_hwmon.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only #include <linux/err.h> #include <linux/module.h> #include <linux/reboot.h> @@ -8,6 +9,9 @@ #include <loongson.h> #include <boot_param.h> #include <loongson_hwmon.h> +#include <loongson_regs.h> + +static int csr_temp_enable = 0; /* * Loongson-3 series cpu has two sensors inside, @@ -19,8 +23,14 @@ int loongson3_cpu_temp(int cpu) { u32 reg, prid_rev; + if (csr_temp_enable) { + reg = (csr_readl(LOONGSON_CSR_CPUTEMP) & 0xff); + goto out; + } + reg = LOONGSON_CHIPTEMP(cpu); prid_rev = read_c0_prid() & PRID_REV_MASK; + switch (prid_rev) { case PRID_REV_LOONGSON3A_R1: reg = (reg >> 8) & 0xff; @@ -33,9 +43,12 @@ int loongson3_cpu_temp(int cpu) break; case PRID_REV_LOONGSON3A_R3_0: case PRID_REV_LOONGSON3A_R3_1: + default: reg = (reg & 0xffff)*731/0x4000 - 273; break; } + +out: return (int)reg * 1000; } @@ -158,9 +171,12 @@ static int __init loongson_hwmon_init(void) pr_info("Loongson Hwmon Enter...\n"); + if (cpu_has_csr()) + csr_temp_enable = csr_readl(LOONGSON_CSR_FEATURES) & LOONGSON_CSRF_TEMP; + cpu_hwmon_dev = hwmon_device_register(NULL); if (IS_ERR(cpu_hwmon_dev)) { - ret = -ENOMEM; + ret = PTR_ERR(cpu_hwmon_dev); pr_err("hwmon_device_register fail!\n"); goto fail_hwmon_device_register; } diff --git a/drivers/platform/olpc/Kconfig b/drivers/platform/olpc/Kconfig new file mode 100644 index 000000000000..919b489e24e8 --- /dev/null +++ b/drivers/platform/olpc/Kconfig @@ -0,0 +1,29 @@ +config OLPC_EC + select REGULATOR + bool + +menuconfig OLPC_XO175 + bool "Platform support for OLPC XO 1.75 hardware" + depends on ARCH_MMP || COMPILE_TEST + help + Say Y here to get to see options for the ARM-based OLPC platform. + This option alone does not add any kernel code. + + Unless you have an OLPC XO laptop, you will want to say N. + +if OLPC_XO175 + +config OLPC_XO175_EC + tristate "OLPC XO 1.75 Embedded Controller" + depends on SPI_SLAVE + depends on INPUT + depends on POWER_SUPPLY + select OLPC_EC + help + Include support for the OLPC XO Embedded Controller (EC). The EC + provides various platform services, including support for the power, + button, restart, shutdown and battery charging status. + + Unless you have an OLPC XO laptop, you will want to say N. + +endif # OLPC_XO175 diff --git a/drivers/platform/olpc/Makefile b/drivers/platform/olpc/Makefile index dc8b26bc7209..e9b67000cbcb 100644 --- a/drivers/platform/olpc/Makefile +++ b/drivers/platform/olpc/Makefile @@ -1,4 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0-only # # OLPC XO platform-specific drivers # -obj-$(CONFIG_OLPC) += olpc-ec.o +obj-$(CONFIG_OLPC_EC) += olpc-ec.o +obj-$(CONFIG_OLPC_XO175_EC) += olpc-xo175-ec.o diff --git a/drivers/platform/olpc/olpc-ec.c b/drivers/platform/olpc/olpc-ec.c index 374a8028fec7..190e4a6186ef 100644 --- a/drivers/platform/olpc/olpc-ec.c +++ b/drivers/platform/olpc/olpc-ec.c @@ -1,11 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * Generic driver for the OLPC Embedded Controller. * * Author: Andres Salomon <dilinger@queued.net> * * Copyright (C) 2011-2012 One Laptop per Child Foundation. - * - * Licensed under the GPL v2 or later. */ #include <linux/completion.h> #include <linux/debugfs.h> @@ -16,8 +15,8 @@ #include <linux/workqueue.h> #include <linux/init.h> #include <linux/list.h> +#include <linux/regulator/driver.h> #include <linux/olpc-ec.h> -#include <asm/olpc.h> struct ec_cmd_desc { u8 cmd; @@ -33,9 +32,14 @@ struct ec_cmd_desc { struct olpc_ec_priv { struct olpc_ec_driver *drv; + u8 version; struct work_struct worker; struct mutex cmd_lock; + /* DCON regulator */ + struct regulator_dev *dcon_rdev; + bool dcon_enabled; + /* Pending EC commands */ struct list_head cmd_q; spinlock_t cmd_q_lock; @@ -43,6 +47,12 @@ struct olpc_ec_priv { struct dentry *dbgfs_dir; /* + * EC event mask to be applied during suspend (defining wakeup + * sources). + */ + u16 ec_wakeup_mask; + + /* * Running an EC command while suspending means we don't always finish * the command before the machine suspends. This means that the EC * is expecting the command protocol to finish, but we after a period @@ -119,8 +129,11 @@ int olpc_ec_cmd(u8 cmd, u8 *inbuf, size_t inlen, u8 *outbuf, size_t outlen) struct olpc_ec_priv *ec = ec_priv; struct ec_cmd_desc desc; - /* Ensure a driver and ec hook have been registered */ - if (WARN_ON(!ec_driver || !ec_driver->ec_cmd)) + /* Driver not yet registered. */ + if (!ec_driver) + return -EPROBE_DEFER; + + if (WARN_ON(!ec_driver->ec_cmd)) return -ENODEV; if (!ec) @@ -150,6 +163,88 @@ int olpc_ec_cmd(u8 cmd, u8 *inbuf, size_t inlen, u8 *outbuf, size_t outlen) } EXPORT_SYMBOL_GPL(olpc_ec_cmd); +void olpc_ec_wakeup_set(u16 value) +{ + struct olpc_ec_priv *ec = ec_priv; + + if (WARN_ON(!ec)) + return; + + ec->ec_wakeup_mask |= value; +} +EXPORT_SYMBOL_GPL(olpc_ec_wakeup_set); + +void olpc_ec_wakeup_clear(u16 value) +{ + struct olpc_ec_priv *ec = ec_priv; + + if (WARN_ON(!ec)) + return; + + ec->ec_wakeup_mask &= ~value; +} +EXPORT_SYMBOL_GPL(olpc_ec_wakeup_clear); + +int olpc_ec_mask_write(u16 bits) +{ + struct olpc_ec_priv *ec = ec_priv; + + if (WARN_ON(!ec)) + return -ENODEV; + + /* EC version 0x5f adds support for wide SCI mask */ + if (ec->version >= 0x5f) { + __be16 ec_word = cpu_to_be16(bits); + + return olpc_ec_cmd(EC_WRITE_EXT_SCI_MASK, (void *)&ec_word, 2, NULL, 0); + } else { + u8 ec_byte = bits & 0xff; + + return olpc_ec_cmd(EC_WRITE_SCI_MASK, &ec_byte, 1, NULL, 0); + } +} +EXPORT_SYMBOL_GPL(olpc_ec_mask_write); + +/* + * Returns true if the compile and runtime configurations allow for EC events + * to wake the system. + */ +bool olpc_ec_wakeup_available(void) +{ + if (WARN_ON(!ec_driver)) + return false; + + return ec_driver->wakeup_available; +} +EXPORT_SYMBOL_GPL(olpc_ec_wakeup_available); + +int olpc_ec_sci_query(u16 *sci_value) +{ + struct olpc_ec_priv *ec = ec_priv; + int ret; + + if (WARN_ON(!ec)) + return -ENODEV; + + /* EC version 0x5f adds support for wide SCI mask */ + if (ec->version >= 0x5f) { + __be16 ec_word; + + ret = olpc_ec_cmd(EC_EXT_SCI_QUERY, NULL, 0, (void *)&ec_word, 2); + if (ret == 0) + *sci_value = be16_to_cpu(ec_word); + } else { + u8 ec_byte; + + ret = olpc_ec_cmd(EC_SCI_QUERY, NULL, 0, &ec_byte, 1); + if (ret == 0) + *sci_value = ec_byte; + } + + return ret; +} +EXPORT_SYMBOL_GPL(olpc_ec_sci_query); + #ifdef CONFIG_DEBUG_FS /* @@ -255,9 +350,61 @@ static struct dentry *olpc_ec_setup_debugfs(void) #endif /* CONFIG_DEBUG_FS */ +static int olpc_ec_set_dcon_power(struct olpc_ec_priv *ec, bool state) +{ + unsigned char ec_byte = state; + int ret; + + if (ec->dcon_enabled == state) + return 0; + + ret = olpc_ec_cmd(EC_DCON_POWER_MODE, &ec_byte, 1, NULL, 0); + if (ret) + return ret; + + ec->dcon_enabled = state; + return 0; +} + +static int dcon_regulator_enable(struct regulator_dev *rdev) +{ + struct olpc_ec_priv *ec = rdev_get_drvdata(rdev); + + return olpc_ec_set_dcon_power(ec, true); +} + +static int dcon_regulator_disable(struct regulator_dev *rdev) +{ + struct olpc_ec_priv *ec = rdev_get_drvdata(rdev); + + return olpc_ec_set_dcon_power(ec, false); +} + +static int dcon_regulator_is_enabled(struct regulator_dev *rdev) +{ + struct olpc_ec_priv *ec = rdev_get_drvdata(rdev); + + return ec->dcon_enabled ? 1 : 0; +} + +static struct regulator_ops dcon_regulator_ops = { + .enable = dcon_regulator_enable, + .disable = dcon_regulator_disable, + .is_enabled = dcon_regulator_is_enabled, +}; + +static const struct regulator_desc dcon_desc = { + .name = "dcon", + .id = 0, + .ops = &dcon_regulator_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, +}; + static int olpc_ec_probe(struct platform_device *pdev) { struct olpc_ec_priv *ec; + struct regulator_config config = { }; int err; if (!ec_driver) @@ -277,14 +424,26 @@ static int olpc_ec_probe(struct platform_device *pdev) ec_priv = ec; platform_set_drvdata(pdev, ec); - err = ec_driver->probe ? ec_driver->probe(pdev) : 0; + /* get the EC revision */ + err = olpc_ec_cmd(EC_FIRMWARE_REV, NULL, 0, &ec->version, 1); if (err) { ec_priv = NULL; kfree(ec); - } else { - ec->dbgfs_dir = olpc_ec_setup_debugfs(); + return err; } + config.dev = pdev->dev.parent; + config.driver_data = ec; + ec->dcon_enabled = true; + ec->dcon_rdev = devm_regulator_register(&pdev->dev, &dcon_desc, + &config); + if (IS_ERR(ec->dcon_rdev)) { + dev_err(&pdev->dev, "failed to register DCON regulator\n"); + return PTR_ERR(ec->dcon_rdev); + } + + ec->dbgfs_dir = olpc_ec_setup_debugfs(); + return err; } @@ -294,6 +453,8 @@ static int olpc_ec_suspend(struct device *dev) struct olpc_ec_priv *ec = platform_get_drvdata(pdev); int err = 0; + olpc_ec_mask_write(ec->ec_wakeup_mask); + if (ec_driver->suspend) err = ec_driver->suspend(pdev); if (!err) diff --git a/drivers/platform/olpc/olpc-xo175-ec.c b/drivers/platform/olpc/olpc-xo175-ec.c new file mode 100644 index 000000000000..83ed1fbf73cf --- /dev/null +++ b/drivers/platform/olpc/olpc-xo175-ec.c @@ -0,0 +1,759 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for the OLPC XO-1.75 Embedded Controller. + * + * The EC protocol is documented at: + * http://wiki.laptop.org/go/XO_1.75_HOST_to_EC_Protocol + * + * Copyright (C) 2010 One Laptop per Child Foundation. + * Copyright (C) 2018 Lubomir Rintel <lkundrak@v3.sk> + */ + +#include <linux/completion.h> +#include <linux/ctype.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/input.h> +#include <linux/kfifo.h> +#include <linux/module.h> +#include <linux/olpc-ec.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/reboot.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/spi/spi.h> + +struct ec_cmd_t { + u8 cmd; + u8 bytes_returned; +}; + +enum ec_chan_t { + CHAN_NONE = 0, + CHAN_SWITCH, + CHAN_CMD_RESP, + CHAN_KEYBOARD, + CHAN_TOUCHPAD, + CHAN_EVENT, + CHAN_DEBUG, + CHAN_CMD_ERROR, +}; + +/* + * EC events + */ +#define EVENT_AC_CHANGE 1 /* AC plugged/unplugged */ +#define EVENT_BATTERY_STATUS 2 /* Battery low/full/error/gone */ +#define EVENT_BATTERY_CRITICAL 3 /* Battery critical voltage */ +#define EVENT_BATTERY_SOC_CHANGE 4 /* 1% SOC Change */ +#define EVENT_BATTERY_ERROR 5 /* Abnormal error, query for cause */ +#define EVENT_POWER_PRESSED 6 /* Power button was pressed */ +#define EVENT_POWER_PRESS_WAKE 7 /* Woken up with a power button */ +#define EVENT_TIMED_HOST_WAKE 8 /* Host wake timer */ +#define EVENT_OLS_HIGH_LIMIT 9 /* OLS crossed dark threshold */ +#define EVENT_OLS_LOW_LIMIT 10 /* OLS crossed light threshold */ + +/* + * EC commands + * (from http://dev.laptop.org/git/users/rsmith/ec-1.75/tree/ec_cmd.h) + */ +#define CMD_GET_API_VERSION 0x08 /* out: u8 */ +#define CMD_READ_VOLTAGE 0x10 /* out: u16, *9.76/32, mV */ +#define CMD_READ_CURRENT 0x11 /* out: s16, *15.625/120, mA */ +#define CMD_READ_ACR 0x12 /* out: s16, *6250/15, uAh */ +#define CMD_READ_BATT_TEMPERATURE 0x13 /* out: u16, *100/256, deg C */ +#define CMD_READ_AMBIENT_TEMPERATURE 0x14 /* unimplemented, no hardware */ +#define CMD_READ_BATTERY_STATUS 0x15 /* out: u8, bitmask */ +#define CMD_READ_SOC 0x16 /* out: u8, percentage */ +#define CMD_READ_GAUGE_ID 0x17 /* out: u8 * 8 */ +#define CMD_READ_GAUGE_DATA 0x18 /* in: u8 addr, out: u8 data */ +#define CMD_READ_BOARD_ID 0x19 /* out: u16 (platform id) */ +#define CMD_READ_BATT_ERR_CODE 0x1f /* out: u8, error bitmask */ +#define CMD_SET_DCON_POWER 0x26 /* in: u8 */ +#define CMD_RESET_EC 0x28 /* none */ +#define CMD_READ_BATTERY_TYPE 0x2c /* out: u8 */ +#define CMD_SET_AUTOWAK 0x33 /* out: u8 */ +#define CMD_SET_EC_WAKEUP_TIMER 0x36 /* in: u32, out: ? */ +#define CMD_READ_EXT_SCI_MASK 0x37 /* ? */ +#define CMD_WRITE_EXT_SCI_MASK 0x38 /* ? */ +#define CMD_CLEAR_EC_WAKEUP_TIMER 0x39 /* none */ +#define CMD_ENABLE_RUNIN_DISCHARGE 0x3B /* none */ +#define CMD_DISABLE_RUNIN_DISCHARGE 0x3C /* none */ +#define CMD_READ_MPPT_ACTIVE 0x3d /* out: u8 */ +#define CMD_READ_MPPT_LIMIT 0x3e /* out: u8 */ +#define CMD_SET_MPPT_LIMIT 0x3f /* in: u8 */ +#define CMD_DISABLE_MPPT 0x40 /* none */ +#define CMD_ENABLE_MPPT 0x41 /* none */ +#define CMD_READ_VIN 0x42 /* out: u16 */ +#define CMD_EXT_SCI_QUERY 0x43 /* ? */ +#define RSP_KEYBOARD_DATA 0x48 /* ? */ +#define RSP_TOUCHPAD_DATA 0x49 /* ? */ +#define CMD_GET_FW_VERSION 0x4a /* out: u8 * 16 */ +#define CMD_POWER_CYCLE 0x4b /* none */ +#define CMD_POWER_OFF 0x4c /* none */ +#define CMD_RESET_EC_SOFT 0x4d /* none */ +#define CMD_READ_GAUGE_U16 0x4e /* ? */ +#define CMD_ENABLE_MOUSE 0x4f /* ? */ +#define CMD_ECHO 0x52 /* in: u8 * 5, out: u8 * 5 */ +#define CMD_GET_FW_DATE 0x53 /* out: u8 * 16 */ +#define CMD_GET_FW_USER 0x54 /* out: u8 * 16 */ +#define CMD_TURN_OFF_POWER 0x55 /* none (same as 0x4c) */ +#define CMD_READ_OLS 0x56 /* out: u16 */ +#define CMD_OLS_SMT_LEDON 0x57 /* none */ +#define CMD_OLS_SMT_LEDOFF 0x58 /* none */ +#define CMD_START_OLS_ASSY 0x59 /* none */ +#define CMD_STOP_OLS_ASSY 0x5a /* none */ +#define CMD_OLS_SMTTEST_STOP 0x5b /* none */ +#define CMD_READ_VIN_SCALED 0x5c /* out: u16 */ +#define CMD_READ_BAT_MIN_W 0x5d /* out: u16 */ +#define CMD_READ_BAR_MAX_W 0x5e /* out: u16 */ +#define CMD_RESET_BAT_MINMAX_W 0x5f /* none */ +#define CMD_READ_LOCATION 0x60 /* in: u16 addr, out: u8 data */ +#define CMD_WRITE_LOCATION 0x61 /* in: u16 addr, u8 data */ +#define CMD_KEYBOARD_CMD 0x62 /* in: u8, out: ? */ +#define CMD_TOUCHPAD_CMD 0x63 /* in: u8, out: ? */ +#define CMD_GET_FW_HASH 0x64 /* out: u8 * 16 */ +#define CMD_SUSPEND_HINT 0x65 /* in: u8 */ +#define CMD_ENABLE_WAKE_TIMER 0x66 /* in: u8 */ +#define CMD_SET_WAKE_TIMER 0x67 /* in: 32 */ +#define CMD_ENABLE_WAKE_AUTORESET 0x68 /* in: u8 */ +#define CMD_OLS_SET_LIMITS 0x69 /* in: u16, u16 */ +#define CMD_OLS_GET_LIMITS 0x6a /* out: u16, u16 */ +#define CMD_OLS_SET_CEILING 0x6b /* in: u16 */ +#define CMD_OLS_GET_CEILING 0x6c /* out: u16 */ + +/* + * Accepted EC commands, and how many bytes they return. There are plenty + * of EC commands that are no longer implemented, or are implemented only on + * certain older boards. + */ +static const struct ec_cmd_t olpc_xo175_ec_cmds[] = { + { CMD_GET_API_VERSION, 1 }, + { CMD_READ_VOLTAGE, 2 }, + { CMD_READ_CURRENT, 2 }, + { CMD_READ_ACR, 2 }, + { CMD_READ_BATT_TEMPERATURE, 2 }, + { CMD_READ_BATTERY_STATUS, 1 }, + { CMD_READ_SOC, 1 }, + { CMD_READ_GAUGE_ID, 8 }, + { CMD_READ_GAUGE_DATA, 1 }, + { CMD_READ_BOARD_ID, 2 }, + { CMD_READ_BATT_ERR_CODE, 1 }, + { CMD_SET_DCON_POWER, 0 }, + { CMD_RESET_EC, 0 }, + { CMD_READ_BATTERY_TYPE, 1 }, + { CMD_ENABLE_RUNIN_DISCHARGE, 0 }, + { CMD_DISABLE_RUNIN_DISCHARGE, 0 }, + { CMD_READ_MPPT_ACTIVE, 1 }, + { CMD_READ_MPPT_LIMIT, 1 }, + { CMD_SET_MPPT_LIMIT, 0 }, + { CMD_DISABLE_MPPT, 0 }, + { CMD_ENABLE_MPPT, 0 }, + { CMD_READ_VIN, 2 }, + { CMD_GET_FW_VERSION, 16 }, + { CMD_POWER_CYCLE, 0 }, + { CMD_POWER_OFF, 0 }, + { CMD_RESET_EC_SOFT, 0 }, + { CMD_ECHO, 5 }, + { CMD_GET_FW_DATE, 16 }, + { CMD_GET_FW_USER, 16 }, + { CMD_TURN_OFF_POWER, 0 }, + { CMD_READ_OLS, 2 }, + { CMD_OLS_SMT_LEDON, 0 }, + { CMD_OLS_SMT_LEDOFF, 0 }, + { CMD_START_OLS_ASSY, 0 }, + { CMD_STOP_OLS_ASSY, 0 }, + { CMD_OLS_SMTTEST_STOP, 0 }, + { CMD_READ_VIN_SCALED, 2 }, + { CMD_READ_BAT_MIN_W, 2 }, + { CMD_READ_BAR_MAX_W, 2 }, + { CMD_RESET_BAT_MINMAX_W, 0 }, + { CMD_READ_LOCATION, 1 }, + { CMD_WRITE_LOCATION, 0 }, + { CMD_GET_FW_HASH, 16 }, + { CMD_SUSPEND_HINT, 0 }, + { CMD_ENABLE_WAKE_TIMER, 0 }, + { CMD_SET_WAKE_TIMER, 0 }, + { CMD_ENABLE_WAKE_AUTORESET, 0 }, + { CMD_OLS_SET_LIMITS, 0 }, + { CMD_OLS_GET_LIMITS, 4 }, + { CMD_OLS_SET_CEILING, 0 }, + { CMD_OLS_GET_CEILING, 2 }, + { CMD_READ_EXT_SCI_MASK, 2 }, + { CMD_WRITE_EXT_SCI_MASK, 0 }, + + { } +}; + +#define EC_MAX_CMD_DATA_LEN 5 +#define EC_MAX_RESP_LEN 16 + +#define LOG_BUF_SIZE 128 + +#define PM_WAKEUP_TIME 1000 + +#define EC_ALL_EVENTS GENMASK(15, 0) + +enum ec_state_t { + CMD_STATE_IDLE = 0, + CMD_STATE_WAITING_FOR_SWITCH, + CMD_STATE_CMD_IN_TX_FIFO, + CMD_STATE_CMD_SENT, + CMD_STATE_RESP_RECEIVED, + CMD_STATE_ERROR_RECEIVED, +}; + +struct olpc_xo175_ec_cmd { + u8 command; + u8 nr_args; + u8 data_len; + u8 args[EC_MAX_CMD_DATA_LEN]; +}; + +struct olpc_xo175_ec_resp { + u8 channel; + u8 byte; +}; + +struct olpc_xo175_ec { + bool suspended; + + /* SPI related stuff. */ + struct spi_device *spi; + struct spi_transfer xfer; + struct spi_message msg; + union { + struct olpc_xo175_ec_cmd cmd; + struct olpc_xo175_ec_resp resp; + } tx_buf, rx_buf; + + /* GPIO for the CMD signals. */ + struct gpio_desc *gpio_cmd; + + /* Command handling related state. */ + spinlock_t cmd_state_lock; + int cmd_state; + bool cmd_running; + struct completion cmd_done; + struct olpc_xo175_ec_cmd cmd; + u8 resp_data[EC_MAX_RESP_LEN]; + int expected_resp_len; + int resp_len; + + /* Power button. */ + struct input_dev *pwrbtn; + + /* Debug handling. */ + char logbuf[LOG_BUF_SIZE]; + int logbuf_len; +}; + +static struct platform_device *olpc_ec; + +static int olpc_xo175_ec_resp_len(u8 cmd) +{ + const struct ec_cmd_t *p; + + for (p = olpc_xo175_ec_cmds; p->cmd; p++) { + if (p->cmd == cmd) + return p->bytes_returned; + } + + return -EINVAL; +} + +static void olpc_xo175_ec_flush_logbuf(struct olpc_xo175_ec *priv) +{ + dev_dbg(&priv->spi->dev, "got debug string [%*pE]\n", + priv->logbuf_len, priv->logbuf); + priv->logbuf_len = 0; +} + +static void olpc_xo175_ec_complete(void *arg); + +static void olpc_xo175_ec_send_command(struct olpc_xo175_ec *priv, void *cmd, + size_t cmdlen) +{ + int ret; + + memcpy(&priv->tx_buf, cmd, cmdlen); + priv->xfer.len = cmdlen; + + spi_message_init_with_transfers(&priv->msg, &priv->xfer, 1); + + priv->msg.complete = olpc_xo175_ec_complete; + priv->msg.context = priv; + + ret = spi_async(priv->spi, &priv->msg); + if (ret) + dev_err(&priv->spi->dev, "spi_async() failed %d\n", ret); +} + +static void olpc_xo175_ec_read_packet(struct olpc_xo175_ec *priv) +{ + u8 nonce[] = {0xA5, 0x5A}; + + olpc_xo175_ec_send_command(priv, nonce, sizeof(nonce)); +} + +static void olpc_xo175_ec_complete(void *arg) +{ + struct olpc_xo175_ec *priv = arg; + struct device *dev = &priv->spi->dev; + struct power_supply *psy; + unsigned long flags; + u8 channel; + u8 byte; + int ret; + + ret = priv->msg.status; + if (ret) { + dev_err(dev, "SPI transfer failed: %d\n", ret); + + spin_lock_irqsave(&priv->cmd_state_lock, flags); + if (priv->cmd_running) { + priv->resp_len = 0; + priv->cmd_state = CMD_STATE_ERROR_RECEIVED; + complete(&priv->cmd_done); + } + spin_unlock_irqrestore(&priv->cmd_state_lock, flags); + + if (ret != -EINTR) + olpc_xo175_ec_read_packet(priv); + + return; + } + + channel = priv->rx_buf.resp.channel; + byte = priv->rx_buf.resp.byte; + + switch (channel) { + case CHAN_NONE: + spin_lock_irqsave(&priv->cmd_state_lock, flags); + + if (!priv->cmd_running) { + /* We can safely ignore these */ + dev_err(dev, "spurious FIFO read packet\n"); + spin_unlock_irqrestore(&priv->cmd_state_lock, flags); + return; + } + + priv->cmd_state = CMD_STATE_CMD_SENT; + if (!priv->expected_resp_len) + complete(&priv->cmd_done); + olpc_xo175_ec_read_packet(priv); + + spin_unlock_irqrestore(&priv->cmd_state_lock, flags); + return; + + case CHAN_SWITCH: + spin_lock_irqsave(&priv->cmd_state_lock, flags); + + if (!priv->cmd_running) { + /* Just go with the flow */ + dev_err(dev, "spurious SWITCH packet\n"); + memset(&priv->cmd, 0, sizeof(priv->cmd)); + priv->cmd.command = CMD_ECHO; + } + + priv->cmd_state = CMD_STATE_CMD_IN_TX_FIFO; + + /* Throw command into TxFIFO */ + gpiod_set_value_cansleep(priv->gpio_cmd, 0); + olpc_xo175_ec_send_command(priv, &priv->cmd, sizeof(priv->cmd)); + + spin_unlock_irqrestore(&priv->cmd_state_lock, flags); + return; + + case CHAN_CMD_RESP: + spin_lock_irqsave(&priv->cmd_state_lock, flags); + + if (!priv->cmd_running) { + dev_err(dev, "spurious response packet\n"); + } else if (priv->resp_len >= priv->expected_resp_len) { + dev_err(dev, "too many response packets\n"); + } else { + priv->resp_data[priv->resp_len++] = byte; + if (priv->resp_len == priv->expected_resp_len) { + priv->cmd_state = CMD_STATE_RESP_RECEIVED; + complete(&priv->cmd_done); + } + } + + spin_unlock_irqrestore(&priv->cmd_state_lock, flags); + break; + + case CHAN_CMD_ERROR: + spin_lock_irqsave(&priv->cmd_state_lock, flags); + + if (!priv->cmd_running) { + dev_err(dev, "spurious cmd error packet\n"); + } else { + priv->resp_data[0] = byte; + priv->resp_len = 1; + priv->cmd_state = CMD_STATE_ERROR_RECEIVED; + complete(&priv->cmd_done); + } + spin_unlock_irqrestore(&priv->cmd_state_lock, flags); + break; + + case CHAN_KEYBOARD: + dev_warn(dev, "keyboard is not supported\n"); + break; + + case CHAN_TOUCHPAD: + dev_warn(dev, "touchpad is not supported\n"); + break; + + case CHAN_EVENT: + dev_dbg(dev, "got event %.2x\n", byte); + switch (byte) { + case EVENT_AC_CHANGE: + psy = power_supply_get_by_name("olpc-ac"); + if (psy) { + power_supply_changed(psy); + power_supply_put(psy); + } + break; + case EVENT_BATTERY_STATUS: + case EVENT_BATTERY_CRITICAL: + case EVENT_BATTERY_SOC_CHANGE: + case EVENT_BATTERY_ERROR: + psy = power_supply_get_by_name("olpc-battery"); + if (psy) { + power_supply_changed(psy); + power_supply_put(psy); + } + break; + case EVENT_POWER_PRESSED: + input_report_key(priv->pwrbtn, KEY_POWER, 1); + input_sync(priv->pwrbtn); + input_report_key(priv->pwrbtn, KEY_POWER, 0); + input_sync(priv->pwrbtn); + /* fall through */ + case EVENT_POWER_PRESS_WAKE: + case EVENT_TIMED_HOST_WAKE: + pm_wakeup_event(priv->pwrbtn->dev.parent, + PM_WAKEUP_TIME); + break; + default: + dev_dbg(dev, "ignored unknown event %.2x\n", byte); + break; + } + break; + + case CHAN_DEBUG: + if (byte == '\n') { + olpc_xo175_ec_flush_logbuf(priv); + } else if (isprint(byte)) { + priv->logbuf[priv->logbuf_len++] = byte; + if (priv->logbuf_len == LOG_BUF_SIZE) + olpc_xo175_ec_flush_logbuf(priv); + } + break; + + default: + dev_warn(dev, "unknown channel: %d, %.2x\n", channel, byte); + break; + } + + /* Most non-command packets get the TxFIFO refilled and an ACK. */ + olpc_xo175_ec_read_packet(priv); +} + +/* + * This function is protected with a mutex. We can safely assume that + * there will be only one instance of this function running at a time. + * One of the ways in which we enforce this is by waiting until we get + * all response bytes back from the EC, rather than just the number that + * the caller requests (otherwise, we might start a new command while an + * old command's response bytes are still incoming). + */ +static int olpc_xo175_ec_cmd(u8 cmd, u8 *inbuf, size_t inlen, u8 *resp, + size_t resp_len, void *ec_cb_arg) +{ + struct olpc_xo175_ec *priv = ec_cb_arg; + struct device *dev = &priv->spi->dev; + unsigned long flags; + size_t nr_bytes; + int ret = 0; + + dev_dbg(dev, "CMD %x, %zd bytes expected\n", cmd, resp_len); + + if (inlen > 5) { + dev_err(dev, "command len %zd too big!\n", resp_len); + return -EOVERFLOW; + } + + /* Suspending in the middle of an EC command hoses things badly! */ + if (WARN_ON(priv->suspended)) + return -EBUSY; + + /* Ensure a valid command and return bytes */ + ret = olpc_xo175_ec_resp_len(cmd); + if (ret < 0) { + dev_err_ratelimited(dev, "unknown command 0x%x\n", cmd); + + /* + * Assume the best in our callers, and allow unknown commands + * through. I'm not the charitable type, but it was beaten + * into me. Just maintain a minimum standard of sanity. + */ + if (resp_len > sizeof(priv->resp_data)) { + dev_err(dev, "response too big: %zd!\n", resp_len); + return -EOVERFLOW; + } + nr_bytes = resp_len; + } else { + nr_bytes = (size_t)ret; + ret = 0; + } + resp_len = min(resp_len, nr_bytes); + + spin_lock_irqsave(&priv->cmd_state_lock, flags); + + /* Initialize the state machine */ + init_completion(&priv->cmd_done); + priv->cmd_running = true; + priv->cmd_state = CMD_STATE_WAITING_FOR_SWITCH; + memset(&priv->cmd, 0, sizeof(priv->cmd)); + priv->cmd.command = cmd; + priv->cmd.nr_args = inlen; + priv->cmd.data_len = 0; + memcpy(priv->cmd.args, inbuf, inlen); + priv->expected_resp_len = nr_bytes; + priv->resp_len = 0; + + /* Tickle the cmd gpio to get things started */ + gpiod_set_value_cansleep(priv->gpio_cmd, 1); + + spin_unlock_irqrestore(&priv->cmd_state_lock, flags); + + /* The irq handler should do the rest */ + if (!wait_for_completion_timeout(&priv->cmd_done, + msecs_to_jiffies(4000))) { + dev_err(dev, "EC cmd error: timeout in STATE %d\n", + priv->cmd_state); + gpiod_set_value_cansleep(priv->gpio_cmd, 0); + spi_slave_abort(priv->spi); + olpc_xo175_ec_read_packet(priv); + return -ETIMEDOUT; + } + + spin_lock_irqsave(&priv->cmd_state_lock, flags); + + /* Deal with the results. */ + if (priv->cmd_state == CMD_STATE_ERROR_RECEIVED) { + /* EC-provided error is in the single response byte */ + dev_err(dev, "command 0x%x returned error 0x%x\n", + cmd, priv->resp_data[0]); + ret = -EREMOTEIO; + } else if (priv->resp_len != nr_bytes) { + dev_err(dev, "command 0x%x returned %d bytes, expected %zd bytes\n", + cmd, priv->resp_len, nr_bytes); + ret = -EREMOTEIO; + } else { + /* + * We may have 8 bytes in priv->resp, but we only care about + * what we've been asked for. If the caller asked for only 2 + * bytes, give them that. We've guaranteed that + * resp_len <= priv->resp_len and priv->resp_len == nr_bytes. + */ + memcpy(resp, priv->resp_data, resp_len); + } + + /* This should already be low, but just in case. */ + gpiod_set_value_cansleep(priv->gpio_cmd, 0); + priv->cmd_running = false; + + spin_unlock_irqrestore(&priv->cmd_state_lock, flags); + + return ret; +} + +static int olpc_xo175_ec_set_event_mask(unsigned int mask) +{ + u8 args[2]; + + args[0] = mask >> 0; + args[1] = mask >> 8; + return olpc_ec_cmd(CMD_WRITE_EXT_SCI_MASK, args, 2, NULL, 0); +} + +static void olpc_xo175_ec_power_off(void) +{ + while (1) { + olpc_ec_cmd(CMD_POWER_OFF, NULL, 0, NULL, 0); + mdelay(1000); + } +} + +static int __maybe_unused olpc_xo175_ec_suspend(struct device *dev) +{ + struct olpc_xo175_ec *priv = dev_get_drvdata(dev); + static struct { + u8 suspend; + u32 suspend_count; + } __packed hintargs; + static unsigned int suspend_count; + + /* + * SOC_SLEEP is not wired to the EC on B3 and earlier boards. + * This command lets the EC know instead. The suspend count doesn't seem + * to be used anywhere but in the EC debug output. + */ + hintargs.suspend = 1; + hintargs.suspend_count = suspend_count++; + olpc_ec_cmd(CMD_SUSPEND_HINT, (void *)&hintargs, sizeof(hintargs), + NULL, 0); + + /* + * After we've sent the suspend hint, don't allow further EC commands + * to be run until we've resumed. Userspace tasks should be frozen, + * but kernel threads and interrupts could still schedule EC commands. + */ + priv->suspended = true; + + return 0; +} + +static int __maybe_unused olpc_xo175_ec_resume_noirq(struct device *dev) +{ + struct olpc_xo175_ec *priv = dev_get_drvdata(dev); + + priv->suspended = false; + + return 0; +} + +static int __maybe_unused olpc_xo175_ec_resume(struct device *dev) +{ + u8 x = 0; + + /* + * The resume hint is only needed if no other commands are + * being sent during resume. all it does is tell the EC + * the SoC is definitely awake. + */ + olpc_ec_cmd(CMD_SUSPEND_HINT, &x, 1, NULL, 0); + + /* Enable all EC events while we're awake */ + olpc_xo175_ec_set_event_mask(EC_ALL_EVENTS); + + return 0; +} + +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) +{ + if (pm_power_off == olpc_xo175_ec_power_off) + pm_power_off = NULL; + + spi_slave_abort(spi); + + platform_device_unregister(olpc_ec); + olpc_ec = NULL; + + return 0; +} + +static int olpc_xo175_ec_probe(struct spi_device *spi) +{ + struct olpc_xo175_ec *priv; + int ret; + + if (olpc_ec) { + dev_err(&spi->dev, "OLPC EC already registered.\n"); + return -EBUSY; + } + + priv = devm_kzalloc(&spi->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->gpio_cmd = devm_gpiod_get(&spi->dev, "cmd", GPIOD_OUT_LOW); + if (IS_ERR(priv->gpio_cmd)) { + dev_err(&spi->dev, "failed to get cmd gpio: %ld\n", + PTR_ERR(priv->gpio_cmd)); + return PTR_ERR(priv->gpio_cmd); + } + + priv->spi = spi; + + spin_lock_init(&priv->cmd_state_lock); + priv->cmd_state = CMD_STATE_IDLE; + init_completion(&priv->cmd_done); + + priv->logbuf_len = 0; + + /* Set up power button input device */ + priv->pwrbtn = devm_input_allocate_device(&spi->dev); + if (!priv->pwrbtn) + return -ENOMEM; + priv->pwrbtn->name = "Power Button"; + priv->pwrbtn->dev.parent = &spi->dev; + input_set_capability(priv->pwrbtn, EV_KEY, KEY_POWER); + ret = input_register_device(priv->pwrbtn); + if (ret) { + dev_err(&spi->dev, "error registering input device: %d\n", ret); + return ret; + } + + spi_set_drvdata(spi, priv); + + priv->xfer.rx_buf = &priv->rx_buf; + priv->xfer.tx_buf = &priv->tx_buf; + + olpc_xo175_ec_read_packet(priv); + + olpc_ec_driver_register(&olpc_xo175_ec_driver, priv); + olpc_ec = platform_device_register_resndata(&spi->dev, "olpc-ec", -1, + NULL, 0, NULL, 0); + + /* Enable all EC events while we're awake */ + olpc_xo175_ec_set_event_mask(EC_ALL_EVENTS); + + if (pm_power_off == NULL) + pm_power_off = olpc_xo175_ec_power_off; + + dev_info(&spi->dev, "OLPC XO-1.75 Embedded Controller driver\n"); + + return 0; +} + +static const struct dev_pm_ops olpc_xo175_ec_pm_ops = { + SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(NULL, olpc_xo175_ec_resume_noirq) + SET_RUNTIME_PM_OPS(olpc_xo175_ec_suspend, olpc_xo175_ec_resume, NULL) +}; + +static const struct of_device_id olpc_xo175_ec_of_match[] = { + { .compatible = "olpc,xo1.75-ec" }, + { } +}; +MODULE_DEVICE_TABLE(of, olpc_xo175_ec_of_match); + +static const struct spi_device_id olpc_xo175_ec_id_table[] = { + { "xo1.75-ec", 0 }, + {} +}; +MODULE_DEVICE_TABLE(spi, olpc_xo175_ec_id_table); + +static struct spi_driver olpc_xo175_ec_spi_driver = { + .driver = { + .name = "olpc-xo175-ec", + .of_match_table = olpc_xo175_ec_of_match, + .pm = &olpc_xo175_ec_pm_ops, + }, + .probe = olpc_xo175_ec_probe, + .remove = olpc_xo175_ec_remove, +}; +module_spi_driver(olpc_xo175_ec_spi_driver); + +MODULE_DESCRIPTION("OLPC XO-1.75 Embedded Controller driver"); +MODULE_AUTHOR("Lennert Buytenhek <buytenh@wantstofly.org>"); /* Functionality */ +MODULE_AUTHOR("Lubomir Rintel <lkundrak@v3.sk>"); /* Bugs */ +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index a1ed13183559..27d5b40fb717 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only # # X86 Platform Specific Drivers # @@ -93,7 +94,6 @@ config ASUS_LAPTOP depends on RFKILL || RFKILL = n depends on ACPI_VIDEO || ACPI_VIDEO = n select INPUT_SPARSEKMAP - select INPUT_POLLDEV ---help--- This is a driver for Asus laptops, Lenovo SL and the Pegatron Lucid tablet. It may also support some MEDION, JVC or VICTOR @@ -117,7 +117,7 @@ config DCDBAS Interrupts (SMIs) and Host Control Actions (system power cycle or power off after OS shutdown) on certain Dell systems. - See <file:Documentation/dcdbas.txt> for more details on the driver + See <file:Documentation/driver-api/dcdbas.rst> for more details on the driver and the Dell systems on which Dell systems management software makes use of this driver. @@ -258,7 +258,7 @@ config DELL_RBU DELL system. Note you need a Dell OpenManage or Dell Update package (DUP) supporting application to communicate with the BIOS regarding the new image for the image update to take effect. - See <file:Documentation/dell_rbu.txt> for more details on the driver. + See <file:Documentation/admin-guide/dell_rbu.rst> for more details on the driver. config FUJITSU_LAPTOP @@ -340,7 +340,7 @@ config HP_ACCEL Support for a led indicating disk protection will be provided as hp::hddprotect. For more information on the feature, refer to - Documentation/misc-devices/lis3lv02d. + Documentation/misc-devices/lis3lv02d.rst. To compile this driver as a module, choose M here: the module will be called hp_accel. @@ -432,9 +432,6 @@ config COMPAL_LAPTOP It adds support for rfkill, Bluetooth, WLAN, LCD brightness, hwmon and battery charging level control. - For a (possibly incomplete) list of supported laptops, please refer - to: Documentation/platform/x86-laptop-drivers.txt - config SONY_LAPTOP tristate "Sony Laptop Extras" depends on ACPI @@ -450,7 +447,7 @@ config SONY_LAPTOP screen brightness control, Fn keys and allows powering on/off some devices. - Read <file:Documentation/laptops/sony-laptop.txt> for more information. + Read <file:Documentation/admin-guide/laptops/sony-laptop.rst> for more information. config SONYPI_COMPAT bool "Sonypi compatibility" @@ -502,7 +499,7 @@ config THINKPAD_ACPI support for Fn-Fx key combinations, Bluetooth control, video output switching, ThinkLight control, UltraBay eject and more. For more information about this driver see - <file:Documentation/laptops/thinkpad-acpi.txt> and + <file:Documentation/admin-guide/laptops/thinkpad-acpi.rst> and <http://ibm-acpi.sf.net/> . This driver was formerly known as ibm-acpi. @@ -625,7 +622,6 @@ config THINKPAD_ACPI_HOTKEY_POLL config SENSORS_HDAPS tristate "Thinkpad Hard Drive Active Protection System (hdaps)" depends on INPUT - select INPUT_POLLDEV help This driver provides support for the IBM Hard Drive Active Protection System (hdaps), which provides an accelerometer and other misc. data. @@ -676,6 +672,7 @@ config EEEPC_LAPTOP config ASUS_WMI tristate "ASUS WMI Driver" depends on ACPI_WMI + depends on ACPI_BATTERY depends on INPUT depends on HWMON depends on BACKLIGHT_CLASS_DEVICE @@ -780,6 +777,16 @@ config INTEL_WMI_THUNDERBOLT To compile this driver as a module, choose M here: the module will be called intel-wmi-thunderbolt. +config XIAOMI_WMI + tristate "Xiaomi WMI key driver" + depends on ACPI_WMI + depends on INPUT + help + Say Y here if you want to support WMI-based keys on Xiaomi notebooks. + + To compile this driver as a module, choose M here: the module will + be called xiaomi-wmi. + config MSI_WMI tristate "MSI WMI extras" depends on ACPI_WMI @@ -797,7 +804,6 @@ config PEAQ_WMI tristate "PEAQ 2-in-1 WMI hotkey driver" depends on ACPI_WMI depends on INPUT - select INPUT_POLLDEV help Say Y here if you want to support WMI-based hotkeys on PEAQ 2-in-1s. @@ -825,7 +831,6 @@ config ACPI_TOSHIBA depends on ACPI_VIDEO || ACPI_VIDEO = n depends on RFKILL || RFKILL = n depends on IIO - select INPUT_POLLDEV select INPUT_SPARSEKMAP ---help--- This driver adds support for access to certain system settings @@ -905,7 +910,6 @@ config TOSHIBA_WMI config ACPI_CMPC tristate "CMPC Laptop Extras" depends on ACPI && INPUT - depends on BACKLIGHT_LCD_SUPPORT depends on RFKILL || RFKILL=n select BACKLIGHT_CLASS_DEVICE help @@ -923,14 +927,20 @@ config INTEL_CHT_INT33FE 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 3 devices: Maxim MAX17047 Fuel Gauge Controller, + 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_TYPEC_FUSB302=m and CONFIG_BATTERY_MAX17042=m. + 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. + config INTEL_INT0002_VGPIO tristate "Intel ACPI INT0002 Virtual GPIO driver" @@ -1129,7 +1139,6 @@ config INTEL_OAKTRAIL config SAMSUNG_Q10 tristate "Samsung Q10 Extras" depends on ACPI - depends on BACKLIGHT_LCD_SUPPORT select BACKLIGHT_CLASS_DEVICE ---help--- This driver provides support for backlight control on Samsung Q10 @@ -1263,6 +1272,17 @@ 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_MRFLD_PWRBTN + tristate "Intel Merrifield Basin Cove power button driver" + depends on INTEL_SOC_PMIC_MRFLD + depends on INPUT + ---help--- + This option adds a power button driver for Basin Cove PMIC + on Intel Merrifield devices. + + To compile this driver as a module, choose M here: the module + will be called intel_mrfld_pwrbtn. + config I2C_MULTI_INSTANTIATE tristate "I2C multi instantiate pseudo device driver" depends on I2C && ACPI @@ -1287,7 +1307,8 @@ config INTEL_ATOMISP2_PM will be called intel_atomisp2_pm. config HUAWEI_WMI - tristate "Huawei WMI hotkeys driver" + tristate "Huawei WMI laptop extras driver" + depends on ACPI_BATTERY depends on ACPI_WMI depends on INPUT select INPUT_SPARSEKMAP @@ -1296,16 +1317,15 @@ config HUAWEI_WMI select LEDS_TRIGGER_AUDIO select NEW_LEDS help - This driver provides support for Huawei WMI hotkeys. - It enables the missing keys and adds support to the micmute - LED found on some of these laptops. + This driver provides support for Huawei WMI hotkeys, battery charge + control, fn-lock, mic-mute LED, and other extra features. To compile this driver as a module, choose M here: the module will be called huawei-wmi. config PCENGINES_APU2 tristate "PC Engines APUv2/3 front button and LEDs driver" - depends on INPUT && INPUT_KEYBOARD + depends on INPUT && INPUT_KEYBOARD && GPIOLIB depends on LEDS_CLASS select GPIO_AMD_FCH select KEYBOARD_GPIO_POLLED @@ -1317,6 +1337,21 @@ config PCENGINES_APU2 To compile this driver as a module, choose M here: the module will be called pcengines-apuv2. +source "drivers/platform/x86/intel_speed_select_if/Kconfig" + +config SYSTEM76_ACPI + tristate "System76 ACPI Driver" + depends on ACPI + select NEW_LEDS + select LEDS_CLASS + select LEDS_TRIGGERS + help + This is a driver for System76 laptops running open firmware. It adds + support for Fn-Fx key combinations, keyboard backlight, and airplane mode + LEDs. + + If you have a System76 laptop running open firmware, say Y or M here. + endif # X86_PLATFORM_DEVICES config PMC_ATOM diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 86cb76677bc8..42d85a00be4e 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -51,6 +51,7 @@ obj-$(CONFIG_SURFACE3_WMI) += surface3-wmi.o obj-$(CONFIG_TOPSTAR_LAPTOP) += topstar-laptop.o obj-$(CONFIG_WMI_BMOF) += wmi-bmof.o obj-$(CONFIG_INTEL_WMI_THUNDERBOLT) += intel-wmi-thunderbolt.o +obj-$(CONFIG_XIAOMI_WMI) += xiaomi-wmi.o # toshiba_acpi must link after wmi to ensure that wmi devices are found # before toshiba_acpi initializes @@ -60,6 +61,10 @@ obj-$(CONFIG_TOSHIBA_BT_RFKILL) += toshiba_bluetooth.o obj-$(CONFIG_TOSHIBA_HAPS) += toshiba_haps.o obj-$(CONFIG_TOSHIBA_WMI) += toshiba-wmi.o obj-$(CONFIG_INTEL_CHT_INT33FE) += intel_cht_int33fe.o +intel_cht_int33fe-objs := intel_cht_int33fe_common.o \ + intel_cht_int33fe_typec.o \ + intel_cht_int33fe_microb.o + obj-$(CONFIG_INTEL_INT0002_VGPIO) += intel_int0002_vgpio.o obj-$(CONFIG_INTEL_HID_EVENT) += intel-hid.o obj-$(CONFIG_INTEL_VBTN) += intel-vbtn.o @@ -89,11 +94,14 @@ obj-$(CONFIG_INTEL_BXTWC_PMIC_TMU) += intel_bxtwc_tmu.o obj-$(CONFIG_INTEL_TELEMETRY) += intel_telemetry_core.o \ intel_telemetry_pltdrv.o \ intel_telemetry_debugfs.o -obj-$(CONFIG_INTEL_PMC_CORE) += intel_pmc_core.o +obj-$(CONFIG_INTEL_PMC_CORE) += intel_pmc_core.o intel_pmc_core_pltdrv.o obj-$(CONFIG_PMC_ATOM) += pmc_atom.o obj-$(CONFIG_MLX_PLATFORM) += mlx-platform.o obj-$(CONFIG_INTEL_TURBO_MAX_3) += intel_turbo_max_3.o obj-$(CONFIG_INTEL_CHTDC_TI_PWRBTN) += intel_chtdc_ti_pwrbtn.o +obj-$(CONFIG_INTEL_MRFLD_PWRBTN) += intel_mrfld_pwrbtn.o obj-$(CONFIG_I2C_MULTI_INSTANTIATE) += i2c-multi-instantiate.o obj-$(CONFIG_INTEL_ATOMISP2_PM) += intel_atomisp2_pm.o obj-$(CONFIG_PCENGINES_APU2) += pcengines-apuv2.o +obj-$(CONFIG_INTEL_SPEED_SELECT_INTERFACE) += intel_speed_select_if/ +obj-$(CONFIG_SYSTEM76_ACPI) += system76_acpi.o diff --git a/drivers/platform/x86/acer-wireless.c b/drivers/platform/x86/acer-wireless.c index 858037987b33..e0976180532a 100644 --- a/drivers/platform/x86/acer-wireless.c +++ b/drivers/platform/x86/acer-wireless.c @@ -1,11 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Acer Wireless Radio Control Driver * * Copyright (C) 2017 Endless Mobile, Inc. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. */ #include <linux/acpi.h> diff --git a/drivers/platform/x86/acer-wmi.c b/drivers/platform/x86/acer-wmi.c index fcfeadd1301f..60c18f21588d 100644 --- a/drivers/platform/x86/acer-wmi.c +++ b/drivers/platform/x86/acer-wmi.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * Acer WMI Laptop Extras * @@ -6,20 +7,6 @@ * Based on acer_acpi: * Copyright (C) 2005-2007 E.M. Smith * Copyright (C) 2007-2008 Carlos Corbacho <cathectic@gmail.com> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt @@ -272,7 +259,6 @@ struct acer_data { struct acer_debug { struct dentry *root; - struct dentry *devices; u32 wmid_devices; }; @@ -1015,6 +1001,7 @@ static acpi_status WMID_get_u32(u32 *value, u32 cap) *value = tmp & 0x1; return 0; } + /* fall through */ default: return AE_ERROR; } @@ -1341,6 +1328,7 @@ static acpi_status get_u32(u32 *value, u32 cap) status = AMW0_get_u32(value, cap); break; } + /* fall through */ case ACER_WMID: status = WMID_get_u32(value, cap); break; @@ -1383,6 +1371,7 @@ static acpi_status set_u32(u32 value, u32 cap) return AMW0_set_u32(value, cap); } + /* fall through */ case ACER_WMID: return WMID_set_u32(value, cap); case ACER_WMID_v2: @@ -1392,6 +1381,7 @@ static acpi_status set_u32(u32 value, u32 cap) return wmid_v2_set_u32(value, cap); else if (wmi_has_guid(WMID_GUID2)) return WMID_set_u32(value, cap); + /* fall through */ default: return AE_BAD_PARAMETER; } @@ -1891,52 +1881,17 @@ static int __init acer_wmi_enable_rf_button(void) return status; } -#define ACER_WMID_ACCEL_HID "BST0001" - -static acpi_status __init acer_wmi_get_handle_cb(acpi_handle ah, u32 level, - void *ctx, void **retval) -{ - struct acpi_device *dev; - - if (!strcmp(ctx, "SENR")) { - if (acpi_bus_get_device(ah, &dev)) - return AE_OK; - if (strcmp(ACER_WMID_ACCEL_HID, acpi_device_hid(dev))) - return AE_OK; - } else - return AE_OK; - - *(acpi_handle *)retval = ah; - - return AE_CTRL_TERMINATE; -} - -static int __init acer_wmi_get_handle(const char *name, const char *prop, - acpi_handle *ah) -{ - acpi_status status; - acpi_handle handle; - - BUG_ON(!name || !ah); - - handle = NULL; - status = acpi_get_devices(prop, acer_wmi_get_handle_cb, - (void *)name, &handle); - if (ACPI_SUCCESS(status) && handle) { - *ah = handle; - return 0; - } else { - return -ENODEV; - } -} - static int __init acer_wmi_accel_setup(void) { + struct acpi_device *adev; int err; - err = acer_wmi_get_handle("SENR", ACER_WMID_ACCEL_HID, &gsensor_handle); - if (err) - return err; + adev = acpi_dev_get_first_match_dev("BST0001", NULL, -1); + if (!adev) + return -ENODEV; + + gsensor_handle = acpi_device_handle(adev); + acpi_dev_put(adev); interface->capability |= ACER_CAP_ACCEL; @@ -2161,29 +2116,15 @@ static struct platform_device *acer_platform_device; static void remove_debugfs(void) { - debugfs_remove(interface->debug.devices); - debugfs_remove(interface->debug.root); + debugfs_remove_recursive(interface->debug.root); } -static int __init create_debugfs(void) +static void __init create_debugfs(void) { interface->debug.root = debugfs_create_dir("acer-wmi", NULL); - if (!interface->debug.root) { - pr_err("Failed to create debugfs directory"); - return -ENOMEM; - } - - interface->debug.devices = debugfs_create_u32("devices", S_IRUGO, - interface->debug.root, - &interface->debug.wmid_devices); - if (!interface->debug.devices) - goto error_debugfs; - return 0; - -error_debugfs: - remove_debugfs(); - return -ENOMEM; + debugfs_create_u32("devices", S_IRUGO, interface->debug.root, + &interface->debug.wmid_devices); } static int __init acer_wmi_init(void) @@ -2313,9 +2254,7 @@ static int __init acer_wmi_init(void) if (wmi_has_guid(WMID_GUID2)) { interface->debug.wmid_devices = get_wmid_devices(); - err = create_debugfs(); - if (err) - goto error_create_debugfs; + create_debugfs(); } /* Override any initial settings with values from the commandline */ @@ -2323,8 +2262,6 @@ static int __init acer_wmi_init(void) return 0; -error_create_debugfs: - platform_device_del(acer_platform_device); error_device_add: platform_device_put(acer_platform_device); error_device_alloc: diff --git a/drivers/platform/x86/acerhdf.c b/drivers/platform/x86/acerhdf.c index 505224225378..8cc86f4e3ac1 100644 --- a/drivers/platform/x86/acerhdf.c +++ b/drivers/platform/x86/acerhdf.c @@ -1,9 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * acerhdf - A driver which monitors the temperature * of the aspire one netbook, turns on/off the fan * as soon as the upper/lower threshold is reached. * - * (C) 2009 - Peter Feuerer peter (a) piie.net + * (C) 2009 - Peter Kaestle peter (a) piie.net * http://piie.net * 2009 Borislav Petkov bp (a) alien8.de * @@ -15,20 +16,6 @@ * o lkml - Matthew Garrett * - Borislav Petkov * - Andreas Mohr - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #define pr_fmt(fmt) "acerhdf: " fmt @@ -237,6 +224,8 @@ static const struct bios_settings bios_tbl[] __initconst = { {"Acer", "Aspire 5739G", "V1.3311", 0x55, 0x58, {0x20, 0x00}, 0}, /* Acer TravelMate 7730 */ {"Acer", "TravelMate 7730G", "v0.3509", 0x55, 0x58, {0xaf, 0x00}, 0}, + /* Acer Aspire 7551 */ + {"Acer", "Aspire 7551", "V1.18", 0x93, 0xa8, {0x14, 0x04}, 1}, /* Acer TravelMate TM8573T */ {"Acer", "TM8573T", "V1.13", 0x93, 0xa8, {0x14, 0x04}, 1}, /* Gateway */ @@ -814,7 +803,7 @@ static void __exit acerhdf_exit(void) } MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Peter Feuerer"); +MODULE_AUTHOR("Peter Kaestle"); MODULE_DESCRIPTION("Aspire One temperature and fan driver"); MODULE_ALIAS("dmi:*:*Acer*:pnAOA*:"); MODULE_ALIAS("dmi:*:*Acer*:pnAO751h*:"); @@ -828,6 +817,7 @@ MODULE_ALIAS("dmi:*:*Acer*:pnAspire*5739G:"); MODULE_ALIAS("dmi:*:*Acer*:pnAspire*One*753:"); MODULE_ALIAS("dmi:*:*Acer*:pnAspire*5315:"); MODULE_ALIAS("dmi:*:*Acer*:TravelMate*7730G:"); +MODULE_ALIAS("dmi:*:*Acer*:pnAspire*7551:"); MODULE_ALIAS("dmi:*:*Acer*:TM8573T:"); MODULE_ALIAS("dmi:*:*Gateway*:pnAOA*:"); MODULE_ALIAS("dmi:*:*Gateway*:pnLT31*:"); diff --git a/drivers/platform/x86/alienware-wmi.c b/drivers/platform/x86/alienware-wmi.c index f10af5c383c5..5bb2859c8285 100644 --- a/drivers/platform/x86/alienware-wmi.c +++ b/drivers/platform/x86/alienware-wmi.c @@ -1,18 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * Alienware AlienFX control * * Copyright (C) 2014 Dell Inc <mario_limonciello@dell.com> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt @@ -522,23 +512,22 @@ static acpi_status alienware_wmax_command(struct wmax_basic_args *in_args, input.length = (acpi_size) sizeof(*in_args); input.pointer = in_args; - if (out_data != NULL) { + if (out_data) { output.length = ACPI_ALLOCATE_BUFFER; output.pointer = NULL; status = wmi_evaluate_method(WMAX_CONTROL_GUID, 0, command, &input, &output); - } else + if (ACPI_SUCCESS(status)) { + obj = (union acpi_object *)output.pointer; + if (obj && obj->type == ACPI_TYPE_INTEGER) + *out_data = (u32)obj->integer.value; + } + kfree(output.pointer); + } else { status = wmi_evaluate_method(WMAX_CONTROL_GUID, 0, command, &input, NULL); - - if (ACPI_SUCCESS(status) && out_data != NULL) { - obj = (union acpi_object *)output.pointer; - if (obj && obj->type == ACPI_TYPE_INTEGER) - *out_data = (u32) obj->integer.value; } - kfree(output.pointer); return status; - } /* @@ -588,7 +577,7 @@ static ssize_t show_hdmi_source(struct device *dev, return scnprintf(buf, PAGE_SIZE, "input [gpu] unknown\n"); } - pr_err("alienware-wmi: unknown HDMI source status: %d\n", out_data); + pr_err("alienware-wmi: unknown HDMI source status: %u\n", status); return scnprintf(buf, PAGE_SIZE, "input gpu [unknown]\n"); } diff --git a/drivers/platform/x86/amilo-rfkill.c b/drivers/platform/x86/amilo-rfkill.c index 0157625cb918..493e169c8f61 100644 --- a/drivers/platform/x86/amilo-rfkill.c +++ b/drivers/platform/x86/amilo-rfkill.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * Support for rfkill on some Fujitsu-Siemens Amilo laptops. * Copyright 2011 Ben Hutchings. @@ -6,11 +7,6 @@ * Copyright 2005 Alejandro Vidal Mata & Javier Vidal Mata. * and on the fsaa1655g driver, which is: * Copyright 2006 Martin VeÄŤeĹ™a. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. */ #include <linux/module.h> diff --git a/drivers/platform/x86/apple-gmux.c b/drivers/platform/x86/apple-gmux.c index fd2ffebc868f..7e3083deb1c5 100644 --- a/drivers/platform/x86/apple-gmux.c +++ b/drivers/platform/x86/apple-gmux.c @@ -1,13 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Gmux driver for Apple laptops * * Copyright (C) Canonical Ltd. <seth.forshee@canonical.com> * Copyright (C) 2010-2012 Andreas Heider <andreas@meetr.de> * Copyright (C) 2015 Lukas Wunner <lukas@wunner.de> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt diff --git a/drivers/platform/x86/asus-laptop.c b/drivers/platform/x86/asus-laptop.c index 700c48ddfa7c..a666fbc2e73b 100644 --- a/drivers/platform/x86/asus-laptop.c +++ b/drivers/platform/x86/asus-laptop.c @@ -1,26 +1,11 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * asus-laptop.c - Asus Laptop Support * - * * Copyright (C) 2002-2005 Julien Lerouge, 2003-2006 Karol Kozimor * Copyright (C) 2006-2007 Corentin Chary * Copyright (C) 2011 Wind River Systems * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - * * The development page for this driver is located at * http://sourceforge.net/projects/acpi4asus/ * @@ -49,7 +34,6 @@ #include <linux/uaccess.h> #include <linux/input.h> #include <linux/input/sparse-keymap.h> -#include <linux/input-polldev.h> #include <linux/rfkill.h> #include <linux/slab.h> #include <linux/dmi.h> @@ -259,7 +243,7 @@ struct asus_laptop { struct input_dev *inputdev; struct key_entry *keymap; - struct input_polled_dev *pega_accel_poll; + struct input_dev *pega_accel_poll; struct asus_led wled; struct asus_led bled; @@ -461,9 +445,9 @@ static int pega_acc_axis(struct asus_laptop *asus, int curr, char *method) return clamp_val((short)val, -PEGA_ACC_CLAMP, PEGA_ACC_CLAMP); } -static void pega_accel_poll(struct input_polled_dev *ipd) +static void pega_accel_poll(struct input_dev *input) { - struct device *parent = ipd->input->dev.parent; + struct device *parent = input->dev.parent; struct asus_laptop *asus = dev_get_drvdata(parent); /* In some cases, the very first call to poll causes a @@ -472,10 +456,10 @@ static void pega_accel_poll(struct input_polled_dev *ipd) * device, and perhaps a firmware bug. Fake the first report. */ if (!asus->pega_acc_live) { asus->pega_acc_live = true; - input_report_abs(ipd->input, ABS_X, 0); - input_report_abs(ipd->input, ABS_Y, 0); - input_report_abs(ipd->input, ABS_Z, 0); - input_sync(ipd->input); + input_report_abs(input, ABS_X, 0); + input_report_abs(input, ABS_Y, 0); + input_report_abs(input, ABS_Z, 0); + input_sync(input); return; } @@ -486,25 +470,24 @@ static void pega_accel_poll(struct input_polled_dev *ipd) /* Note transform, convert to "right/up/out" in the native * landscape orientation (i.e. the vector is the direction of * "real up" in the device's cartiesian coordinates). */ - input_report_abs(ipd->input, ABS_X, -asus->pega_acc_x); - input_report_abs(ipd->input, ABS_Y, -asus->pega_acc_y); - input_report_abs(ipd->input, ABS_Z, asus->pega_acc_z); - input_sync(ipd->input); + input_report_abs(input, ABS_X, -asus->pega_acc_x); + input_report_abs(input, ABS_Y, -asus->pega_acc_y); + input_report_abs(input, ABS_Z, asus->pega_acc_z); + input_sync(input); } static void pega_accel_exit(struct asus_laptop *asus) { if (asus->pega_accel_poll) { - input_unregister_polled_device(asus->pega_accel_poll); - input_free_polled_device(asus->pega_accel_poll); + input_unregister_device(asus->pega_accel_poll); + asus->pega_accel_poll = NULL; } - asus->pega_accel_poll = NULL; } static int pega_accel_init(struct asus_laptop *asus) { int err; - struct input_polled_dev *ipd; + struct input_dev *input; if (!asus->is_pega_lucid) return -ENODEV; @@ -514,37 +497,39 @@ static int pega_accel_init(struct asus_laptop *asus) acpi_check_handle(asus->handle, METHOD_XLRZ, NULL)) return -ENODEV; - ipd = input_allocate_polled_device(); - if (!ipd) + input = input_allocate_device(); + if (!input) return -ENOMEM; - ipd->poll = pega_accel_poll; - ipd->poll_interval = 125; - ipd->poll_interval_min = 50; - ipd->poll_interval_max = 2000; - - ipd->input->name = PEGA_ACCEL_DESC; - ipd->input->phys = PEGA_ACCEL_NAME "/input0"; - ipd->input->dev.parent = &asus->platform_device->dev; - ipd->input->id.bustype = BUS_HOST; + input->name = PEGA_ACCEL_DESC; + input->phys = PEGA_ACCEL_NAME "/input0"; + input->dev.parent = &asus->platform_device->dev; + input->id.bustype = BUS_HOST; - set_bit(EV_ABS, ipd->input->evbit); - input_set_abs_params(ipd->input, ABS_X, + input_set_abs_params(input, ABS_X, -PEGA_ACC_CLAMP, PEGA_ACC_CLAMP, 0, 0); - input_set_abs_params(ipd->input, ABS_Y, + input_set_abs_params(input, ABS_Y, -PEGA_ACC_CLAMP, PEGA_ACC_CLAMP, 0, 0); - input_set_abs_params(ipd->input, ABS_Z, + input_set_abs_params(input, ABS_Z, -PEGA_ACC_CLAMP, PEGA_ACC_CLAMP, 0, 0); - err = input_register_polled_device(ipd); + err = input_setup_polling(input, pega_accel_poll); + if (err) + goto exit; + + input_set_poll_interval(input, 125); + input_set_min_poll_interval(input, 50); + input_set_max_poll_interval(input, 2000); + + err = input_register_device(input); if (err) goto exit; - asus->pega_accel_poll = ipd; + asus->pega_accel_poll = input; return 0; exit: - input_free_polled_device(ipd); + input_free_device(input); return err; } @@ -1163,7 +1148,7 @@ static void asus_als_switch(struct asus_laptop *asus, int value) ret = write_acpi_int(asus->handle, METHOD_ALS_CONTROL, value); } if (ret) - pr_warning("Error setting light sensor switch\n"); + pr_warn("Error setting light sensor switch\n"); asus->light_switch = value; } @@ -1565,8 +1550,7 @@ static void asus_acpi_notify(struct acpi_device *device, u32 event) /* Accelerometer "coarse orientation change" event */ if (asus->pega_accel_poll && event == 0xEA) { - kobject_uevent(&asus->pega_accel_poll->input->dev.kobj, - KOBJ_CHANGE); + kobject_uevent(&asus->pega_accel_poll->dev.kobj, KOBJ_CHANGE); return ; } diff --git a/drivers/platform/x86/asus-nb-wmi.c b/drivers/platform/x86/asus-nb-wmi.c index b6f2ff95c3ed..b361c73636a4 100644 --- a/drivers/platform/x86/asus-nb-wmi.c +++ b/drivers/platform/x86/asus-nb-wmi.c @@ -1,21 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * Asus Notebooks WMI hotkey driver * * Copyright(C) 2010 Corentin Chary <corentin.chary@gmail.com> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt @@ -78,10 +65,12 @@ static bool asus_q500a_i8042_filter(unsigned char data, unsigned char str, static struct quirk_entry quirk_asus_unknown = { .wapf = 0, + .wmi_backlight_set_devstate = true, }; static struct quirk_entry quirk_asus_q500a = { .i8042_filter = asus_q500a_i8042_filter, + .wmi_backlight_set_devstate = true, }; /* @@ -92,26 +81,32 @@ static struct quirk_entry quirk_asus_q500a = { static struct quirk_entry quirk_asus_x55u = { .wapf = 4, .wmi_backlight_power = true, + .wmi_backlight_set_devstate = true, .no_display_toggle = true, }; static struct quirk_entry quirk_asus_wapf4 = { .wapf = 4, + .wmi_backlight_set_devstate = true, }; static struct quirk_entry quirk_asus_x200ca = { .wapf = 2, + .wmi_backlight_set_devstate = true, }; static struct quirk_entry quirk_asus_ux303ub = { .wmi_backlight_native = true, + .wmi_backlight_set_devstate = true, }; static struct quirk_entry quirk_asus_x550lb = { + .wmi_backlight_set_devstate = true, .xusb2pr = 0x01D9, }; static struct quirk_entry quirk_asus_forceals = { + .wmi_backlight_set_devstate = true, .wmi_force_als_set = true, }; @@ -407,6 +402,15 @@ static const struct dmi_system_id asus_quirks[] = { }, .driver_data = &quirk_asus_forceals, }, + { + .callback = dmi_matched, + .ident = "ASUSTeK COMPUTER INC. UX430UNR", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), + DMI_MATCH(DMI_PRODUCT_NAME, "UX430UNR"), + }, + .driver_data = &quirk_asus_forceals, + }, {}, }; @@ -468,6 +472,7 @@ static const struct key_entry asus_nb_wmi_keymap[] = { { KE_KEY, 0x6B, { KEY_TOUCHPAD_TOGGLE } }, { KE_IGNORE, 0x6E, }, /* Low Battery notification */ { KE_KEY, 0x7a, { KEY_ALS_TOGGLE } }, /* Ambient Light Sensor Toggle */ + { KE_KEY, 0x7c, { KEY_MICMUTE } }, { KE_KEY, 0x7D, { KEY_BLUETOOTH } }, /* Bluetooth Enable */ { KE_KEY, 0x7E, { KEY_BLUETOOTH } }, /* Bluetooth Disable */ { KE_KEY, 0x82, { KEY_CAMERA } }, @@ -482,7 +487,7 @@ static const struct key_entry asus_nb_wmi_keymap[] = { { KE_KEY, 0x92, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT + TV + DVI */ { KE_KEY, 0x93, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + CRT + TV + DVI */ { KE_KEY, 0x95, { KEY_MEDIA } }, - { KE_KEY, 0x99, { KEY_PHONE } }, + { KE_KEY, 0x99, { KEY_PHONE } }, /* Conflicts with fan mode switch */ { KE_KEY, 0xA0, { KEY_SWITCHVIDEOMODE } }, /* SDSP HDMI only */ { KE_KEY, 0xA1, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + HDMI */ { KE_KEY, 0xA2, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT + HDMI */ diff --git a/drivers/platform/x86/asus-wireless.c b/drivers/platform/x86/asus-wireless.c index 7458f7602d5e..d3e7171928e5 100644 --- a/drivers/platform/x86/asus-wireless.c +++ b/drivers/platform/x86/asus-wireless.c @@ -1,11 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Asus Wireless Radio Control Driver * * Copyright (C) 2015-2016 Endless Mobile, Inc. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. */ #include <linux/kernel.h> diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index ee1fa93708ec..821b08e01635 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * Asus PC WMI hotkey driver * @@ -8,20 +9,6 @@ * Copyright (C) 2005 Miloslav Trmac <mitr@volny.cz> * Copyright (C) 2005 Bernhard Rosenkraenzer <bero@arklinux.org> * Copyright (C) 2005 Dmitry Torokhov <dtor@mail.ru> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt @@ -39,6 +26,7 @@ #include <linux/rfkill.h> #include <linux/pci.h> #include <linux/pci_hotplug.h> +#include <linux/power_supply.h> #include <linux/hwmon.h> #include <linux/hwmon-sysfs.h> #include <linux/debugfs.h> @@ -48,6 +36,8 @@ #include <linux/thermal.h> #include <linux/acpi.h> #include <linux/dmi.h> + +#include <acpi/battery.h> #include <acpi/video.h> #include "asus-wmi.h" @@ -66,20 +56,45 @@ MODULE_LICENSE("GPL"); #define NOTIFY_BRNUP_MAX 0x1f #define NOTIFY_BRNDOWN_MIN 0x20 #define NOTIFY_BRNDOWN_MAX 0x2e +#define NOTIFY_FNLOCK_TOGGLE 0x4e #define NOTIFY_KBD_BRTUP 0xc4 #define NOTIFY_KBD_BRTDWN 0xc5 #define NOTIFY_KBD_BRTTOGGLE 0xc7 +#define NOTIFY_KBD_FBM 0x99 + +#define ASUS_WMI_FNLOCK_BIOS_DISABLED BIT(0) #define ASUS_FAN_DESC "cpu_fan" #define ASUS_FAN_MFUN 0x13 #define ASUS_FAN_SFUN_READ 0x06 #define ASUS_FAN_SFUN_WRITE 0x07 + +/* Based on standard hwmon pwmX_enable values */ +#define ASUS_FAN_CTRL_FULLSPEED 0 #define ASUS_FAN_CTRL_MANUAL 1 #define ASUS_FAN_CTRL_AUTO 2 +#define ASUS_FAN_BOOST_MODE_NORMAL 0 +#define ASUS_FAN_BOOST_MODE_OVERBOOST 1 +#define ASUS_FAN_BOOST_MODE_OVERBOOST_MASK 0x01 +#define ASUS_FAN_BOOST_MODE_SILENT 2 +#define ASUS_FAN_BOOST_MODE_SILENT_MASK 0x02 +#define ASUS_FAN_BOOST_MODES_MASK 0x03 + #define USB_INTEL_XUSB2PR 0xD0 #define PCI_DEVICE_ID_INTEL_LYNXPOINT_LP_XHCI 0x9c31 +#define ASUS_ACPI_UID_ASUSWMI "ASUSWMI" +#define ASUS_ACPI_UID_ATK "ATK" + +#define WMI_EVENT_QUEUE_SIZE 0x10 +#define WMI_EVENT_QUEUE_END 0x1 +#define WMI_EVENT_MASK 0xFFFF +/* The WMI hotkey event value is always the same. */ +#define WMI_EVENT_VALUE_ATK 0xFF + +#define WMI_EVENT_MASK 0xFFFF + static const char * const ashs_ids[] = { "ATK4001", "ATK4002", NULL }; static bool ashs_present(void) @@ -95,6 +110,7 @@ static bool ashs_present(void) struct bios_args { u32 arg0; u32 arg1; + u32 arg2; /* At least TUF Gaming series uses 3 dword input buffer. */ } __packed; /* @@ -110,7 +126,7 @@ struct agfn_args { } __packed; /* struct used for calling fan read and write methods */ -struct fan_args { +struct agfn_fan_args { struct agfn_args agfn; /* common fields */ u8 fan; /* fan number: 0: set auto mode 1: 1st fan */ u32 speed; /* read: RPM/100 - write: 0-255 */ @@ -138,10 +154,17 @@ struct asus_rfkill { u32 dev_id; }; +enum fan_type { + FAN_TYPE_NONE = 0, + FAN_TYPE_AGFN, /* deprecated on newer platforms */ + FAN_TYPE_SPEC83, /* starting in Spec 8.3, use CPU_FAN_CTRL */ +}; + struct asus_wmi { int dsts_id; int spec; int sfun; + bool wmi_event_queue; struct input_dev *inputdev; struct backlight_device *backlight_device; @@ -167,9 +190,16 @@ struct asus_wmi { struct asus_rfkill gps; struct asus_rfkill uwb; - bool asus_hwmon_fan_manual_mode; - int asus_hwmon_num_fans; - int asus_hwmon_pwm; + enum fan_type fan_type; + int fan_pwm_mode; + int agfn_pwm; + + bool fan_boost_mode_available; + u8 fan_boost_mode_mask; + u8 fan_boost_mode; + + // The RSOC controls the maximum charging percentage. + bool battery_rsoc_available; struct hotplug_slot hotplug_slot; struct mutex hotplug_lock; @@ -177,11 +207,15 @@ struct asus_wmi { struct workqueue_struct *hotplug_workqueue; struct work_struct hotplug_work; + bool fnlock_locked; + struct asus_wmi_debug debug; struct asus_wmi_driver *driver; }; +/* Input **********************************************************************/ + static int asus_wmi_input_init(struct asus_wmi *asus) { int err; @@ -219,11 +253,15 @@ static void asus_wmi_input_exit(struct asus_wmi *asus) asus->inputdev = NULL; } -int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1, u32 *retval) +/* WMI ************************************************************************/ + +static int asus_wmi_evaluate_method3(u32 method_id, + u32 arg0, u32 arg1, u32 arg2, u32 *retval) { struct bios_args args = { .arg0 = arg0, .arg1 = arg1, + .arg2 = arg2, }; struct acpi_buffer input = { (acpi_size) sizeof(args), &args }; struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; @@ -235,7 +273,7 @@ int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1, u32 *retval) &input, &output); if (ACPI_FAILURE(status)) - goto exit; + return -EIO; obj = (union acpi_object *)output.pointer; if (obj && obj->type == ACPI_TYPE_INTEGER) @@ -246,15 +284,16 @@ int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1, u32 *retval) kfree(obj); -exit: - if (ACPI_FAILURE(status)) - return -EIO; - if (tmp == ASUS_WMI_UNSUPPORTED_METHOD) return -ENODEV; return 0; } + +int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1, u32 *retval) +{ + return asus_wmi_evaluate_method3(method_id, arg0, arg1, 0, retval); +} EXPORT_SYMBOL_GPL(asus_wmi_evaluate_method); static int asus_wmi_evaluate_method_agfn(const struct acpi_buffer args) @@ -268,12 +307,11 @@ static int asus_wmi_evaluate_method_agfn(const struct acpi_buffer args) * Copy to dma capable address otherwise memory corruption occurs as * bios has to be able to access it. */ - input.pointer = kzalloc(args.length, GFP_DMA | GFP_KERNEL); + input.pointer = kmemdup(args.pointer, args.length, GFP_DMA | GFP_KERNEL); input.length = args.length; if (!input.pointer) return -ENOMEM; phys_addr = virt_to_phys(input.pointer); - memcpy(input.pointer, args.pointer, args.length); status = asus_wmi_evaluate_method(ASUS_WMI_METHODID_AGFN, phys_addr, 0, &retval); @@ -307,7 +345,6 @@ static int asus_wmi_get_devstate_bits(struct asus_wmi *asus, int err; err = asus_wmi_get_devstate(asus, dev_id, &retval); - if (err < 0) return err; @@ -328,9 +365,107 @@ static int asus_wmi_get_devstate_simple(struct asus_wmi *asus, u32 dev_id) ASUS_WMI_DSTS_STATUS_BIT); } -/* - * LEDs - */ +static bool asus_wmi_dev_is_present(struct asus_wmi *asus, u32 dev_id) +{ + u32 retval; + int status = asus_wmi_get_devstate(asus, dev_id, &retval); + + return status == 0 && (retval & ASUS_WMI_DSTS_PRESENCE_BIT); +} + +/* Battery ********************************************************************/ + +/* The battery maximum charging percentage */ +static int charge_end_threshold; + +static ssize_t charge_control_end_threshold_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int value, ret, rv; + + ret = kstrtouint(buf, 10, &value); + if (ret) + return ret; + + if (value < 0 || value > 100) + return -EINVAL; + + ret = asus_wmi_set_devstate(ASUS_WMI_DEVID_RSOC, value, &rv); + if (ret) + return ret; + + if (rv != 1) + return -EIO; + + /* There isn't any method in the DSDT to read the threshold, so we + * save the threshold. + */ + charge_end_threshold = value; + return count; +} + +static ssize_t charge_control_end_threshold_show(struct device *device, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%d\n", charge_end_threshold); +} + +static DEVICE_ATTR_RW(charge_control_end_threshold); + +static int asus_wmi_battery_add(struct power_supply *battery) +{ + /* The WMI method does not provide a way to specific a battery, so we + * just assume it is the first battery. + */ + if (strcmp(battery->desc->name, "BAT0") != 0) + return -ENODEV; + + if (device_create_file(&battery->dev, + &dev_attr_charge_control_end_threshold)) + return -ENODEV; + + /* The charge threshold is only reset when the system is power cycled, + * and we can't get the current threshold so let set it to 100% when + * a battery is added. + */ + asus_wmi_set_devstate(ASUS_WMI_DEVID_RSOC, 100, NULL); + charge_end_threshold = 100; + + return 0; +} + +static int asus_wmi_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 = asus_wmi_battery_add, + .remove_battery = asus_wmi_battery_remove, + .name = "ASUS Battery Extension", +}; + +static void asus_wmi_battery_init(struct asus_wmi *asus) +{ + asus->battery_rsoc_available = false; + if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_RSOC)) { + asus->battery_rsoc_available = true; + battery_hook_register(&battery_hook); + } +} + +static void asus_wmi_battery_exit(struct asus_wmi *asus) +{ + if (asus->battery_rsoc_available) + battery_hook_unregister(&battery_hook); +} + +/* LEDs ***********************************************************************/ + /* * These functions actually update the LED's, and are called from a * workqueue. By doing this as separate work rather than when the LED @@ -404,15 +539,14 @@ static int kbd_led_read(struct asus_wmi *asus, int *level, int *env) if (retval == 0x8000) retval = 0; - if (retval >= 0) { - if (level) - *level = retval & 0x7F; - if (env) - *env = (retval >> 8) & 0x7F; - retval = 0; - } + if (retval < 0) + return retval; - return retval; + if (level) + *level = retval & 0x7F; + if (env) + *env = (retval >> 8) & 0x7F; + return 0; } static void do_kbd_led_set(struct led_classdev *led_cdev, int value) @@ -423,18 +557,17 @@ static void do_kbd_led_set(struct led_classdev *led_cdev, int value) asus = container_of(led_cdev, struct asus_wmi, kbd_led); max_level = asus->kbd_led.max_brightness; - if (value > max_level) - value = max_level; - else if (value < 0) - value = 0; - - asus->kbd_led_wk = value; + asus->kbd_led_wk = clamp_val(value, 0, max_level); kbd_led_update(asus); } static void kbd_led_set(struct led_classdev *led_cdev, enum led_brightness value) { + /* Prevent disabling keyboard backlight on module unregister */ + if (led_cdev->flags & LED_UNREGISTERING) + return; + do_kbd_led_set(led_cdev, value); } @@ -454,7 +587,6 @@ static enum led_brightness kbd_led_get(struct led_classdev *led_cdev) asus = container_of(led_cdev, struct asus_wmi, kbd_led); retval = kbd_led_read(asus, &value, NULL); - if (retval < 0) return retval; @@ -470,15 +602,6 @@ static int wlan_led_unknown_state(struct asus_wmi *asus) return result & ASUS_WMI_DSTS_UNKNOWN_BIT; } -static int wlan_led_presence(struct asus_wmi *asus) -{ - u32 result; - - asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_WIRELESS_LED, &result); - - return result & ASUS_WMI_DSTS_PRESENCE_BIT; -} - static void wlan_led_update(struct work_struct *work) { int ctrl_param; @@ -545,15 +668,6 @@ static enum led_brightness lightbar_led_get(struct led_classdev *led_cdev) return result & ASUS_WMI_DSTS_LIGHTBAR_MASK; } -static int lightbar_led_presence(struct asus_wmi *asus) -{ - u32 result; - - asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_LIGHTBAR, &result); - - return result & ASUS_WMI_DSTS_PRESENCE_BIT; -} - static void asus_wmi_led_exit(struct asus_wmi *asus) { if (!IS_ERR_OR_NULL(asus->kbd_led.dev)) @@ -590,8 +704,7 @@ static int asus_wmi_led_init(struct asus_wmi *asus) goto error; } - led_val = kbd_led_read(asus, NULL, NULL); - if (led_val >= 0) { + if (!kbd_led_read(asus, &led_val, NULL)) { asus->kbd_led_wk = led_val; asus->kbd_led.name = "asus::kbd_backlight"; asus->kbd_led.flags = LED_BRIGHT_HW_CHANGED; @@ -605,7 +718,8 @@ static int asus_wmi_led_init(struct asus_wmi *asus) goto error; } - if (wlan_led_presence(asus) && (asus->driver->quirks->wapf > 0)) { + if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_WIRELESS_LED) + && (asus->driver->quirks->wapf > 0)) { INIT_WORK(&asus->wlan_led_work, wlan_led_update); asus->wlan_led.name = "asus::wlan"; @@ -622,7 +736,7 @@ static int asus_wmi_led_init(struct asus_wmi *asus) goto error; } - if (lightbar_led_presence(asus)) { + if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_LIGHTBAR)) { INIT_WORK(&asus->lightbar_led_work, lightbar_led_update); asus->lightbar_led.name = "asus::lightbar"; @@ -641,6 +755,7 @@ error: return rv; } +/* RF *************************************************************************/ /* * PCI hotplug (for wlan rfkill) @@ -744,16 +859,14 @@ static int asus_register_rfkill_notifier(struct asus_wmi *asus, char *node) acpi_handle handle; status = acpi_get_handle(NULL, node, &handle); - - if (ACPI_SUCCESS(status)) { - status = acpi_install_notify_handler(handle, - ACPI_SYSTEM_NOTIFY, - asus_rfkill_notify, asus); - if (ACPI_FAILURE(status)) - pr_warn("Failed to register notify on %s\n", node); - } else + if (ACPI_FAILURE(status)) return -ENODEV; + status = acpi_install_notify_handler(handle, ACPI_SYSTEM_NOTIFY, + asus_rfkill_notify, asus); + if (ACPI_FAILURE(status)) + pr_warn("Failed to register notify on %s\n", node); + return 0; } @@ -763,15 +876,13 @@ static void asus_unregister_rfkill_notifier(struct asus_wmi *asus, char *node) acpi_handle handle; status = acpi_get_handle(NULL, node, &handle); + if (ACPI_FAILURE(status)) + return; - if (ACPI_SUCCESS(status)) { - status = acpi_remove_notify_handler(handle, - ACPI_SYSTEM_NOTIFY, - asus_rfkill_notify); - if (ACPI_FAILURE(status)) - pr_err("Error removing rfkill notify handler %s\n", - node); - } + status = acpi_remove_notify_handler(handle, ACPI_SYSTEM_NOTIFY, + asus_rfkill_notify); + if (ACPI_FAILURE(status)) + pr_err("Error removing rfkill notify handler %s\n", node); } static int asus_get_adapter_status(struct hotplug_slot *hotplug_slot, @@ -1063,6 +1174,8 @@ exit: return result; } +/* Quirks *********************************************************************/ + static void asus_wmi_set_xusb2pr(struct asus_wmi *asus) { struct pci_dev *xhci_pdev; @@ -1095,13 +1208,12 @@ static void asus_wmi_set_als(void) asus_wmi_set_devstate(ASUS_WMI_DEVID_ALS_ENABLE, 1, NULL); } -/* - * Hwmon device - */ -static int asus_hwmon_agfn_fan_speed_read(struct asus_wmi *asus, int fan, +/* Hwmon device ***************************************************************/ + +static int asus_agfn_fan_speed_read(struct asus_wmi *asus, int fan, int *speed) { - struct fan_args args = { + struct agfn_fan_args args = { .agfn.len = sizeof(args), .agfn.mfun = ASUS_FAN_MFUN, .agfn.sfun = ASUS_FAN_SFUN_READ, @@ -1125,10 +1237,10 @@ static int asus_hwmon_agfn_fan_speed_read(struct asus_wmi *asus, int fan, return 0; } -static int asus_hwmon_agfn_fan_speed_write(struct asus_wmi *asus, int fan, +static int asus_agfn_fan_speed_write(struct asus_wmi *asus, int fan, int *speed) { - struct fan_args args = { + struct agfn_fan_args args = { .agfn.len = sizeof(args), .agfn.mfun = ASUS_FAN_MFUN, .agfn.sfun = ASUS_FAN_SFUN_WRITE, @@ -1148,7 +1260,7 @@ static int asus_hwmon_agfn_fan_speed_write(struct asus_wmi *asus, int fan, return -ENXIO; if (speed && fan == 1) - asus->asus_hwmon_pwm = *speed; + asus->agfn_pwm = *speed; return 0; } @@ -1157,77 +1269,60 @@ static int asus_hwmon_agfn_fan_speed_write(struct asus_wmi *asus, int fan, * Check if we can read the speed of one fan. If true we assume we can also * control it. */ -static int asus_hwmon_get_fan_number(struct asus_wmi *asus, int *num_fans) +static bool asus_wmi_has_agfn_fan(struct asus_wmi *asus) { int status; - int speed = 0; + int speed; + u32 value; - *num_fans = 0; + status = asus_agfn_fan_speed_read(asus, 1, &speed); + if (status != 0) + return false; - status = asus_hwmon_agfn_fan_speed_read(asus, 1, &speed); - if (!status) - *num_fans = 1; + status = asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_FAN_CTRL, &value); + if (status != 0) + return false; - return 0; + /* + * We need to find a better way, probably using sfun, + * bits or spec ... + * Currently we disable it if: + * - ASUS_WMI_UNSUPPORTED_METHOD is returned + * - reverved bits are non-zero + * - sfun and presence bit are not set + */ + return !(value == ASUS_WMI_UNSUPPORTED_METHOD || value & 0xFFF80000 + || (!asus->sfun && !(value & ASUS_WMI_DSTS_PRESENCE_BIT))); } -static int asus_hwmon_fan_set_auto(struct asus_wmi *asus) +static int asus_fan_set_auto(struct asus_wmi *asus) { int status; + u32 retval; - status = asus_hwmon_agfn_fan_speed_write(asus, 0, NULL); - if (status) - return -ENXIO; + switch (asus->fan_type) { + case FAN_TYPE_SPEC83: + status = asus_wmi_set_devstate(ASUS_WMI_DEVID_CPU_FAN_CTRL, + 0, &retval); + if (status) + return status; - asus->asus_hwmon_fan_manual_mode = false; + if (retval != 1) + return -EIO; + break; - return 0; -} + case FAN_TYPE_AGFN: + status = asus_agfn_fan_speed_write(asus, 0, NULL); + if (status) + return -ENXIO; + break; -static int asus_hwmon_fan_rpm_show(struct device *dev, int fan) -{ - struct asus_wmi *asus = dev_get_drvdata(dev); - int value; - int ret; - - /* no speed readable on manual mode */ - if (asus->asus_hwmon_fan_manual_mode) - return -ENXIO; - - ret = asus_hwmon_agfn_fan_speed_read(asus, fan+1, &value); - if (ret) { - pr_warn("reading fan speed failed: %d\n", ret); + default: return -ENXIO; } - return value; -} -static void asus_hwmon_pwm_show(struct asus_wmi *asus, int fan, int *value) -{ - int err; - - if (asus->asus_hwmon_pwm >= 0) { - *value = asus->asus_hwmon_pwm; - return; - } - - err = asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_FAN_CTRL, value); - if (err < 0) - return; - - *value &= 0xFF; - - if (*value == 1) /* Low Speed */ - *value = 85; - else if (*value == 2) - *value = 170; - else if (*value == 3) - *value = 255; - else if (*value) { - pr_err("Unknown fan speed %#x\n", *value); - *value = -1; - } + return 0; } static ssize_t pwm1_show(struct device *dev, @@ -1235,9 +1330,33 @@ static ssize_t pwm1_show(struct device *dev, char *buf) { struct asus_wmi *asus = dev_get_drvdata(dev); + int err; int value; - asus_hwmon_pwm_show(asus, 0, &value); + /* If we already set a value then just return it */ + if (asus->agfn_pwm >= 0) + return sprintf(buf, "%d\n", asus->agfn_pwm); + + /* + * If we haven't set already set a value through the AGFN interface, + * we read a current value through the (now-deprecated) FAN_CTRL device. + */ + err = asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_FAN_CTRL, &value); + if (err < 0) + return err; + + value &= 0xFF; + + if (value == 1) /* Low Speed */ + value = 85; + else if (value == 2) + value = 170; + else if (value == 3) + value = 255; + else if (value) { + pr_err("Unknown fan speed %#x\n", value); + value = -1; + } return sprintf(buf, "%d\n", value); } @@ -1251,17 +1370,16 @@ static ssize_t pwm1_store(struct device *dev, int ret; ret = kstrtouint(buf, 10, &value); - if (ret) return ret; value = clamp(value, 0, 255); - state = asus_hwmon_agfn_fan_speed_write(asus, 1, &value); + state = asus_agfn_fan_speed_write(asus, 1, &value); if (state) pr_warn("Setting fan speed failed: %d\n", state); else - asus->asus_hwmon_fan_manual_mode = true; + asus->fan_pwm_mode = ASUS_FAN_CTRL_MANUAL; return count; } @@ -1270,10 +1388,37 @@ static ssize_t fan1_input_show(struct device *dev, struct device_attribute *attr, char *buf) { - int value = asus_hwmon_fan_rpm_show(dev, 0); + struct asus_wmi *asus = dev_get_drvdata(dev); + int value; + int ret; - return sprintf(buf, "%d\n", value < 0 ? -1 : value*100); + switch (asus->fan_type) { + case FAN_TYPE_SPEC83: + ret = asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_CPU_FAN_CTRL, + &value); + if (ret < 0) + return ret; + + value &= 0xffff; + break; + case FAN_TYPE_AGFN: + /* no speed readable on manual mode */ + if (asus->fan_pwm_mode == ASUS_FAN_CTRL_MANUAL) + return -ENXIO; + + ret = asus_agfn_fan_speed_read(asus, 1, &value); + if (ret) { + pr_warn("reading fan speed failed: %d\n", ret); + return -ENXIO; + } + break; + + default: + return -ENXIO; + } + + return sprintf(buf, "%d\n", value < 0 ? -1 : value*100); } static ssize_t pwm1_enable_show(struct device *dev, @@ -1282,10 +1427,16 @@ static ssize_t pwm1_enable_show(struct device *dev, { struct asus_wmi *asus = dev_get_drvdata(dev); - if (asus->asus_hwmon_fan_manual_mode) - return sprintf(buf, "%d\n", ASUS_FAN_CTRL_MANUAL); - - return sprintf(buf, "%d\n", ASUS_FAN_CTRL_AUTO); + /* + * Just read back the cached pwm mode. + * + * For the CPU_FAN device, the spec indicates that we should be + * able to read the device status and consult bit 19 to see if we + * are in Full On or Automatic mode. However, this does not work + * in practice on X532FL at least (the bit is always 0) and there's + * also nothing in the DSDT to indicate that this behaviour exists. + */ + return sprintf(buf, "%d\n", asus->fan_pwm_mode); } static ssize_t pwm1_enable_store(struct device *dev, @@ -1295,21 +1446,50 @@ static ssize_t pwm1_enable_store(struct device *dev, struct asus_wmi *asus = dev_get_drvdata(dev); int status = 0; int state; + int value; int ret; + u32 retval; ret = kstrtouint(buf, 10, &state); - if (ret) return ret; - if (state == ASUS_FAN_CTRL_MANUAL) - asus->asus_hwmon_fan_manual_mode = true; - else - status = asus_hwmon_fan_set_auto(asus); + if (asus->fan_type == FAN_TYPE_SPEC83) { + switch (state) { /* standard documented hwmon values */ + case ASUS_FAN_CTRL_FULLSPEED: + value = 1; + break; + case ASUS_FAN_CTRL_AUTO: + value = 0; + break; + default: + return -EINVAL; + } - if (status) - return status; + ret = asus_wmi_set_devstate(ASUS_WMI_DEVID_CPU_FAN_CTRL, + value, &retval); + if (ret) + return ret; + if (retval != 1) + return -EIO; + } else if (asus->fan_type == FAN_TYPE_AGFN) { + switch (state) { + case ASUS_FAN_CTRL_MANUAL: + break; + + case ASUS_FAN_CTRL_AUTO: + status = asus_fan_set_auto(asus); + if (status) + return status; + break; + + default: + return -EINVAL; + } + } + + asus->fan_pwm_mode = state; return count; } @@ -1329,7 +1509,6 @@ static ssize_t asus_hwmon_temp1(struct device *dev, int err; err = asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_THERMAL_CTRL, &value); - if (err < 0) return err; @@ -1361,58 +1540,34 @@ static umode_t asus_hwmon_sysfs_is_visible(struct kobject *kobj, struct attribute *attr, int idx) { struct device *dev = container_of(kobj, struct device, kobj); - struct platform_device *pdev = to_platform_device(dev->parent); - struct asus_wmi *asus = platform_get_drvdata(pdev); - int dev_id = -1; - int fan_attr = -1; + struct asus_wmi *asus = dev_get_drvdata(dev->parent); u32 value = ASUS_WMI_UNSUPPORTED_METHOD; - bool ok = true; - - if (attr == &dev_attr_pwm1.attr) - dev_id = ASUS_WMI_DEVID_FAN_CTRL; - else if (attr == &dev_attr_temp1_input.attr) - dev_id = ASUS_WMI_DEVID_THERMAL_CTRL; - - if (attr == &dev_attr_fan1_input.attr + if (attr == &dev_attr_pwm1.attr) { + if (asus->fan_type != FAN_TYPE_AGFN) + return 0; + } else if (attr == &dev_attr_fan1_input.attr || attr == &dev_attr_fan1_label.attr - || attr == &dev_attr_pwm1.attr || attr == &dev_attr_pwm1_enable.attr) { - fan_attr = 1; - } - - if (dev_id != -1) { - int err = asus_wmi_get_devstate(asus, dev_id, &value); - - if (err < 0 && fan_attr == -1) + if (asus->fan_type == FAN_TYPE_NONE) + return 0; + } else if (attr == &dev_attr_temp1_input.attr) { + int err = asus_wmi_get_devstate(asus, + ASUS_WMI_DEVID_THERMAL_CTRL, + &value); + + if (err < 0) return 0; /* can't return negative here */ - } - if (dev_id == ASUS_WMI_DEVID_FAN_CTRL) { /* - * We need to find a better way, probably using sfun, - * bits or spec ... - * Currently we disable it if: - * - ASUS_WMI_UNSUPPORTED_METHOD is returned - * - reverved bits are non-zero - * - sfun and presence bit are not set + * If the temperature value in deci-Kelvin is near the absolute + * zero temperature, something is clearly wrong */ - if (value == ASUS_WMI_UNSUPPORTED_METHOD || value & 0xFFF80000 - || (!asus->sfun && !(value & ASUS_WMI_DSTS_PRESENCE_BIT))) - ok = false; - else - ok = fan_attr <= asus->asus_hwmon_num_fans; - } else if (dev_id == ASUS_WMI_DEVID_THERMAL_CTRL) { - /* If value is zero, something is clearly wrong */ - if (!value) - ok = false; - } else if (fan_attr <= asus->asus_hwmon_num_fans && fan_attr != -1) { - ok = true; - } else { - ok = false; + if (value == 0 || value == 1) + return 0; } - return ok ? attr->mode : 0; + return attr->mode; } static const struct attribute_group hwmon_attribute_group = { @@ -1423,11 +1578,12 @@ __ATTRIBUTE_GROUPS(hwmon_attribute); static int asus_wmi_hwmon_init(struct asus_wmi *asus) { + struct device *dev = &asus->platform_device->dev; struct device *hwmon; - hwmon = hwmon_device_register_with_groups(&asus->platform_device->dev, - "asus", asus, - hwmon_attribute_groups); + hwmon = devm_hwmon_device_register_with_groups(dev, "asus", asus, + hwmon_attribute_groups); + if (IS_ERR(hwmon)) { pr_err("Could not register asus hwmon device\n"); return PTR_ERR(hwmon); @@ -1435,12 +1591,145 @@ static int asus_wmi_hwmon_init(struct asus_wmi *asus) return 0; } -/* - * Backlight - */ +static int asus_wmi_fan_init(struct asus_wmi *asus) +{ + asus->fan_type = FAN_TYPE_NONE; + asus->agfn_pwm = -1; + + if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_CPU_FAN_CTRL)) + asus->fan_type = FAN_TYPE_SPEC83; + else if (asus_wmi_has_agfn_fan(asus)) + asus->fan_type = FAN_TYPE_AGFN; + + if (asus->fan_type == FAN_TYPE_NONE) + return -ENODEV; + + asus_fan_set_auto(asus); + asus->fan_pwm_mode = ASUS_FAN_CTRL_AUTO; + return 0; +} + +/* Fan mode *******************************************************************/ + +static int fan_boost_mode_check_present(struct asus_wmi *asus) +{ + u32 result; + int err; + + asus->fan_boost_mode_available = false; + + err = asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_FAN_BOOST_MODE, + &result); + if (err) { + if (err == -ENODEV) + return 0; + else + return err; + } + + if ((result & ASUS_WMI_DSTS_PRESENCE_BIT) && + (result & ASUS_FAN_BOOST_MODES_MASK)) { + asus->fan_boost_mode_available = true; + asus->fan_boost_mode_mask = result & ASUS_FAN_BOOST_MODES_MASK; + } + + return 0; +} + +static int fan_boost_mode_write(struct asus_wmi *asus) +{ + int err; + u8 value; + u32 retval; + + value = asus->fan_boost_mode; + + pr_info("Set fan boost mode: %u\n", value); + err = asus_wmi_set_devstate(ASUS_WMI_DEVID_FAN_BOOST_MODE, value, + &retval); + if (err) { + pr_warn("Failed to set fan boost mode: %d\n", err); + return err; + } + + if (retval != 1) { + pr_warn("Failed to set fan boost mode (retval): 0x%x\n", + retval); + return -EIO; + } + + return 0; +} + +static int fan_boost_mode_switch_next(struct asus_wmi *asus) +{ + u8 mask = asus->fan_boost_mode_mask; + + if (asus->fan_boost_mode == ASUS_FAN_BOOST_MODE_NORMAL) { + if (mask & ASUS_FAN_BOOST_MODE_OVERBOOST_MASK) + asus->fan_boost_mode = ASUS_FAN_BOOST_MODE_OVERBOOST; + else if (mask & ASUS_FAN_BOOST_MODE_SILENT_MASK) + asus->fan_boost_mode = ASUS_FAN_BOOST_MODE_SILENT; + } else if (asus->fan_boost_mode == ASUS_FAN_BOOST_MODE_OVERBOOST) { + if (mask & ASUS_FAN_BOOST_MODE_SILENT_MASK) + asus->fan_boost_mode = ASUS_FAN_BOOST_MODE_SILENT; + else + asus->fan_boost_mode = ASUS_FAN_BOOST_MODE_NORMAL; + } else { + asus->fan_boost_mode = ASUS_FAN_BOOST_MODE_NORMAL; + } + + return fan_boost_mode_write(asus); +} + +static ssize_t fan_boost_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct asus_wmi *asus = dev_get_drvdata(dev); + + return scnprintf(buf, PAGE_SIZE, "%d\n", asus->fan_boost_mode); +} + +static ssize_t fan_boost_mode_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int result; + u8 new_mode; + struct asus_wmi *asus = dev_get_drvdata(dev); + u8 mask = asus->fan_boost_mode_mask; + + result = kstrtou8(buf, 10, &new_mode); + if (result < 0) { + pr_warn("Trying to store invalid value\n"); + return result; + } + + if (new_mode == ASUS_FAN_BOOST_MODE_OVERBOOST) { + if (!(mask & ASUS_FAN_BOOST_MODE_OVERBOOST_MASK)) + return -EINVAL; + } else if (new_mode == ASUS_FAN_BOOST_MODE_SILENT) { + if (!(mask & ASUS_FAN_BOOST_MODE_SILENT_MASK)) + return -EINVAL; + } else if (new_mode != ASUS_FAN_BOOST_MODE_NORMAL) { + return -EINVAL; + } + + asus->fan_boost_mode = new_mode; + fan_boost_mode_write(asus); + + return result; +} + +// Fan boost mode: 0 - normal, 1 - overboost, 2 - silent +static DEVICE_ATTR_RW(fan_boost_mode); + +/* Backlight ******************************************************************/ + static int read_backlight_power(struct asus_wmi *asus) { int ret; + if (asus->driver->quirks->store_backlight_power) ret = !asus->driver->panel_power; else @@ -1459,7 +1748,6 @@ static int read_brightness_max(struct asus_wmi *asus) int err; err = asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_BRIGHTNESS, &retval); - if (err < 0) return err; @@ -1479,7 +1767,6 @@ static int read_brightness(struct backlight_device *bd) int err; err = asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_BRIGHTNESS, &retval); - if (err < 0) return err; @@ -1569,7 +1856,6 @@ static int asus_wmi_backlight_init(struct asus_wmi *asus) return max; power = read_backlight_power(asus); - if (power == -ENODEV) power = FB_BLANK_UNBLANK; else if (power < 0) @@ -1619,108 +1905,184 @@ static int is_display_toggle(int code) return 0; } -static void asus_wmi_notify(u32 value, void *context) +/* Fn-lock ********************************************************************/ + +static bool asus_wmi_has_fnlock_key(struct asus_wmi *asus) +{ + u32 result; + + asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_FNLOCK, &result); + + return (result & ASUS_WMI_DSTS_PRESENCE_BIT) && + !(result & ASUS_WMI_FNLOCK_BIOS_DISABLED); +} + +static void asus_wmi_fnlock_update(struct asus_wmi *asus) +{ + int mode = asus->fnlock_locked; + + asus_wmi_set_devstate(ASUS_WMI_DEVID_FNLOCK, mode, NULL); +} + +/* WMI events *****************************************************************/ + +static int asus_wmi_get_event_code(u32 value) { - struct asus_wmi *asus = context; struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL }; union acpi_object *obj; acpi_status status; int code; - int orig_code; - unsigned int key_value = 1; - bool autorelease = 1; status = wmi_get_event_data(value, &response); - if (status != AE_OK) { - pr_err("bad event status 0x%x\n", status); - return; + if (ACPI_FAILURE(status)) { + pr_warn("Failed to get WMI notify code: %s\n", + acpi_format_exception(status)); + return -EIO; } obj = (union acpi_object *)response.pointer; - if (!obj || obj->type != ACPI_TYPE_INTEGER) - goto exit; + if (obj && obj->type == ACPI_TYPE_INTEGER) + code = (int)(obj->integer.value & WMI_EVENT_MASK); + else + code = -EIO; + + kfree(obj); + return code; +} + +static void asus_wmi_handle_event_code(int code, struct asus_wmi *asus) +{ + int orig_code; + unsigned int key_value = 1; + bool autorelease = 1; - code = obj->integer.value; orig_code = code; if (asus->driver->key_filter) { asus->driver->key_filter(asus->driver, &code, &key_value, &autorelease); if (code == ASUS_WMI_KEY_IGNORE) - goto exit; + return; } if (code >= NOTIFY_BRNUP_MIN && code <= NOTIFY_BRNUP_MAX) code = ASUS_WMI_BRN_UP; - else if (code >= NOTIFY_BRNDOWN_MIN && - code <= NOTIFY_BRNDOWN_MAX) + else if (code >= NOTIFY_BRNDOWN_MIN && code <= NOTIFY_BRNDOWN_MAX) code = ASUS_WMI_BRN_DOWN; if (code == ASUS_WMI_BRN_DOWN || code == ASUS_WMI_BRN_UP) { if (acpi_video_get_backlight_type() == acpi_backlight_vendor) { asus_wmi_backlight_notify(asus, orig_code); - goto exit; + return; } } if (code == NOTIFY_KBD_BRTUP) { kbd_led_set_by_kbd(asus, asus->kbd_led_wk + 1); - goto exit; + return; } if (code == NOTIFY_KBD_BRTDWN) { kbd_led_set_by_kbd(asus, asus->kbd_led_wk - 1); - goto exit; + return; } if (code == NOTIFY_KBD_BRTTOGGLE) { if (asus->kbd_led_wk == asus->kbd_led.max_brightness) kbd_led_set_by_kbd(asus, 0); else kbd_led_set_by_kbd(asus, asus->kbd_led_wk + 1); - goto exit; + return; } - if (is_display_toggle(code) && - asus->driver->quirks->no_display_toggle) - goto exit; + if (code == NOTIFY_FNLOCK_TOGGLE) { + asus->fnlock_locked = !asus->fnlock_locked; + asus_wmi_fnlock_update(asus); + return; + } + + if (asus->fan_boost_mode_available && code == NOTIFY_KBD_FBM) { + fan_boost_mode_switch_next(asus); + return; + } + + if (is_display_toggle(code) && asus->driver->quirks->no_display_toggle) + return; if (!sparse_keymap_report_event(asus->inputdev, code, key_value, autorelease)) pr_info("Unknown key %x pressed\n", code); +} -exit: - kfree(obj); +static void asus_wmi_notify(u32 value, void *context) +{ + struct asus_wmi *asus = context; + int code; + int i; + + for (i = 0; i < WMI_EVENT_QUEUE_SIZE + 1; i++) { + code = asus_wmi_get_event_code(value); + if (code < 0) { + pr_warn("Failed to get notify code: %d\n", code); + return; + } + + if (code == WMI_EVENT_QUEUE_END || code == WMI_EVENT_MASK) + return; + + asus_wmi_handle_event_code(code, asus); + + /* + * Double check that queue is present: + * ATK (with queue) uses 0xff, ASUSWMI (without) 0xd2. + */ + if (!asus->wmi_event_queue || value != WMI_EVENT_VALUE_ATK) + return; + } + + pr_warn("Failed to process event queue, last code: 0x%x\n", code); } -/* - * Sys helpers - */ -static int parse_arg(const char *buf, unsigned long count, int *val) +static int asus_wmi_notify_queue_flush(struct asus_wmi *asus) { - if (!count) - return 0; - if (sscanf(buf, "%i", val) != 1) - return -EINVAL; - return count; + int code; + int i; + + for (i = 0; i < WMI_EVENT_QUEUE_SIZE + 1; i++) { + code = asus_wmi_get_event_code(WMI_EVENT_VALUE_ATK); + if (code < 0) { + pr_warn("Failed to get event during flush: %d\n", code); + return code; + } + + if (code == WMI_EVENT_QUEUE_END || code == WMI_EVENT_MASK) + return 0; + } + + pr_warn("Failed to flush event queue\n"); + return -EIO; } +/* Sysfs **********************************************************************/ + static ssize_t store_sys_wmi(struct asus_wmi *asus, int devid, const char *buf, size_t count) { u32 retval; - int rv, err, value; + int err, value; value = asus_wmi_get_devstate_simple(asus, devid); if (value < 0) return value; - rv = parse_arg(buf, count, &value); - err = asus_wmi_set_devstate(devid, value, &retval); + err = kstrtoint(buf, 0, &value); + if (err) + return err; + err = asus_wmi_set_devstate(devid, value, &retval); if (err < 0) return err; - return rv; + return count; } static ssize_t show_sys_wmi(struct asus_wmi *asus, int devid, char *buf) @@ -1769,8 +2131,10 @@ static ssize_t cpufv_store(struct device *dev, struct device_attribute *attr, { int value, rv; - if (!count || sscanf(buf, "%i", &value) != 1) - return -EINVAL; + rv = kstrtoint(buf, 0, &value); + if (rv) + return rv; + if (value < 0 || value > 2) return -EINVAL; @@ -1790,6 +2154,7 @@ static struct attribute *platform_attributes[] = { &dev_attr_touchpad.attr, &dev_attr_lid_resume.attr, &dev_attr_als_enable.attr, + &dev_attr_fan_boost_mode.attr, NULL }; @@ -1811,6 +2176,8 @@ static umode_t asus_sysfs_is_visible(struct kobject *kobj, devid = ASUS_WMI_DEVID_LID_RESUME; else if (attr == &dev_attr_als_enable.attr) devid = ASUS_WMI_DEVID_ALS_ENABLE; + else if (attr == &dev_attr_fan_boost_mode.attr) + ok = asus->fan_boost_mode_available; if (devid != -1) ok = !(asus_wmi_get_devstate_simple(asus, devid) < 0); @@ -1833,11 +2200,12 @@ static int asus_wmi_sysfs_init(struct platform_device *device) return sysfs_create_group(&device->dev.kobj, &platform_attribute_group); } -/* - * Platform device - */ +/* Platform device ************************************************************/ + static int asus_wmi_platform_init(struct asus_wmi *asus) { + struct device *dev = &asus->platform_device->dev; + char *wmi_uid; int rv; /* INIT enable hotkeys on some models */ @@ -1867,11 +2235,41 @@ static int asus_wmi_platform_init(struct asus_wmi *asus) * Note, on most Eeepc, there is no way to check if a method exist * or note, while on notebooks, they returns 0xFFFFFFFE on failure, * but once again, SPEC may probably be used for that kind of things. + * + * Additionally at least TUF Gaming series laptops return nothing for + * unknown methods, so the detection in this way is not possible. + * + * There is strong indication that only ACPI WMI devices that have _UID + * equal to "ASUSWMI" use DCTS whereas those with "ATK" use DSTS. */ - if (!asus_wmi_evaluate_method(ASUS_WMI_METHODID_DSTS, 0, 0, NULL)) + wmi_uid = wmi_get_acpi_device_uid(ASUS_WMI_MGMT_GUID); + if (!wmi_uid) + return -ENODEV; + + if (!strcmp(wmi_uid, ASUS_ACPI_UID_ASUSWMI)) { + dev_info(dev, "Detected ASUSWMI, use DCTS\n"); + asus->dsts_id = ASUS_WMI_METHODID_DCTS; + } else { + dev_info(dev, "Detected %s, not ASUSWMI, use DSTS\n", wmi_uid); asus->dsts_id = ASUS_WMI_METHODID_DSTS; - else - asus->dsts_id = ASUS_WMI_METHODID_DSTS2; + } + + /* + * Some devices can have multiple event codes stored in a queue before + * the module load if it was unloaded intermittently after calling + * the INIT method (enables event handling). The WMI notify handler is + * expected to retrieve all event codes until a retrieved code equals + * queue end marker (One or Ones). Old codes are flushed from the queue + * upon module load. Not enabling this when it should be has minimal + * visible impact so fall back if anything goes wrong. + */ + wmi_uid = wmi_get_acpi_device_uid(asus->driver->event_guid); + if (wmi_uid && !strcmp(wmi_uid, ASUS_ACPI_UID_ATK)) { + dev_info(dev, "Detected ATK, enable event queue\n"); + + if (!asus_wmi_notify_queue_flush(asus)) + asus->wmi_event_queue = true; + } /* CWAP allow to define the behavior of the Fn+F2 key, * this method doesn't seems to be present on Eee PCs */ @@ -1879,17 +2277,11 @@ static int asus_wmi_platform_init(struct asus_wmi *asus) asus_wmi_set_devstate(ASUS_WMI_DEVID_CWAP, asus->driver->quirks->wapf, NULL); - return asus_wmi_sysfs_init(asus->platform_device); + return 0; } -static void asus_wmi_platform_exit(struct asus_wmi *asus) -{ - asus_wmi_sysfs_exit(asus->platform_device); -} +/* debugfs ********************************************************************/ -/* - * debugfs - */ struct asus_wmi_debugfs_node { struct asus_wmi *asus; char *name; @@ -1903,7 +2295,6 @@ static int show_dsts(struct seq_file *m, void *data) u32 retval = -1; err = asus_wmi_get_devstate(asus, asus->debug.dev_id, &retval); - if (err < 0) return err; @@ -1920,7 +2311,6 @@ static int show_devs(struct seq_file *m, void *data) err = asus_wmi_set_devstate(asus->debug.dev_id, asus->debug.ctrl_param, &retval); - if (err < 0) return err; @@ -1990,74 +2380,33 @@ static void asus_wmi_debugfs_exit(struct asus_wmi *asus) debugfs_remove_recursive(asus->debug.root); } -static int asus_wmi_debugfs_init(struct asus_wmi *asus) +static void asus_wmi_debugfs_init(struct asus_wmi *asus) { - struct dentry *dent; int i; asus->debug.root = debugfs_create_dir(asus->driver->name, NULL); - if (!asus->debug.root) { - pr_err("failed to create debugfs directory\n"); - goto error_debugfs; - } - dent = debugfs_create_x32("method_id", S_IRUGO | S_IWUSR, - asus->debug.root, &asus->debug.method_id); - if (!dent) - goto error_debugfs; + debugfs_create_x32("method_id", S_IRUGO | S_IWUSR, asus->debug.root, + &asus->debug.method_id); - dent = debugfs_create_x32("dev_id", S_IRUGO | S_IWUSR, - asus->debug.root, &asus->debug.dev_id); - if (!dent) - goto error_debugfs; + debugfs_create_x32("dev_id", S_IRUGO | S_IWUSR, asus->debug.root, + &asus->debug.dev_id); - dent = debugfs_create_x32("ctrl_param", S_IRUGO | S_IWUSR, - asus->debug.root, &asus->debug.ctrl_param); - if (!dent) - goto error_debugfs; + debugfs_create_x32("ctrl_param", S_IRUGO | S_IWUSR, asus->debug.root, + &asus->debug.ctrl_param); for (i = 0; i < ARRAY_SIZE(asus_wmi_debug_files); i++) { struct asus_wmi_debugfs_node *node = &asus_wmi_debug_files[i]; node->asus = asus; - dent = debugfs_create_file(node->name, S_IFREG | S_IRUGO, - asus->debug.root, node, - &asus_wmi_debugfs_io_ops); - if (!dent) { - pr_err("failed to create debug file: %s\n", node->name); - goto error_debugfs; - } + debugfs_create_file(node->name, S_IFREG | S_IRUGO, + asus->debug.root, node, + &asus_wmi_debugfs_io_ops); } - - return 0; - -error_debugfs: - asus_wmi_debugfs_exit(asus); - return -ENOMEM; } -static int asus_wmi_fan_init(struct asus_wmi *asus) -{ - int status; - - asus->asus_hwmon_pwm = -1; - asus->asus_hwmon_num_fans = -1; - asus->asus_hwmon_fan_manual_mode = false; +/* Init / exit ****************************************************************/ - status = asus_hwmon_get_fan_number(asus, &asus->asus_hwmon_num_fans); - if (status) { - asus->asus_hwmon_num_fans = 0; - pr_warn("Could not determine number of fans: %d\n", status); - return -ENXIO; - } - - pr_info("Number of fans: %d\n", asus->asus_hwmon_num_fans); - return 0; -} - -/* - * WMI Driver - */ static int asus_wmi_add(struct platform_device *pdev) { struct platform_driver *pdrv = to_platform_driver(pdev->dev.driver); @@ -2084,12 +2433,19 @@ static int asus_wmi_add(struct platform_device *pdev) if (err) goto fail_platform; + err = fan_boost_mode_check_present(asus); + if (err) + goto fail_fan_boost_mode; + + err = asus_wmi_sysfs_init(asus->platform_device); + if (err) + goto fail_sysfs; + err = asus_wmi_input_init(asus); if (err) goto fail_input; err = asus_wmi_fan_init(asus); /* probably no problems on error */ - asus_hwmon_fan_set_auto(asus); err = asus_wmi_hwmon_init(asus); if (err) @@ -2131,9 +2487,14 @@ static int asus_wmi_add(struct platform_device *pdev) err = asus_wmi_backlight_init(asus); if (err && err != -ENODEV) goto fail_backlight; - } else + } else if (asus->driver->quirks->wmi_backlight_set_devstate) err = asus_wmi_set_devstate(ASUS_WMI_DEVID_BACKLIGHT, 2, NULL); + if (asus_wmi_has_fnlock_key(asus)) { + asus->fnlock_locked = true; + asus_wmi_fnlock_update(asus); + } + status = wmi_install_notify_handler(asus->driver->event_guid, asus_wmi_notify, asus); if (ACPI_FAILURE(status)) { @@ -2142,14 +2503,12 @@ static int asus_wmi_add(struct platform_device *pdev) goto fail_wmi_handler; } - err = asus_wmi_debugfs_init(asus); - if (err) - goto fail_debugfs; + asus_wmi_battery_init(asus); + + asus_wmi_debugfs_init(asus); return 0; -fail_debugfs: - wmi_remove_notify_handler(asus->driver->event_guid); fail_wmi_handler: asus_wmi_backlight_exit(asus); fail_backlight: @@ -2160,7 +2519,9 @@ fail_leds: fail_hwmon: asus_wmi_input_exit(asus); fail_input: - asus_wmi_platform_exit(asus); + asus_wmi_sysfs_exit(asus->platform_device); +fail_sysfs: +fail_fan_boost_mode: fail_platform: kfree(asus); return err; @@ -2177,16 +2538,16 @@ static int asus_wmi_remove(struct platform_device *device) asus_wmi_led_exit(asus); asus_wmi_rfkill_exit(asus); asus_wmi_debugfs_exit(asus); - asus_wmi_platform_exit(asus); - asus_hwmon_fan_set_auto(asus); + asus_wmi_sysfs_exit(asus->platform_device); + asus_fan_set_auto(asus); + asus_wmi_battery_exit(asus); kfree(asus); return 0; } -/* - * Platform driver - hibernate/resume callbacks - */ +/* Platform driver - hibernate/resume callbacks *******************************/ + static int asus_hotk_thaw(struct device *device) { struct asus_wmi *asus = dev_get_drvdata(device); @@ -2213,6 +2574,8 @@ static int asus_hotk_resume(struct device *device) if (!IS_ERR_OR_NULL(asus->kbd_led.dev)) kbd_led_update(asus); + if (asus_wmi_has_fnlock_key(asus)) + asus_wmi_fnlock_update(asus); return 0; } @@ -2249,6 +2612,8 @@ static int asus_hotk_restore(struct device *device) if (!IS_ERR_OR_NULL(asus->kbd_led.dev)) kbd_led_update(asus); + if (asus_wmi_has_fnlock_key(asus)) + asus_wmi_fnlock_update(asus); return 0; } @@ -2258,6 +2623,8 @@ static const struct dev_pm_ops asus_pm_ops = { .resume = asus_hotk_resume, }; +/* Registration ***************************************************************/ + static int asus_wmi_probe(struct platform_device *pdev) { struct platform_driver *pdrv = to_platform_driver(pdev->dev.driver); diff --git a/drivers/platform/x86/asus-wmi.h b/drivers/platform/x86/asus-wmi.h index 6c1311f4b04d..4f31b68642a0 100644 --- a/drivers/platform/x86/asus-wmi.h +++ b/drivers/platform/x86/asus-wmi.h @@ -1,3 +1,4 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * Asus PC WMI hotkey driver * @@ -8,20 +9,6 @@ * Copyright (C) 2005 Miloslav Trmac <mitr@volny.cz> * Copyright (C) 2005 Bernhard Rosenkraenzer <bero@arklinux.org> * Copyright (C) 2005 Dmitry Torokhov <dtor@mail.ru> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef _ASUS_WMI_H_ @@ -44,6 +31,7 @@ struct quirk_entry { bool store_backlight_power; bool wmi_backlight_power; bool wmi_backlight_native; + bool wmi_backlight_set_devstate; bool wmi_force_als_set; int wapf; /* diff --git a/drivers/platform/x86/classmate-laptop.c b/drivers/platform/x86/classmate-laptop.c index 55cf10bc7817..af063f690846 100644 --- a/drivers/platform/x86/classmate-laptop.c +++ b/drivers/platform/x86/classmate-laptop.c @@ -1,19 +1,6 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (C) 2009 Thadeu Lima de Souza Cascardo <cascardo@holoscopio.com> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ @@ -433,12 +420,6 @@ failed_sensitivity: static int cmpc_accel_remove_v4(struct acpi_device *acpi) { - struct input_dev *inputdev; - struct cmpc_accel *accel; - - inputdev = dev_get_drvdata(&acpi->dev); - accel = dev_get_drvdata(&inputdev->dev); - device_remove_file(&acpi->dev, &cmpc_accel_sensitivity_attr_v4); device_remove_file(&acpi->dev, &cmpc_accel_g_select_attr_v4); return cmpc_remove_acpi_notify_device(acpi); @@ -669,12 +650,6 @@ failed_file: static int cmpc_accel_remove(struct acpi_device *acpi) { - struct input_dev *inputdev; - struct cmpc_accel *accel; - - inputdev = dev_get_drvdata(&acpi->dev); - accel = dev_get_drvdata(&inputdev->dev); - device_remove_file(&acpi->dev, &cmpc_accel_sensitivity_attr); return cmpc_remove_acpi_notify_device(acpi); } diff --git a/drivers/platform/x86/compal-laptop.c b/drivers/platform/x86/compal-laptop.c index 4f9bc72f0584..ab610376fdad 100644 --- a/drivers/platform/x86/compal-laptop.c +++ b/drivers/platform/x86/compal-laptop.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /*-*-linux-c-*-*/ /* @@ -7,20 +8,6 @@ Copyright (C) 2006 Lennart Poettering <mzxreary (at) 0pointer (dot) de> - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - 02110-1301, USA. */ /* @@ -239,7 +226,7 @@ static const unsigned char pwm_lookup_table[256] = { /* General access */ static u8 ec_read_u8(u8 addr) { - u8 value; + u8 value = 0; ec_read(addr, &value); return value; } diff --git a/drivers/platform/x86/dcdbas.c b/drivers/platform/x86/dcdbas.c index 88bd7efafe14..84f4cc839cc3 100644 --- a/drivers/platform/x86/dcdbas.c +++ b/drivers/platform/x86/dcdbas.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * dcdbas.c: Dell Systems Management Base Driver * @@ -6,18 +7,9 @@ * and Host Control Actions (power cycle or power off after OS shutdown) on * Dell systems. * - * See Documentation/dcdbas.txt for more information. + * See Documentation/driver-api/dcdbas.rst for more information. * * Copyright (C) 1995-2006 Dell Inc. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License v2.0 as published by - * the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. */ #include <linux/platform_device.h> diff --git a/drivers/platform/x86/dcdbas.h b/drivers/platform/x86/dcdbas.h index 52729a494b00..c3cca5433525 100644 --- a/drivers/platform/x86/dcdbas.h +++ b/drivers/platform/x86/dcdbas.h @@ -1,16 +1,8 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ /* * dcdbas.h: Definitions for Dell Systems Management Base driver * * Copyright (C) 1995-2005 Dell Inc. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License v2.0 as published by - * the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. */ #ifndef _DCDBAS_H_ diff --git a/drivers/platform/x86/dell-laptop.c b/drivers/platform/x86/dell-laptop.c index 95e6ca116e00..74e988f839e8 100644 --- a/drivers/platform/x86/dell-laptop.c +++ b/drivers/platform/x86/dell-laptop.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Driver for Dell laptop extras * @@ -7,10 +8,6 @@ * * Based on documentation in the libsmbios package: * Copyright (C) 2005-2014 Dell Inc. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt @@ -36,6 +33,7 @@ struct quirk_entry { bool touchpad_led; + bool kbd_led_not_present; bool kbd_led_levels_off_1; bool kbd_missing_ac_tag; @@ -76,6 +74,10 @@ static struct quirk_entry quirk_dell_latitude_e6410 = { .kbd_led_levels_off_1 = true, }; +static struct quirk_entry quirk_dell_inspiron_1012 = { + .kbd_led_not_present = true, +}; + static struct platform_driver platform_driver = { .driver = { .name = "dell-laptop", @@ -313,6 +315,24 @@ static const struct dmi_system_id dell_quirks[] __initconst = { }, .driver_data = &quirk_dell_latitude_e6410, }, + { + .callback = dmi_matched, + .ident = "Dell Inspiron 1012", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 1012"), + }, + .driver_data = &quirk_dell_inspiron_1012, + }, + { + .callback = dmi_matched, + .ident = "Dell Inspiron 1018", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 1018"), + }, + .driver_data = &quirk_dell_inspiron_1012, + }, { } }; @@ -531,7 +551,7 @@ static void dell_rfkill_query(struct rfkill *rfkill, void *data) return; } - dell_fill_request(&buffer, 0, 0x2, 0, 0); + dell_fill_request(&buffer, 0x2, 0, 0, 0); ret = dell_send_request(&buffer, CLASS_INFO, SELECT_RFKILL); hwswitch = buffer.output[1]; @@ -562,7 +582,7 @@ static int dell_debugfs_show(struct seq_file *s, void *data) return ret; status = buffer.output[1]; - dell_fill_request(&buffer, 0, 0x2, 0, 0); + dell_fill_request(&buffer, 0x2, 0, 0, 0); hwswitch_ret = dell_send_request(&buffer, CLASS_INFO, SELECT_RFKILL); if (hwswitch_ret) return hwswitch_ret; @@ -647,7 +667,7 @@ static void dell_update_rfkill(struct work_struct *ignored) if (ret != 0) return; - dell_fill_request(&buffer, 0, 0x2, 0, 0); + dell_fill_request(&buffer, 0x2, 0, 0, 0); ret = dell_send_request(&buffer, CLASS_INFO, SELECT_RFKILL); if (ret == 0 && (status & BIT(0))) @@ -1496,6 +1516,9 @@ static void kbd_init(void) { int ret; + if (quirks && quirks->kbd_led_not_present) + return; + ret = kbd_init_info(); kbd_init_tokens(); @@ -2176,9 +2199,8 @@ static int __init dell_init(void) kbd_led_init(&platform_device->dev); dell_laptop_dir = debugfs_create_dir("dell_laptop", NULL); - if (dell_laptop_dir != NULL) - debugfs_create_file("rfkill", 0444, dell_laptop_dir, NULL, - &dell_debugfs_fops); + debugfs_create_file("rfkill", 0444, dell_laptop_dir, NULL, + &dell_debugfs_fops); dell_laptop_register_notifier(&dell_laptop_notifier); diff --git a/drivers/platform/x86/dell-rbtn.c b/drivers/platform/x86/dell-rbtn.c index f3afe778001e..a6b856cd86bd 100644 --- a/drivers/platform/x86/dell-rbtn.c +++ b/drivers/platform/x86/dell-rbtn.c @@ -1,16 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* Dell Airplane Mode Switch driver Copyright (C) 2014-2015 Pali Rohár <pali.rohar@gmail.com> - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. */ #include <linux/module.h> @@ -18,6 +10,8 @@ #include <linux/rfkill.h> #include <linux/input.h> +#include "dell-rbtn.h" + enum rbtn_type { RBTN_UNKNOWN, RBTN_TOGGLE, diff --git a/drivers/platform/x86/dell-rbtn.h b/drivers/platform/x86/dell-rbtn.h index c59cc6b8ec2b..0fdc81644458 100644 --- a/drivers/platform/x86/dell-rbtn.h +++ b/drivers/platform/x86/dell-rbtn.h @@ -1,16 +1,8 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ /* Dell Airplane Mode Switch driver Copyright (C) 2014-2015 Pali Rohár <pali.rohar@gmail.com> - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. */ #ifndef _DELL_RBTN_H_ diff --git a/drivers/platform/x86/dell-smbios-base.c b/drivers/platform/x86/dell-smbios-base.c index 0537d44d45a6..fe59b0ebff31 100644 --- a/drivers/platform/x86/dell-smbios-base.c +++ b/drivers/platform/x86/dell-smbios-base.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Common functions for kernel modules using Dell SMBIOS * @@ -7,10 +8,6 @@ * * Based on documentation in the libsmbios package: * Copyright (C) 2005-2014 Dell Inc. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt diff --git a/drivers/platform/x86/dell-smbios-smm.c b/drivers/platform/x86/dell-smbios-smm.c index ab9b822a6dfe..d6854d1c4119 100644 --- a/drivers/platform/x86/dell-smbios-smm.c +++ b/drivers/platform/x86/dell-smbios-smm.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * SMI methods for use with dell-smbios * @@ -5,10 +6,6 @@ * Copyright (c) 2014 Gabriele Mazzotta <gabriele.mzt@gmail.com> * Copyright (c) 2014 Pali Rohár <pali.rohar@gmail.com> * Copyright (c) 2017 Dell Inc. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt diff --git a/drivers/platform/x86/dell-smbios-wmi.c b/drivers/platform/x86/dell-smbios-wmi.c index c3ed3c8c17b9..27a298b7c541 100644 --- a/drivers/platform/x86/dell-smbios-wmi.c +++ b/drivers/platform/x86/dell-smbios-wmi.c @@ -1,11 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * WMI methods for use with dell-smbios * * Copyright (c) 2017 Dell Inc. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt @@ -146,7 +143,7 @@ fail_smbios_cmd: return ret; } -static int dell_smbios_wmi_probe(struct wmi_device *wdev) +static int dell_smbios_wmi_probe(struct wmi_device *wdev, const void *context) { struct wmi_driver *wdriver = container_of(wdev->dev.driver, struct wmi_driver, driver); diff --git a/drivers/platform/x86/dell-smbios.h b/drivers/platform/x86/dell-smbios.h index d8adaf959740..a7ff9803f41a 100644 --- a/drivers/platform/x86/dell-smbios.h +++ b/drivers/platform/x86/dell-smbios.h @@ -1,3 +1,4 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ /* * Common functions for kernel modules using Dell SMBIOS * @@ -7,10 +8,6 @@ * * Based on documentation in the libsmbios package: * Copyright (C) 2005-2014 Dell Inc. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. */ #ifndef _DELL_SMBIOS_H_ diff --git a/drivers/platform/x86/dell-smo8800.c b/drivers/platform/x86/dell-smo8800.c index 1d87237bc731..bfcc1d1b9b96 100644 --- a/drivers/platform/x86/dell-smo8800.c +++ b/drivers/platform/x86/dell-smo8800.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * dell-smo8800.c - Dell Latitude ACPI SMO88XX freefall sensor driver * @@ -5,16 +6,6 @@ * Copyright (C) 2014 Pali Rohár <pali.rohar@gmail.com> * * This is loosely based on lis3lv02d driver. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. */ #define DRIVER_NAME "smo8800" @@ -207,6 +198,7 @@ static int smo8800_remove(struct acpi_device *device) return 0; } +/* NOTE: Keep this list in sync with drivers/i2c/busses/i2c-i801.c */ static const struct acpi_device_id smo8800_ids[] = { { "SMO8800", 0 }, { "SMO8801", 0 }, diff --git a/drivers/platform/x86/dell-wmi-aio.c b/drivers/platform/x86/dell-wmi-aio.c index 50c2078715d6..c7b7f1e403fb 100644 --- a/drivers/platform/x86/dell-wmi-aio.c +++ b/drivers/platform/x86/dell-wmi-aio.c @@ -1,19 +1,6 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * WMI hotkeys support for Dell All-In-One series - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt diff --git a/drivers/platform/x86/dell-wmi-descriptor.c b/drivers/platform/x86/dell-wmi-descriptor.c index 14ab250b7d5a..a068900ae8a1 100644 --- a/drivers/platform/x86/dell-wmi-descriptor.c +++ b/drivers/platform/x86/dell-wmi-descriptor.c @@ -1,16 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Dell WMI descriptor driver * * Copyright (C) 2017 Dell Inc. All Rights Reserved. - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 as published - * by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt @@ -106,7 +98,8 @@ EXPORT_SYMBOL_GPL(dell_wmi_get_hotfix); * WMI buffer length 12 4 <length> * WMI hotfix number 16 4 <hotfix> */ -static int dell_wmi_descriptor_probe(struct wmi_device *wdev) +static int dell_wmi_descriptor_probe(struct wmi_device *wdev, + const void *context) { union acpi_object *obj = NULL; struct descriptor_priv *priv; diff --git a/drivers/platform/x86/dell-wmi-descriptor.h b/drivers/platform/x86/dell-wmi-descriptor.h index a6123a4d06a7..1f469fef1535 100644 --- a/drivers/platform/x86/dell-wmi-descriptor.h +++ b/drivers/platform/x86/dell-wmi-descriptor.h @@ -1,11 +1,8 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ /* * Dell WMI descriptor driver * * Copyright (c) 2017 Dell Inc. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. */ #ifndef _DELL_WMI_DESCRIPTOR_H_ diff --git a/drivers/platform/x86/dell-wmi.c b/drivers/platform/x86/dell-wmi.c index d118bb73fcae..6669db2555fb 100644 --- a/drivers/platform/x86/dell-wmi.c +++ b/drivers/platform/x86/dell-wmi.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * Dell WMI hotkeys * @@ -8,20 +9,6 @@ * Copyright (C) 2005 Miloslav Trmac <mitr@volny.cz> * Copyright (C) 2005 Bernhard Rosenkraenzer <bero@arklinux.org> * Copyright (C) 2005 Dmitry Torokhov <dtor@mail.ru> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt @@ -324,11 +311,13 @@ static const struct key_entry dell_wmi_keymap_type_0011[] = { { KE_IGNORE, 0xfff1, { KEY_RESERVED } }, /* Keyboard backlight level changed */ - { KE_IGNORE, 0x01e1, { KEY_RESERVED } }, - { KE_IGNORE, 0x02ea, { KEY_RESERVED } }, - { KE_IGNORE, 0x02eb, { KEY_RESERVED } }, - { KE_IGNORE, 0x02ec, { KEY_RESERVED } }, - { KE_IGNORE, 0x02f6, { KEY_RESERVED } }, + { KE_IGNORE, KBD_LED_OFF_TOKEN, { KEY_RESERVED } }, + { KE_IGNORE, KBD_LED_ON_TOKEN, { KEY_RESERVED } }, + { KE_IGNORE, KBD_LED_AUTO_TOKEN, { KEY_RESERVED } }, + { KE_IGNORE, KBD_LED_AUTO_25_TOKEN, { KEY_RESERVED } }, + { KE_IGNORE, KBD_LED_AUTO_50_TOKEN, { KEY_RESERVED } }, + { KE_IGNORE, KBD_LED_AUTO_75_TOKEN, { KEY_RESERVED } }, + { KE_IGNORE, KBD_LED_AUTO_100_TOKEN, { KEY_RESERVED } }, }; static void dell_wmi_process_key(struct wmi_device *wdev, int type, int code) @@ -672,7 +661,7 @@ static int dell_wmi_events_set_enabled(bool enable) return dell_smbios_error(ret); } -static int dell_wmi_probe(struct wmi_device *wdev) +static int dell_wmi_probe(struct wmi_device *wdev, const void *context) { struct dell_wmi_priv *priv; int ret; diff --git a/drivers/platform/x86/dell_rbu.c b/drivers/platform/x86/dell_rbu.c index 031c68903583..7d5453326b43 100644 --- a/drivers/platform/x86/dell_rbu.c +++ b/drivers/platform/x86/dell_rbu.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * dell_rbu.c * Bios Update driver for Dell systems @@ -23,16 +24,7 @@ * on every time the packet data is written. This driver requires an * application to break the BIOS image in to fixed sized packet chunks. * - * See Documentation/dell_rbu.txt for more info. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License v2.0 as published by - * the Free Software Foundation - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. + * See Documentation/admin-guide/dell_rbu.rst for more info. */ #include <linux/init.h> #include <linux/module.h> diff --git a/drivers/platform/x86/eeepc-laptop.c b/drivers/platform/x86/eeepc-laptop.c index e6946a9beb5a..776868d5e458 100644 --- a/drivers/platform/x86/eeepc-laptop.c +++ b/drivers/platform/x86/eeepc-laptop.c @@ -1,19 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * eeepc-laptop.c - Asus Eee PC extras * * Based on asus_acpi.c as patched for the Eee PC by Asus: * ftp://ftp.asus.com/pub/ASUS/EeePC/701/ASUS_ACPI_071126.rar * Based on eee.c from eeepc-linux - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt @@ -587,7 +578,7 @@ static void eeepc_rfkill_hotplug(struct eeepc_laptop *eeepc, acpi_handle handle) port = acpi_get_pci_dev(handle); if (!port) { - pr_warning("Unable to find port\n"); + pr_warn("Unable to find port\n"); goto out_unlock; } diff --git a/drivers/platform/x86/eeepc-wmi.c b/drivers/platform/x86/eeepc-wmi.c index 17b365f26f9d..ce86d84ee796 100644 --- a/drivers/platform/x86/eeepc-wmi.c +++ b/drivers/platform/x86/eeepc-wmi.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * Eee PC WMI hotkey driver * @@ -8,20 +9,6 @@ * Copyright (C) 2005 Miloslav Trmac <mitr@volny.cz> * Copyright (C) 2005 Bernhard Rosenkraenzer <bero@arklinux.org> * Copyright (C) 2005 Dmitry Torokhov <dtor@mail.ru> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt diff --git a/drivers/platform/x86/fujitsu-laptop.c b/drivers/platform/x86/fujitsu-laptop.c index 6afeaece2f50..80929380ec7e 100644 --- a/drivers/platform/x86/fujitsu-laptop.c +++ b/drivers/platform/x86/fujitsu-laptop.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /*-*-linux-c-*-*/ /* @@ -11,20 +12,6 @@ Templated from msi-laptop.c and thinkpad_acpi.c which is copyright by its respective authors. - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - 02110-1301, USA. */ /* diff --git a/drivers/platform/x86/fujitsu-tablet.c b/drivers/platform/x86/fujitsu-tablet.c index baea077a02cc..7fb7fe5eb55a 100644 --- a/drivers/platform/x86/fujitsu-tablet.c +++ b/drivers/platform/x86/fujitsu-tablet.c @@ -1,19 +1,7 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (C) 2006-2012 Robert Gerlach <khnz@gmx.de> * Copyright (C) 2005-2006 Jan Rychter <jan@rychter.com> - * - * You can redistribute and/or modify this program under the terms of the - * GNU General Public License version 2 as published by the Free Software - * Foundation. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 59 Temple Place Suite 330, Boston, MA 02111-1307, USA. */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt diff --git a/drivers/platform/x86/hdaps.c b/drivers/platform/x86/hdaps.c index c26baf77938e..04c4da6692d7 100644 --- a/drivers/platform/x86/hdaps.c +++ b/drivers/platform/x86/hdaps.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * hdaps.c - driver for IBM's Hard Drive Active Protection System * @@ -11,26 +12,13 @@ * This driver is based on the document by Mark A. Smith available at * http://www.almaden.ibm.com/cs/people/marksmith/tpaps.html and a lot of trial * and error. - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License v2 as published by the - * Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along with - * this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include <linux/delay.h> #include <linux/platform_device.h> -#include <linux/input-polldev.h> +#include <linux/input.h> #include <linux/kernel.h> #include <linux/mutex.h> #include <linux/module.h> @@ -71,7 +59,7 @@ #define HDAPS_BOTH_AXES (HDAPS_X_AXIS | HDAPS_Y_AXIS) static struct platform_device *pdev; -static struct input_polled_dev *hdaps_idev; +static struct input_dev *hdaps_idev; static unsigned int hdaps_invert; static u8 km_activity; static int rest_x; @@ -330,9 +318,8 @@ static void hdaps_calibrate(void) __hdaps_read_pair(HDAPS_PORT_XPOS, HDAPS_PORT_YPOS, &rest_x, &rest_y); } -static void hdaps_mousedev_poll(struct input_polled_dev *dev) +static void hdaps_mousedev_poll(struct input_dev *input_dev) { - struct input_dev *input_dev = dev->input; int x, y; mutex_lock(&hdaps_mtx); @@ -543,7 +530,6 @@ static const struct dmi_system_id hdaps_whitelist[] __initconst = { static int __init hdaps_init(void) { - struct input_dev *idev; int ret; if (!dmi_check_system(hdaps_whitelist)) { @@ -571,31 +557,32 @@ static int __init hdaps_init(void) if (ret) goto out_device; - hdaps_idev = input_allocate_polled_device(); + hdaps_idev = input_allocate_device(); if (!hdaps_idev) { ret = -ENOMEM; goto out_group; } - hdaps_idev->poll = hdaps_mousedev_poll; - hdaps_idev->poll_interval = HDAPS_POLL_INTERVAL; - /* initial calibrate for the input device */ hdaps_calibrate(); /* initialize the input class */ - idev = hdaps_idev->input; - idev->name = "hdaps"; - idev->phys = "isa1600/input0"; - idev->id.bustype = BUS_ISA; - idev->dev.parent = &pdev->dev; - idev->evbit[0] = BIT_MASK(EV_ABS); - input_set_abs_params(idev, ABS_X, + hdaps_idev->name = "hdaps"; + hdaps_idev->phys = "isa1600/input0"; + hdaps_idev->id.bustype = BUS_ISA; + hdaps_idev->dev.parent = &pdev->dev; + input_set_abs_params(hdaps_idev, ABS_X, -256, 256, HDAPS_INPUT_FUZZ, HDAPS_INPUT_FLAT); - input_set_abs_params(idev, ABS_Y, + input_set_abs_params(hdaps_idev, ABS_Y, -256, 256, HDAPS_INPUT_FUZZ, HDAPS_INPUT_FLAT); - ret = input_register_polled_device(hdaps_idev); + ret = input_setup_polling(hdaps_idev, hdaps_mousedev_poll); + if (ret) + goto out_idev; + + input_set_poll_interval(hdaps_idev, HDAPS_POLL_INTERVAL); + + ret = input_register_device(hdaps_idev); if (ret) goto out_idev; @@ -603,7 +590,7 @@ static int __init hdaps_init(void) return 0; out_idev: - input_free_polled_device(hdaps_idev); + input_free_device(hdaps_idev); out_group: sysfs_remove_group(&pdev->dev.kobj, &hdaps_attribute_group); out_device: @@ -619,8 +606,7 @@ out: static void __exit hdaps_exit(void) { - input_unregister_polled_device(hdaps_idev); - input_free_polled_device(hdaps_idev); + input_unregister_device(hdaps_idev); sysfs_remove_group(&pdev->dev.kobj, &hdaps_attribute_group); platform_device_unregister(pdev); platform_driver_unregister(&hdaps_driver); diff --git a/drivers/platform/x86/hp-wireless.c b/drivers/platform/x86/hp-wireless.c index d6ea5e998fb8..12c31fd5d5ae 100644 --- a/drivers/platform/x86/hp-wireless.c +++ b/drivers/platform/x86/hp-wireless.c @@ -1,21 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * Airplane mode button for HP & Xiaomi laptops * * Copyright (C) 2014-2017 Alex Hung <alex.hung@canonical.com> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include <linux/kernel.h> diff --git a/drivers/platform/x86/hp-wmi.c b/drivers/platform/x86/hp-wmi.c index b4224389febe..9579a706fc08 100644 --- a/drivers/platform/x86/hp-wmi.c +++ b/drivers/platform/x86/hp-wmi.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * HP WMI hotkeys * @@ -8,20 +9,6 @@ * Copyright (C) 2005 Miloslav Trmac <mitr@volny.cz> * Copyright (C) 2005 Bernhard Rosenkraenzer <bero@arklinux.org> * Copyright (C) 2005 Dmitry Torokhov <dtor@mail.ru> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt @@ -78,7 +65,7 @@ struct bios_args { u32 command; u32 commandtype; u32 datasize; - u32 data; + u8 data[128]; }; enum hp_wmi_commandtype { @@ -229,7 +216,7 @@ static int hp_wmi_perform_query(int query, enum hp_wmi_command command, .command = command, .commandtype = query, .datasize = insize, - .data = 0, + .data = { 0 }, }; struct acpi_buffer input = { sizeof(struct bios_args), &args }; struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; @@ -241,7 +228,7 @@ static int hp_wmi_perform_query(int query, enum hp_wmi_command command, if (WARN_ON(insize > sizeof(args.data))) return -EINVAL; - memcpy(&args.data, buffer, insize); + memcpy(&args.data[0], buffer, insize); wmi_evaluate_method(HPWMI_BIOS_GUID, 0, mid, &input, &output); @@ -393,7 +380,7 @@ static int hp_wmi_rfkill2_refresh(void) int err, i; err = hp_wmi_perform_query(HPWMI_WIRELESS2_QUERY, HPWMI_READ, &state, - 0, sizeof(state)); + sizeof(state), sizeof(state)); if (err) return err; @@ -515,6 +502,17 @@ static DEVICE_ATTR_RO(dock); static DEVICE_ATTR_RO(tablet); static DEVICE_ATTR_RW(postcode); +static struct attribute *hp_wmi_attrs[] = { + &dev_attr_display.attr, + &dev_attr_hddtemp.attr, + &dev_attr_als.attr, + &dev_attr_dock.attr, + &dev_attr_tablet.attr, + &dev_attr_postcode.attr, + NULL, +}; +ATTRIBUTE_GROUPS(hp_wmi); + static void hp_wmi_notify(u32 value, void *context) { struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL }; @@ -691,16 +689,6 @@ static void hp_wmi_input_destroy(void) input_unregister_device(hp_wmi_input_dev); } -static void cleanup_sysfs(struct platform_device *device) -{ - device_remove_file(&device->dev, &dev_attr_display); - device_remove_file(&device->dev, &dev_attr_hddtemp); - device_remove_file(&device->dev, &dev_attr_als); - device_remove_file(&device->dev, &dev_attr_dock); - device_remove_file(&device->dev, &dev_attr_tablet); - device_remove_file(&device->dev, &dev_attr_postcode); -} - static int __init hp_wmi_rfkill_setup(struct platform_device *device) { int err, wireless; @@ -790,7 +778,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, - 0, sizeof(state)); + sizeof(state), sizeof(state)); if (err) return err < 0 ? err : -EINVAL; @@ -871,8 +859,6 @@ fail: static int __init hp_wmi_bios_setup(struct platform_device *device) { - int err; - /* clear detected rfkill devices */ wifi_rfkill = NULL; bluetooth_rfkill = NULL; @@ -882,35 +868,12 @@ static int __init hp_wmi_bios_setup(struct platform_device *device) if (hp_wmi_rfkill_setup(device)) hp_wmi_rfkill2_setup(device); - err = device_create_file(&device->dev, &dev_attr_display); - if (err) - goto add_sysfs_error; - err = device_create_file(&device->dev, &dev_attr_hddtemp); - if (err) - goto add_sysfs_error; - err = device_create_file(&device->dev, &dev_attr_als); - if (err) - goto add_sysfs_error; - err = device_create_file(&device->dev, &dev_attr_dock); - if (err) - goto add_sysfs_error; - err = device_create_file(&device->dev, &dev_attr_tablet); - if (err) - goto add_sysfs_error; - err = device_create_file(&device->dev, &dev_attr_postcode); - if (err) - goto add_sysfs_error; return 0; - -add_sysfs_error: - cleanup_sysfs(device); - return err; } static int __exit hp_wmi_bios_remove(struct platform_device *device) { int i; - cleanup_sysfs(device); for (i = 0; i < rfkill2_count; i++) { rfkill_unregister(rfkill2[i].rfkill); @@ -979,6 +942,7 @@ static struct platform_driver hp_wmi_driver = { .driver = { .name = "hp-wmi", .pm = &hp_wmi_pm_ops, + .dev_groups = hp_wmi_groups, }, .remove = __exit_p(hp_wmi_bios_remove), }; diff --git a/drivers/platform/x86/hp_accel.c b/drivers/platform/x86/hp_accel.c index 7b12abe86b94..799cbe2ffcf3 100644 --- a/drivers/platform/x86/hp_accel.c +++ b/drivers/platform/x86/hp_accel.c @@ -1,23 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * hp_accel.c - Interface between LIS3LV02DL driver and HP ACPI BIOS * * Copyright (C) 2007-2008 Yan Burman * Copyright (C) 2008 Eric Piel * Copyright (C) 2008-2009 Pavel Machek - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt @@ -242,6 +229,7 @@ static const struct dmi_system_id lis3lv02d_dmi_ids[] = { AXIS_DMI_MATCH("HPB440G3", "HP ProBook 440 G3", x_inverted_usd), AXIS_DMI_MATCH("HPB440G4", "HP ProBook 440 G4", x_inverted), AXIS_DMI_MATCH("HPB442x", "HP ProBook 442", xy_rotated_left), + AXIS_DMI_MATCH("HPB450G0", "HP ProBook 450 G0", x_inverted), AXIS_DMI_MATCH("HPB452x", "HP ProBook 452", y_inverted), AXIS_DMI_MATCH("HPB522x", "HP ProBook 522", xy_swap), AXIS_DMI_MATCH("HPB532x", "HP ProBook 532", y_inverted), @@ -252,6 +240,7 @@ static const struct dmi_system_id lis3lv02d_dmi_ids[] = { AXIS_DMI_MATCH("HPB64xx", "HP EliteBook 84", xy_swap), AXIS_DMI_MATCH("HPB65xx", "HP ProBook 65", x_inverted), AXIS_DMI_MATCH("HPZBook15", "HP ZBook 15", x_inverted), + AXIS_DMI_MATCH("HPZBook17G5", "HP ZBook 17 G5", x_inverted), AXIS_DMI_MATCH("HPZBook17", "HP ZBook 17", xy_swap_yz_inverted), { NULL, } /* Laptop models without axis info (yet): diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c index 52fcac5b393a..a2d846c4a7ee 100644 --- a/drivers/platform/x86/huawei-wmi.c +++ b/drivers/platform/x86/huawei-wmi.c @@ -1,32 +1,77 @@ // SPDX-License-Identifier: GPL-2.0 /* - * Huawei WMI hotkeys + * Huawei WMI laptop extras driver * * Copyright (C) 2018 Ayman Bagabas <ayman.bagabas@gmail.com> */ #include <linux/acpi.h> +#include <linux/debugfs.h> +#include <linux/delay.h> +#include <linux/dmi.h> #include <linux/input.h> #include <linux/input/sparse-keymap.h> #include <linux/leds.h> #include <linux/module.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/sysfs.h> #include <linux/wmi.h> +#include <acpi/battery.h> /* * Huawei WMI GUIDs */ -#define WMI0_EVENT_GUID "59142400-C6A3-40fa-BADB-8A2652834100" -#define AMW0_EVENT_GUID "ABBC0F5C-8EA1-11D1-A000-C90629100000" +#define HWMI_METHOD_GUID "ABBC0F5B-8EA1-11D1-A000-C90629100000" +#define HWMI_EVENT_GUID "ABBC0F5C-8EA1-11D1-A000-C90629100000" +/* Legacy GUIDs */ #define WMI0_EXPENSIVE_GUID "39142400-C6A3-40fa-BADB-8A2652834100" +#define WMI0_EVENT_GUID "59142400-C6A3-40fa-BADB-8A2652834100" + +/* HWMI commands */ + +enum { + BATTERY_THRESH_GET = 0x00001103, /* \GBTT */ + BATTERY_THRESH_SET = 0x00001003, /* \SBTT */ + FN_LOCK_GET = 0x00000604, /* \GFRS */ + FN_LOCK_SET = 0x00000704, /* \SFRS */ + MICMUTE_LED_SET = 0x00000b04, /* \SMLS */ +}; + +union hwmi_arg { + u64 cmd; + u8 args[8]; +}; + +struct quirk_entry { + bool battery_reset; + bool ec_micmute; + bool report_brightness; +}; + +static struct quirk_entry *quirks; -struct huawei_wmi_priv { - struct input_dev *idev; +struct huawei_wmi_debug { + struct dentry *root; + u64 arg; +}; + +struct huawei_wmi { + bool battery_available; + bool fn_lock_available; + + struct huawei_wmi_debug debug; + struct input_dev *idev[2]; struct led_classdev cdev; - acpi_handle handle; - char *acpi_method; + struct device *dev; + + struct mutex wmi_lock; }; +static struct huawei_wmi *huawei_wmi; + static const struct key_entry huawei_wmi_keymap[] = { { KE_KEY, 0x281, { KEY_BRIGHTNESSDOWN } }, { KE_KEY, 0x282, { KEY_BRIGHTNESSUP } }, @@ -37,73 +82,614 @@ static const struct key_entry huawei_wmi_keymap[] = { { KE_KEY, 0x289, { KEY_WLAN } }, // Huawei |M| key { KE_KEY, 0x28a, { KEY_CONFIG } }, - // Keyboard backlight + // Keyboard backlit { KE_IGNORE, 0x293, { KEY_KBDILLUMTOGGLE } }, { KE_IGNORE, 0x294, { KEY_KBDILLUMUP } }, { KE_IGNORE, 0x295, { KEY_KBDILLUMUP } }, { KE_END, 0 } }; +static int battery_reset = -1; +static int report_brightness = -1; + +module_param(battery_reset, bint, 0444); +MODULE_PARM_DESC(battery_reset, + "Reset battery charge values to (0-0) before disabling it using (0-100)"); +module_param(report_brightness, bint, 0444); +MODULE_PARM_DESC(report_brightness, + "Report brightness keys."); + +/* Quirks */ + +static int __init dmi_matched(const struct dmi_system_id *dmi) +{ + quirks = dmi->driver_data; + return 1; +} + +static struct quirk_entry quirk_unknown = { +}; + +static struct quirk_entry quirk_battery_reset = { + .battery_reset = true, +}; + +static struct quirk_entry quirk_matebook_x = { + .ec_micmute = true, + .report_brightness = true, +}; + +static const struct dmi_system_id huawei_quirks[] = { + { + .callback = dmi_matched, + .ident = "Huawei MACH-WX9", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "HUAWEI"), + DMI_MATCH(DMI_PRODUCT_NAME, "MACH-WX9"), + }, + .driver_data = &quirk_battery_reset + }, + { + .callback = dmi_matched, + .ident = "Huawei MateBook X", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "HUAWEI"), + DMI_MATCH(DMI_PRODUCT_NAME, "HUAWEI MateBook X") + }, + .driver_data = &quirk_matebook_x + }, + { } +}; + +/* Utils */ + +static int huawei_wmi_call(struct huawei_wmi *huawei, + struct acpi_buffer *in, struct acpi_buffer *out) +{ + acpi_status status; + + mutex_lock(&huawei->wmi_lock); + status = wmi_evaluate_method(HWMI_METHOD_GUID, 0, 1, in, out); + mutex_unlock(&huawei->wmi_lock); + if (ACPI_FAILURE(status)) { + dev_err(huawei->dev, "Failed to evaluate wmi method\n"); + return -ENODEV; + } + + return 0; +} + +/* HWMI takes a 64 bit input and returns either a package with 2 buffers, one of + * 4 bytes and the other of 256 bytes, or one buffer of size 0x104 (260) bytes. + * The first 4 bytes are ignored, we ignore the first 4 bytes buffer if we got a + * package, or skip the first 4 if a buffer of 0x104 is used. The first byte of + * the remaining 0x100 sized buffer has the return status of every call. In case + * the return status is non-zero, we return -ENODEV but still copy the returned + * buffer to the given buffer parameter (buf). + */ +static int huawei_wmi_cmd(u64 arg, u8 *buf, size_t buflen) +{ + struct huawei_wmi *huawei = huawei_wmi; + struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL }; + struct acpi_buffer in; + union acpi_object *obj; + size_t len; + int err, i; + + in.length = sizeof(arg); + in.pointer = &arg; + + /* Some models require calling HWMI twice to execute a command. We evaluate + * HWMI and if we get a non-zero return status we evaluate it again. + */ + for (i = 0; i < 2; i++) { + err = huawei_wmi_call(huawei, &in, &out); + if (err) + goto fail_cmd; + + obj = out.pointer; + if (!obj) { + err = -EIO; + goto fail_cmd; + } + + switch (obj->type) { + /* Models that implement both "legacy" and HWMI tend to return a 0x104 + * sized buffer instead of a package of 0x4 and 0x100 buffers. + */ + case ACPI_TYPE_BUFFER: + if (obj->buffer.length == 0x104) { + // Skip the first 4 bytes. + obj->buffer.pointer += 4; + len = 0x100; + } else { + dev_err(huawei->dev, "Bad buffer length, got %d\n", obj->buffer.length); + err = -EIO; + goto fail_cmd; + } + + break; + /* HWMI returns a package with 2 buffer elements, one of 4 bytes and the + * other is 256 bytes. + */ + case ACPI_TYPE_PACKAGE: + if (obj->package.count != 2) { + dev_err(huawei->dev, "Bad package count, got %d\n", obj->package.count); + err = -EIO; + goto fail_cmd; + } + + obj = &obj->package.elements[1]; + if (obj->type != ACPI_TYPE_BUFFER) { + dev_err(huawei->dev, "Bad package element type, got %d\n", obj->type); + err = -EIO; + goto fail_cmd; + } + len = obj->buffer.length; + + break; + /* Shouldn't get here! */ + default: + dev_err(huawei->dev, "Unexpected obj type, got: %d\n", obj->type); + err = -EIO; + goto fail_cmd; + } + + if (!*obj->buffer.pointer) + break; + } + + err = (*obj->buffer.pointer) ? -ENODEV : 0; + + if (buf) { + len = min(buflen, len); + memcpy(buf, obj->buffer.pointer, len); + } + +fail_cmd: + kfree(out.pointer); + return err; +} + +/* LEDs */ + static int huawei_wmi_micmute_led_set(struct led_classdev *led_cdev, enum led_brightness brightness) { - struct huawei_wmi_priv *priv = dev_get_drvdata(led_cdev->dev->parent); - acpi_status status; - union acpi_object args[3]; - struct acpi_object_list arg_list = { - .pointer = args, - .count = ARRAY_SIZE(args), - }; - - args[0].type = args[1].type = args[2].type = ACPI_TYPE_INTEGER; - args[1].integer.value = 0x04; - - if (strcmp(priv->acpi_method, "SPIN") == 0) { - args[0].integer.value = 0; - args[2].integer.value = brightness ? 1 : 0; - } else if (strcmp(priv->acpi_method, "WPIN") == 0) { - args[0].integer.value = 1; - args[2].integer.value = brightness ? 0 : 1; + /* This is a workaround until the "legacy" interface is implemented. */ + if (quirks && quirks->ec_micmute) { + char *acpi_method; + acpi_handle handle; + acpi_status status; + union acpi_object args[3]; + struct acpi_object_list arg_list = { + .pointer = args, + .count = ARRAY_SIZE(args), + }; + + handle = ec_get_handle(); + if (!handle) + return -ENODEV; + + args[0].type = args[1].type = args[2].type = ACPI_TYPE_INTEGER; + args[1].integer.value = 0x04; + + if (acpi_has_method(handle, "SPIN")) { + acpi_method = "SPIN"; + args[0].integer.value = 0; + args[2].integer.value = brightness ? 1 : 0; + } else if (acpi_has_method(handle, "WPIN")) { + acpi_method = "WPIN"; + args[0].integer.value = 1; + args[2].integer.value = brightness ? 0 : 1; + } else { + return -ENODEV; + } + + status = acpi_evaluate_object(handle, acpi_method, &arg_list, NULL); + if (ACPI_FAILURE(status)) + return -ENODEV; + + return 0; } else { + union hwmi_arg arg; + + arg.cmd = MICMUTE_LED_SET; + arg.args[2] = brightness; + + return huawei_wmi_cmd(arg.cmd, NULL, 0); + } +} + +static void huawei_wmi_leds_setup(struct device *dev) +{ + struct huawei_wmi *huawei = dev_get_drvdata(dev); + + huawei->cdev.name = "platform::micmute"; + huawei->cdev.max_brightness = 1; + huawei->cdev.brightness_set_blocking = &huawei_wmi_micmute_led_set; + huawei->cdev.default_trigger = "audio-micmute"; + huawei->cdev.brightness = ledtrig_audio_get(LED_AUDIO_MICMUTE); + huawei->cdev.dev = dev; + huawei->cdev.flags = LED_CORE_SUSPENDRESUME; + + devm_led_classdev_register(dev, &huawei->cdev); +} + +/* Battery protection */ + +static int huawei_wmi_battery_get(int *start, int *end) +{ + u8 ret[0x100]; + int err, i; + + err = huawei_wmi_cmd(BATTERY_THRESH_GET, ret, 0x100); + if (err) + return err; + + /* Find the last two non-zero values. Return status is ignored. */ + i = 0xff; + do { + if (start) + *start = ret[i-1]; + if (end) + *end = ret[i]; + } while (i > 2 && !ret[i--]); + + return 0; +} + +static int huawei_wmi_battery_set(int start, int end) +{ + union hwmi_arg arg; + int err; + + if (start < 0 || end < 0 || start > 100 || end > 100) return -EINVAL; + + arg.cmd = BATTERY_THRESH_SET; + arg.args[2] = start; + arg.args[3] = end; + + /* This is an edge case were some models turn battery protection + * off without changing their thresholds values. We clear the + * values before turning off protection. Sometimes we need a sleep delay to + * make sure these values make their way to EC memory. + */ + if (quirks && quirks->battery_reset && start == 0 && end == 100) { + err = huawei_wmi_battery_set(0, 0); + if (err) + return err; + + msleep(1000); } - status = acpi_evaluate_object(priv->handle, priv->acpi_method, &arg_list, NULL); - if (ACPI_FAILURE(status)) - return -ENXIO; + err = huawei_wmi_cmd(arg.cmd, NULL, 0); + + return err; +} + +static ssize_t charge_control_start_threshold_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int err, start; + + err = huawei_wmi_battery_get(&start, NULL); + if (err) + return err; + + return sprintf(buf, "%d\n", start); +} + +static ssize_t charge_control_end_threshold_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int err, end; + + err = huawei_wmi_battery_get(NULL, &end); + if (err) + return err; + + return sprintf(buf, "%d\n", end); +} + +static ssize_t charge_control_thresholds_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int err, start, end; + + err = huawei_wmi_battery_get(&start, &end); + if (err) + return err; + + return sprintf(buf, "%d %d\n", start, end); +} + +static ssize_t charge_control_start_threshold_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int err, start, end; + + err = huawei_wmi_battery_get(NULL, &end); + if (err) + return err; + + if (sscanf(buf, "%d", &start) != 1) + return -EINVAL; + + err = huawei_wmi_battery_set(start, end); + if (err) + return err; + + return size; +} + +static ssize_t charge_control_end_threshold_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int err, start, end; + + err = huawei_wmi_battery_get(&start, NULL); + if (err) + return err; + + if (sscanf(buf, "%d", &end) != 1) + return -EINVAL; + + err = huawei_wmi_battery_set(start, end); + if (err) + return err; + + return size; +} + +static ssize_t charge_control_thresholds_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int err, start, end; + + if (sscanf(buf, "%d %d", &start, &end) != 2) + return -EINVAL; + + err = huawei_wmi_battery_set(start, end); + if (err) + return err; + + return size; +} + +static DEVICE_ATTR_RW(charge_control_start_threshold); +static DEVICE_ATTR_RW(charge_control_end_threshold); +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); return 0; } -static int huawei_wmi_leds_setup(struct wmi_device *wdev) +static int huawei_wmi_battery_remove(struct power_supply *battery) { - struct huawei_wmi_priv *priv = dev_get_drvdata(&wdev->dev); + device_remove_file(&battery->dev, &dev_attr_charge_control_start_threshold); + device_remove_file(&battery->dev, &dev_attr_charge_control_end_threshold); - priv->handle = ec_get_handle(); - if (!priv->handle) - return 0; + return 0; +} - if (acpi_has_method(priv->handle, "SPIN")) - priv->acpi_method = "SPIN"; - else if (acpi_has_method(priv->handle, "WPIN")) - priv->acpi_method = "WPIN"; - else - return 0; +static struct acpi_battery_hook huawei_wmi_battery_hook = { + .add_battery = huawei_wmi_battery_add, + .remove_battery = huawei_wmi_battery_remove, + .name = "Huawei Battery Extension" +}; + +static void huawei_wmi_battery_setup(struct device *dev) +{ + struct huawei_wmi *huawei = dev_get_drvdata(dev); - priv->cdev.name = "platform::micmute"; - priv->cdev.max_brightness = 1; - priv->cdev.brightness_set_blocking = huawei_wmi_micmute_led_set; - priv->cdev.default_trigger = "audio-micmute"; - priv->cdev.brightness = ledtrig_audio_get(LED_AUDIO_MICMUTE); - priv->cdev.dev = &wdev->dev; - priv->cdev.flags = LED_CORE_SUSPENDRESUME; + huawei->battery_available = true; + if (huawei_wmi_battery_get(NULL, NULL)) { + huawei->battery_available = false; + return; + } - return devm_led_classdev_register(&wdev->dev, &priv->cdev); + battery_hook_register(&huawei_wmi_battery_hook); + device_create_file(dev, &dev_attr_charge_control_thresholds); } -static void huawei_wmi_process_key(struct wmi_device *wdev, int code) +static void huawei_wmi_battery_exit(struct device *dev) +{ + struct huawei_wmi *huawei = dev_get_drvdata(dev); + + if (huawei->battery_available) { + battery_hook_unregister(&huawei_wmi_battery_hook); + device_remove_file(dev, &dev_attr_charge_control_thresholds); + } +} + +/* Fn lock */ + +static int huawei_wmi_fn_lock_get(int *on) +{ + u8 ret[0x100] = { 0 }; + int err, i; + + err = huawei_wmi_cmd(FN_LOCK_GET, ret, 0x100); + if (err) + return err; + + /* Find the first non-zero value. Return status is ignored. */ + i = 1; + do { + if (on) + *on = ret[i] - 1; // -1 undefined, 0 off, 1 on. + } while (i < 0xff && !ret[i++]); + + return 0; +} + +static int huawei_wmi_fn_lock_set(int on) +{ + union hwmi_arg arg; + + arg.cmd = FN_LOCK_SET; + arg.args[2] = on + 1; // 0 undefined, 1 off, 2 on. + + return huawei_wmi_cmd(arg.cmd, NULL, 0); +} + +static ssize_t fn_lock_state_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int err, on; + + err = huawei_wmi_fn_lock_get(&on); + if (err) + return err; + + return sprintf(buf, "%d\n", on); +} + +static ssize_t fn_lock_state_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + int on, err; + + if (kstrtoint(buf, 10, &on) || + on < 0 || on > 1) + return -EINVAL; + + err = huawei_wmi_fn_lock_set(on); + if (err) + return err; + + return size; +} + +static DEVICE_ATTR_RW(fn_lock_state); + +static void huawei_wmi_fn_lock_setup(struct device *dev) +{ + struct huawei_wmi *huawei = dev_get_drvdata(dev); + + huawei->fn_lock_available = true; + if (huawei_wmi_fn_lock_get(NULL)) { + huawei->fn_lock_available = false; + return; + } + + device_create_file(dev, &dev_attr_fn_lock_state); +} + +static void huawei_wmi_fn_lock_exit(struct device *dev) +{ + struct huawei_wmi *huawei = dev_get_drvdata(dev); + + if (huawei->fn_lock_available) + device_remove_file(dev, &dev_attr_fn_lock_state); +} + +/* debugfs */ + +static void huawei_wmi_debugfs_call_dump(struct seq_file *m, void *data, + union acpi_object *obj) +{ + struct huawei_wmi *huawei = m->private; + int i; + + switch (obj->type) { + case ACPI_TYPE_INTEGER: + seq_printf(m, "0x%llx", obj->integer.value); + break; + case ACPI_TYPE_STRING: + seq_printf(m, "\"%.*s\"", obj->string.length, obj->string.pointer); + break; + case ACPI_TYPE_BUFFER: + seq_puts(m, "{"); + for (i = 0; i < obj->buffer.length; i++) { + seq_printf(m, "0x%02x", obj->buffer.pointer[i]); + if (i < obj->buffer.length - 1) + seq_puts(m, ","); + } + seq_puts(m, "}"); + break; + case ACPI_TYPE_PACKAGE: + seq_puts(m, "["); + for (i = 0; i < obj->package.count; i++) { + huawei_wmi_debugfs_call_dump(m, huawei, &obj->package.elements[i]); + if (i < obj->package.count - 1) + seq_puts(m, ","); + } + seq_puts(m, "]"); + break; + default: + dev_err(huawei->dev, "Unexpected obj type, got %d\n", obj->type); + return; + } +} + +static int huawei_wmi_debugfs_call_show(struct seq_file *m, void *data) +{ + struct huawei_wmi *huawei = m->private; + struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL }; + struct acpi_buffer in; + union acpi_object *obj; + int err; + + in.length = sizeof(u64); + in.pointer = &huawei->debug.arg; + + err = huawei_wmi_call(huawei, &in, &out); + if (err) + return err; + + obj = out.pointer; + if (!obj) { + err = -EIO; + goto fail_debugfs_call; + } + + huawei_wmi_debugfs_call_dump(m, huawei, obj); + +fail_debugfs_call: + kfree(out.pointer); + return err; +} + +DEFINE_SHOW_ATTRIBUTE(huawei_wmi_debugfs_call); + +static void huawei_wmi_debugfs_setup(struct device *dev) +{ + struct huawei_wmi *huawei = dev_get_drvdata(dev); + + huawei->debug.root = debugfs_create_dir("huawei-wmi", NULL); + + debugfs_create_x64("arg", 0644, huawei->debug.root, + &huawei->debug.arg); + debugfs_create_file("call", 0400, + huawei->debug.root, huawei, &huawei_wmi_debugfs_call_fops); +} + +static void huawei_wmi_debugfs_exit(struct device *dev) +{ + struct huawei_wmi *huawei = dev_get_drvdata(dev); + + debugfs_remove_recursive(huawei->debug.root); +} + +/* Input */ + +static void huawei_wmi_process_key(struct input_dev *idev, int code) { - struct huawei_wmi_priv *priv = dev_get_drvdata(&wdev->dev); const struct key_entry *key; /* @@ -127,81 +713,187 @@ static void huawei_wmi_process_key(struct wmi_device *wdev, int code) kfree(response.pointer); } - key = sparse_keymap_entry_from_scancode(priv->idev, code); + key = sparse_keymap_entry_from_scancode(idev, code); if (!key) { - dev_info(&wdev->dev, "Unknown key pressed, code: 0x%04x\n", code); + dev_info(&idev->dev, "Unknown key pressed, code: 0x%04x\n", code); return; } - sparse_keymap_report_entry(priv->idev, key, 1, true); + if (quirks && !quirks->report_brightness && + (key->sw.code == KEY_BRIGHTNESSDOWN || + key->sw.code == KEY_BRIGHTNESSUP)) + return; + + sparse_keymap_report_entry(idev, key, 1, true); } -static void huawei_wmi_notify(struct wmi_device *wdev, - union acpi_object *obj) +static void huawei_wmi_input_notify(u32 value, void *context) { - if (obj->type == ACPI_TYPE_INTEGER) - huawei_wmi_process_key(wdev, obj->integer.value); + struct input_dev *idev = (struct input_dev *)context; + struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL }; + union acpi_object *obj; + acpi_status status; + + status = wmi_get_event_data(value, &response); + if (ACPI_FAILURE(status)) { + dev_err(&idev->dev, "Unable to get event data\n"); + return; + } + + obj = (union acpi_object *)response.pointer; + if (obj && obj->type == ACPI_TYPE_INTEGER) + huawei_wmi_process_key(idev, obj->integer.value); else - dev_info(&wdev->dev, "Bad response type %d\n", obj->type); + dev_err(&idev->dev, "Bad response type\n"); + + kfree(response.pointer); } -static int huawei_wmi_input_setup(struct wmi_device *wdev) +static int huawei_wmi_input_setup(struct device *dev, + const char *guid, + struct input_dev **idev) { - struct huawei_wmi_priv *priv = dev_get_drvdata(&wdev->dev); - int err; - - priv->idev = devm_input_allocate_device(&wdev->dev); - if (!priv->idev) + *idev = devm_input_allocate_device(dev); + if (!*idev) return -ENOMEM; - priv->idev->name = "Huawei WMI hotkeys"; - priv->idev->phys = "wmi/input0"; - priv->idev->id.bustype = BUS_HOST; - priv->idev->dev.parent = &wdev->dev; + (*idev)->name = "Huawei WMI hotkeys"; + (*idev)->phys = "wmi/input0"; + (*idev)->id.bustype = BUS_HOST; + (*idev)->dev.parent = dev; - err = sparse_keymap_setup(priv->idev, huawei_wmi_keymap, NULL); - if (err) - return err; + return sparse_keymap_setup(*idev, huawei_wmi_keymap, NULL) || + input_register_device(*idev) || + wmi_install_notify_handler(guid, huawei_wmi_input_notify, + *idev); +} - return input_register_device(priv->idev); +static void huawei_wmi_input_exit(struct device *dev, const char *guid) +{ + wmi_remove_notify_handler(guid); } -static int huawei_wmi_probe(struct wmi_device *wdev) +/* Huawei driver */ + +static const struct wmi_device_id huawei_wmi_events_id_table[] = { + { .guid_string = WMI0_EVENT_GUID }, + { .guid_string = HWMI_EVENT_GUID }, + { } +}; + +static int huawei_wmi_probe(struct platform_device *pdev) { - struct huawei_wmi_priv *priv; + const struct wmi_device_id *guid = huawei_wmi_events_id_table; int err; - priv = devm_kzalloc(&wdev->dev, sizeof(struct huawei_wmi_priv), GFP_KERNEL); - if (!priv) - return -ENOMEM; + platform_set_drvdata(pdev, huawei_wmi); + huawei_wmi->dev = &pdev->dev; - dev_set_drvdata(&wdev->dev, priv); + while (*guid->guid_string) { + struct input_dev *idev = *huawei_wmi->idev; - err = huawei_wmi_input_setup(wdev); - if (err) - return err; + if (wmi_has_guid(guid->guid_string)) { + err = huawei_wmi_input_setup(&pdev->dev, guid->guid_string, &idev); + if (err) { + dev_err(&pdev->dev, "Failed to setup input on %s\n", guid->guid_string); + return err; + } + } + + idev++; + guid++; + } + + if (wmi_has_guid(HWMI_METHOD_GUID)) { + mutex_init(&huawei_wmi->wmi_lock); - return huawei_wmi_leds_setup(wdev); + huawei_wmi_leds_setup(&pdev->dev); + huawei_wmi_fn_lock_setup(&pdev->dev); + huawei_wmi_battery_setup(&pdev->dev); + huawei_wmi_debugfs_setup(&pdev->dev); + } + + return 0; } -static const struct wmi_device_id huawei_wmi_id_table[] = { - { .guid_string = WMI0_EVENT_GUID }, - { .guid_string = AMW0_EVENT_GUID }, - { } -}; +static int huawei_wmi_remove(struct platform_device *pdev) +{ + const struct wmi_device_id *guid = huawei_wmi_events_id_table; -static struct wmi_driver huawei_wmi_driver = { + while (*guid->guid_string) { + if (wmi_has_guid(guid->guid_string)) + huawei_wmi_input_exit(&pdev->dev, guid->guid_string); + + guid++; + } + + if (wmi_has_guid(HWMI_METHOD_GUID)) { + huawei_wmi_debugfs_exit(&pdev->dev); + huawei_wmi_battery_exit(&pdev->dev); + huawei_wmi_fn_lock_exit(&pdev->dev); + } + + return 0; +} + +static struct platform_driver huawei_wmi_driver = { .driver = { .name = "huawei-wmi", }, - .id_table = huawei_wmi_id_table, .probe = huawei_wmi_probe, - .notify = huawei_wmi_notify, + .remove = huawei_wmi_remove, }; -module_wmi_driver(huawei_wmi_driver); +static __init int huawei_wmi_init(void) +{ + struct platform_device *pdev; + int err; + + huawei_wmi = kzalloc(sizeof(struct huawei_wmi), GFP_KERNEL); + if (!huawei_wmi) + return -ENOMEM; + + quirks = &quirk_unknown; + dmi_check_system(huawei_quirks); + if (battery_reset != -1) + quirks->battery_reset = battery_reset; + if (report_brightness != -1) + quirks->report_brightness = report_brightness; + + err = platform_driver_register(&huawei_wmi_driver); + if (err) + goto pdrv_err; + + pdev = platform_device_register_simple("huawei-wmi", -1, NULL, 0); + if (IS_ERR(pdev)) { + err = PTR_ERR(pdev); + goto pdev_err; + } + + return 0; + +pdev_err: + platform_driver_unregister(&huawei_wmi_driver); +pdrv_err: + kfree(huawei_wmi); + return err; +} + +static __exit void huawei_wmi_exit(void) +{ + struct platform_device *pdev = to_platform_device(huawei_wmi->dev); + + platform_device_unregister(pdev); + platform_driver_unregister(&huawei_wmi_driver); + + kfree(huawei_wmi); +} + +module_init(huawei_wmi_init); +module_exit(huawei_wmi_exit); -MODULE_DEVICE_TABLE(wmi, huawei_wmi_id_table); +MODULE_ALIAS("wmi:"HWMI_METHOD_GUID); +MODULE_DEVICE_TABLE(wmi, huawei_wmi_events_id_table); MODULE_AUTHOR("Ayman Bagabas <ayman.bagabas@gmail.com>"); -MODULE_DESCRIPTION("Huawei WMI hotkeys"); +MODULE_DESCRIPTION("Huawei WMI laptop extras driver"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/platform/x86/i2c-multi-instantiate.c b/drivers/platform/x86/i2c-multi-instantiate.c index 197d8a192721..ffb8d5d1eb5f 100644 --- a/drivers/platform/x86/i2c-multi-instantiate.c +++ b/drivers/platform/x86/i2c-multi-instantiate.c @@ -81,9 +81,7 @@ static int i2c_multi_inst_probe(struct platform_device *pdev) if (ret < 0) return ret; - multi = devm_kmalloc(dev, - offsetof(struct i2c_multi_inst_data, clients[ret]), - GFP_KERNEL); + multi = devm_kmalloc(dev, struct_size(multi, clients, ret), GFP_KERNEL); if (!multi) return -ENOMEM; @@ -92,7 +90,7 @@ static int i2c_multi_inst_probe(struct platform_device *pdev) 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", match->id, + 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) { @@ -110,6 +108,7 @@ static int i2c_multi_inst_probe(struct platform_device *pdev) 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; diff --git a/drivers/platform/x86/ibm_rtl.c b/drivers/platform/x86/ibm_rtl.c index 18d55cee5bcd..5fc665f7d9b3 100644 --- a/drivers/platform/x86/ibm_rtl.c +++ b/drivers/platform/x86/ibm_rtl.c @@ -1,25 +1,11 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * IBM Real-Time Linux driver * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * * Copyright (C) IBM Corporation, 2010 * * Author: Keith Mannthey <kmannth@us.ibm.com> * Vernon Mauery <vernux@us.ibm.com> - * */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt diff --git a/drivers/platform/x86/ideapad-laptop.c b/drivers/platform/x86/ideapad-laptop.c index c53ae86b59c7..7598cd46cf60 100644 --- a/drivers/platform/x86/ideapad-laptop.c +++ b/drivers/platform/x86/ideapad-laptop.c @@ -1,23 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * ideapad-laptop.c - Lenovo IdeaPad ACPI Extras * * Copyright © 2010 Intel Corporation * Copyright © 2010 David Woodhouse <dwmw2@infradead.org> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt @@ -330,34 +316,15 @@ static int debugfs_cfg_show(struct seq_file *s, void *data) } DEFINE_SHOW_ATTRIBUTE(debugfs_cfg); -static int ideapad_debugfs_init(struct ideapad_private *priv) +static void ideapad_debugfs_init(struct ideapad_private *priv) { - struct dentry *node; - - priv->debug = debugfs_create_dir("ideapad", NULL); - if (priv->debug == NULL) { - pr_err("failed to create debugfs directory"); - goto errout; - } - - node = debugfs_create_file("cfg", S_IRUGO, priv->debug, priv, - &debugfs_cfg_fops); - if (!node) { - pr_err("failed to create cfg in debugfs"); - goto errout; - } + struct dentry *dir; - node = debugfs_create_file("status", S_IRUGO, priv->debug, priv, - &debugfs_status_fops); - if (!node) { - pr_err("failed to create status in debugfs"); - goto errout; - } - - return 0; + dir = debugfs_create_dir("ideapad", NULL); + priv->debug = dir; -errout: - return -ENOMEM; + debugfs_create_file("cfg", S_IRUGO, dir, priv, &debugfs_cfg_fops); + debugfs_create_file("status", S_IRUGO, dir, priv, &debugfs_status_fops); } static void ideapad_debugfs_exit(struct ideapad_private *priv) @@ -980,312 +947,21 @@ static void ideapad_wmi_notify(u32 value, void *context) #endif /* - * Some ideapads don't have a hardware rfkill switch, reading VPCCMD_R_RF - * always results in 0 on these models, causing ideapad_laptop to wrongly - * report all radios as hardware-blocked. + * Some ideapads have a hardware rfkill switch, but most do not have one. + * Reading VPCCMD_R_RF always results in 0 on models without a hardware rfkill, + * switch causing ideapad_laptop to wrongly report all radios as hw-blocked. + * There used to be a long list of DMI ids for models without a hw rfkill + * switch here, but that resulted in playing whack a mole. + * More importantly wrongly reporting the wifi radio as hw-blocked, results in + * non working wifi. Whereas not reporting it hw-blocked, when it actually is + * hw-blocked results in an empty SSID list, which is a much more benign + * failure mode. + * So the default now is the much safer option of assuming there is no + * hardware rfkill switch. This default also actually matches most hardware, + * since having a hw rfkill switch is quite rare on modern hardware, so this + * also leads to a much shorter list. */ -static const struct dmi_system_id no_hw_rfkill_list[] = { - { - .ident = "Lenovo RESCUER R720-15IKBN", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), - DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo R720-15IKBN"), - }, - }, - { - .ident = "Lenovo G40-30", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), - DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo G40-30"), - }, - }, - { - .ident = "Lenovo G50-30", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), - DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo G50-30"), - }, - }, - { - .ident = "Lenovo V310-14IKB", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), - DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo V310-14IKB"), - }, - }, - { - .ident = "Lenovo V310-14ISK", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), - DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo V310-14ISK"), - }, - }, - { - .ident = "Lenovo V310-15IKB", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), - DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo V310-15IKB"), - }, - }, - { - .ident = "Lenovo V310-15ISK", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), - DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo V310-15ISK"), - }, - }, - { - .ident = "Lenovo V510-15IKB", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), - DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo V510-15IKB"), - }, - }, - { - .ident = "Lenovo ideapad 300-15IBR", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), - DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo ideapad 300-15IBR"), - }, - }, - { - .ident = "Lenovo ideapad 300-15IKB", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), - DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo ideapad 300-15IKB"), - }, - }, - { - .ident = "Lenovo ideapad 300S-11IBR", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), - DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo ideapad 300S-11BR"), - }, - }, - { - .ident = "Lenovo ideapad 310-15ABR", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), - DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo ideapad 310-15ABR"), - }, - }, - { - .ident = "Lenovo ideapad 310-15IAP", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), - DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo ideapad 310-15IAP"), - }, - }, - { - .ident = "Lenovo ideapad 310-15IKB", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), - DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo ideapad 310-15IKB"), - }, - }, - { - .ident = "Lenovo ideapad 310-15ISK", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), - DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo ideapad 310-15ISK"), - }, - }, - { - .ident = "Lenovo ideapad 330-15ICH", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), - DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo ideapad 330-15ICH"), - }, - }, - { - .ident = "Lenovo ideapad 530S-14ARR", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), - DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo ideapad 530S-14ARR"), - }, - }, - { - .ident = "Lenovo ideapad S130-14IGM", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), - DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo ideapad S130-14IGM"), - }, - }, - { - .ident = "Lenovo ideapad Y700-14ISK", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), - DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo ideapad Y700-14ISK"), - }, - }, - { - .ident = "Lenovo ideapad Y700-15ACZ", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), - DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo ideapad Y700-15ACZ"), - }, - }, - { - .ident = "Lenovo ideapad Y700-15ISK", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), - DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo ideapad Y700-15ISK"), - }, - }, - { - .ident = "Lenovo ideapad Y700 Touch-15ISK", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), - DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo ideapad Y700 Touch-15ISK"), - }, - }, - { - .ident = "Lenovo ideapad Y700-17ISK", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), - DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo ideapad Y700-17ISK"), - }, - }, - { - .ident = "Lenovo ideapad MIIX 720-12IKB", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), - DMI_MATCH(DMI_PRODUCT_VERSION, "MIIX 720-12IKB"), - }, - }, - { - .ident = "Lenovo Legion Y520-15IKB", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), - DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo Y520-15IKB"), - }, - }, - { - .ident = "Lenovo Y520-15IKBM", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), - DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo Y520-15IKBM"), - }, - }, - { - .ident = "Lenovo Legion Y530-15ICH", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), - DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo Legion Y530-15ICH"), - }, - }, - { - .ident = "Lenovo Legion Y530-15ICH-1060", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), - DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo Legion Y530-15ICH-1060"), - }, - }, - { - .ident = "Lenovo Legion Y720-15IKB", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), - DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo Y720-15IKB"), - }, - }, - { - .ident = "Lenovo Legion Y720-15IKBN", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), - DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo Y720-15IKBN"), - }, - }, - { - .ident = "Lenovo Y720-15IKBM", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), - DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo Y720-15IKBM"), - }, - }, - { - .ident = "Lenovo Yoga 2 11 / 13 / Pro", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), - DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo Yoga 2"), - }, - }, - { - .ident = "Lenovo Yoga 2 11 / 13 / Pro", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), - DMI_MATCH(DMI_BOARD_NAME, "Yoga2"), - }, - }, - { - .ident = "Lenovo Yoga 2 13", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), - DMI_MATCH(DMI_PRODUCT_VERSION, "Yoga 2 13"), - }, - }, - { - .ident = "Lenovo Yoga 3 1170 / 1470", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), - DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo Yoga 3"), - }, - }, - { - .ident = "Lenovo Yoga 3 Pro 1370", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), - DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo YOGA 3"), - }, - }, - { - .ident = "Lenovo Yoga 700", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), - DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo YOGA 700"), - }, - }, - { - .ident = "Lenovo Yoga 900", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), - DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo YOGA 900"), - }, - }, - { - .ident = "Lenovo Yoga 900", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), - DMI_MATCH(DMI_BOARD_NAME, "VIUU4"), - }, - }, - { - .ident = "Lenovo YOGA 910-13IKB", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), - DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo YOGA 910-13IKB"), - }, - }, - { - .ident = "Lenovo YOGA 920-13IKB", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), - DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo YOGA 920-13IKB"), - }, - }, - { - .ident = "Lenovo YOGA C930-13IKB", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), - DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo YOGA C930-13IKB"), - }, - }, - { - .ident = "Lenovo Zhaoyang E42-80", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), - DMI_MATCH(DMI_PRODUCT_VERSION, "ZHAOYANG E42-80"), - }, - }, +static const struct dmi_system_id hw_rfkill_list[] = { {} }; @@ -1311,15 +987,13 @@ static int ideapad_acpi_add(struct platform_device *pdev) priv->cfg = cfg; priv->adev = adev; priv->platform_device = pdev; - priv->has_hw_rfkill_switch = !dmi_check_system(no_hw_rfkill_list); + priv->has_hw_rfkill_switch = dmi_check_system(hw_rfkill_list); ret = ideapad_sysfs_init(priv); if (ret) return ret; - ret = ideapad_debugfs_init(priv); - if (ret) - goto debugfs_failed; + ideapad_debugfs_init(priv); ret = ideapad_input_init(priv); if (ret) @@ -1376,7 +1050,6 @@ backlight_failed: ideapad_input_exit(priv); input_failed: ideapad_debugfs_exit(priv); -debugfs_failed: ideapad_sysfs_exit(priv); return ret; } diff --git a/drivers/platform/x86/intel-hid.c b/drivers/platform/x86/intel-hid.c index bc0d55a59015..ef6d4bd77b1a 100644 --- a/drivers/platform/x86/intel-hid.c +++ b/drivers/platform/x86/intel-hid.c @@ -253,35 +253,45 @@ static void intel_button_array_enable(struct device *device, bool enable) static int intel_hid_pm_prepare(struct device *device) { - struct intel_hid_priv *priv = dev_get_drvdata(device); + if (device_may_wakeup(device)) { + struct intel_hid_priv *priv = dev_get_drvdata(device); - priv->wakeup_mode = true; + priv->wakeup_mode = true; + } return 0; } +static void intel_hid_pm_complete(struct device *device) +{ + struct intel_hid_priv *priv = dev_get_drvdata(device); + + priv->wakeup_mode = false; +} + static int intel_hid_pl_suspend_handler(struct device *device) { - if (pm_suspend_via_firmware()) { + intel_button_array_enable(device, false); + + if (!pm_suspend_no_platform()) intel_hid_set_enable(device, false); - intel_button_array_enable(device, false); - } + return 0; } static int intel_hid_pl_resume_handler(struct device *device) { - struct intel_hid_priv *priv = dev_get_drvdata(device); + intel_hid_pm_complete(device); - priv->wakeup_mode = false; - if (pm_resume_via_firmware()) { + if (!pm_suspend_no_platform()) intel_hid_set_enable(device, true); - intel_button_array_enable(device, true); - } + + intel_button_array_enable(device, true); return 0; } static const struct dev_pm_ops intel_hid_pl_pm_ops = { .prepare = intel_hid_pm_prepare, + .complete = intel_hid_pm_complete, .freeze = intel_hid_pl_suspend_handler, .thaw = intel_hid_pl_resume_handler, .restore = intel_hid_pl_resume_handler, @@ -491,6 +501,12 @@ static int intel_hid_probe(struct platform_device *device) } device_init_wakeup(&device->dev, true); + /* + * In order for system wakeup to work, the EC GPE has to be marked as + * a wakeup one, so do that here (this setting will persist, but it has + * no effect until the wakeup mask is set for the EC GPE). + */ + acpi_ec_mark_gpe_for_wake(); return 0; err_remove_notify: diff --git a/drivers/platform/x86/intel-vbtn.c b/drivers/platform/x86/intel-vbtn.c index 06cd7e818ed5..b74932307d69 100644 --- a/drivers/platform/x86/intel-vbtn.c +++ b/drivers/platform/x86/intel-vbtn.c @@ -76,12 +76,24 @@ static void notify_handler(acpi_handle handle, u32 event, void *context) struct platform_device *device = context; struct intel_vbtn_priv *priv = dev_get_drvdata(&device->dev); unsigned int val = !(event & 1); /* Even=press, Odd=release */ - const struct key_entry *ke_rel; + const struct key_entry *ke, *ke_rel; bool autorelease; if (priv->wakeup_mode) { - if (sparse_keymap_entry_from_scancode(priv->input_dev, event)) { + ke = sparse_keymap_entry_from_scancode(priv->input_dev, event); + if (ke) { pm_wakeup_hard_event(&device->dev); + + /* + * Switch events like tablet mode will wake the device + * and report the new switch position to the input + * subsystem. + */ + if (ke->type == KE_SW) + sparse_keymap_report_event(priv->input_dev, + event, + val, + 0); return; } goto out_unknown; @@ -164,6 +176,12 @@ static int intel_vbtn_probe(struct platform_device *device) return -EBUSY; device_init_wakeup(&device->dev, true); + /* + * In order for system wakeup to work, the EC GPE has to be marked as + * a wakeup one, so do that here (this setting will persist, but it has + * no effect until the wakeup mask is set for the EC GPE). + */ + acpi_ec_mark_gpe_for_wake(); return 0; } @@ -183,22 +201,30 @@ static int intel_vbtn_remove(struct platform_device *device) static int intel_vbtn_pm_prepare(struct device *dev) { - struct intel_vbtn_priv *priv = dev_get_drvdata(dev); + if (device_may_wakeup(dev)) { + struct intel_vbtn_priv *priv = dev_get_drvdata(dev); - priv->wakeup_mode = true; + priv->wakeup_mode = true; + } return 0; } -static int intel_vbtn_pm_resume(struct device *dev) +static void intel_vbtn_pm_complete(struct device *dev) { struct intel_vbtn_priv *priv = dev_get_drvdata(dev); priv->wakeup_mode = false; +} + +static int intel_vbtn_pm_resume(struct device *dev) +{ + intel_vbtn_pm_complete(dev); return 0; } static const struct dev_pm_ops intel_vbtn_pm_ops = { .prepare = intel_vbtn_pm_prepare, + .complete = intel_vbtn_pm_complete, .resume = intel_vbtn_pm_resume, .restore = intel_vbtn_pm_resume, .thaw = intel_vbtn_pm_resume, diff --git a/drivers/platform/x86/intel-wmi-thunderbolt.c b/drivers/platform/x86/intel-wmi-thunderbolt.c index 4dfa61434a76..974c22a7ff61 100644 --- a/drivers/platform/x86/intel-wmi-thunderbolt.c +++ b/drivers/platform/x86/intel-wmi-thunderbolt.c @@ -56,7 +56,8 @@ static const struct attribute_group tbt_attribute_group = { .attrs = tbt_attrs, }; -static int intel_wmi_thunderbolt_probe(struct wmi_device *wdev) +static int intel_wmi_thunderbolt_probe(struct wmi_device *wdev, + const void *context) { int ret; diff --git a/drivers/platform/x86/intel_bxtwc_tmu.c b/drivers/platform/x86/intel_bxtwc_tmu.c index 951c105bafc1..7ccf583649e6 100644 --- a/drivers/platform/x86/intel_bxtwc_tmu.c +++ b/drivers/platform/x86/intel_bxtwc_tmu.c @@ -60,11 +60,8 @@ static int bxt_wcove_tmu_probe(struct platform_device *pdev) wctmu->regmap = pmic->regmap; irq = platform_get_irq(pdev, 0); - - if (irq < 0) { - dev_err(&pdev->dev, "invalid irq %d\n", irq); + if (irq < 0) return irq; - } regmap_irq_chip = pmic->irq_chip_data_tmu; virq = regmap_irq_get_virq(regmap_irq_chip, irq); diff --git a/drivers/platform/x86/intel_cht_int33fe.c b/drivers/platform/x86/intel_cht_int33fe.c deleted file mode 100644 index 6fa3cced6f8e..000000000000 --- a/drivers/platform/x86/intel_cht_int33fe.c +++ /dev/null @@ -1,256 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Intel Cherry Trail ACPI INT33FE pseudo device driver - * - * Copyright (C) 2017 Hans de Goede <hdegoede@redhat.com> - * - * Some Intel Cherry Trail based device which ship with Windows 10, have - * this weird INT33FE ACPI device with a CRS table with 4 I2cSerialBusV2 - * resources, for 4 different chips attached to various i2c busses: - * 1. The Whiskey Cove pmic, which is also described by the INT34D3 ACPI device - * 2. Maxim MAX17047 Fuel Gauge Controller - * 3. FUSB302 USB Type-C Controller - * 4. PI3USB30532 USB switch - * - * So this driver is a stub / pseudo driver whose only purpose is to - * instantiate i2c-clients for chips 2 - 4, so that standard i2c drivers - * for these chips can bind to the them. - */ - -#include <linux/acpi.h> -#include <linux/i2c.h> -#include <linux/interrupt.h> -#include <linux/module.h> -#include <linux/platform_device.h> -#include <linux/regulator/consumer.h> -#include <linux/slab.h> - -#define EXPECTED_PTYPE 4 - -struct cht_int33fe_data { - struct i2c_client *max17047; - struct i2c_client *fusb302; - struct i2c_client *pi3usb30532; - /* Contain a list-head must be per device */ - struct device_connection connections[4]; -}; - -/* - * Grrr I severly dislike buggy BIOS-es. At least one BIOS enumerates - * the max17047 both through the INT33FE ACPI device (it is right there - * in the resources table) as well as through a separate MAX17047 device. - * - * These helpers are used to work around this by checking if an i2c-client - * for the max17047 has already been registered. - */ -static int cht_int33fe_check_for_max17047(struct device *dev, void *data) -{ - struct i2c_client **max17047 = data; - struct acpi_device *adev; - const char *hid; - - adev = ACPI_COMPANION(dev); - if (!adev) - return 0; - - hid = acpi_device_hid(adev); - - /* The MAX17047 ACPI node doesn't have an UID, so we don't check that */ - if (strcmp(hid, "MAX17047")) - return 0; - - *max17047 = to_i2c_client(dev); - return 1; -} - -static struct i2c_client *cht_int33fe_find_max17047(void) -{ - struct i2c_client *max17047 = NULL; - - i2c_for_each_dev(&max17047, cht_int33fe_check_for_max17047); - return max17047; -} - -static const char * const max17047_suppliers[] = { "bq24190-charger" }; - -static const struct property_entry max17047_props[] = { - PROPERTY_ENTRY_STRING_ARRAY("supplied-from", max17047_suppliers), - { } -}; - -static const struct property_entry fusb302_props[] = { - PROPERTY_ENTRY_STRING("linux,extcon-name", "cht_wcove_pwrsrc"), - PROPERTY_ENTRY_U32("fcs,max-sink-microvolt", 12000000), - PROPERTY_ENTRY_U32("fcs,max-sink-microamp", 3000000), - PROPERTY_ENTRY_U32("fcs,max-sink-microwatt", 36000000), - { } -}; - -static int cht_int33fe_probe(struct platform_device *pdev) -{ - struct device *dev = &pdev->dev; - struct i2c_board_info board_info; - struct cht_int33fe_data *data; - struct i2c_client *max17047; - struct regulator *regulator; - unsigned long long ptyp; - acpi_status status; - int fusb302_irq; - 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; - } - - /* - * 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: - * 1) The bq24292i allows charging with up to 12V, setting the fusb302's - * max-snk voltage to 12V with another charger-IC is not good. - * 2) For the fusb302 driver to get the bq24292i vbus regulator, the - * regulator-map, which is part of the bq24292i regulator_init_data, - * must be registered before the fusb302 is instantiated, otherwise - * it will end up with a dummy-regulator. - * Note "cht_wc_usb_typec_vbus" comes from the regulator_init_data - * which is defined in i2c-cht-wc.c from where the bq24292i i2c-client - * gets instantiated. We use regulator_get_optional here so that we - * don't end up getting a dummy-regulator ourselves. - */ - regulator = regulator_get_optional(dev, "cht_wc_usb_typec_vbus"); - if (IS_ERR(regulator)) { - ret = PTR_ERR(regulator); - return (ret == -ENODEV) ? -EPROBE_DEFER : ret; - } - regulator_put(regulator); - - /* The FUSB302 uses the irq at index 1 and is the only irq user */ - fusb302_irq = acpi_dev_gpio_irq_get(ACPI_COMPANION(dev), 1); - if (fusb302_irq < 0) { - if (fusb302_irq != -EPROBE_DEFER) - dev_err(dev, "Error getting FUSB302 irq\n"); - return fusb302_irq; - } - - data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); - if (!data) - return -ENOMEM; - - /* Work around BIOS bug, see comment on cht_int33fe_find_max17047 */ - max17047 = cht_int33fe_find_max17047(); - if (max17047) { - /* Pre-existing i2c-client for the max17047, add device-props */ - ret = device_add_properties(&max17047->dev, max17047_props); - if (ret) - return ret; - /* And re-probe to get the new device-props applied. */ - ret = device_reprobe(&max17047->dev); - if (ret) - dev_warn(dev, "Reprobing max17047 error: %d\n", ret); - } else { - memset(&board_info, 0, sizeof(board_info)); - strlcpy(board_info.type, "max17047", I2C_NAME_SIZE); - board_info.dev_name = "max17047"; - board_info.properties = max17047_props; - data->max17047 = i2c_acpi_new_device(dev, 1, &board_info); - if (IS_ERR(data->max17047)) - return PTR_ERR(data->max17047); - } - - data->connections[0].endpoint[0] = "port0"; - data->connections[0].endpoint[1] = "i2c-pi3usb30532"; - data->connections[0].id = "orientation-switch"; - data->connections[1].endpoint[0] = "port0"; - data->connections[1].endpoint[1] = "i2c-pi3usb30532"; - data->connections[1].id = "mode-switch"; - data->connections[2].endpoint[0] = "i2c-fusb302"; - data->connections[2].endpoint[1] = "intel_xhci_usb_sw-role-switch"; - data->connections[2].id = "usb-role-switch"; - - device_connections_add(data->connections); - - memset(&board_info, 0, sizeof(board_info)); - strlcpy(board_info.type, "typec_fusb302", I2C_NAME_SIZE); - board_info.dev_name = "fusb302"; - board_info.properties = fusb302_props; - board_info.irq = fusb302_irq; - - data->fusb302 = i2c_acpi_new_device(dev, 2, &board_info); - if (IS_ERR(data->fusb302)) { - ret = PTR_ERR(data->fusb302); - goto out_unregister_max17047; - } - - memset(&board_info, 0, sizeof(board_info)); - board_info.dev_name = "pi3usb30532"; - strlcpy(board_info.type, "pi3usb30532", I2C_NAME_SIZE); - - data->pi3usb30532 = i2c_acpi_new_device(dev, 3, &board_info); - if (IS_ERR(data->pi3usb30532)) { - ret = PTR_ERR(data->pi3usb30532); - goto out_unregister_fusb302; - } - - platform_set_drvdata(pdev, data); - - return 0; - -out_unregister_fusb302: - i2c_unregister_device(data->fusb302); - -out_unregister_max17047: - i2c_unregister_device(data->max17047); - - device_connections_remove(data->connections); - - return ret; -} - -static int cht_int33fe_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->max17047); - - device_connections_remove(data->connections); - - return 0; -} - -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("Hans de Goede <hdegoede@redhat.com>"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/platform/x86/intel_cht_int33fe_common.c b/drivers/platform/x86/intel_cht_int33fe_common.c new file mode 100644 index 000000000000..42dd11623f56 --- /dev/null +++ b/drivers/platform/x86/intel_cht_int33fe_common.c @@ -0,0 +1,147 @@ +// 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_i2c_res_filter(struct acpi_resource *ares, void *data) +{ + struct acpi_resource_i2c_serialbus *sb; + int *count = data; + + if (i2c_acpi_get_i2c_resource(ares, &sb)) + (*count)++; + + return 1; +} + +static int cht_int33fe_count_i2c_clients(struct device *dev) +{ + struct acpi_device *adev; + LIST_HEAD(resource_list); + int count = 0; + + adev = ACPI_COMPANION(dev); + if (!adev) + return -EINVAL; + + acpi_dev_get_resources(adev, &resource_list, + cht_int33fe_i2c_res_filter, &count); + + acpi_dev_free_resource_list(&resource_list); + + return count; +} + +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 = cht_int33fe_count_i2c_clients(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_cht_int33fe_common.h b/drivers/platform/x86/intel_cht_int33fe_common.h new file mode 100644 index 000000000000..03cd45f4e8cb --- /dev/null +++ b/drivers/platform/x86/intel_cht_int33fe_common.h @@ -0,0 +1,41 @@ +/* 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_cht_int33fe_microb.c b/drivers/platform/x86/intel_cht_int33fe_microb.c new file mode 100644 index 000000000000..20b11e0d9a75 --- /dev/null +++ b/drivers/platform/x86/intel_cht_int33fe_microb.c @@ -0,0 +1,57 @@ +// 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), + { } +}; + +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.properties = bq27xxx_props; + 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_cht_int33fe_typec.c b/drivers/platform/x86/intel_cht_int33fe_typec.c new file mode 100644 index 000000000000..2d097fc2dd46 --- /dev/null +++ b/drivers/platform/x86/intel_cht_int33fe_typec.c @@ -0,0 +1,354 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Intel Cherry Trail ACPI INT33FE pseudo device driver + * + * Copyright (C) 2017 Hans de Goede <hdegoede@redhat.com> + * + * Some Intel Cherry Trail based device which ship with Windows 10, have + * this weird INT33FE ACPI device with a CRS table with 4 I2cSerialBusV2 + * resources, for 4 different chips attached to various i2c busses: + * 1. The Whiskey Cove pmic, which is also described by the INT34D3 ACPI device + * 2. Maxim MAX17047 Fuel Gauge Controller + * 3. FUSB302 USB Type-C Controller + * 4. PI3USB30532 USB switch + * + * So this driver is a stub / pseudo driver whose only purpose is to + * instantiate i2c-clients for chips 2 - 4, so that standard i2c drivers + * for these chips can bind to the them. + */ + +#include <linux/i2c.h> +#include <linux/interrupt.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" + +enum { + INT33FE_NODE_FUSB302, + INT33FE_NODE_MAX17047, + INT33FE_NODE_PI3USB30532, + INT33FE_NODE_DISPLAYPORT, + INT33FE_NODE_USB_CONNECTOR, + INT33FE_NODE_MAX, +}; + +static const struct software_node nodes[]; + +static const struct software_node_ref_args pi3usb30532_ref = { + &nodes[INT33FE_NODE_PI3USB30532] +}; + +static const struct software_node_ref_args dp_ref = { + &nodes[INT33FE_NODE_DISPLAYPORT] +}; + +static struct software_node_ref_args mux_ref; + +static const struct software_node_reference usb_connector_refs[] = { + { "orientation-switch", 1, &pi3usb30532_ref}, + { "mode-switch", 1, &pi3usb30532_ref}, + { "displayport", 1, &dp_ref}, + { } +}; + +static const struct software_node_reference fusb302_refs[] = { + { "usb-role-switch", 1, &mux_ref}, + { } +}; + +/* + * Grrr I severly dislike buggy BIOS-es. At least one BIOS enumerates + * the max17047 both through the INT33FE ACPI device (it is right there + * in the resources table) as well as through a separate MAX17047 device. + * + * These helpers are used to work around this by checking if an i2c-client + * for the max17047 has already been registered. + */ +static int cht_int33fe_check_for_max17047(struct device *dev, void *data) +{ + struct i2c_client **max17047 = data; + struct acpi_device *adev; + const char *hid; + + adev = ACPI_COMPANION(dev); + if (!adev) + return 0; + + hid = acpi_device_hid(adev); + + /* The MAX17047 ACPI node doesn't have an UID, so we don't check that */ + if (strcmp(hid, "MAX17047")) + return 0; + + *max17047 = to_i2c_client(dev); + return 1; +} + +static const char * const max17047_suppliers[] = { "bq24190-charger" }; + +static const struct property_entry max17047_props[] = { + PROPERTY_ENTRY_STRING_ARRAY("supplied-from", max17047_suppliers), + { } +}; + +static const struct property_entry fusb302_props[] = { + PROPERTY_ENTRY_STRING("linux,extcon-name", "cht_wcove_pwrsrc"), + { } +}; + +#define PDO_FIXED_FLAGS \ + (PDO_FIXED_DUAL_ROLE | PDO_FIXED_DATA_SWAP | PDO_FIXED_USB_COMM) + +static const u32 src_pdo[] = { + PDO_FIXED(5000, 1500, PDO_FIXED_FLAGS), +}; + +static const u32 snk_pdo[] = { + PDO_FIXED(5000, 400, PDO_FIXED_FLAGS), + PDO_VAR(5000, 12000, 3000), +}; + +static const struct property_entry usb_connector_props[] = { + PROPERTY_ENTRY_STRING("data-role", "dual"), + PROPERTY_ENTRY_STRING("power-role", "dual"), + PROPERTY_ENTRY_STRING("try-power-role", "sink"), + PROPERTY_ENTRY_U32_ARRAY("source-pdos", src_pdo), + PROPERTY_ENTRY_U32_ARRAY("sink-pdos", snk_pdo), + PROPERTY_ENTRY_U32("op-sink-microwatt", 2500000), + { } +}; + +static const struct software_node nodes[] = { + { "fusb302", NULL, fusb302_props, fusb302_refs }, + { "max17047", NULL, max17047_props }, + { "pi3usb30532" }, + { "displayport" }, + { "connector", &nodes[0], usb_connector_props, usb_connector_refs }, + { } +}; + +static int cht_int33fe_setup_dp(struct cht_int33fe_data *data) +{ + struct fwnode_handle *fwnode; + struct pci_dev *pdev; + + fwnode = software_node_fwnode(&nodes[INT33FE_NODE_DISPLAYPORT]); + if (!fwnode) + return -ENODEV; + + /* First let's find the GPU PCI device */ + pdev = pci_get_class(PCI_CLASS_DISPLAY_VGA << 8, NULL); + if (!pdev || pdev->vendor != PCI_VENDOR_ID_INTEL) { + pci_dev_put(pdev); + return -ENODEV; + } + + /* Then the DP child device node */ + data->dp = device_get_named_child_node(&pdev->dev, "DD02"); + pci_dev_put(pdev); + if (!data->dp) + return -ENODEV; + + fwnode->secondary = ERR_PTR(-ENODEV); + data->dp->secondary = fwnode; + + return 0; +} + +static void cht_int33fe_remove_nodes(struct cht_int33fe_data *data) +{ + software_node_unregister_nodes(nodes); + + if (mux_ref.node) { + fwnode_handle_put(software_node_fwnode(mux_ref.node)); + mux_ref.node = NULL; + } + + if (data->dp) { + data->dp->secondary = NULL; + fwnode_handle_put(data->dp); + data->dp = NULL; + } +} + +static int cht_int33fe_add_nodes(struct cht_int33fe_data *data) +{ + int ret; + + ret = software_node_register_nodes(nodes); + if (ret) + return ret; + + /* The devices that are not created in this driver need extra steps. */ + + /* + * There is no ACPI device node for the USB role mux, so we need to wait + * until the mux driver has created software node for the mux device. + * It means we depend on the mux driver. This function will return + * -EPROBE_DEFER until the mux device is registered. + */ + mux_ref.node = software_node_find_by_name(NULL, "intel-xhci-usb-sw"); + if (!mux_ref.node) { + ret = -EPROBE_DEFER; + goto err_remove_nodes; + } + + /* + * The DP connector does have ACPI device node. In this case we can just + * find that ACPI node and assign our node as the secondary node to it. + */ + ret = cht_int33fe_setup_dp(data); + if (ret) + goto err_remove_nodes; + + return 0; + +err_remove_nodes: + cht_int33fe_remove_nodes(data); + + return ret; +} + +static int +cht_int33fe_register_max17047(struct device *dev, struct cht_int33fe_data *data) +{ + struct i2c_client *max17047 = NULL; + struct i2c_board_info board_info; + struct fwnode_handle *fwnode; + int ret; + + fwnode = software_node_fwnode(&nodes[INT33FE_NODE_MAX17047]); + if (!fwnode) + return -ENODEV; + + i2c_for_each_dev(&max17047, cht_int33fe_check_for_max17047); + if (max17047) { + /* Pre-existing i2c-client for the max17047, add device-props */ + fwnode->secondary = ERR_PTR(-ENODEV); + max17047->dev.fwnode->secondary = fwnode; + /* And re-probe to get the new device-props applied. */ + ret = device_reprobe(&max17047->dev); + if (ret) + dev_warn(dev, "Reprobing max17047 error: %d\n", ret); + return 0; + } + + memset(&board_info, 0, sizeof(board_info)); + strlcpy(board_info.type, "max17047", I2C_NAME_SIZE); + board_info.dev_name = "max17047"; + board_info.fwnode = fwnode; + data->battery_fg = i2c_acpi_new_device(dev, 1, &board_info); + + return PTR_ERR_OR_ZERO(data->battery_fg); +} + +int cht_int33fe_typec_probe(struct cht_int33fe_data *data) +{ + struct device *dev = data->dev; + struct i2c_board_info board_info; + struct fwnode_handle *fwnode; + struct regulator *regulator; + int fusb302_irq; + int ret; + + /* + * 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: + * 1) The bq24292i allows charging with up to 12V, setting the fusb302's + * max-snk voltage to 12V with another charger-IC is not good. + * 2) For the fusb302 driver to get the bq24292i vbus regulator, the + * regulator-map, which is part of the bq24292i regulator_init_data, + * must be registered before the fusb302 is instantiated, otherwise + * it will end up with a dummy-regulator. + * Note "cht_wc_usb_typec_vbus" comes from the regulator_init_data + * which is defined in i2c-cht-wc.c from where the bq24292i i2c-client + * gets instantiated. We use regulator_get_optional here so that we + * don't end up getting a dummy-regulator ourselves. + */ + regulator = regulator_get_optional(dev, "cht_wc_usb_typec_vbus"); + if (IS_ERR(regulator)) { + ret = PTR_ERR(regulator); + return (ret == -ENODEV) ? -EPROBE_DEFER : ret; + } + regulator_put(regulator); + + /* The FUSB302 uses the irq at index 1 and is the only irq user */ + fusb302_irq = acpi_dev_gpio_irq_get(ACPI_COMPANION(dev), 1); + if (fusb302_irq < 0) { + if (fusb302_irq != -EPROBE_DEFER) + dev_err(dev, "Error getting FUSB302 irq\n"); + return fusb302_irq; + } + + ret = cht_int33fe_add_nodes(data); + if (ret) + return ret; + + /* Work around BIOS bug, see comment on cht_int33fe_check_for_max17047 */ + ret = cht_int33fe_register_max17047(dev, data); + if (ret) + goto out_remove_nodes; + + fwnode = software_node_fwnode(&nodes[INT33FE_NODE_FUSB302]); + if (!fwnode) { + ret = -ENODEV; + goto out_unregister_max17047; + } + + memset(&board_info, 0, sizeof(board_info)); + strlcpy(board_info.type, "typec_fusb302", I2C_NAME_SIZE); + board_info.dev_name = "fusb302"; + board_info.fwnode = fwnode; + board_info.irq = fusb302_irq; + + data->fusb302 = i2c_acpi_new_device(dev, 2, &board_info); + if (IS_ERR(data->fusb302)) { + ret = PTR_ERR(data->fusb302); + goto out_unregister_max17047; + } + + fwnode = software_node_fwnode(&nodes[INT33FE_NODE_PI3USB30532]); + if (!fwnode) { + ret = -ENODEV; + goto out_unregister_fusb302; + } + + memset(&board_info, 0, sizeof(board_info)); + board_info.dev_name = "pi3usb30532"; + board_info.fwnode = fwnode; + strlcpy(board_info.type, "pi3usb30532", I2C_NAME_SIZE); + + data->pi3usb30532 = i2c_acpi_new_device(dev, 3, &board_info); + if (IS_ERR(data->pi3usb30532)) { + ret = PTR_ERR(data->pi3usb30532); + goto out_unregister_fusb302; + } + + return 0; + +out_unregister_fusb302: + i2c_unregister_device(data->fusb302); + +out_unregister_max17047: + i2c_unregister_device(data->battery_fg); + +out_remove_nodes: + cht_int33fe_remove_nodes(data); + + return ret; +} + +int cht_int33fe_typec_remove(struct cht_int33fe_data *data) +{ + i2c_unregister_device(data->pi3usb30532); + i2c_unregister_device(data->fusb302); + i2c_unregister_device(data->battery_fg); + + cht_int33fe_remove_nodes(data); + + return 0; +} diff --git a/drivers/platform/x86/intel_int0002_vgpio.c b/drivers/platform/x86/intel_int0002_vgpio.c index 1694a9aec77c..f14e2c5f9da5 100644 --- a/drivers/platform/x86/intel_int0002_vgpio.c +++ b/drivers/platform/x86/intel_int0002_vgpio.c @@ -51,17 +51,6 @@ #define GPE0A_STS_PORT 0x420 #define GPE0A_EN_PORT 0x428 -#define BAYTRAIL 0x01 -#define CHERRYTRAIL 0x02 - -#define ICPU(model, data) { X86_VENDOR_INTEL, 6, model, X86_FEATURE_ANY, data } - -static const struct x86_cpu_id int0002_cpu_ids[] = { - ICPU(INTEL_FAM6_ATOM_SILVERMONT, BAYTRAIL), /* Valleyview, Bay Trail */ - ICPU(INTEL_FAM6_ATOM_AIRMONT, CHERRYTRAIL), /* Braswell, Cherry Trail */ - {} -}; - /* * As this is not a real GPIO at all, but just a hack to model an event in * ACPI the get / set functions are dummy functions. @@ -133,7 +122,7 @@ static irqreturn_t int0002_irq(int irq, void *data) generic_handle_irq(irq_find_mapping(chip->irq.domain, GPE0A_PME_B0_VIRT_GPIO_PIN)); - pm_system_wakeup(); + pm_wakeup_hard_event(chip->parent); return IRQ_HANDLED; } @@ -155,14 +144,28 @@ static struct irq_chip int0002_cht_irqchip = { * No set_wake, on CHT the IRQ is typically shared with the ACPI SCI * and we don't want to mess with the ACPI SCI irq settings. */ + .flags = IRQCHIP_SKIP_SET_WAKE, }; +static const struct x86_cpu_id int0002_cpu_ids[] = { + INTEL_CPU_FAM6(ATOM_SILVERMONT, int0002_byt_irqchip), /* Valleyview, Bay Trail */ + INTEL_CPU_FAM6(ATOM_AIRMONT, int0002_cht_irqchip), /* Braswell, Cherry Trail */ + {} +}; + +static void int0002_init_irq_valid_mask(struct gpio_chip *chip, + unsigned long *valid_mask, + unsigned int ngpios) +{ + bitmap_clear(valid_mask, 0, GPE0A_PME_B0_VIRT_GPIO_PIN); +} + static int int0002_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; const struct x86_cpu_id *cpu_id; - struct irq_chip *irq_chip; struct gpio_chip *chip; + struct gpio_irq_chip *girq; int irq, ret; /* Menlow has a different INT0002 device? <sigh> */ @@ -171,10 +174,8 @@ static int int0002_probe(struct platform_device *pdev) return -ENODEV; irq = platform_get_irq(pdev, 0); - if (irq < 0) { - dev_err(dev, "Error getting IRQ: %d\n", irq); + if (irq < 0) return irq; - } chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL); if (!chip) @@ -189,19 +190,13 @@ static int int0002_probe(struct platform_device *pdev) chip->direction_output = int0002_gpio_direction_output; chip->base = -1; chip->ngpio = GPE0A_PME_B0_VIRT_GPIO_PIN + 1; - chip->irq.need_valid_mask = true; - - ret = devm_gpiochip_add_data(&pdev->dev, chip, NULL); - if (ret) { - dev_err(dev, "Error adding gpio chip: %d\n", ret); - return ret; - } - - bitmap_clear(chip->irq.valid_mask, 0, GPE0A_PME_B0_VIRT_GPIO_PIN); + chip->irq.init_valid_mask = int0002_init_irq_valid_mask; /* - * We manually request the irq here instead of passing a flow-handler + * We directly request the irq here instead of passing a flow-handler * to gpiochip_set_chained_irqchip, because the irq is shared. + * FIXME: augment this if we managed to pull handling of shared + * IRQs into gpiolib. */ ret = devm_request_irq(dev, irq, int0002_irq, IRQF_SHARED, "INT0002", chip); @@ -210,20 +205,28 @@ static int int0002_probe(struct platform_device *pdev) return ret; } - if (cpu_id->driver_data == BAYTRAIL) - irq_chip = &int0002_byt_irqchip; - else - irq_chip = &int0002_cht_irqchip; + girq = &chip->irq; + girq->chip = (struct irq_chip *)cpu_id->driver_data; + /* This let us handle the parent IRQ in the driver */ + girq->parent_handler = NULL; + girq->num_parents = 0; + girq->parents = NULL; + girq->default_type = IRQ_TYPE_NONE; + girq->handler = handle_edge_irq; - ret = gpiochip_irqchip_add(chip, irq_chip, 0, handle_edge_irq, - IRQ_TYPE_NONE); + ret = devm_gpiochip_add_data(dev, chip, NULL); if (ret) { - dev_err(dev, "Error adding irqchip: %d\n", ret); + dev_err(dev, "Error adding gpio chip: %d\n", ret); return ret; } - gpiochip_set_chained_irqchip(chip, irq_chip, irq, NULL); + device_init_wakeup(dev, true); + return 0; +} +static int int0002_remove(struct platform_device *pdev) +{ + device_init_wakeup(&pdev->dev, false); return 0; } @@ -239,6 +242,7 @@ static struct platform_driver int0002_driver = { .acpi_match_table = int0002_acpi_ids, }, .probe = int0002_probe, + .remove = int0002_remove, }; module_platform_driver(int0002_driver); diff --git a/drivers/platform/x86/intel_menlow.c b/drivers/platform/x86/intel_menlow.c index 77eb8709c931..b102f6dd5693 100644 --- a/drivers/platform/x86/intel_menlow.c +++ b/drivers/platform/x86/intel_menlow.c @@ -180,9 +180,13 @@ static int intel_menlow_memory_add(struct acpi_device *device) static int intel_menlow_memory_remove(struct acpi_device *device) { - struct thermal_cooling_device *cdev = acpi_driver_data(device); + struct thermal_cooling_device *cdev; + + if (!device) + return -EINVAL; - if (!device || !cdev) + cdev = acpi_driver_data(device); + if (!cdev) return -EINVAL; sysfs_remove_link(&device->dev.kobj, "thermal_cooling"); diff --git a/drivers/platform/x86/intel_mrfld_pwrbtn.c b/drivers/platform/x86/intel_mrfld_pwrbtn.c new file mode 100644 index 000000000000..d58fea51747e --- /dev/null +++ b/drivers/platform/x86/intel_mrfld_pwrbtn.c @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Power-button driver for Basin Cove PMIC + * + * Copyright (c) 2019, Intel Corporation. + * Author: Andy Shevchenko <andriy.shevchenko@linux.intel.com> + */ + +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/device.h> +#include <linux/mfd/intel_soc_pmic.h> +#include <linux/mfd/intel_soc_pmic_mrfld.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/pm_wakeirq.h> +#include <linux/slab.h> + +#define BCOVE_PBSTATUS 0x27 +#define BCOVE_PBSTATUS_PBLVL BIT(4) /* 1 - release, 0 - press */ + +static irqreturn_t mrfld_pwrbtn_interrupt(int irq, void *dev_id) +{ + struct input_dev *input = dev_id; + struct device *dev = input->dev.parent; + struct regmap *regmap = dev_get_drvdata(dev); + unsigned int state; + int ret; + + ret = regmap_read(regmap, BCOVE_PBSTATUS, &state); + if (ret) + return IRQ_NONE; + + dev_dbg(dev, "PBSTATUS=0x%x\n", state); + input_report_key(input, KEY_POWER, !(state & BCOVE_PBSTATUS_PBLVL)); + input_sync(input); + + regmap_update_bits(regmap, BCOVE_MIRQLVL1, BCOVE_LVL1_PWRBTN, 0); + return IRQ_HANDLED; +} + +static int mrfld_pwrbtn_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct intel_soc_pmic *pmic = dev_get_drvdata(dev->parent); + struct input_dev *input; + int irq, ret; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + input = devm_input_allocate_device(dev); + if (!input) + return -ENOMEM; + input->name = pdev->name; + input->phys = "power-button/input0"; + input->id.bustype = BUS_HOST; + input->dev.parent = dev; + input_set_capability(input, EV_KEY, KEY_POWER); + ret = input_register_device(input); + if (ret) + return ret; + + dev_set_drvdata(dev, pmic->regmap); + + ret = devm_request_threaded_irq(dev, irq, NULL, mrfld_pwrbtn_interrupt, + IRQF_ONESHOT | IRQF_SHARED, pdev->name, + input); + if (ret) + return ret; + + regmap_update_bits(pmic->regmap, BCOVE_MIRQLVL1, BCOVE_LVL1_PWRBTN, 0); + regmap_update_bits(pmic->regmap, BCOVE_MPBIRQ, BCOVE_PBIRQ_PBTN, 0); + + device_init_wakeup(dev, true); + dev_pm_set_wake_irq(dev, irq); + return 0; +} + +static int mrfld_pwrbtn_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + + dev_pm_clear_wake_irq(dev); + device_init_wakeup(dev, false); + return 0; +} + +static const struct platform_device_id mrfld_pwrbtn_id_table[] = { + { .name = "mrfld_bcove_pwrbtn" }, + {} +}; +MODULE_DEVICE_TABLE(platform, mrfld_pwrbtn_id_table); + +static struct platform_driver mrfld_pwrbtn_driver = { + .driver = { + .name = "mrfld_bcove_pwrbtn", + }, + .probe = mrfld_pwrbtn_probe, + .remove = mrfld_pwrbtn_remove, + .id_table = mrfld_pwrbtn_id_table, +}; +module_platform_driver(mrfld_pwrbtn_driver); + +MODULE_DESCRIPTION("Power-button driver for Basin Cove PMIC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/platform/x86/intel_oaktrail.c b/drivers/platform/x86/intel_oaktrail.c index 3c0438ba385e..1a09a75bd16d 100644 --- a/drivers/platform/x86/intel_oaktrail.c +++ b/drivers/platform/x86/intel_oaktrail.c @@ -243,7 +243,7 @@ static int oaktrail_backlight_init(void) if (IS_ERR(bd)) { oaktrail_bl_device = NULL; - pr_warning("Unable to register backlight device\n"); + pr_warn("Unable to register backlight device\n"); return PTR_ERR(bd); } @@ -313,20 +313,20 @@ static int __init oaktrail_init(void) ret = platform_driver_register(&oaktrail_driver); if (ret) { - pr_warning("Unable to register platform driver\n"); + pr_warn("Unable to register platform driver\n"); goto err_driver_reg; } oaktrail_device = platform_device_alloc(DRIVER_NAME, -1); if (!oaktrail_device) { - pr_warning("Unable to allocate platform device\n"); + pr_warn("Unable to allocate platform device\n"); ret = -ENOMEM; goto err_device_alloc; } ret = platform_device_add(oaktrail_device); if (ret) { - pr_warning("Unable to add platform device\n"); + pr_warn("Unable to add platform device\n"); goto err_device_add; } @@ -338,7 +338,7 @@ static int __init oaktrail_init(void) ret = oaktrail_rfkill_init(); if (ret) { - pr_warning("Setup rfkill failed\n"); + pr_warn("Setup rfkill failed\n"); goto err_rfkill; } diff --git a/drivers/platform/x86/intel_pmc_core.c b/drivers/platform/x86/intel_pmc_core.c index f2c621b55f49..571b4754477c 100644 --- a/drivers/platform/x86/intel_pmc_core.c +++ b/drivers/platform/x86/intel_pmc_core.c @@ -19,11 +19,14 @@ #include <linux/io.h> #include <linux/module.h> #include <linux/pci.h> +#include <linux/platform_device.h> +#include <linux/suspend.h> #include <linux/uaccess.h> #include <asm/cpu_device_id.h> #include <asm/intel-family.h> #include <asm/msr.h> +#include <asm/tsc.h> #include "intel_pmc_core.h" @@ -155,8 +158,9 @@ static const struct pmc_reg_map spt_reg_map = { .pm_vric1_offset = SPT_PMC_VRIC1_OFFSET, }; -/* Cannonlake: PGD PFET Enable Ack Status Register(s) bitmap */ +/* Cannon Lake: PGD PFET Enable Ack Status Register(s) bitmap */ static const struct pmc_bit_map cnp_pfear_map[] = { + /* Reserved for Cannon Lake but valid for Comet Lake */ {"PMC", BIT(0)}, {"OPI-DMI", BIT(1)}, {"SPI/eSPI", BIT(2)}, @@ -182,7 +186,7 @@ static const struct pmc_bit_map cnp_pfear_map[] = { {"SDX", BIT(4)}, {"SPE", BIT(5)}, {"Fuse", BIT(6)}, - /* Reserved for Cannonlake but valid for Icelake */ + /* Reserved for Cannon Lake but valid for Ice Lake and Comet Lake */ {"SBR8", BIT(7)}, {"CSME_FSC", BIT(0)}, @@ -226,12 +230,12 @@ static const struct pmc_bit_map cnp_pfear_map[] = { {"HDA_PGD4", BIT(2)}, {"HDA_PGD5", BIT(3)}, {"HDA_PGD6", BIT(4)}, - /* Reserved for Cannonlake but valid for Icelake */ + /* Reserved for Cannon Lake but valid for Ice Lake and Comet Lake */ {"PSF6", BIT(5)}, {"PSF7", BIT(6)}, {"PSF8", BIT(7)}, - /* Icelake generation onwards only */ + /* Ice Lake generation onwards only */ {"RES_65", BIT(0)}, {"RES_66", BIT(1)}, {"RES_67", BIT(2)}, @@ -321,7 +325,7 @@ static const struct pmc_bit_map cnp_ltr_show_map[] = { {"ISH", CNP_PMC_LTR_ISH}, {"UFSX2", CNP_PMC_LTR_UFSX2}, {"EMMC", CNP_PMC_LTR_EMMC}, - /* Reserved for Cannonlake but valid for Icelake */ + /* Reserved for Cannon Lake but valid for Ice Lake */ {"WIGIG", ICL_PMC_LTR_WIGIG}, /* Below two cannot be used for LTR_IGNORE */ {"CURRENT_PLATFORM", CNP_PMC_LTR_CUR_PLT}, @@ -738,7 +742,9 @@ static int pmc_core_pkgc_show(struct seq_file *s, void *unused) if (rdmsrl_safe(map[index].bit_mask, &pcstate_count)) continue; - seq_printf(s, "%-8s : 0x%llx\n", map[index].name, + pcstate_count *= 1000; + do_div(pcstate_count, tsc_khz); + seq_printf(s, "%-8s : %llu\n", map[index].name, pcstate_count); } @@ -751,14 +757,11 @@ static void pmc_core_dbgfs_unregister(struct pmc_dev *pmcdev) debugfs_remove_recursive(pmcdev->dbgfs_dir); } -static int pmc_core_dbgfs_register(struct pmc_dev *pmcdev) +static void pmc_core_dbgfs_register(struct pmc_dev *pmcdev) { struct dentry *dir; dir = debugfs_create_dir("pmc_core", NULL); - if (!dir) - return -ENOMEM; - pmcdev->dbgfs_dir = dir; debugfs_create_file("slp_s0_residency_usec", 0444, dir, pmcdev, @@ -792,13 +795,10 @@ static int pmc_core_dbgfs_register(struct pmc_dev *pmcdev) debugfs_create_bool("slp_s0_dbg_latch", 0644, dir, &slps0_dbg_latch); } - - return 0; } #else -static inline int pmc_core_dbgfs_register(struct pmc_dev *pmcdev) +static inline void pmc_core_dbgfs_register(struct pmc_dev *pmcdev) { - return 0; } static inline void pmc_core_dbgfs_unregister(struct pmc_dev *pmcdev) @@ -807,12 +807,15 @@ static inline void pmc_core_dbgfs_unregister(struct pmc_dev *pmcdev) #endif /* CONFIG_DEBUG_FS */ static const struct x86_cpu_id intel_pmc_core_ids[] = { - INTEL_CPU_FAM6(SKYLAKE_MOBILE, spt_reg_map), - INTEL_CPU_FAM6(SKYLAKE_DESKTOP, spt_reg_map), - INTEL_CPU_FAM6(KABYLAKE_MOBILE, spt_reg_map), - INTEL_CPU_FAM6(KABYLAKE_DESKTOP, spt_reg_map), - INTEL_CPU_FAM6(CANNONLAKE_MOBILE, cnp_reg_map), - INTEL_CPU_FAM6(ICELAKE_MOBILE, icl_reg_map), + INTEL_CPU_FAM6(SKYLAKE_L, spt_reg_map), + INTEL_CPU_FAM6(SKYLAKE, spt_reg_map), + INTEL_CPU_FAM6(KABYLAKE_L, spt_reg_map), + INTEL_CPU_FAM6(KABYLAKE, spt_reg_map), + INTEL_CPU_FAM6(CANNONLAKE_L, cnp_reg_map), + INTEL_CPU_FAM6(ICELAKE_L, icl_reg_map), + INTEL_CPU_FAM6(ICELAKE_NNPI, icl_reg_map), + INTEL_CPU_FAM6(COMETLAKE, cnp_reg_map), + INTEL_CPU_FAM6(COMETLAKE_L, cnp_reg_map), {} }; @@ -828,7 +831,7 @@ static const struct pci_device_id pmc_pci_ids[] = { * the platform BIOS enforces 24Mhx Crystal to shutdown * before PMC can assert SLP_S0#. */ -int quirk_xtal_ignore(const struct dmi_system_id *id) +static int quirk_xtal_ignore(const struct dmi_system_id *id) { struct pmc_dev *pmcdev = &pmc; u32 value; @@ -854,12 +857,15 @@ static const struct dmi_system_id pmc_core_dmi_table[] = { {} }; -static int __init pmc_core_probe(void) +static int pmc_core_probe(struct platform_device *pdev) { + static bool device_initialized; struct pmc_dev *pmcdev = &pmc; const struct x86_cpu_id *cpu_id; u64 slp_s0_addr; - int err; + + if (device_initialized) + return -ENODEV; cpu_id = x86_match_cpu(intel_pmc_core_ids); if (!cpu_id) @@ -868,17 +874,21 @@ static int __init pmc_core_probe(void) pmcdev->map = (struct pmc_reg_map *)cpu_id->driver_data; /* - * Coffeelake has CPU ID of Kabylake and Cannonlake PCH. So here - * Sunrisepoint PCH regmap can't be used. Use Cannonlake PCH regmap + * Coffee Lake has CPU ID of Kaby Lake and Cannon Lake PCH. So here + * Sunrisepoint PCH regmap can't be used. Use Cannon Lake PCH regmap * in this case. */ if (pmcdev->map == &spt_reg_map && !pci_dev_present(pmc_pci_ids)) pmcdev->map = &cnp_reg_map; - if (lpit_read_residency_count_address(&slp_s0_addr)) + if (lpit_read_residency_count_address(&slp_s0_addr)) { pmcdev->base_addr = PMC_BASE_ADDR_DEFAULT; - else + + if (page_is_ram(PHYS_PFN(pmcdev->base_addr))) + return -ENODEV; + } else { pmcdev->base_addr = slp_s0_addr - pmcdev->map->slp_s0_offset; + } pmcdev->regbase = ioremap(pmcdev->base_addr, pmcdev->map->regmap_length); @@ -886,30 +896,149 @@ static int __init pmc_core_probe(void) return -ENOMEM; mutex_init(&pmcdev->lock); + platform_set_drvdata(pdev, pmcdev); pmcdev->pmc_xram_read_bit = pmc_core_check_read_lock_bit(); + dmi_check_system(pmc_core_dmi_table); - err = pmc_core_dbgfs_register(pmcdev); - if (err < 0) { - pr_warn(" debugfs register failed.\n"); - iounmap(pmcdev->regbase); - return err; - } + pmc_core_dbgfs_register(pmcdev); + + device_initialized = true; + dev_info(&pdev->dev, " initialized\n"); - dmi_check_system(pmc_core_dmi_table); - pr_info(" initialized\n"); return 0; } -module_init(pmc_core_probe) -static void __exit pmc_core_remove(void) +static int pmc_core_remove(struct platform_device *pdev) { - struct pmc_dev *pmcdev = &pmc; + struct pmc_dev *pmcdev = platform_get_drvdata(pdev); pmc_core_dbgfs_unregister(pmcdev); + platform_set_drvdata(pdev, NULL); mutex_destroy(&pmcdev->lock); iounmap(pmcdev->regbase); + return 0; } -module_exit(pmc_core_remove) + +#ifdef CONFIG_PM_SLEEP + +static bool warn_on_s0ix_failures; +module_param(warn_on_s0ix_failures, bool, 0644); +MODULE_PARM_DESC(warn_on_s0ix_failures, "Check and warn for S0ix failures"); + +static int pmc_core_suspend(struct device *dev) +{ + struct pmc_dev *pmcdev = dev_get_drvdata(dev); + + pmcdev->check_counters = false; + + /* No warnings on S0ix failures */ + if (!warn_on_s0ix_failures) + return 0; + + /* Check if the syspend will actually use S0ix */ + if (pm_suspend_via_firmware()) + return 0; + + /* Save PC10 residency for checking later */ + if (rdmsrl_safe(MSR_PKG_C10_RESIDENCY, &pmcdev->pc10_counter)) + return -EIO; + + /* Save S0ix residency for checking later */ + if (pmc_core_dev_state_get(pmcdev, &pmcdev->s0ix_counter)) + return -EIO; + + pmcdev->check_counters = true; + return 0; +} + +static inline bool pmc_core_is_pc10_failed(struct pmc_dev *pmcdev) +{ + u64 pc10_counter; + + if (rdmsrl_safe(MSR_PKG_C10_RESIDENCY, &pc10_counter)) + return false; + + if (pc10_counter == pmcdev->pc10_counter) + return true; + + return false; +} + +static inline bool pmc_core_is_s0ix_failed(struct pmc_dev *pmcdev) +{ + u64 s0ix_counter; + + if (pmc_core_dev_state_get(pmcdev, &s0ix_counter)) + return false; + + if (s0ix_counter == pmcdev->s0ix_counter) + return true; + + return false; +} + +static int pmc_core_resume(struct device *dev) +{ + struct pmc_dev *pmcdev = dev_get_drvdata(dev); + const struct pmc_bit_map **maps = pmcdev->map->slps0_dbg_maps; + int offset = pmcdev->map->slps0_dbg_offset; + const struct pmc_bit_map *map; + u32 data; + + if (!pmcdev->check_counters) + return 0; + + if (!pmc_core_is_s0ix_failed(pmcdev)) + return 0; + + if (pmc_core_is_pc10_failed(pmcdev)) { + /* S0ix failed because of PC10 entry failure */ + dev_info(dev, "CPU did not enter PC10!!! (PC10 cnt=0x%llx)\n", + pmcdev->pc10_counter); + return 0; + } + + /* The real interesting case - S0ix failed - lets ask PMC why. */ + dev_warn(dev, "CPU did not enter SLP_S0!!! (S0ix cnt=%llu)\n", + pmcdev->s0ix_counter); + while (*maps) { + map = *maps; + data = pmc_core_reg_read(pmcdev, offset); + offset += 4; + while (map->name) { + dev_dbg(dev, "SLP_S0_DBG: %-32s\tState: %s\n", + map->name, + data & map->bit_mask ? "Yes" : "No"); + map++; + } + maps++; + } + return 0; +} + +#endif + +static const struct dev_pm_ops pmc_core_pm_ops = { + SET_LATE_SYSTEM_SLEEP_PM_OPS(pmc_core_suspend, pmc_core_resume) +}; + +static const struct acpi_device_id pmc_core_acpi_ids[] = { + {"INT33A1", 0}, /* _HID for Intel Power Engine, _CID PNP0D80*/ + { } +}; +MODULE_DEVICE_TABLE(acpi, pmc_core_acpi_ids); + +static struct platform_driver pmc_core_driver = { + .driver = { + .name = "intel_pmc_core", + .acpi_match_table = ACPI_PTR(pmc_core_acpi_ids), + .pm = &pmc_core_pm_ops, + }, + .probe = pmc_core_probe, + .remove = pmc_core_remove, +}; + +module_platform_driver(pmc_core_driver); MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("Intel PMC Core Driver"); diff --git a/drivers/platform/x86/intel_pmc_core.h b/drivers/platform/x86/intel_pmc_core.h index 88d9c0653a5f..fdee5772e532 100644 --- a/drivers/platform/x86/intel_pmc_core.h +++ b/drivers/platform/x86/intel_pmc_core.h @@ -241,6 +241,9 @@ struct pmc_reg_map { * @pmc_xram_read_bit: flag to indicate whether PMC XRAM shadow registers * used to read MPHY PG and PLL status are available * @mutex_lock: mutex to complete one transcation + * @check_counters: On resume, check if counters are getting incremented + * @pc10_counter: PC10 residency counter + * @s0ix_counter: S0ix residency (step adjusted) * * pmc_dev contains info about power management controller device. */ @@ -253,6 +256,10 @@ struct pmc_dev { #endif /* CONFIG_DEBUG_FS */ int pmc_xram_read_bit; struct mutex lock; /* generic mutex lock for PMC Core */ + + bool check_counters; /* Check for counter increments on resume */ + u64 pc10_counter; + u64 s0ix_counter; }; #endif /* PMC_CORE_H */ diff --git a/drivers/platform/x86/intel_pmc_core_pltdrv.c b/drivers/platform/x86/intel_pmc_core_pltdrv.c new file mode 100644 index 000000000000..6fe829f30997 --- /dev/null +++ b/drivers/platform/x86/intel_pmc_core_pltdrv.c @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* + * Intel PMC Core platform init + * Copyright (c) 2019, Google Inc. + * Author - Rajat Jain + * + * This code instantiates platform devices for intel_pmc_core driver, only + * on supported platforms that may not have the ACPI devices in the ACPI tables. + * No new platforms should be added here, because we expect that new platforms + * should all have the ACPI device, which is the preferred way of enumeration. + */ + +#include <linux/acpi.h> +#include <linux/module.h> +#include <linux/platform_device.h> + +#include <asm/cpu_device_id.h> +#include <asm/intel-family.h> + +static void intel_pmc_core_release(struct device *dev) +{ + /* Nothing to do. */ +} + +static struct platform_device pmc_core_device = { + .name = "intel_pmc_core", + .dev = { + .release = intel_pmc_core_release, + }, +}; + +/* + * intel_pmc_core_platform_ids is the list of platforms where we want to + * instantiate the platform_device if not already instantiated. This is + * different than intel_pmc_core_ids in intel_pmc_core.c which is the + * list of platforms that the driver supports for pmc_core device. The + * other list may grow, but this list should not. + */ +static const struct x86_cpu_id intel_pmc_core_platform_ids[] = { + INTEL_CPU_FAM6(SKYLAKE_L, pmc_core_device), + INTEL_CPU_FAM6(SKYLAKE, pmc_core_device), + INTEL_CPU_FAM6(KABYLAKE_L, pmc_core_device), + INTEL_CPU_FAM6(KABYLAKE, pmc_core_device), + INTEL_CPU_FAM6(CANNONLAKE_L, pmc_core_device), + INTEL_CPU_FAM6(ICELAKE_L, pmc_core_device), + {} +}; +MODULE_DEVICE_TABLE(x86cpu, intel_pmc_core_platform_ids); + +static int __init pmc_core_platform_init(void) +{ + /* Skip creating the platform device if ACPI already has a device */ + if (acpi_dev_present("INT33A1", NULL, -1)) + return -ENODEV; + + if (!x86_match_cpu(intel_pmc_core_platform_ids)) + return -ENODEV; + + return platform_device_register(&pmc_core_device); +} + +static void __exit pmc_core_platform_exit(void) +{ + platform_device_unregister(&pmc_core_device); +} + +module_init(pmc_core_platform_init); +module_exit(pmc_core_platform_exit); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/platform/x86/intel_pmc_ipc.c b/drivers/platform/x86/intel_pmc_ipc.c index 7964ba22ef8d..5c1da2bb1435 100644 --- a/drivers/platform/x86/intel_pmc_ipc.c +++ b/drivers/platform/x86/intel_pmc_ipc.c @@ -40,14 +40,14 @@ * The ARC handles the interrupt and services it, writing optional data to * the IPC1 registers, updates the IPC_STS response register with the status. */ -#define IPC_CMD 0x0 -#define IPC_CMD_MSI 0x100 +#define IPC_CMD 0x00 +#define IPC_CMD_MSI BIT(8) #define IPC_CMD_SIZE 16 #define IPC_CMD_SUBCMD 12 #define IPC_STATUS 0x04 -#define IPC_STATUS_IRQ 0x4 -#define IPC_STATUS_ERR 0x2 -#define IPC_STATUS_BUSY 0x1 +#define IPC_STATUS_IRQ BIT(2) +#define IPC_STATUS_ERR BIT(1) +#define IPC_STATUS_BUSY BIT(0) #define IPC_SPTR 0x08 #define IPC_DPTR 0x0C #define IPC_WRITE_BUFFER 0x80 @@ -101,13 +101,13 @@ #define TELEM_SSRAM_SIZE 240 #define TELEM_PMC_SSRAM_OFFSET 0x1B00 #define TELEM_PUNIT_SSRAM_OFFSET 0x1A00 -#define TCO_PMC_OFFSET 0x8 -#define TCO_PMC_SIZE 0x4 +#define TCO_PMC_OFFSET 0x08 +#define TCO_PMC_SIZE 0x04 /* PMC register bit definitions */ /* PMC_CFG_REG bit masks */ -#define PMC_CFG_NO_REBOOT_MASK (1 << 4) +#define PMC_CFG_NO_REBOOT_MASK BIT_MASK(4) #define PMC_CFG_NO_REBOOT_EN (1 << 4) #define PMC_CFG_NO_REBOOT_DIS (0 << 4) @@ -131,6 +131,7 @@ static struct intel_pmc_ipc_dev { /* punit */ struct platform_device *punit_dev; + unsigned int punit_res_count; /* Telemetry */ resource_size_t telem_pmc_ssram_base; @@ -682,7 +683,7 @@ static int ipc_create_punit_device(void) .name = PUNIT_DEVICE_NAME, .id = -1, .res = punit_res_array, - .num_res = ARRAY_SIZE(punit_res_array), + .num_res = ipcdev.punit_res_count, }; pdev = platform_device_register_full(&pdevinfo); @@ -771,13 +772,17 @@ static int ipc_create_pmc_devices(void) if (ret) { dev_err(ipcdev.dev, "Failed to add punit platform device\n"); platform_device_unregister(ipcdev.tco_dev); + return ret; } if (!ipcdev.telem_res_inval) { ret = ipc_create_telemetry_device(); - if (ret) + if (ret) { dev_warn(ipcdev.dev, "Failed to add telemetry platform device\n"); + platform_device_unregister(ipcdev.punit_dev); + platform_device_unregister(ipcdev.tco_dev); + } } return ret; @@ -785,7 +790,7 @@ static int ipc_create_pmc_devices(void) static int ipc_plat_get_res(struct platform_device *pdev) { - struct resource *res, *punit_res; + struct resource *res, *punit_res = punit_res_array; void __iomem *addr; int size; @@ -800,7 +805,8 @@ static int ipc_plat_get_res(struct platform_device *pdev) ipcdev.acpi_io_size = size; dev_info(&pdev->dev, "io res: %pR\n", res); - punit_res = punit_res_array; + ipcdev.punit_res_count = 0; + /* This is index 0 to cover BIOS data register */ res = platform_get_resource(pdev, IORESOURCE_MEM, PLAT_RESOURCE_BIOS_DATA_INDEX); @@ -808,7 +814,7 @@ static int ipc_plat_get_res(struct platform_device *pdev) dev_err(&pdev->dev, "Failed to get res of punit BIOS data\n"); return -ENXIO; } - *punit_res = *res; + punit_res[ipcdev.punit_res_count++] = *res; dev_info(&pdev->dev, "punit BIOS data res: %pR\n", res); /* This is index 1 to cover BIOS interface register */ @@ -818,42 +824,38 @@ static int ipc_plat_get_res(struct platform_device *pdev) dev_err(&pdev->dev, "Failed to get res of punit BIOS iface\n"); return -ENXIO; } - *++punit_res = *res; + punit_res[ipcdev.punit_res_count++] = *res; dev_info(&pdev->dev, "punit BIOS interface res: %pR\n", res); /* This is index 2 to cover ISP data register, optional */ res = platform_get_resource(pdev, IORESOURCE_MEM, PLAT_RESOURCE_ISP_DATA_INDEX); - ++punit_res; if (res) { - *punit_res = *res; + punit_res[ipcdev.punit_res_count++] = *res; dev_info(&pdev->dev, "punit ISP data res: %pR\n", res); } /* This is index 3 to cover ISP interface register, optional */ res = platform_get_resource(pdev, IORESOURCE_MEM, PLAT_RESOURCE_ISP_IFACE_INDEX); - ++punit_res; if (res) { - *punit_res = *res; + punit_res[ipcdev.punit_res_count++] = *res; dev_info(&pdev->dev, "punit ISP interface res: %pR\n", res); } /* This is index 4 to cover GTD data register, optional */ res = platform_get_resource(pdev, IORESOURCE_MEM, PLAT_RESOURCE_GTD_DATA_INDEX); - ++punit_res; if (res) { - *punit_res = *res; + punit_res[ipcdev.punit_res_count++] = *res; dev_info(&pdev->dev, "punit GTD data res: %pR\n", res); } /* This is index 5 to cover GTD interface register, optional */ res = platform_get_resource(pdev, IORESOURCE_MEM, PLAT_RESOURCE_GTD_IFACE_INDEX); - ++punit_res; if (res) { - *punit_res = *res; + punit_res[ipcdev.punit_res_count++] = *res; dev_info(&pdev->dev, "punit GTD interface res: %pR\n", res); } @@ -934,10 +936,8 @@ static int ipc_plat_probe(struct platform_device *pdev) spin_lock_init(&ipcdev.gcr_lock); ipcdev.irq = platform_get_irq(pdev, 0); - if (ipcdev.irq < 0) { - dev_err(&pdev->dev, "Failed to get irq\n"); + if (ipcdev.irq < 0) return -EINVAL; - } ret = ipc_plat_get_res(pdev); if (ret) { diff --git a/drivers/platform/x86/intel_punit_ipc.c b/drivers/platform/x86/intel_punit_ipc.c index 79671927f4ef..05cced59e251 100644 --- a/drivers/platform/x86/intel_punit_ipc.c +++ b/drivers/platform/x86/intel_punit_ipc.c @@ -224,7 +224,6 @@ static irqreturn_t intel_punit_ioc(int irq, void *dev_id) static int intel_punit_get_bars(struct platform_device *pdev) { - struct resource *res; void __iomem *addr; /* @@ -232,14 +231,12 @@ static int intel_punit_get_bars(struct platform_device *pdev) * - BIOS_IPC BASE_DATA * - BIOS_IPC BASE_IFACE */ - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - addr = devm_ioremap_resource(&pdev->dev, res); + addr = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(addr)) return PTR_ERR(addr); punit_ipcdev->base[BIOS_IPC][BASE_DATA] = addr; - res = platform_get_resource(pdev, IORESOURCE_MEM, 1); - addr = devm_ioremap_resource(&pdev->dev, res); + addr = devm_platform_ioremap_resource(pdev, 1); if (IS_ERR(addr)) return PTR_ERR(addr); punit_ipcdev->base[BIOS_IPC][BASE_IFACE] = addr; @@ -251,33 +248,21 @@ static int intel_punit_get_bars(struct platform_device *pdev) * - GTDRIVER_IPC BASE_DATA * - GTDRIVER_IPC BASE_IFACE */ - res = platform_get_resource(pdev, IORESOURCE_MEM, 2); - if (res && resource_size(res) > 1) { - addr = devm_ioremap_resource(&pdev->dev, res); - if (!IS_ERR(addr)) - punit_ipcdev->base[ISPDRIVER_IPC][BASE_DATA] = addr; - } + addr = devm_platform_ioremap_resource(pdev, 2); + if (!IS_ERR(addr)) + punit_ipcdev->base[ISPDRIVER_IPC][BASE_DATA] = addr; - res = platform_get_resource(pdev, IORESOURCE_MEM, 3); - if (res && resource_size(res) > 1) { - addr = devm_ioremap_resource(&pdev->dev, res); - if (!IS_ERR(addr)) - punit_ipcdev->base[ISPDRIVER_IPC][BASE_IFACE] = addr; - } + addr = devm_platform_ioremap_resource(pdev, 3); + if (!IS_ERR(addr)) + punit_ipcdev->base[ISPDRIVER_IPC][BASE_IFACE] = addr; - res = platform_get_resource(pdev, IORESOURCE_MEM, 4); - if (res && resource_size(res) > 1) { - addr = devm_ioremap_resource(&pdev->dev, res); - if (!IS_ERR(addr)) - punit_ipcdev->base[GTDRIVER_IPC][BASE_DATA] = addr; - } + addr = devm_platform_ioremap_resource(pdev, 4); + if (!IS_ERR(addr)) + punit_ipcdev->base[GTDRIVER_IPC][BASE_DATA] = addr; - res = platform_get_resource(pdev, IORESOURCE_MEM, 5); - if (res && resource_size(res) > 1) { - addr = devm_ioremap_resource(&pdev->dev, res); - if (!IS_ERR(addr)) - punit_ipcdev->base[GTDRIVER_IPC][BASE_IFACE] = addr; - } + addr = devm_platform_ioremap_resource(pdev, 5); + if (!IS_ERR(addr)) + punit_ipcdev->base[GTDRIVER_IPC][BASE_IFACE] = addr; return 0; } @@ -293,9 +278,8 @@ static int intel_punit_ipc_probe(struct platform_device *pdev) platform_set_drvdata(pdev, punit_ipcdev); - irq = platform_get_irq(pdev, 0); + irq = platform_get_irq_optional(pdev, 0); if (irq < 0) { - punit_ipcdev->irq = 0; dev_warn(&pdev->dev, "Invalid IRQ, using polling mode\n"); } else { ret = devm_request_irq(&pdev->dev, irq, intel_punit_ioc, @@ -310,14 +294,13 @@ static int intel_punit_ipc_probe(struct platform_device *pdev) ret = intel_punit_get_bars(pdev); if (ret) - goto out; + return ret; punit_ipcdev->dev = &pdev->dev; mutex_init(&punit_ipcdev->lock); init_completion(&punit_ipcdev->cmd_complete); -out: - return ret; + return 0; } static int intel_punit_ipc_remove(struct platform_device *pdev) diff --git a/drivers/platform/x86/intel_speed_select_if/Kconfig b/drivers/platform/x86/intel_speed_select_if/Kconfig new file mode 100644 index 000000000000..ce3e3dc076d2 --- /dev/null +++ b/drivers/platform/x86/intel_speed_select_if/Kconfig @@ -0,0 +1,17 @@ +menu "Intel Speed Select Technology interface support" + depends on PCI + depends on X86_64 || COMPILE_TEST + +config INTEL_SPEED_SELECT_INTERFACE + tristate "Intel(R) Speed Select Technology interface drivers" + help + This config enables the Intel(R) Speed Select Technology interface + drivers. The Intel(R) speed select technology features are non + architectural and only supported on specific Xeon(R) servers. + These drivers provide interface to directly communicate with hardware + via MMIO and Mail boxes to enumerate and control all the speed select + features. + + Enable this config, if there is a need to enable and control the + Intel(R) Speed Select Technology features from the user space. +endmenu diff --git a/drivers/platform/x86/intel_speed_select_if/Makefile b/drivers/platform/x86/intel_speed_select_if/Makefile new file mode 100644 index 000000000000..856076206f35 --- /dev/null +++ b/drivers/platform/x86/intel_speed_select_if/Makefile @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile - Intel Speed Select Interface drivers +# Copyright (c) 2019, Intel Corporation. +# + +obj-$(CONFIG_INTEL_SPEED_SELECT_INTERFACE) += isst_if_common.o +obj-$(CONFIG_INTEL_SPEED_SELECT_INTERFACE) += isst_if_mmio.o +obj-$(CONFIG_INTEL_SPEED_SELECT_INTERFACE) += isst_if_mbox_pci.o +obj-$(CONFIG_INTEL_SPEED_SELECT_INTERFACE) += isst_if_mbox_msr.o diff --git a/drivers/platform/x86/intel_speed_select_if/isst_if_common.c b/drivers/platform/x86/intel_speed_select_if/isst_if_common.c new file mode 100644 index 000000000000..3de5a3c66529 --- /dev/null +++ b/drivers/platform/x86/intel_speed_select_if/isst_if_common.c @@ -0,0 +1,674 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Intel Speed Select Interface: Common functions + * Copyright (c) 2019, Intel Corporation. + * All rights reserved. + * + * Author: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com> + */ + +#include <linux/cpufeature.h> +#include <linux/cpuhotplug.h> +#include <linux/fs.h> +#include <linux/hashtable.h> +#include <linux/miscdevice.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/sched/signal.h> +#include <linux/slab.h> +#include <linux/uaccess.h> +#include <uapi/linux/isst_if.h> + +#include "isst_if_common.h" + +#define MSR_THREAD_ID_INFO 0x53 +#define MSR_CPU_BUS_NUMBER 0x128 + +static struct isst_if_cmd_cb punit_callbacks[ISST_IF_DEV_MAX]; + +static int punit_msr_white_list[] = { + MSR_TURBO_RATIO_LIMIT, + MSR_CONFIG_TDP_CONTROL, + MSR_TURBO_RATIO_LIMIT1, + MSR_TURBO_RATIO_LIMIT2, +}; + +struct isst_valid_cmd_ranges { + u16 cmd; + u16 sub_cmd_beg; + u16 sub_cmd_end; +}; + +struct isst_cmd_set_req_type { + u16 cmd; + u16 sub_cmd; + u16 param; +}; + +static const struct isst_valid_cmd_ranges isst_valid_cmds[] = { + {0xD0, 0x00, 0x03}, + {0x7F, 0x00, 0x0B}, + {0x7F, 0x10, 0x12}, + {0x7F, 0x20, 0x23}, +}; + +static const struct isst_cmd_set_req_type isst_cmd_set_reqs[] = { + {0xD0, 0x00, 0x08}, + {0xD0, 0x01, 0x08}, + {0xD0, 0x02, 0x08}, + {0xD0, 0x03, 0x08}, + {0x7F, 0x02, 0x00}, + {0x7F, 0x08, 0x00}, +}; + +struct isst_cmd { + struct hlist_node hnode; + u64 data; + u32 cmd; + int cpu; + int mbox_cmd_type; + u32 param; +}; + +static DECLARE_HASHTABLE(isst_hash, 8); +static DEFINE_MUTEX(isst_hash_lock); + +static int isst_store_new_cmd(int cmd, u32 cpu, int mbox_cmd_type, u32 param, + u32 data) +{ + struct isst_cmd *sst_cmd; + + sst_cmd = kmalloc(sizeof(*sst_cmd), GFP_KERNEL); + if (!sst_cmd) + return -ENOMEM; + + sst_cmd->cpu = cpu; + sst_cmd->cmd = cmd; + sst_cmd->mbox_cmd_type = mbox_cmd_type; + sst_cmd->param = param; + sst_cmd->data = data; + + hash_add(isst_hash, &sst_cmd->hnode, sst_cmd->cmd); + + return 0; +} + +static void isst_delete_hash(void) +{ + struct isst_cmd *sst_cmd; + struct hlist_node *tmp; + int i; + + hash_for_each_safe(isst_hash, i, tmp, sst_cmd, hnode) { + hash_del(&sst_cmd->hnode); + kfree(sst_cmd); + } +} + +/** + * isst_store_cmd() - Store command to a hash table + * @cmd: Mailbox command. + * @sub_cmd: Mailbox sub-command or MSR id. + * @mbox_cmd_type: Mailbox or MSR command. + * @param: Mailbox parameter. + * @data: Mailbox request data or MSR data. + * + * Stores the command to a hash table if there is no such command already + * stored. If already stored update the latest parameter and data for the + * command. + * + * Return: Return result of store to hash table, 0 for success, others for + * failure. + */ +int isst_store_cmd(int cmd, int sub_cmd, u32 cpu, int mbox_cmd_type, + u32 param, u64 data) +{ + struct isst_cmd *sst_cmd; + int full_cmd, ret; + + full_cmd = (cmd & GENMASK_ULL(15, 0)) << 16; + full_cmd |= (sub_cmd & GENMASK_ULL(15, 0)); + mutex_lock(&isst_hash_lock); + hash_for_each_possible(isst_hash, sst_cmd, hnode, full_cmd) { + if (sst_cmd->cmd == full_cmd && sst_cmd->cpu == cpu && + sst_cmd->mbox_cmd_type == mbox_cmd_type) { + sst_cmd->param = param; + sst_cmd->data = data; + mutex_unlock(&isst_hash_lock); + return 0; + } + } + + ret = isst_store_new_cmd(full_cmd, cpu, mbox_cmd_type, param, data); + mutex_unlock(&isst_hash_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(isst_store_cmd); + +static void isst_mbox_resume_command(struct isst_if_cmd_cb *cb, + struct isst_cmd *sst_cmd) +{ + struct isst_if_mbox_cmd mbox_cmd; + int wr_only; + + mbox_cmd.command = (sst_cmd->cmd & GENMASK_ULL(31, 16)) >> 16; + mbox_cmd.sub_command = sst_cmd->cmd & GENMASK_ULL(15, 0); + mbox_cmd.parameter = sst_cmd->param; + mbox_cmd.req_data = sst_cmd->data; + mbox_cmd.logical_cpu = sst_cmd->cpu; + (cb->cmd_callback)((u8 *)&mbox_cmd, &wr_only, 1); +} + +/** + * isst_resume_common() - Process Resume request + * + * On resume replay all mailbox commands and MSRs. + * + * Return: None. + */ +void isst_resume_common(void) +{ + struct isst_cmd *sst_cmd; + int i; + + hash_for_each(isst_hash, i, sst_cmd, hnode) { + struct isst_if_cmd_cb *cb; + + if (sst_cmd->mbox_cmd_type) { + cb = &punit_callbacks[ISST_IF_DEV_MBOX]; + if (cb->registered) + isst_mbox_resume_command(cb, sst_cmd); + } else { + wrmsrl_safe_on_cpu(sst_cmd->cpu, sst_cmd->cmd, + sst_cmd->data); + } + } +} +EXPORT_SYMBOL_GPL(isst_resume_common); + +static void isst_restore_msr_local(int cpu) +{ + struct isst_cmd *sst_cmd; + int i; + + mutex_lock(&isst_hash_lock); + for (i = 0; i < ARRAY_SIZE(punit_msr_white_list); ++i) { + if (!punit_msr_white_list[i]) + break; + + hash_for_each_possible(isst_hash, sst_cmd, hnode, + punit_msr_white_list[i]) { + if (!sst_cmd->mbox_cmd_type && sst_cmd->cpu == cpu) + wrmsrl_safe(sst_cmd->cmd, sst_cmd->data); + } + } + mutex_unlock(&isst_hash_lock); +} + +/** + * isst_if_mbox_cmd_invalid() - Check invalid mailbox commands + * @cmd: Pointer to the command structure to verify. + * + * Invalid command to PUNIT to may result in instability of the platform. + * This function has a whitelist of commands, which are allowed. + * + * Return: Return true if the command is invalid, else false. + */ +bool isst_if_mbox_cmd_invalid(struct isst_if_mbox_cmd *cmd) +{ + int i; + + if (cmd->logical_cpu >= nr_cpu_ids) + return true; + + for (i = 0; i < ARRAY_SIZE(isst_valid_cmds); ++i) { + if (cmd->command == isst_valid_cmds[i].cmd && + (cmd->sub_command >= isst_valid_cmds[i].sub_cmd_beg && + cmd->sub_command <= isst_valid_cmds[i].sub_cmd_end)) { + return false; + } + } + + return true; +} +EXPORT_SYMBOL_GPL(isst_if_mbox_cmd_invalid); + +/** + * isst_if_mbox_cmd_set_req() - Check mailbox command is a set request + * @cmd: Pointer to the command structure to verify. + * + * Check if the given mail box level is set request and not a get request. + * + * Return: Return true if the command is set_req, else false. + */ +bool isst_if_mbox_cmd_set_req(struct isst_if_mbox_cmd *cmd) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(isst_cmd_set_reqs); ++i) { + if (cmd->command == isst_cmd_set_reqs[i].cmd && + cmd->sub_command == isst_cmd_set_reqs[i].sub_cmd && + cmd->parameter == isst_cmd_set_reqs[i].param) { + return true; + } + } + + return false; +} +EXPORT_SYMBOL_GPL(isst_if_mbox_cmd_set_req); + +static int isst_if_get_platform_info(void __user *argp) +{ + struct isst_if_platform_info info; + + info.api_version = ISST_IF_API_VERSION, + info.driver_version = ISST_IF_DRIVER_VERSION, + info.max_cmds_per_ioctl = ISST_IF_CMD_LIMIT, + info.mbox_supported = punit_callbacks[ISST_IF_DEV_MBOX].registered; + info.mmio_supported = punit_callbacks[ISST_IF_DEV_MMIO].registered; + + if (copy_to_user(argp, &info, sizeof(info))) + return -EFAULT; + + return 0; +} + + +struct isst_if_cpu_info { + /* For BUS 0 and BUS 1 only, which we need for PUNIT interface */ + int bus_info[2]; + int punit_cpu_id; +}; + +static struct isst_if_cpu_info *isst_cpu_info; + +/** + * isst_if_get_pci_dev() - Get the PCI device instance for a CPU + * @cpu: Logical CPU number. + * @bus_number: The bus number assigned by the hardware. + * @dev: The device number assigned by the hardware. + * @fn: The function number assigned by the hardware. + * + * Using cached bus information, find out the PCI device for a bus number, + * device and function. + * + * Return: Return pci_dev pointer or NULL. + */ +struct pci_dev *isst_if_get_pci_dev(int cpu, int bus_no, int dev, int fn) +{ + int bus_number; + + if (bus_no < 0 || bus_no > 1 || cpu < 0 || cpu >= nr_cpu_ids || + cpu >= num_possible_cpus()) + return NULL; + + bus_number = isst_cpu_info[cpu].bus_info[bus_no]; + if (bus_number < 0) + return NULL; + + return pci_get_domain_bus_and_slot(0, bus_number, PCI_DEVFN(dev, fn)); +} +EXPORT_SYMBOL_GPL(isst_if_get_pci_dev); + +static int isst_if_cpu_online(unsigned int cpu) +{ + u64 data; + int ret; + + ret = rdmsrl_safe(MSR_CPU_BUS_NUMBER, &data); + if (ret) { + /* This is not a fatal error on MSR mailbox only I/F */ + isst_cpu_info[cpu].bus_info[0] = -1; + isst_cpu_info[cpu].bus_info[1] = -1; + } else { + isst_cpu_info[cpu].bus_info[0] = data & 0xff; + isst_cpu_info[cpu].bus_info[1] = (data >> 8) & 0xff; + } + + ret = rdmsrl_safe(MSR_THREAD_ID_INFO, &data); + if (ret) { + isst_cpu_info[cpu].punit_cpu_id = -1; + return ret; + } + isst_cpu_info[cpu].punit_cpu_id = data; + + isst_restore_msr_local(cpu); + + return 0; +} + +static int isst_if_online_id; + +static int isst_if_cpu_info_init(void) +{ + int ret; + + isst_cpu_info = kcalloc(num_possible_cpus(), + sizeof(*isst_cpu_info), + GFP_KERNEL); + if (!isst_cpu_info) + return -ENOMEM; + + ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, + "platform/x86/isst-if:online", + isst_if_cpu_online, NULL); + if (ret < 0) { + kfree(isst_cpu_info); + return ret; + } + + isst_if_online_id = ret; + + return 0; +} + +static void isst_if_cpu_info_exit(void) +{ + cpuhp_remove_state(isst_if_online_id); + kfree(isst_cpu_info); +}; + +static long isst_if_proc_phyid_req(u8 *cmd_ptr, int *write_only, int resume) +{ + struct isst_if_cpu_map *cpu_map; + + cpu_map = (struct isst_if_cpu_map *)cmd_ptr; + if (cpu_map->logical_cpu >= nr_cpu_ids || + cpu_map->logical_cpu >= num_possible_cpus()) + return -EINVAL; + + *write_only = 0; + cpu_map->physical_cpu = isst_cpu_info[cpu_map->logical_cpu].punit_cpu_id; + + return 0; +} + +static bool match_punit_msr_white_list(int msr) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(punit_msr_white_list); ++i) { + if (punit_msr_white_list[i] == msr) + return true; + } + + return false; +} + +static long isst_if_msr_cmd_req(u8 *cmd_ptr, int *write_only, int resume) +{ + struct isst_if_msr_cmd *msr_cmd; + int ret; + + msr_cmd = (struct isst_if_msr_cmd *)cmd_ptr; + + if (!match_punit_msr_white_list(msr_cmd->msr)) + return -EINVAL; + + if (msr_cmd->logical_cpu >= nr_cpu_ids) + return -EINVAL; + + if (msr_cmd->read_write) { + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + ret = wrmsrl_safe_on_cpu(msr_cmd->logical_cpu, + msr_cmd->msr, + msr_cmd->data); + *write_only = 1; + if (!ret && !resume) + ret = isst_store_cmd(0, msr_cmd->msr, + msr_cmd->logical_cpu, + 0, 0, msr_cmd->data); + } else { + u64 data; + + ret = rdmsrl_safe_on_cpu(msr_cmd->logical_cpu, + msr_cmd->msr, &data); + if (!ret) { + msr_cmd->data = data; + *write_only = 0; + } + } + + + return ret; +} + +static long isst_if_exec_multi_cmd(void __user *argp, struct isst_if_cmd_cb *cb) +{ + unsigned char __user *ptr; + u32 cmd_count; + u8 *cmd_ptr; + long ret; + int i; + + /* Each multi command has u32 command count as the first field */ + if (copy_from_user(&cmd_count, argp, sizeof(cmd_count))) + return -EFAULT; + + if (!cmd_count || cmd_count > ISST_IF_CMD_LIMIT) + return -EINVAL; + + cmd_ptr = kmalloc(cb->cmd_size, GFP_KERNEL); + if (!cmd_ptr) + return -ENOMEM; + + /* cb->offset points to start of the command after the command count */ + ptr = argp + cb->offset; + + for (i = 0; i < cmd_count; ++i) { + int wr_only; + + if (signal_pending(current)) { + ret = -EINTR; + break; + } + + if (copy_from_user(cmd_ptr, ptr, cb->cmd_size)) { + ret = -EFAULT; + break; + } + + ret = cb->cmd_callback(cmd_ptr, &wr_only, 0); + if (ret) + break; + + if (!wr_only && copy_to_user(ptr, cmd_ptr, cb->cmd_size)) { + ret = -EFAULT; + break; + } + + ptr += cb->cmd_size; + } + + kfree(cmd_ptr); + + return i ? i : ret; +} + +static long isst_if_def_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + void __user *argp = (void __user *)arg; + struct isst_if_cmd_cb cmd_cb; + struct isst_if_cmd_cb *cb; + long ret = -ENOTTY; + + switch (cmd) { + case ISST_IF_GET_PLATFORM_INFO: + ret = isst_if_get_platform_info(argp); + break; + case ISST_IF_GET_PHY_ID: + cmd_cb.cmd_size = sizeof(struct isst_if_cpu_map); + cmd_cb.offset = offsetof(struct isst_if_cpu_maps, cpu_map); + cmd_cb.cmd_callback = isst_if_proc_phyid_req; + ret = isst_if_exec_multi_cmd(argp, &cmd_cb); + break; + case ISST_IF_IO_CMD: + cb = &punit_callbacks[ISST_IF_DEV_MMIO]; + if (cb->registered) + ret = isst_if_exec_multi_cmd(argp, cb); + break; + case ISST_IF_MBOX_COMMAND: + cb = &punit_callbacks[ISST_IF_DEV_MBOX]; + if (cb->registered) + ret = isst_if_exec_multi_cmd(argp, cb); + break; + case ISST_IF_MSR_COMMAND: + cmd_cb.cmd_size = sizeof(struct isst_if_msr_cmd); + cmd_cb.offset = offsetof(struct isst_if_msr_cmds, msr_cmd); + cmd_cb.cmd_callback = isst_if_msr_cmd_req; + ret = isst_if_exec_multi_cmd(argp, &cmd_cb); + break; + default: + break; + } + + return ret; +} + +static DEFINE_MUTEX(punit_misc_dev_lock); +static int misc_usage_count; +static int misc_device_ret; +static int misc_device_open; + +static int isst_if_open(struct inode *inode, struct file *file) +{ + int i, ret = 0; + + /* Fail open, if a module is going away */ + mutex_lock(&punit_misc_dev_lock); + for (i = 0; i < ISST_IF_DEV_MAX; ++i) { + struct isst_if_cmd_cb *cb = &punit_callbacks[i]; + + if (cb->registered && !try_module_get(cb->owner)) { + ret = -ENODEV; + break; + } + } + if (ret) { + int j; + + for (j = 0; j < i; ++j) { + struct isst_if_cmd_cb *cb; + + cb = &punit_callbacks[j]; + if (cb->registered) + module_put(cb->owner); + } + } else { + misc_device_open++; + } + mutex_unlock(&punit_misc_dev_lock); + + return ret; +} + +static int isst_if_relase(struct inode *inode, struct file *f) +{ + int i; + + mutex_lock(&punit_misc_dev_lock); + misc_device_open--; + for (i = 0; i < ISST_IF_DEV_MAX; ++i) { + struct isst_if_cmd_cb *cb = &punit_callbacks[i]; + + if (cb->registered) + module_put(cb->owner); + } + mutex_unlock(&punit_misc_dev_lock); + + return 0; +} + +static const struct file_operations isst_if_char_driver_ops = { + .open = isst_if_open, + .unlocked_ioctl = isst_if_def_ioctl, + .release = isst_if_relase, +}; + +static struct miscdevice isst_if_char_driver = { + .minor = MISC_DYNAMIC_MINOR, + .name = "isst_interface", + .fops = &isst_if_char_driver_ops, +}; + +/** + * isst_if_cdev_register() - Register callback for IOCTL + * @device_type: The device type this callback handling. + * @cb: Callback structure. + * + * This function registers a callback to device type. On very first call + * it will register a misc device, which is used for user kernel interface. + * Other calls simply increment ref count. Registry will fail, if the user + * already opened misc device for operation. Also if the misc device + * creation failed, then it will not try again and all callers will get + * failure code. + * + * Return: Return the return value from the misc creation device or -EINVAL + * for unsupported device type. + */ +int isst_if_cdev_register(int device_type, struct isst_if_cmd_cb *cb) +{ + if (misc_device_ret) + return misc_device_ret; + + if (device_type >= ISST_IF_DEV_MAX) + return -EINVAL; + + mutex_lock(&punit_misc_dev_lock); + if (misc_device_open) { + mutex_unlock(&punit_misc_dev_lock); + return -EAGAIN; + } + if (!misc_usage_count) { + int ret; + + misc_device_ret = misc_register(&isst_if_char_driver); + if (misc_device_ret) + goto unlock_exit; + + ret = isst_if_cpu_info_init(); + if (ret) { + misc_deregister(&isst_if_char_driver); + misc_device_ret = ret; + goto unlock_exit; + } + } + memcpy(&punit_callbacks[device_type], cb, sizeof(*cb)); + punit_callbacks[device_type].registered = 1; + misc_usage_count++; +unlock_exit: + mutex_unlock(&punit_misc_dev_lock); + + return misc_device_ret; +} +EXPORT_SYMBOL_GPL(isst_if_cdev_register); + +/** + * isst_if_cdev_unregister() - Unregister callback for IOCTL + * @device_type: The device type to unregister. + * + * This function unregisters the previously registered callback. If this + * is the last callback unregistering, then misc device is removed. + * + * Return: None. + */ +void isst_if_cdev_unregister(int device_type) +{ + mutex_lock(&punit_misc_dev_lock); + misc_usage_count--; + punit_callbacks[device_type].registered = 0; + if (device_type == ISST_IF_DEV_MBOX) + isst_delete_hash(); + if (!misc_usage_count && !misc_device_ret) { + misc_deregister(&isst_if_char_driver); + isst_if_cpu_info_exit(); + } + mutex_unlock(&punit_misc_dev_lock); +} +EXPORT_SYMBOL_GPL(isst_if_cdev_unregister); + +MODULE_LICENSE("GPL v2"); diff --git a/drivers/platform/x86/intel_speed_select_if/isst_if_common.h b/drivers/platform/x86/intel_speed_select_if/isst_if_common.h new file mode 100644 index 000000000000..1409a5bb5582 --- /dev/null +++ b/drivers/platform/x86/intel_speed_select_if/isst_if_common.h @@ -0,0 +1,69 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Intel Speed Select Interface: Drivers Internal defines + * Copyright (c) 2019, Intel Corporation. + * All rights reserved. + * + * Author: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com> + */ + +#ifndef __ISST_IF_COMMON_H +#define __ISST_IF_COMMON_H + +#define INTEL_RAPL_PRIO_DEVID_0 0x3451 +#define INTEL_CFG_MBOX_DEVID_0 0x3459 + +/* + * Validate maximum commands in a single request. + * This is enough to handle command to every core in one ioctl, or all + * possible message id to one CPU. Limit is also helpful for resonse time + * per IOCTL request, as PUNIT may take different times to process each + * request and may hold for long for too many commands. + */ +#define ISST_IF_CMD_LIMIT 64 + +#define ISST_IF_API_VERSION 0x01 +#define ISST_IF_DRIVER_VERSION 0x01 + +#define ISST_IF_DEV_MBOX 0 +#define ISST_IF_DEV_MMIO 1 +#define ISST_IF_DEV_MAX 2 + +/** + * struct isst_if_cmd_cb - Used to register a IOCTL handler + * @registered: Used by the common code to store registry. Caller don't + * to touch this field + * @cmd_size: The command size of the individual command in IOCTL + * @offset: Offset to the first valid member in command structure. + * This will be the offset of the start of the command + * after command count field + * @cmd_callback: Callback function to handle IOCTL. The callback has the + * command pointer with data for command. There is a pointer + * called write_only, which when set, will not copy the + * response to user ioctl buffer. The "resume" argument + * can be used to avoid storing the command for replay + * during system resume + * + * This structure is used to register an handler for IOCTL. To avoid + * code duplication common code handles all the IOCTL command read/write + * including handling multiple command in single IOCTL. The caller just + * need to execute a command via the registered callback. + */ +struct isst_if_cmd_cb { + int registered; + int cmd_size; + int offset; + struct module *owner; + long (*cmd_callback)(u8 *ptr, int *write_only, int resume); +}; + +/* Internal interface functions */ +int isst_if_cdev_register(int type, struct isst_if_cmd_cb *cb); +void isst_if_cdev_unregister(int type); +struct pci_dev *isst_if_get_pci_dev(int cpu, int bus, int dev, int fn); +bool isst_if_mbox_cmd_set_req(struct isst_if_mbox_cmd *mbox_cmd); +bool isst_if_mbox_cmd_invalid(struct isst_if_mbox_cmd *cmd); +int isst_store_cmd(int cmd, int sub_command, u32 cpu, int mbox_cmd, + u32 param, u64 data); +void isst_resume_common(void); +#endif diff --git a/drivers/platform/x86/intel_speed_select_if/isst_if_mbox_msr.c b/drivers/platform/x86/intel_speed_select_if/isst_if_mbox_msr.c new file mode 100644 index 000000000000..89b042aecef3 --- /dev/null +++ b/drivers/platform/x86/intel_speed_select_if/isst_if_mbox_msr.c @@ -0,0 +1,216 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Intel Speed Select Interface: Mbox via MSR Interface + * Copyright (c) 2019, Intel Corporation. + * All rights reserved. + * + * Author: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com> + */ + +#include <linux/module.h> +#include <linux/cpuhotplug.h> +#include <linux/pci.h> +#include <linux/sched/signal.h> +#include <linux/slab.h> +#include <linux/suspend.h> +#include <linux/topology.h> +#include <linux/uaccess.h> +#include <uapi/linux/isst_if.h> +#include <asm/cpu_device_id.h> +#include <asm/intel-family.h> + +#include "isst_if_common.h" + +#define MSR_OS_MAILBOX_INTERFACE 0xB0 +#define MSR_OS_MAILBOX_DATA 0xB1 +#define MSR_OS_MAILBOX_BUSY_BIT 31 + +/* + * Based on experiments count is never more than 1, as the MSR overhead + * is enough to finish the command. So here this is the worst case number. + */ +#define OS_MAILBOX_RETRY_COUNT 3 + +static int isst_if_send_mbox_cmd(u8 command, u8 sub_command, u32 parameter, + u32 command_data, u32 *response_data) +{ + u32 retries; + u64 data; + int ret; + + /* Poll for rb bit == 0 */ + retries = OS_MAILBOX_RETRY_COUNT; + do { + rdmsrl(MSR_OS_MAILBOX_INTERFACE, data); + if (data & BIT_ULL(MSR_OS_MAILBOX_BUSY_BIT)) { + ret = -EBUSY; + continue; + } + ret = 0; + break; + } while (--retries); + + if (ret) + return ret; + + /* Write DATA register */ + wrmsrl(MSR_OS_MAILBOX_DATA, command_data); + + /* Write command register */ + data = BIT_ULL(MSR_OS_MAILBOX_BUSY_BIT) | + (parameter & GENMASK_ULL(13, 0)) << 16 | + (sub_command << 8) | + command; + wrmsrl(MSR_OS_MAILBOX_INTERFACE, data); + + /* Poll for rb bit == 0 */ + retries = OS_MAILBOX_RETRY_COUNT; + do { + rdmsrl(MSR_OS_MAILBOX_INTERFACE, data); + if (data & BIT_ULL(MSR_OS_MAILBOX_BUSY_BIT)) { + ret = -EBUSY; + continue; + } + + if (data & 0xff) + return -ENXIO; + + if (response_data) { + rdmsrl(MSR_OS_MAILBOX_DATA, data); + *response_data = data; + } + ret = 0; + break; + } while (--retries); + + return ret; +} + +struct msrl_action { + int err; + struct isst_if_mbox_cmd *mbox_cmd; +}; + +/* revisit, smp_call_function_single should be enough for atomic mailbox! */ +static void msrl_update_func(void *info) +{ + struct msrl_action *act = info; + + act->err = isst_if_send_mbox_cmd(act->mbox_cmd->command, + act->mbox_cmd->sub_command, + act->mbox_cmd->parameter, + act->mbox_cmd->req_data, + &act->mbox_cmd->resp_data); +} + +static long isst_if_mbox_proc_cmd(u8 *cmd_ptr, int *write_only, int resume) +{ + struct msrl_action action; + int ret; + + action.mbox_cmd = (struct isst_if_mbox_cmd *)cmd_ptr; + + if (isst_if_mbox_cmd_invalid(action.mbox_cmd)) + return -EINVAL; + + if (isst_if_mbox_cmd_set_req(action.mbox_cmd) && + !capable(CAP_SYS_ADMIN)) + return -EPERM; + + /* + * To complete mailbox command, we need to access two MSRs. + * So we don't want race to complete a mailbox transcation. + * Here smp_call ensures that msrl_update_func() has no race + * and also with wait flag, wait for completion. + * smp_call_function_single is using get_cpu() and put_cpu(). + */ + ret = smp_call_function_single(action.mbox_cmd->logical_cpu, + msrl_update_func, &action, 1); + if (ret) + return ret; + + if (!action.err && !resume && isst_if_mbox_cmd_set_req(action.mbox_cmd)) + action.err = isst_store_cmd(action.mbox_cmd->command, + action.mbox_cmd->sub_command, + action.mbox_cmd->logical_cpu, 1, + action.mbox_cmd->parameter, + action.mbox_cmd->req_data); + *write_only = 0; + + return action.err; +} + + +static int isst_pm_notify(struct notifier_block *nb, + unsigned long mode, void *_unused) +{ + switch (mode) { + case PM_POST_HIBERNATION: + case PM_POST_RESTORE: + case PM_POST_SUSPEND: + isst_resume_common(); + break; + default: + break; + } + return 0; +} + +static struct notifier_block isst_pm_nb = { + .notifier_call = isst_pm_notify, +}; + +#define ICPU(model) { X86_VENDOR_INTEL, 6, model, X86_FEATURE_ANY, } + +static const struct x86_cpu_id isst_if_cpu_ids[] = { + ICPU(INTEL_FAM6_SKYLAKE_X), + {} +}; +MODULE_DEVICE_TABLE(x86cpu, isst_if_cpu_ids); + +static int __init isst_if_mbox_init(void) +{ + struct isst_if_cmd_cb cb; + const struct x86_cpu_id *id; + u64 data; + int ret; + + id = x86_match_cpu(isst_if_cpu_ids); + if (!id) + return -ENODEV; + + /* Check presence of mailbox MSRs */ + ret = rdmsrl_safe(MSR_OS_MAILBOX_INTERFACE, &data); + if (ret) + return ret; + + ret = rdmsrl_safe(MSR_OS_MAILBOX_DATA, &data); + if (ret) + return ret; + + memset(&cb, 0, sizeof(cb)); + cb.cmd_size = sizeof(struct isst_if_mbox_cmd); + cb.offset = offsetof(struct isst_if_mbox_cmds, mbox_cmd); + cb.cmd_callback = isst_if_mbox_proc_cmd; + cb.owner = THIS_MODULE; + ret = isst_if_cdev_register(ISST_IF_DEV_MBOX, &cb); + if (ret) + return ret; + + ret = register_pm_notifier(&isst_pm_nb); + if (ret) + isst_if_cdev_unregister(ISST_IF_DEV_MBOX); + + return ret; +} +module_init(isst_if_mbox_init) + +static void __exit isst_if_mbox_exit(void) +{ + unregister_pm_notifier(&isst_pm_nb); + isst_if_cdev_unregister(ISST_IF_DEV_MBOX); +} +module_exit(isst_if_mbox_exit) + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Intel speed select interface mailbox driver"); diff --git a/drivers/platform/x86/intel_speed_select_if/isst_if_mbox_pci.c b/drivers/platform/x86/intel_speed_select_if/isst_if_mbox_pci.c new file mode 100644 index 000000000000..de4169d0796b --- /dev/null +++ b/drivers/platform/x86/intel_speed_select_if/isst_if_mbox_pci.c @@ -0,0 +1,214 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Intel Speed Select Interface: Mbox via PCI Interface + * Copyright (c) 2019, Intel Corporation. + * All rights reserved. + * + * Author: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com> + */ + +#include <linux/cpufeature.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/sched/signal.h> +#include <linux/uaccess.h> +#include <uapi/linux/isst_if.h> + +#include "isst_if_common.h" + +#define PUNIT_MAILBOX_DATA 0xA0 +#define PUNIT_MAILBOX_INTERFACE 0xA4 +#define PUNIT_MAILBOX_BUSY_BIT 31 + +/* + * Commands has variable amount of processing time. Most of the commands will + * be done in 0-3 tries, but some takes up to 50. + * The real processing time was observed as 25us for the most of the commands + * at 2GHz. It is possible to optimize this count taking samples on customer + * systems. + */ +#define OS_MAILBOX_RETRY_COUNT 50 + +struct isst_if_device { + struct mutex mutex; +}; + +static int isst_if_mbox_cmd(struct pci_dev *pdev, + struct isst_if_mbox_cmd *mbox_cmd) +{ + u32 retries, data; + int ret; + + /* Poll for rb bit == 0 */ + retries = OS_MAILBOX_RETRY_COUNT; + do { + ret = pci_read_config_dword(pdev, PUNIT_MAILBOX_INTERFACE, + &data); + if (ret) + return ret; + + if (data & BIT_ULL(PUNIT_MAILBOX_BUSY_BIT)) { + ret = -EBUSY; + continue; + } + ret = 0; + break; + } while (--retries); + + if (ret) + return ret; + + /* Write DATA register */ + ret = pci_write_config_dword(pdev, PUNIT_MAILBOX_DATA, + mbox_cmd->req_data); + if (ret) + return ret; + + /* Write command register */ + data = BIT_ULL(PUNIT_MAILBOX_BUSY_BIT) | + (mbox_cmd->parameter & GENMASK_ULL(13, 0)) << 16 | + (mbox_cmd->sub_command << 8) | + mbox_cmd->command; + + ret = pci_write_config_dword(pdev, PUNIT_MAILBOX_INTERFACE, data); + if (ret) + return ret; + + /* Poll for rb bit == 0 */ + retries = OS_MAILBOX_RETRY_COUNT; + do { + ret = pci_read_config_dword(pdev, PUNIT_MAILBOX_INTERFACE, + &data); + if (ret) + return ret; + + if (data & BIT_ULL(PUNIT_MAILBOX_BUSY_BIT)) { + ret = -EBUSY; + continue; + } + + if (data & 0xff) + return -ENXIO; + + ret = pci_read_config_dword(pdev, PUNIT_MAILBOX_DATA, &data); + if (ret) + return ret; + + mbox_cmd->resp_data = data; + ret = 0; + break; + } while (--retries); + + return ret; +} + +static long isst_if_mbox_proc_cmd(u8 *cmd_ptr, int *write_only, int resume) +{ + struct isst_if_mbox_cmd *mbox_cmd; + struct isst_if_device *punit_dev; + struct pci_dev *pdev; + int ret; + + mbox_cmd = (struct isst_if_mbox_cmd *)cmd_ptr; + + if (isst_if_mbox_cmd_invalid(mbox_cmd)) + return -EINVAL; + + if (isst_if_mbox_cmd_set_req(mbox_cmd) && !capable(CAP_SYS_ADMIN)) + return -EPERM; + + pdev = isst_if_get_pci_dev(mbox_cmd->logical_cpu, 1, 30, 1); + if (!pdev) + return -EINVAL; + + punit_dev = pci_get_drvdata(pdev); + if (!punit_dev) + return -EINVAL; + + /* + * Basically we are allowing one complete mailbox transaction on + * a mapped PCI device at a time. + */ + mutex_lock(&punit_dev->mutex); + ret = isst_if_mbox_cmd(pdev, mbox_cmd); + if (!ret && !resume && isst_if_mbox_cmd_set_req(mbox_cmd)) + ret = isst_store_cmd(mbox_cmd->command, + mbox_cmd->sub_command, + mbox_cmd->logical_cpu, 1, + mbox_cmd->parameter, + mbox_cmd->req_data); + mutex_unlock(&punit_dev->mutex); + if (ret) + return ret; + + *write_only = 0; + + return 0; +} + +static const struct pci_device_id isst_if_mbox_ids[] = { + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, INTEL_CFG_MBOX_DEVID_0)}, + { 0 }, +}; +MODULE_DEVICE_TABLE(pci, isst_if_mbox_ids); + +static int isst_if_mbox_probe(struct pci_dev *pdev, + const struct pci_device_id *ent) +{ + struct isst_if_device *punit_dev; + struct isst_if_cmd_cb cb; + int ret; + + punit_dev = devm_kzalloc(&pdev->dev, sizeof(*punit_dev), GFP_KERNEL); + if (!punit_dev) + return -ENOMEM; + + ret = pcim_enable_device(pdev); + if (ret) + return ret; + + mutex_init(&punit_dev->mutex); + pci_set_drvdata(pdev, punit_dev); + + memset(&cb, 0, sizeof(cb)); + cb.cmd_size = sizeof(struct isst_if_mbox_cmd); + cb.offset = offsetof(struct isst_if_mbox_cmds, mbox_cmd); + cb.cmd_callback = isst_if_mbox_proc_cmd; + cb.owner = THIS_MODULE; + ret = isst_if_cdev_register(ISST_IF_DEV_MBOX, &cb); + + if (ret) + mutex_destroy(&punit_dev->mutex); + + return ret; +} + +static void isst_if_mbox_remove(struct pci_dev *pdev) +{ + struct isst_if_device *punit_dev; + + punit_dev = pci_get_drvdata(pdev); + isst_if_cdev_unregister(ISST_IF_DEV_MBOX); + mutex_destroy(&punit_dev->mutex); +} + +static int __maybe_unused isst_if_resume(struct device *device) +{ + isst_resume_common(); + return 0; +} + +static SIMPLE_DEV_PM_OPS(isst_if_pm_ops, NULL, isst_if_resume); + +static struct pci_driver isst_if_pci_driver = { + .name = "isst_if_mbox_pci", + .id_table = isst_if_mbox_ids, + .probe = isst_if_mbox_probe, + .remove = isst_if_mbox_remove, + .driver.pm = &isst_if_pm_ops, +}; + +module_pci_driver(isst_if_pci_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Intel speed select interface pci mailbox driver"); diff --git a/drivers/platform/x86/intel_speed_select_if/isst_if_mmio.c b/drivers/platform/x86/intel_speed_select_if/isst_if_mmio.c new file mode 100644 index 000000000000..ad8c7c0df4d9 --- /dev/null +++ b/drivers/platform/x86/intel_speed_select_if/isst_if_mmio.c @@ -0,0 +1,176 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Intel Speed Select Interface: MMIO Interface + * Copyright (c) 2019, Intel Corporation. + * All rights reserved. + * + * Author: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com> + */ + +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/sched/signal.h> +#include <linux/uaccess.h> +#include <uapi/linux/isst_if.h> + +#include "isst_if_common.h" + +struct isst_mmio_range { + int beg; + int end; +}; + +struct isst_mmio_range mmio_range[] = { + {0x04, 0x14}, + {0x20, 0xD0}, +}; + +struct isst_if_device { + void __iomem *punit_mmio; + u32 range_0[5]; + u32 range_1[45]; + struct mutex mutex; +}; + +static long isst_if_mmio_rd_wr(u8 *cmd_ptr, int *write_only, int resume) +{ + struct isst_if_device *punit_dev; + struct isst_if_io_reg *io_reg; + struct pci_dev *pdev; + + io_reg = (struct isst_if_io_reg *)cmd_ptr; + if (io_reg->reg < 0x04 || io_reg->reg > 0xD0) + return -EINVAL; + + if (io_reg->read_write && !capable(CAP_SYS_ADMIN)) + return -EPERM; + + pdev = isst_if_get_pci_dev(io_reg->logical_cpu, 0, 0, 1); + if (!pdev) + return -EINVAL; + + punit_dev = pci_get_drvdata(pdev); + if (!punit_dev) + return -EINVAL; + + /* + * Ensure that operation is complete on a PCI device to avoid read + * write race by using per PCI device mutex. + */ + mutex_lock(&punit_dev->mutex); + if (io_reg->read_write) { + writel(io_reg->value, punit_dev->punit_mmio+io_reg->reg); + *write_only = 1; + } else { + io_reg->value = readl(punit_dev->punit_mmio+io_reg->reg); + *write_only = 0; + } + mutex_unlock(&punit_dev->mutex); + + return 0; +} + +static const struct pci_device_id isst_if_ids[] = { + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, INTEL_RAPL_PRIO_DEVID_0)}, + { 0 }, +}; +MODULE_DEVICE_TABLE(pci, isst_if_ids); + +static int isst_if_probe(struct pci_dev *pdev, const struct pci_device_id *ent) +{ + struct isst_if_device *punit_dev; + struct isst_if_cmd_cb cb; + u32 mmio_base, pcu_base; + u64 base_addr; + int ret; + + punit_dev = devm_kzalloc(&pdev->dev, sizeof(*punit_dev), GFP_KERNEL); + if (!punit_dev) + return -ENOMEM; + + ret = pcim_enable_device(pdev); + if (ret) + return ret; + + ret = pci_read_config_dword(pdev, 0xD0, &mmio_base); + if (ret) + return ret; + + ret = pci_read_config_dword(pdev, 0xFC, &pcu_base); + if (ret) + return ret; + + pcu_base &= GENMASK(10, 0); + base_addr = (u64)mmio_base << 23 | (u64) pcu_base << 12; + punit_dev->punit_mmio = devm_ioremap(&pdev->dev, base_addr, 256); + if (!punit_dev->punit_mmio) + return -ENOMEM; + + mutex_init(&punit_dev->mutex); + pci_set_drvdata(pdev, punit_dev); + + memset(&cb, 0, sizeof(cb)); + cb.cmd_size = sizeof(struct isst_if_io_reg); + cb.offset = offsetof(struct isst_if_io_regs, io_reg); + cb.cmd_callback = isst_if_mmio_rd_wr; + cb.owner = THIS_MODULE; + ret = isst_if_cdev_register(ISST_IF_DEV_MMIO, &cb); + if (ret) + mutex_destroy(&punit_dev->mutex); + + return ret; +} + +static void isst_if_remove(struct pci_dev *pdev) +{ + struct isst_if_device *punit_dev; + + punit_dev = pci_get_drvdata(pdev); + isst_if_cdev_unregister(ISST_IF_DEV_MBOX); + mutex_destroy(&punit_dev->mutex); +} + +static int __maybe_unused isst_if_suspend(struct device *device) +{ + struct isst_if_device *punit_dev = dev_get_drvdata(device); + int i; + + for (i = 0; i < ARRAY_SIZE(punit_dev->range_0); ++i) + punit_dev->range_0[i] = readl(punit_dev->punit_mmio + + mmio_range[0].beg + 4 * i); + for (i = 0; i < ARRAY_SIZE(punit_dev->range_1); ++i) + punit_dev->range_1[i] = readl(punit_dev->punit_mmio + + mmio_range[1].beg + 4 * i); + + return 0; +} + +static int __maybe_unused isst_if_resume(struct device *device) +{ + struct isst_if_device *punit_dev = dev_get_drvdata(device); + int i; + + for (i = 0; i < ARRAY_SIZE(punit_dev->range_0); ++i) + writel(punit_dev->range_0[i], punit_dev->punit_mmio + + mmio_range[0].beg + 4 * i); + for (i = 0; i < ARRAY_SIZE(punit_dev->range_1); ++i) + writel(punit_dev->range_1[i], punit_dev->punit_mmio + + mmio_range[1].beg + 4 * i); + + return 0; +} + +static SIMPLE_DEV_PM_OPS(isst_if_pm_ops, isst_if_suspend, isst_if_resume); + +static struct pci_driver isst_if_pci_driver = { + .name = "isst_if_pci", + .id_table = isst_if_ids, + .probe = isst_if_probe, + .remove = isst_if_remove, + .driver.pm = &isst_if_pm_ops, +}; + +module_pci_driver(isst_if_pci_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Intel speed select interface mmio driver"); diff --git a/drivers/platform/x86/intel_telemetry_debugfs.c b/drivers/platform/x86/intel_telemetry_debugfs.c index 98ba9185a27b..e84d3e983e0c 100644 --- a/drivers/platform/x86/intel_telemetry_debugfs.c +++ b/drivers/platform/x86/intel_telemetry_debugfs.c @@ -900,7 +900,7 @@ static int __init telemetry_debugfs_init(void) { const struct x86_cpu_id *id; int err; - struct dentry *f; + struct dentry *dir; /* Only APL supported for now */ id = x86_match_cpu(telemetry_debugfs_cpu_ids); @@ -923,68 +923,22 @@ static int __init telemetry_debugfs_init(void) register_pm_notifier(&pm_notifier); - err = -ENOMEM; - debugfs_conf->telemetry_dbg_dir = debugfs_create_dir("telemetry", NULL); - if (!debugfs_conf->telemetry_dbg_dir) - goto out_pm; - - f = debugfs_create_file("pss_info", S_IFREG | S_IRUGO, - debugfs_conf->telemetry_dbg_dir, NULL, - &telem_pss_states_fops); - if (!f) { - pr_err("pss_sample_info debugfs register failed\n"); - goto out; - } - - f = debugfs_create_file("ioss_info", S_IFREG | S_IRUGO, - debugfs_conf->telemetry_dbg_dir, NULL, - &telem_ioss_states_fops); - if (!f) { - pr_err("ioss_sample_info debugfs register failed\n"); - goto out; - } - - f = debugfs_create_file("soc_states", S_IFREG | S_IRUGO, - debugfs_conf->telemetry_dbg_dir, - NULL, &telem_soc_states_fops); - if (!f) { - pr_err("ioss_sample_info debugfs register failed\n"); - goto out; - } - - f = debugfs_create_file("s0ix_residency_usec", S_IFREG | S_IRUGO, - debugfs_conf->telemetry_dbg_dir, - NULL, &telem_s0ix_fops); - if (!f) { - pr_err("s0ix_residency_usec debugfs register failed\n"); - goto out; - } - - f = debugfs_create_file("pss_trace_verbosity", S_IFREG | S_IRUGO, - debugfs_conf->telemetry_dbg_dir, NULL, - &telem_pss_trc_verb_ops); - if (!f) { - pr_err("pss_trace_verbosity debugfs register failed\n"); - goto out; - } - - f = debugfs_create_file("ioss_trace_verbosity", S_IFREG | S_IRUGO, - debugfs_conf->telemetry_dbg_dir, NULL, - &telem_ioss_trc_verb_ops); - if (!f) { - pr_err("ioss_trace_verbosity debugfs register failed\n"); - goto out; - } - + dir = debugfs_create_dir("telemetry", NULL); + debugfs_conf->telemetry_dbg_dir = dir; + + debugfs_create_file("pss_info", S_IFREG | S_IRUGO, dir, NULL, + &telem_pss_states_fops); + debugfs_create_file("ioss_info", S_IFREG | S_IRUGO, dir, NULL, + &telem_ioss_states_fops); + debugfs_create_file("soc_states", S_IFREG | S_IRUGO, dir, NULL, + &telem_soc_states_fops); + debugfs_create_file("s0ix_residency_usec", S_IFREG | S_IRUGO, dir, NULL, + &telem_s0ix_fops); + debugfs_create_file("pss_trace_verbosity", S_IFREG | S_IRUGO, dir, NULL, + &telem_pss_trc_verb_ops); + debugfs_create_file("ioss_trace_verbosity", S_IFREG | S_IRUGO, dir, + NULL, &telem_ioss_trc_verb_ops); return 0; - -out: - debugfs_remove_recursive(debugfs_conf->telemetry_dbg_dir); - debugfs_conf->telemetry_dbg_dir = NULL; -out_pm: - unregister_pm_notifier(&pm_notifier); - - return err; } static void __exit telemetry_debugfs_exit(void) diff --git a/drivers/platform/x86/mlx-platform.c b/drivers/platform/x86/mlx-platform.c index 48fa7573e29b..8fe51e43f1bc 100644 --- a/drivers/platform/x86/mlx-platform.c +++ b/drivers/platform/x86/mlx-platform.c @@ -44,6 +44,8 @@ #define MLXPLAT_CPLD_LPC_REG_AGGR_MASK_OFFSET 0x3b #define MLXPLAT_CPLD_LPC_REG_AGGRLO_OFFSET 0x40 #define MLXPLAT_CPLD_LPC_REG_AGGRLO_MASK_OFFSET 0x41 +#define MLXPLAT_CPLD_LPC_REG_AGGRCO_OFFSET 0x42 +#define MLXPLAT_CPLD_LPC_REG_AGGRCO_MASK_OFFSET 0x43 #define MLXPLAT_CPLD_LPC_REG_ASIC_HEALTH_OFFSET 0x50 #define MLXPLAT_CPLD_LPC_REG_ASIC_EVENT_OFFSET 0x51 #define MLXPLAT_CPLD_LPC_REG_ASIC_MASK_OFFSET 0x52 @@ -56,6 +58,16 @@ #define MLXPLAT_CPLD_LPC_REG_FAN_OFFSET 0x88 #define MLXPLAT_CPLD_LPC_REG_FAN_EVENT_OFFSET 0x89 #define MLXPLAT_CPLD_LPC_REG_FAN_MASK_OFFSET 0x8a +#define MLXPLAT_CPLD_LPC_REG_WD_CLEAR_OFFSET 0xc7 +#define MLXPLAT_CPLD_LPC_REG_WD_CLEAR_WP_OFFSET 0xc8 +#define MLXPLAT_CPLD_LPC_REG_WD1_TMR_OFFSET 0xc9 +#define MLXPLAT_CPLD_LPC_REG_WD1_ACT_OFFSET 0xcb +#define MLXPLAT_CPLD_LPC_REG_WD2_TMR_OFFSET 0xcd +#define MLXPLAT_CPLD_LPC_REG_WD2_TLEFT_OFFSET 0xce +#define MLXPLAT_CPLD_LPC_REG_WD2_ACT_OFFSET 0xcf +#define MLXPLAT_CPLD_LPC_REG_WD3_TMR_OFFSET 0xd1 +#define MLXPLAT_CPLD_LPC_REG_WD3_TLEFT_OFFSET 0xd2 +#define MLXPLAT_CPLD_LPC_REG_WD3_ACT_OFFSET 0xd3 #define MLXPLAT_CPLD_LPC_REG_PWM1_OFFSET 0xe3 #define MLXPLAT_CPLD_LPC_REG_TACHO1_OFFSET 0xe4 #define MLXPLAT_CPLD_LPC_REG_TACHO2_OFFSET 0xe5 @@ -72,6 +84,7 @@ #define MLXPLAT_CPLD_LPC_REG_FAN_CAP1_OFFSET 0xf5 #define MLXPLAT_CPLD_LPC_REG_FAN_CAP2_OFFSET 0xf6 #define MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET 0xf7 +#define MLXPLAT_CPLD_LPC_REG_TACHO_SPEED_OFFSET 0xf8 #define MLXPLAT_CPLD_LPC_IO_RANGE 0x100 #define MLXPLAT_CPLD_LPC_I2C_CH1_OFF 0xdb #define MLXPLAT_CPLD_LPC_I2C_CH2_OFF 0xda @@ -94,7 +107,9 @@ MLXPLAT_CPLD_AGGR_FAN_MASK_DEF) #define MLXPLAT_CPLD_AGGR_ASIC_MASK_NG 0x01 #define MLXPLAT_CPLD_AGGR_MASK_NG_DEF 0x04 +#define MLXPLAT_CPLD_AGGR_MASK_COMEX BIT(0) #define MLXPLAT_CPLD_LOW_AGGR_MASK_LOW 0xc1 +#define MLXPLAT_CPLD_LOW_AGGR_MASK_I2C BIT(6) #define MLXPLAT_CPLD_PSU_MASK GENMASK(1, 0) #define MLXPLAT_CPLD_PWR_MASK GENMASK(1, 0) #define MLXPLAT_CPLD_FAN_MASK GENMASK(3, 0) @@ -128,6 +143,18 @@ #define MLXPLAT_CPLD_FAN3_DEFAULT_NR 13 #define MLXPLAT_CPLD_FAN4_DEFAULT_NR 14 +/* Masks and default values for watchdogs */ +#define MLXPLAT_CPLD_WD1_CLEAR_MASK GENMASK(7, 1) +#define MLXPLAT_CPLD_WD2_CLEAR_MASK (GENMASK(7, 0) & ~BIT(1)) + +#define MLXPLAT_CPLD_WD_TYPE1_TO_MASK GENMASK(7, 4) +#define MLXPLAT_CPLD_WD_TYPE2_TO_MASK 0 +#define MLXPLAT_CPLD_WD_RESET_ACT_MASK GENMASK(7, 1) +#define MLXPLAT_CPLD_WD_FAN_ACT_MASK (GENMASK(7, 0) & ~BIT(4)) +#define MLXPLAT_CPLD_WD_COUNT_ACT_MASK (GENMASK(7, 0) & ~BIT(7)) +#define MLXPLAT_CPLD_WD_DFLT_TIMEOUT 30 +#define MLXPLAT_CPLD_WD_MAX_DEVS 2 + /* mlxplat_priv - platform private data * @pdev_i2c - i2c controller platform device * @pdev_mux - array of mux platform devices @@ -135,6 +162,8 @@ * @pdev_led - led platform devices * @pdev_io_regs - register access platform devices * @pdev_fan - FAN platform devices + * @pdev_wd - array of watchdog platform devices + * @regmap: device register map */ struct mlxplat_priv { struct platform_device *pdev_i2c; @@ -143,6 +172,8 @@ struct mlxplat_priv { struct platform_device *pdev_led; struct platform_device *pdev_io_regs; struct platform_device *pdev_fan; + struct platform_device *pdev_wd[MLXPLAT_CPLD_WD_MAX_DEVS]; + void *regmap; }; /* Regions for LPC I2C controller and LPC base register space */ @@ -156,6 +187,14 @@ static const struct resource mlxplat_lpc_resources[] = { IORESOURCE_IO), }; +/* Platform next generation systems i2c data */ +static struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_i2c_ng_data = { + .cell = MLXPLAT_CPLD_LPC_REG_AGGR_OFFSET, + .mask = MLXPLAT_CPLD_AGGR_MASK_COMEX, + .cell_low = MLXPLAT_CPLD_LPC_REG_AGGRCO_OFFSET, + .mask_low = MLXPLAT_CPLD_LOW_AGGR_MASK_I2C, +}; + /* Platform default channels */ static const int mlxplat_default_channels[][MLXPLAT_CPLD_GRP_CHNL_NUM] = { { @@ -679,7 +718,7 @@ struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_default_ng_data = { .items = mlxplat_mlxcpld_default_ng_items, .counter = ARRAY_SIZE(mlxplat_mlxcpld_default_ng_items), .cell = MLXPLAT_CPLD_LPC_REG_AGGR_OFFSET, - .mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF, + .mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF | MLXPLAT_CPLD_AGGR_MASK_COMEX, .cell_low = MLXPLAT_CPLD_LPC_REG_AGGRLO_OFFSET, .mask_low = MLXPLAT_CPLD_LOW_AGGR_MASK_LOW, }; @@ -1088,6 +1127,12 @@ static struct mlxreg_core_data mlxplat_mlxcpld_msn21xx_regs_io_data[] = { .mode = 0444, }, { + .label = "reset_sff_wd", + .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(6), + .mode = 0444, + }, + { .label = "psu1_on", .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, .mask = GENMASK(7, 0) & ~BIT(0), @@ -1176,6 +1221,18 @@ static struct mlxreg_core_data mlxplat_mlxcpld_default_ng_regs_io_data[] = { .mode = 0444, }, { + .label = "reset_from_asic", + .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(5), + .mode = 0444, + }, + { + .label = "reset_swb_wd", + .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(6), + .mode = 0444, + }, + { .label = "reset_asic_thermal", .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, .mask = GENMASK(7, 0) & ~BIT(7), @@ -1188,6 +1245,12 @@ static struct mlxreg_core_data mlxplat_mlxcpld_default_ng_regs_io_data[] = { .mode = 0444, }, { + .label = "reset_comex_wd", + .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(6), + .mode = 0444, + }, + { .label = "reset_voltmon_upgrade_fail", .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET, .mask = GENMASK(7, 0) & ~BIT(0), @@ -1200,6 +1263,18 @@ static struct mlxreg_core_data mlxplat_mlxcpld_default_ng_regs_io_data[] = { .mode = 0444, }, { + .label = "reset_comex_thermal", + .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(3), + .mode = 0444, + }, + { + .label = "reset_reload_bios", + .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(5), + .mode = 0444, + }, + { .label = "psu1_on", .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, .mask = GENMASK(7, 0) & ~BIT(0), @@ -1339,6 +1414,10 @@ static struct mlxreg_core_data mlxplat_mlxcpld_default_fan_data[] = { .capability = MLXPLAT_CPLD_LPC_REG_FAN_CAP2_OFFSET, .bit = BIT(3), }, + { + .label = "conf", + .capability = MLXPLAT_CPLD_LPC_REG_TACHO_SPEED_OFFSET, + }, }; static struct mlxreg_core_platform_data mlxplat_default_fan_data = { @@ -1346,6 +1425,148 @@ static struct mlxreg_core_platform_data mlxplat_default_fan_data = { .counter = ARRAY_SIZE(mlxplat_mlxcpld_default_fan_data), }; +/* Watchdog type1: hardware implementation version1 + * (MSN2700, MSN2410, MSN2740, MSN2100 and MSN2140 systems). + */ +static struct mlxreg_core_data mlxplat_mlxcpld_wd_main_regs_type1[] = { + { + .label = "action", + .reg = MLXPLAT_CPLD_LPC_REG_WD1_ACT_OFFSET, + .mask = MLXPLAT_CPLD_WD_RESET_ACT_MASK, + .bit = 0, + }, + { + .label = "timeout", + .reg = MLXPLAT_CPLD_LPC_REG_WD1_TMR_OFFSET, + .mask = MLXPLAT_CPLD_WD_TYPE1_TO_MASK, + .health_cntr = MLXPLAT_CPLD_WD_DFLT_TIMEOUT, + }, + { + .label = "ping", + .reg = MLXPLAT_CPLD_LPC_REG_WD_CLEAR_OFFSET, + .mask = MLXPLAT_CPLD_WD1_CLEAR_MASK, + .bit = 0, + }, + { + .label = "reset", + .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(6), + .bit = 6, + }, +}; + +static struct mlxreg_core_data mlxplat_mlxcpld_wd_aux_regs_type1[] = { + { + .label = "action", + .reg = MLXPLAT_CPLD_LPC_REG_WD2_ACT_OFFSET, + .mask = MLXPLAT_CPLD_WD_FAN_ACT_MASK, + .bit = 4, + }, + { + .label = "timeout", + .reg = MLXPLAT_CPLD_LPC_REG_WD2_TMR_OFFSET, + .mask = MLXPLAT_CPLD_WD_TYPE1_TO_MASK, + .health_cntr = MLXPLAT_CPLD_WD_DFLT_TIMEOUT, + }, + { + .label = "ping", + .reg = MLXPLAT_CPLD_LPC_REG_WD_CLEAR_OFFSET, + .mask = MLXPLAT_CPLD_WD1_CLEAR_MASK, + .bit = 1, + }, +}; + +static struct mlxreg_core_platform_data mlxplat_mlxcpld_wd_set_type1[] = { + { + .data = mlxplat_mlxcpld_wd_main_regs_type1, + .counter = ARRAY_SIZE(mlxplat_mlxcpld_wd_main_regs_type1), + .version = MLX_WDT_TYPE1, + .identity = "mlx-wdt-main", + }, + { + .data = mlxplat_mlxcpld_wd_aux_regs_type1, + .counter = ARRAY_SIZE(mlxplat_mlxcpld_wd_aux_regs_type1), + .version = MLX_WDT_TYPE1, + .identity = "mlx-wdt-aux", + }, +}; + +/* Watchdog type2: hardware implementation version 2 + * (all systems except (MSN2700, MSN2410, MSN2740, MSN2100 and MSN2140). + */ +static struct mlxreg_core_data mlxplat_mlxcpld_wd_main_regs_type2[] = { + { + .label = "action", + .reg = MLXPLAT_CPLD_LPC_REG_WD2_ACT_OFFSET, + .mask = MLXPLAT_CPLD_WD_RESET_ACT_MASK, + .bit = 0, + }, + { + .label = "timeout", + .reg = MLXPLAT_CPLD_LPC_REG_WD2_TMR_OFFSET, + .mask = MLXPLAT_CPLD_WD_TYPE2_TO_MASK, + .health_cntr = MLXPLAT_CPLD_WD_DFLT_TIMEOUT, + }, + { + .label = "timeleft", + .reg = MLXPLAT_CPLD_LPC_REG_WD2_TLEFT_OFFSET, + .mask = MLXPLAT_CPLD_WD_TYPE2_TO_MASK, + }, + { + .label = "ping", + .reg = MLXPLAT_CPLD_LPC_REG_WD2_ACT_OFFSET, + .mask = MLXPLAT_CPLD_WD_RESET_ACT_MASK, + .bit = 0, + }, + { + .label = "reset", + .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(6), + .bit = 6, + }, +}; + +static struct mlxreg_core_data mlxplat_mlxcpld_wd_aux_regs_type2[] = { + { + .label = "action", + .reg = MLXPLAT_CPLD_LPC_REG_WD3_ACT_OFFSET, + .mask = MLXPLAT_CPLD_WD_FAN_ACT_MASK, + .bit = 4, + }, + { + .label = "timeout", + .reg = MLXPLAT_CPLD_LPC_REG_WD3_TMR_OFFSET, + .mask = MLXPLAT_CPLD_WD_TYPE2_TO_MASK, + .health_cntr = MLXPLAT_CPLD_WD_DFLT_TIMEOUT, + }, + { + .label = "timeleft", + .reg = MLXPLAT_CPLD_LPC_REG_WD3_TLEFT_OFFSET, + .mask = MLXPLAT_CPLD_WD_TYPE2_TO_MASK, + }, + { + .label = "ping", + .reg = MLXPLAT_CPLD_LPC_REG_WD3_ACT_OFFSET, + .mask = MLXPLAT_CPLD_WD_FAN_ACT_MASK, + .bit = 4, + }, +}; + +static struct mlxreg_core_platform_data mlxplat_mlxcpld_wd_set_type2[] = { + { + .data = mlxplat_mlxcpld_wd_main_regs_type2, + .counter = ARRAY_SIZE(mlxplat_mlxcpld_wd_main_regs_type2), + .version = MLX_WDT_TYPE2, + .identity = "mlx-wdt-main", + }, + { + .data = mlxplat_mlxcpld_wd_aux_regs_type2, + .counter = ARRAY_SIZE(mlxplat_mlxcpld_wd_aux_regs_type2), + .version = MLX_WDT_TYPE2, + .identity = "mlx-wdt-aux", + }, +}; + static bool mlxplat_mlxcpld_writeable_reg(struct device *dev, unsigned int reg) { switch (reg) { @@ -1360,6 +1581,7 @@ static bool mlxplat_mlxcpld_writeable_reg(struct device *dev, unsigned int reg) case MLXPLAT_CPLD_LPC_REG_WP2_OFFSET: case MLXPLAT_CPLD_LPC_REG_AGGR_MASK_OFFSET: case MLXPLAT_CPLD_LPC_REG_AGGRLO_MASK_OFFSET: + case MLXPLAT_CPLD_LPC_REG_AGGRCO_MASK_OFFSET: case MLXPLAT_CPLD_LPC_REG_ASIC_EVENT_OFFSET: case MLXPLAT_CPLD_LPC_REG_ASIC_MASK_OFFSET: case MLXPLAT_CPLD_LPC_REG_PSU_EVENT_OFFSET: @@ -1368,6 +1590,14 @@ static bool mlxplat_mlxcpld_writeable_reg(struct device *dev, unsigned int reg) case MLXPLAT_CPLD_LPC_REG_PWR_MASK_OFFSET: case MLXPLAT_CPLD_LPC_REG_FAN_EVENT_OFFSET: case MLXPLAT_CPLD_LPC_REG_FAN_MASK_OFFSET: + case MLXPLAT_CPLD_LPC_REG_WD_CLEAR_OFFSET: + case MLXPLAT_CPLD_LPC_REG_WD_CLEAR_WP_OFFSET: + case MLXPLAT_CPLD_LPC_REG_WD1_TMR_OFFSET: + case MLXPLAT_CPLD_LPC_REG_WD1_ACT_OFFSET: + case MLXPLAT_CPLD_LPC_REG_WD2_TMR_OFFSET: + case MLXPLAT_CPLD_LPC_REG_WD2_ACT_OFFSET: + case MLXPLAT_CPLD_LPC_REG_WD3_TMR_OFFSET: + case MLXPLAT_CPLD_LPC_REG_WD3_ACT_OFFSET: case MLXPLAT_CPLD_LPC_REG_PWM1_OFFSET: case MLXPLAT_CPLD_LPC_REG_PWM_CONTROL_OFFSET: return true; @@ -1399,6 +1629,8 @@ static bool mlxplat_mlxcpld_readable_reg(struct device *dev, unsigned int reg) case MLXPLAT_CPLD_LPC_REG_AGGR_MASK_OFFSET: case MLXPLAT_CPLD_LPC_REG_AGGRLO_OFFSET: case MLXPLAT_CPLD_LPC_REG_AGGRLO_MASK_OFFSET: + case MLXPLAT_CPLD_LPC_REG_AGGRCO_OFFSET: + case MLXPLAT_CPLD_LPC_REG_AGGRCO_MASK_OFFSET: case MLXPLAT_CPLD_LPC_REG_ASIC_HEALTH_OFFSET: case MLXPLAT_CPLD_LPC_REG_ASIC_EVENT_OFFSET: case MLXPLAT_CPLD_LPC_REG_ASIC_MASK_OFFSET: @@ -1411,6 +1643,16 @@ static bool mlxplat_mlxcpld_readable_reg(struct device *dev, unsigned int reg) case MLXPLAT_CPLD_LPC_REG_FAN_OFFSET: case MLXPLAT_CPLD_LPC_REG_FAN_EVENT_OFFSET: case MLXPLAT_CPLD_LPC_REG_FAN_MASK_OFFSET: + case MLXPLAT_CPLD_LPC_REG_WD_CLEAR_OFFSET: + case MLXPLAT_CPLD_LPC_REG_WD_CLEAR_WP_OFFSET: + case MLXPLAT_CPLD_LPC_REG_WD1_TMR_OFFSET: + case MLXPLAT_CPLD_LPC_REG_WD1_ACT_OFFSET: + case MLXPLAT_CPLD_LPC_REG_WD2_TMR_OFFSET: + case MLXPLAT_CPLD_LPC_REG_WD2_TLEFT_OFFSET: + case MLXPLAT_CPLD_LPC_REG_WD2_ACT_OFFSET: + case MLXPLAT_CPLD_LPC_REG_WD3_TMR_OFFSET: + case MLXPLAT_CPLD_LPC_REG_WD3_TLEFT_OFFSET: + case MLXPLAT_CPLD_LPC_REG_WD3_ACT_OFFSET: case MLXPLAT_CPLD_LPC_REG_PWM1_OFFSET: case MLXPLAT_CPLD_LPC_REG_TACHO1_OFFSET: case MLXPLAT_CPLD_LPC_REG_TACHO2_OFFSET: @@ -1428,6 +1670,7 @@ static bool mlxplat_mlxcpld_readable_reg(struct device *dev, unsigned int reg) case MLXPLAT_CPLD_LPC_REG_FAN_CAP1_OFFSET: case MLXPLAT_CPLD_LPC_REG_FAN_CAP2_OFFSET: case MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET: + case MLXPLAT_CPLD_LPC_REG_TACHO_SPEED_OFFSET: return true; } return false; @@ -1455,6 +1698,8 @@ static bool mlxplat_mlxcpld_volatile_reg(struct device *dev, unsigned int reg) case MLXPLAT_CPLD_LPC_REG_AGGR_MASK_OFFSET: case MLXPLAT_CPLD_LPC_REG_AGGRLO_OFFSET: case MLXPLAT_CPLD_LPC_REG_AGGRLO_MASK_OFFSET: + case MLXPLAT_CPLD_LPC_REG_AGGRCO_OFFSET: + case MLXPLAT_CPLD_LPC_REG_AGGRCO_MASK_OFFSET: case MLXPLAT_CPLD_LPC_REG_ASIC_HEALTH_OFFSET: case MLXPLAT_CPLD_LPC_REG_ASIC_EVENT_OFFSET: case MLXPLAT_CPLD_LPC_REG_ASIC_MASK_OFFSET: @@ -1467,6 +1712,10 @@ static bool mlxplat_mlxcpld_volatile_reg(struct device *dev, unsigned int reg) case MLXPLAT_CPLD_LPC_REG_FAN_OFFSET: case MLXPLAT_CPLD_LPC_REG_FAN_EVENT_OFFSET: case MLXPLAT_CPLD_LPC_REG_FAN_MASK_OFFSET: + case MLXPLAT_CPLD_LPC_REG_WD2_TMR_OFFSET: + case MLXPLAT_CPLD_LPC_REG_WD2_TLEFT_OFFSET: + case MLXPLAT_CPLD_LPC_REG_WD3_TMR_OFFSET: + case MLXPLAT_CPLD_LPC_REG_WD3_TLEFT_OFFSET: case MLXPLAT_CPLD_LPC_REG_PWM1_OFFSET: case MLXPLAT_CPLD_LPC_REG_TACHO1_OFFSET: case MLXPLAT_CPLD_LPC_REG_TACHO2_OFFSET: @@ -1484,6 +1733,7 @@ static bool mlxplat_mlxcpld_volatile_reg(struct device *dev, unsigned int reg) case MLXPLAT_CPLD_LPC_REG_FAN_CAP1_OFFSET: case MLXPLAT_CPLD_LPC_REG_FAN_CAP2_OFFSET: case MLXPLAT_CPLD_LPC_REG_FAN_DRW_CAP_OFFSET: + case MLXPLAT_CPLD_LPC_REG_TACHO_SPEED_OFFSET: return true; } return false; @@ -1493,6 +1743,12 @@ static const struct reg_default mlxplat_mlxcpld_regmap_default[] = { { MLXPLAT_CPLD_LPC_REG_WP1_OFFSET, 0x00 }, { MLXPLAT_CPLD_LPC_REG_WP2_OFFSET, 0x00 }, { MLXPLAT_CPLD_LPC_REG_PWM_CONTROL_OFFSET, 0x00 }, + { MLXPLAT_CPLD_LPC_REG_WD_CLEAR_WP_OFFSET, 0x00 }, +}; + +static const struct reg_default mlxplat_mlxcpld_regmap_ng[] = { + { MLXPLAT_CPLD_LPC_REG_PWM_CONTROL_OFFSET, 0x00 }, + { MLXPLAT_CPLD_LPC_REG_WD_CLEAR_WP_OFFSET, 0x00 }, }; struct mlxplat_mlxcpld_regmap_context { @@ -1533,15 +1789,33 @@ static const struct regmap_config mlxplat_mlxcpld_regmap_config = { .reg_write = mlxplat_mlxcpld_reg_write, }; +static const struct regmap_config mlxplat_mlxcpld_regmap_config_ng = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 255, + .cache_type = REGCACHE_FLAT, + .writeable_reg = mlxplat_mlxcpld_writeable_reg, + .readable_reg = mlxplat_mlxcpld_readable_reg, + .volatile_reg = mlxplat_mlxcpld_volatile_reg, + .reg_defaults = mlxplat_mlxcpld_regmap_ng, + .num_reg_defaults = ARRAY_SIZE(mlxplat_mlxcpld_regmap_ng), + .reg_read = mlxplat_mlxcpld_reg_read, + .reg_write = mlxplat_mlxcpld_reg_write, +}; + static struct resource mlxplat_mlxcpld_resources[] = { [0] = DEFINE_RES_IRQ_NAMED(17, "mlxreg-hotplug"), }; static struct platform_device *mlxplat_dev; +static struct mlxreg_core_hotplug_platform_data *mlxplat_i2c; static struct mlxreg_core_hotplug_platform_data *mlxplat_hotplug; static struct mlxreg_core_platform_data *mlxplat_led; static struct mlxreg_core_platform_data *mlxplat_regs_io; static struct mlxreg_core_platform_data *mlxplat_fan; +static struct mlxreg_core_platform_data + *mlxplat_wd_data[MLXPLAT_CPLD_WD_MAX_DEVS]; +static const struct regmap_config *mlxplat_regmap_config; static int __init mlxplat_dmi_default_matched(const struct dmi_system_id *dmi) { @@ -1557,6 +1831,7 @@ static int __init mlxplat_dmi_default_matched(const struct dmi_system_id *dmi) mlxplat_default_channels[i - 1][MLXPLAT_CPLD_GRP_CHNL_NUM - 1]; mlxplat_led = &mlxplat_default_led_data; mlxplat_regs_io = &mlxplat_default_regs_io_data; + mlxplat_wd_data[0] = &mlxplat_mlxcpld_wd_set_type1[0]; return 1; }; @@ -1575,6 +1850,7 @@ static int __init mlxplat_dmi_msn21xx_matched(const struct dmi_system_id *dmi) mlxplat_msn21xx_channels[MLXPLAT_CPLD_GRP_CHNL_NUM - 1]; mlxplat_led = &mlxplat_msn21xx_led_data; mlxplat_regs_io = &mlxplat_msn21xx_regs_io_data; + mlxplat_wd_data[0] = &mlxplat_mlxcpld_wd_set_type1[0]; return 1; }; @@ -1593,6 +1869,7 @@ static int __init mlxplat_dmi_msn274x_matched(const struct dmi_system_id *dmi) mlxplat_msn21xx_channels[MLXPLAT_CPLD_GRP_CHNL_NUM - 1]; mlxplat_led = &mlxplat_default_led_data; mlxplat_regs_io = &mlxplat_msn21xx_regs_io_data; + mlxplat_wd_data[0] = &mlxplat_mlxcpld_wd_set_type1[0]; return 1; }; @@ -1611,6 +1888,7 @@ static int __init mlxplat_dmi_msn201x_matched(const struct dmi_system_id *dmi) mlxplat_default_channels[i - 1][MLXPLAT_CPLD_GRP_CHNL_NUM - 1]; mlxplat_led = &mlxplat_msn21xx_led_data; mlxplat_regs_io = &mlxplat_msn21xx_regs_io_data; + mlxplat_wd_data[0] = &mlxplat_mlxcpld_wd_set_type1[0]; return 1; }; @@ -1630,12 +1908,52 @@ static int __init mlxplat_dmi_qmb7xx_matched(const struct dmi_system_id *dmi) mlxplat_led = &mlxplat_default_ng_led_data; mlxplat_regs_io = &mlxplat_default_ng_regs_io_data; mlxplat_fan = &mlxplat_default_fan_data; + for (i = 0; i < ARRAY_SIZE(mlxplat_mlxcpld_wd_set_type2); i++) + mlxplat_wd_data[i] = &mlxplat_mlxcpld_wd_set_type2[i]; + mlxplat_i2c = &mlxplat_mlxcpld_i2c_ng_data; + mlxplat_regmap_config = &mlxplat_mlxcpld_regmap_config_ng; return 1; }; static const struct dmi_system_id mlxplat_dmi_table[] __initconst = { { + .callback = mlxplat_dmi_default_matched, + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "VMOD0001"), + }, + }, + { + .callback = mlxplat_dmi_msn21xx_matched, + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "VMOD0002"), + }, + }, + { + .callback = mlxplat_dmi_msn274x_matched, + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "VMOD0003"), + }, + }, + { + .callback = mlxplat_dmi_msn201x_matched, + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "VMOD0004"), + }, + }, + { + .callback = mlxplat_dmi_qmb7xx_matched, + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "VMOD0005"), + }, + }, + { + .callback = mlxplat_dmi_qmb7xx_matched, + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "VMOD0007"), + }, + }, + { .callback = mlxplat_dmi_msn274x_matched, .matches = { DMI_MATCH(DMI_BOARD_VENDOR, "Mellanox Technologies"), @@ -1712,42 +2030,6 @@ static const struct dmi_system_id mlxplat_dmi_table[] __initconst = { DMI_MATCH(DMI_PRODUCT_NAME, "MSN38"), }, }, - { - .callback = mlxplat_dmi_default_matched, - .matches = { - DMI_MATCH(DMI_BOARD_NAME, "VMOD0001"), - }, - }, - { - .callback = mlxplat_dmi_msn21xx_matched, - .matches = { - DMI_MATCH(DMI_BOARD_NAME, "VMOD0002"), - }, - }, - { - .callback = mlxplat_dmi_msn274x_matched, - .matches = { - DMI_MATCH(DMI_BOARD_NAME, "VMOD0003"), - }, - }, - { - .callback = mlxplat_dmi_msn201x_matched, - .matches = { - DMI_MATCH(DMI_BOARD_NAME, "VMOD0004"), - }, - }, - { - .callback = mlxplat_dmi_qmb7xx_matched, - .matches = { - DMI_MATCH(DMI_BOARD_NAME, "VMOD0005"), - }, - }, - { - .callback = mlxplat_dmi_qmb7xx_matched, - .matches = { - DMI_MATCH(DMI_BOARD_NAME, "VMOD0007"), - }, - }, { } }; @@ -1814,13 +2096,36 @@ static int __init mlxplat_init(void) } platform_set_drvdata(mlxplat_dev, priv); + mlxplat_mlxcpld_regmap_ctx.base = devm_ioport_map(&mlxplat_dev->dev, + mlxplat_lpc_resources[1].start, 1); + if (!mlxplat_mlxcpld_regmap_ctx.base) { + err = -ENOMEM; + goto fail_alloc; + } + + if (!mlxplat_regmap_config) + mlxplat_regmap_config = &mlxplat_mlxcpld_regmap_config; + + priv->regmap = devm_regmap_init(&mlxplat_dev->dev, NULL, + &mlxplat_mlxcpld_regmap_ctx, + mlxplat_regmap_config); + if (IS_ERR(priv->regmap)) { + err = PTR_ERR(priv->regmap); + goto fail_alloc; + } + err = mlxplat_mlxcpld_verify_bus_topology(&nr); if (nr < 0) goto fail_alloc; nr = (nr == MLXPLAT_CPLD_MAX_PHYS_ADAPTER_NUM) ? -1 : nr; - priv->pdev_i2c = platform_device_register_simple("i2c_mlxcpld", nr, - NULL, 0); + if (mlxplat_i2c) + mlxplat_i2c->regmap = priv->regmap; + priv->pdev_i2c = platform_device_register_resndata( + &mlxplat_dev->dev, "i2c_mlxcpld", + nr, mlxplat_mlxcpld_resources, + ARRAY_SIZE(mlxplat_mlxcpld_resources), + mlxplat_i2c, sizeof(*mlxplat_i2c)); if (IS_ERR(priv->pdev_i2c)) { err = PTR_ERR(priv->pdev_i2c); goto fail_alloc; @@ -1828,7 +2133,7 @@ static int __init mlxplat_init(void) for (i = 0; i < ARRAY_SIZE(mlxplat_mux_data); i++) { priv->pdev_mux[i] = platform_device_register_resndata( - &mlxplat_dev->dev, + &priv->pdev_i2c->dev, "i2c-mux-reg", i, NULL, 0, &mlxplat_mux_data[i], sizeof(mlxplat_mux_data[i])); @@ -1838,21 +2143,8 @@ static int __init mlxplat_init(void) } } - mlxplat_mlxcpld_regmap_ctx.base = devm_ioport_map(&mlxplat_dev->dev, - mlxplat_lpc_resources[1].start, 1); - if (!mlxplat_mlxcpld_regmap_ctx.base) { - err = -ENOMEM; - goto fail_platform_mux_register; - } - - mlxplat_hotplug->regmap = devm_regmap_init(&mlxplat_dev->dev, NULL, - &mlxplat_mlxcpld_regmap_ctx, - &mlxplat_mlxcpld_regmap_config); - if (IS_ERR(mlxplat_hotplug->regmap)) { - err = PTR_ERR(mlxplat_hotplug->regmap); - goto fail_platform_mux_register; - } - + /* Add hotplug driver */ + mlxplat_hotplug->regmap = priv->regmap; priv->pdev_hotplug = platform_device_register_resndata( &mlxplat_dev->dev, "mlxreg-hotplug", PLATFORM_DEVID_NONE, @@ -1865,16 +2157,16 @@ static int __init mlxplat_init(void) } /* Set default registers. */ - for (j = 0; j < mlxplat_mlxcpld_regmap_config.num_reg_defaults; j++) { - err = regmap_write(mlxplat_hotplug->regmap, - mlxplat_mlxcpld_regmap_default[j].reg, - mlxplat_mlxcpld_regmap_default[j].def); + for (j = 0; j < mlxplat_regmap_config->num_reg_defaults; j++) { + err = regmap_write(priv->regmap, + mlxplat_regmap_config->reg_defaults[j].reg, + mlxplat_regmap_config->reg_defaults[j].def); if (err) goto fail_platform_mux_register; } /* Add LED driver. */ - mlxplat_led->regmap = mlxplat_hotplug->regmap; + mlxplat_led->regmap = priv->regmap; priv->pdev_led = platform_device_register_resndata( &mlxplat_dev->dev, "leds-mlxreg", PLATFORM_DEVID_NONE, NULL, 0, @@ -1886,7 +2178,7 @@ static int __init mlxplat_init(void) /* Add registers io access driver. */ if (mlxplat_regs_io) { - mlxplat_regs_io->regmap = mlxplat_hotplug->regmap; + mlxplat_regs_io->regmap = priv->regmap; priv->pdev_io_regs = platform_device_register_resndata( &mlxplat_dev->dev, "mlxreg-io", PLATFORM_DEVID_NONE, NULL, 0, @@ -1900,7 +2192,7 @@ static int __init mlxplat_init(void) /* Add FAN driver. */ if (mlxplat_fan) { - mlxplat_fan->regmap = mlxplat_hotplug->regmap; + mlxplat_fan->regmap = priv->regmap; priv->pdev_fan = platform_device_register_resndata( &mlxplat_dev->dev, "mlxreg-fan", PLATFORM_DEVID_NONE, NULL, 0, @@ -1912,15 +2204,33 @@ static int __init mlxplat_init(void) } } + /* Add WD drivers. */ + for (j = 0; j < MLXPLAT_CPLD_WD_MAX_DEVS; j++) { + if (mlxplat_wd_data[j]) { + mlxplat_wd_data[j]->regmap = priv->regmap; + priv->pdev_wd[j] = platform_device_register_resndata( + &mlxplat_dev->dev, "mlx-wdt", + j, NULL, 0, + mlxplat_wd_data[j], + sizeof(*mlxplat_wd_data[j])); + if (IS_ERR(priv->pdev_wd[j])) { + err = PTR_ERR(priv->pdev_wd[j]); + goto fail_platform_wd_register; + } + } + } + /* Sync registers with hardware. */ - regcache_mark_dirty(mlxplat_hotplug->regmap); - err = regcache_sync(mlxplat_hotplug->regmap); + regcache_mark_dirty(priv->regmap); + err = regcache_sync(priv->regmap); if (err) - goto fail_platform_fan_register; + goto fail_platform_wd_register; return 0; -fail_platform_fan_register: +fail_platform_wd_register: + while (--j >= 0) + platform_device_unregister(priv->pdev_wd[j]); if (mlxplat_fan) platform_device_unregister(priv->pdev_fan); fail_platform_io_regs_register: @@ -1946,6 +2256,8 @@ static void __exit mlxplat_exit(void) struct mlxplat_priv *priv = platform_get_drvdata(mlxplat_dev); int i; + for (i = MLXPLAT_CPLD_WD_MAX_DEVS - 1; i >= 0 ; i--) + platform_device_unregister(priv->pdev_wd[i]); if (priv->pdev_fan) platform_device_unregister(priv->pdev_fan); if (priv->pdev_io_regs) diff --git a/drivers/platform/x86/msi-laptop.c b/drivers/platform/x86/msi-laptop.c index d5bfcc602090..24ffc8e2d2d1 100644 --- a/drivers/platform/x86/msi-laptop.c +++ b/drivers/platform/x86/msi-laptop.c @@ -1,22 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /*-*-linux-c-*-*/ /* Copyright (C) 2006 Lennart Poettering <mzxreary (at) 0pointer (dot) de> - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - 02110-1301, USA. */ /* diff --git a/drivers/platform/x86/msi-wmi.c b/drivers/platform/x86/msi-wmi.c index 620138236c89..64ee7819c9d3 100644 --- a/drivers/platform/x86/msi-wmi.c +++ b/drivers/platform/x86/msi-wmi.c @@ -1,23 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * MSI WMI hotkeys * * Copyright (C) 2009 Novell <trenn@suse.de> * * Most stuff taken over from hp-wmi - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt diff --git a/drivers/platform/x86/mxm-wmi.c b/drivers/platform/x86/mxm-wmi.c index 35d8b9a939f9..9a19fbd2f734 100644 --- a/drivers/platform/x86/mxm-wmi.c +++ b/drivers/platform/x86/mxm-wmi.c @@ -1,21 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * MXM WMI driver * * Copyright(C) 2010 Red Hat. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include <linux/kernel.h> #include <linux/module.h> diff --git a/drivers/platform/x86/panasonic-laptop.c b/drivers/platform/x86/panasonic-laptop.c index 8361ad75389a..59e38a1d2830 100644 --- a/drivers/platform/x86/panasonic-laptop.c +++ b/drivers/platform/x86/panasonic-laptop.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Panasonic HotKey and LCD brightness control driver * (C) 2004 Hiroshi Miura <miura@da-cha.org> @@ -8,19 +9,6 @@ * * derived from toshiba_acpi.c, Copyright (C) 2002-2004 John Belmonte * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * publicshed by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * *--------------------------------------------------------------------------- * * ChangeLog: @@ -113,7 +101,6 @@ * * Jul.17, 2004 Hiroshi Miura <miura@da-cha.org> * - v0.1 start from toshiba_acpi driver written by John Belmonte - * */ #include <linux/kernel.h> diff --git a/drivers/platform/x86/pcengines-apuv2.c b/drivers/platform/x86/pcengines-apuv2.c index c1ca931e1fab..48b112b4f0b0 100644 --- a/drivers/platform/x86/pcengines-apuv2.c +++ b/drivers/platform/x86/pcengines-apuv2.c @@ -32,6 +32,8 @@ #define APU2_GPIO_REG_LED3 AMD_FCH_GPIO_REG_GPIO59_DEVSLP1 #define APU2_GPIO_REG_MODESW AMD_FCH_GPIO_REG_GPIO32_GE1 #define APU2_GPIO_REG_SIMSWAP AMD_FCH_GPIO_REG_GPIO33_GE2 +#define APU2_GPIO_REG_MPCIE2 AMD_FCH_GPIO_REG_GPIO59_DEVSLP0 +#define APU2_GPIO_REG_MPCIE3 AMD_FCH_GPIO_REG_GPIO51 /* order in which the gpio lines are defined in the register list */ #define APU2_GPIO_LINE_LED1 0 @@ -39,6 +41,8 @@ #define APU2_GPIO_LINE_LED3 2 #define APU2_GPIO_LINE_MODESW 3 #define APU2_GPIO_LINE_SIMSWAP 4 +#define APU2_GPIO_LINE_MPCIE2 5 +#define APU2_GPIO_LINE_MPCIE3 6 /* gpio device */ @@ -48,6 +52,8 @@ static int apu2_gpio_regs[] = { [APU2_GPIO_LINE_LED3] = APU2_GPIO_REG_LED3, [APU2_GPIO_LINE_MODESW] = APU2_GPIO_REG_MODESW, [APU2_GPIO_LINE_SIMSWAP] = APU2_GPIO_REG_SIMSWAP, + [APU2_GPIO_LINE_MPCIE2] = APU2_GPIO_REG_MPCIE2, + [APU2_GPIO_LINE_MPCIE3] = APU2_GPIO_REG_MPCIE3, }; static const char * const apu2_gpio_names[] = { @@ -56,6 +62,8 @@ static const char * const apu2_gpio_names[] = { [APU2_GPIO_LINE_LED3] = "front-led3", [APU2_GPIO_LINE_MODESW] = "front-button", [APU2_GPIO_LINE_SIMSWAP] = "simswap", + [APU2_GPIO_LINE_MPCIE2] = "mpcie2_reset", + [APU2_GPIO_LINE_MPCIE3] = "mpcie3_reset", }; static const struct amd_fch_gpio_pdata board_apu2 = { @@ -69,7 +77,8 @@ static const struct amd_fch_gpio_pdata board_apu2 = { static const struct gpio_led apu2_leds[] = { { .name = "apu:green:1" }, { .name = "apu:green:2" }, - { .name = "apu:green:3" } + { .name = "apu:green:3" }, + { .name = "apu:simswap" }, }; static const struct gpio_led_platform_data apu2_leds_pdata = { @@ -77,7 +86,7 @@ static const struct gpio_led_platform_data apu2_leds_pdata = { .leds = apu2_leds, }; -struct gpiod_lookup_table gpios_led_table = { +static struct gpiod_lookup_table gpios_led_table = { .dev_id = "leds-gpio", .table = { GPIO_LOOKUP_IDX(AMD_FCH_GPIO_DRIVER_NAME, APU2_GPIO_LINE_LED1, @@ -86,6 +95,8 @@ struct gpiod_lookup_table gpios_led_table = { NULL, 1, GPIO_ACTIVE_LOW), GPIO_LOOKUP_IDX(AMD_FCH_GPIO_DRIVER_NAME, APU2_GPIO_LINE_LED3, NULL, 2, GPIO_ACTIVE_LOW), + GPIO_LOOKUP_IDX(AMD_FCH_GPIO_DRIVER_NAME, APU2_GPIO_REG_SIMSWAP, + NULL, 3, GPIO_ACTIVE_LOW), } }; @@ -93,7 +104,7 @@ struct gpiod_lookup_table gpios_led_table = { static struct gpio_keys_button apu2_keys_buttons[] = { { - .code = KEY_SETUP, + .code = KEY_RESTART, .active_low = 1, .desc = "front button", .type = EV_KEY, @@ -110,7 +121,7 @@ static const struct gpio_keys_platform_data apu2_keys_pdata = { .name = "apu2-keys", }; -struct gpiod_lookup_table gpios_key_table = { +static struct gpiod_lookup_table gpios_key_table = { .dev_id = "gpio-keys-polled", .table = { GPIO_LOOKUP_IDX(AMD_FCH_GPIO_DRIVER_NAME, APU2_GPIO_LINE_MODESW, @@ -255,6 +266,4 @@ MODULE_DESCRIPTION("PC Engines APUv2/APUv3 board GPIO/LED/keys driver"); MODULE_LICENSE("GPL"); MODULE_DEVICE_TABLE(dmi, apu_gpio_dmi_table); MODULE_ALIAS("platform:pcengines-apuv2"); -MODULE_SOFTDEP("pre: platform:" AMD_FCH_GPIO_DRIVER_NAME); -MODULE_SOFTDEP("pre: platform:leds-gpio"); -MODULE_SOFTDEP("pre: platform:gpio_keys_polled"); +MODULE_SOFTDEP("pre: platform:" AMD_FCH_GPIO_DRIVER_NAME " platform:leds-gpio platform:gpio_keys_polled"); diff --git a/drivers/platform/x86/peaq-wmi.c b/drivers/platform/x86/peaq-wmi.c index 9b9e1f39bbfb..cf9c44c20a82 100644 --- a/drivers/platform/x86/peaq-wmi.c +++ b/drivers/platform/x86/peaq-wmi.c @@ -1,15 +1,12 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * PEAQ 2-in-1 WMI hotkey driver * Copyright (C) 2017 Hans de Goede <hdegoede@redhat.com> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. */ #include <linux/acpi.h> #include <linux/dmi.h> -#include <linux/input-polldev.h> +#include <linux/input.h> #include <linux/kernel.h> #include <linux/module.h> @@ -21,8 +18,7 @@ MODULE_ALIAS("wmi:"PEAQ_DOLBY_BUTTON_GUID); -static unsigned int peaq_ignore_events_counter; -static struct input_polled_dev *peaq_poll_dev; +static struct input_dev *peaq_poll_dev; /* * The Dolby button (yes really a Dolby button) causes an ACPI variable to get @@ -31,8 +27,10 @@ static struct input_polled_dev *peaq_poll_dev; * (if polling after the release) or twice (polling between press and release). * We ignore events for 0.5s after the first event to avoid reporting 2 presses. */ -static void peaq_wmi_poll(struct input_polled_dev *dev) +static void peaq_wmi_poll(struct input_dev *input_dev) { + static unsigned long last_event_time; + static bool had_events; union acpi_object obj; acpi_status status; u32 dummy = 0; @@ -47,22 +45,25 @@ static void peaq_wmi_poll(struct input_polled_dev *dev) return; if (obj.type != ACPI_TYPE_INTEGER) { - dev_err(&peaq_poll_dev->input->dev, + dev_err(&input_dev->dev, "Error WMBC did not return an integer\n"); return; } - if (peaq_ignore_events_counter && peaq_ignore_events_counter--) + if (!obj.integer.value) return; - if (obj.integer.value) { - input_event(peaq_poll_dev->input, EV_KEY, KEY_SOUND, 1); - input_sync(peaq_poll_dev->input); - input_event(peaq_poll_dev->input, EV_KEY, KEY_SOUND, 0); - input_sync(peaq_poll_dev->input); - peaq_ignore_events_counter = max(1u, - PEAQ_POLL_IGNORE_MS / peaq_poll_dev->poll_interval); - } + if (had_events && time_before(jiffies, last_event_time + + msecs_to_jiffies(PEAQ_POLL_IGNORE_MS))) + return; + + input_event(input_dev, EV_KEY, KEY_SOUND, 1); + input_sync(input_dev); + input_event(input_dev, EV_KEY, KEY_SOUND, 0); + input_sync(input_dev); + + last_event_time = jiffies; + had_events = true; } /* Some other devices (Shuttle XS35) use the same WMI GUID for other purposes */ @@ -78,6 +79,8 @@ static const struct dmi_system_id peaq_dmi_table[] __initconst = { static int __init peaq_wmi_init(void) { + int err; + /* WMI GUID is not unique, also check for a DMI match */ if (!dmi_check_system(peaq_dmi_table)) return -ENODEV; @@ -85,24 +88,36 @@ static int __init peaq_wmi_init(void) if (!wmi_has_guid(PEAQ_DOLBY_BUTTON_GUID)) return -ENODEV; - peaq_poll_dev = input_allocate_polled_device(); + peaq_poll_dev = input_allocate_device(); if (!peaq_poll_dev) return -ENOMEM; - peaq_poll_dev->poll = peaq_wmi_poll; - peaq_poll_dev->poll_interval = PEAQ_POLL_INTERVAL_MS; - peaq_poll_dev->poll_interval_max = PEAQ_POLL_MAX_MS; - peaq_poll_dev->input->name = "PEAQ WMI hotkeys"; - peaq_poll_dev->input->phys = "wmi/input0"; - peaq_poll_dev->input->id.bustype = BUS_HOST; - input_set_capability(peaq_poll_dev->input, EV_KEY, KEY_SOUND); + peaq_poll_dev->name = "PEAQ WMI hotkeys"; + peaq_poll_dev->phys = "wmi/input0"; + peaq_poll_dev->id.bustype = BUS_HOST; + input_set_capability(peaq_poll_dev, EV_KEY, KEY_SOUND); + + err = input_setup_polling(peaq_poll_dev, peaq_wmi_poll); + if (err) + goto err_out; + + input_set_poll_interval(peaq_poll_dev, PEAQ_POLL_INTERVAL_MS); + input_set_max_poll_interval(peaq_poll_dev, PEAQ_POLL_MAX_MS); + + err = input_register_device(peaq_poll_dev); + if (err) + goto err_out; + + return 0; - return input_register_polled_device(peaq_poll_dev); +err_out: + input_free_device(peaq_poll_dev); + return err; } static void __exit peaq_wmi_exit(void) { - input_unregister_polled_device(peaq_poll_dev); + input_unregister_device(peaq_poll_dev); } module_init(peaq_wmi_init); diff --git a/drivers/platform/x86/pmc_atom.c b/drivers/platform/x86/pmc_atom.c index 8f018b3f3cd4..07d1b911e72f 100644 --- a/drivers/platform/x86/pmc_atom.c +++ b/drivers/platform/x86/pmc_atom.c @@ -1,22 +1,14 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Intel Atom SOC Power Management Controller Driver * Copyright (c) 2014, Intel Corporation. - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include <linux/debugfs.h> #include <linux/device.h> +#include <linux/dmi.h> #include <linux/init.h> #include <linux/io.h> #include <linux/platform_data/x86/clk-pmc-atom.h> @@ -349,53 +341,103 @@ static int pmc_sleep_tmr_show(struct seq_file *s, void *unused) DEFINE_SHOW_ATTRIBUTE(pmc_sleep_tmr); -static void pmc_dbgfs_unregister(struct pmc_dev *pmc) +static void pmc_dbgfs_register(struct pmc_dev *pmc) { - debugfs_remove_recursive(pmc->dbgfs_dir); -} - -static int pmc_dbgfs_register(struct pmc_dev *pmc) -{ - struct dentry *dir, *f; + struct dentry *dir; dir = debugfs_create_dir("pmc_atom", NULL); - if (!dir) - return -ENOMEM; pmc->dbgfs_dir = dir; - f = debugfs_create_file("dev_state", S_IFREG | S_IRUGO, - dir, pmc, &pmc_dev_state_fops); - if (!f) - goto err; - - f = debugfs_create_file("pss_state", S_IFREG | S_IRUGO, - dir, pmc, &pmc_pss_state_fops); - if (!f) - goto err; - - f = debugfs_create_file("sleep_state", S_IFREG | S_IRUGO, - dir, pmc, &pmc_sleep_tmr_fops); - if (!f) - goto err; - - return 0; -err: - pmc_dbgfs_unregister(pmc); - return -ENODEV; + debugfs_create_file("dev_state", S_IFREG | S_IRUGO, dir, pmc, + &pmc_dev_state_fops); + debugfs_create_file("pss_state", S_IFREG | S_IRUGO, dir, pmc, + &pmc_pss_state_fops); + debugfs_create_file("sleep_state", S_IFREG | S_IRUGO, dir, pmc, + &pmc_sleep_tmr_fops); } #else -static int pmc_dbgfs_register(struct pmc_dev *pmc) +static void pmc_dbgfs_register(struct pmc_dev *pmc) { - return 0; } #endif /* CONFIG_DEBUG_FS */ +/* + * Some systems need one or more of their pmc_plt_clks to be + * marked as critical. + */ +static const struct dmi_system_id critclk_systems[] = { + { + /* pmc_plt_clk0 is used for an external HSIC USB HUB */ + .ident = "MPL CEC1x", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "MPL AG"), + DMI_MATCH(DMI_PRODUCT_NAME, "CEC10 Family"), + }, + }, + { + /* pmc_plt_clk0 - 3 are used for the 4 ethernet controllers */ + .ident = "Lex 3I380D", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Lex BayTrail"), + DMI_MATCH(DMI_PRODUCT_NAME, "3I380D"), + }, + }, + { + /* pmc_plt_clk* - are used for ethernet controllers */ + .ident = "Beckhoff CB3163", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Beckhoff Automation"), + DMI_MATCH(DMI_BOARD_NAME, "CB3163"), + }, + }, + { + /* pmc_plt_clk* - are used for ethernet controllers */ + .ident = "Beckhoff CB4063", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Beckhoff Automation"), + DMI_MATCH(DMI_BOARD_NAME, "CB4063"), + }, + }, + { + /* pmc_plt_clk* - are used for ethernet controllers */ + .ident = "Beckhoff CB6263", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Beckhoff Automation"), + DMI_MATCH(DMI_BOARD_NAME, "CB6263"), + }, + }, + { + /* pmc_plt_clk* - are used for ethernet controllers */ + .ident = "Beckhoff CB6363", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Beckhoff Automation"), + DMI_MATCH(DMI_BOARD_NAME, "CB6363"), + }, + }, + { + .ident = "SIMATIC IPC227E", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "SIEMENS AG"), + DMI_MATCH(DMI_PRODUCT_VERSION, "6ES7647-8B"), + }, + }, + { + .ident = "SIMATIC IPC277E", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "SIEMENS AG"), + DMI_MATCH(DMI_PRODUCT_VERSION, "6AV7882-0"), + }, + }, + { /*sentinel*/ } +}; + static int pmc_setup_clks(struct pci_dev *pdev, void __iomem *pmc_regmap, const struct pmc_data *pmc_data) { struct platform_device *clkdev; struct pmc_clk_data *clk_data; + const struct dmi_system_id *d = dmi_first_match(critclk_systems); clk_data = kzalloc(sizeof(*clk_data), GFP_KERNEL); if (!clk_data) @@ -403,6 +445,10 @@ static int pmc_setup_clks(struct pci_dev *pdev, void __iomem *pmc_regmap, clk_data->base = pmc_regmap; /* offset is added by client */ clk_data->clks = pmc_data->clks; + if (d) { + clk_data->critical = true; + pr_info("%s critclks quirk enabled\n", d->ident); + } clkdev = platform_device_register_data(&pdev->dev, "clk-pmc-atom", PLATFORM_DEVID_NONE, @@ -446,9 +492,7 @@ static int pmc_setup_dev(struct pci_dev *pdev, const struct pci_device_id *ent) /* PMC hardware registers setup */ pmc_hw_reg_setup(pmc); - ret = pmc_dbgfs_register(pmc); - if (ret) - dev_warn(&pdev->dev, "debugfs register failed\n"); + pmc_dbgfs_register(pmc); /* Register platform clocks - PMC_PLT_CLK [0..5] */ ret = pmc_setup_clks(pdev, pmc->regmap, data); diff --git a/drivers/platform/x86/samsung-laptop.c b/drivers/platform/x86/samsung-laptop.c index 7b160ee98115..9b6a93ff41ff 100644 --- a/drivers/platform/x86/samsung-laptop.c +++ b/drivers/platform/x86/samsung-laptop.c @@ -1,13 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Samsung Laptop driver * * Copyright (C) 2009,2011 Greg Kroah-Hartman (gregkh@suse.de) * Copyright (C) 2009,2011 Novell Inc. - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 as published by - * the Free Software Foundation. - * */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt @@ -1280,15 +1276,12 @@ static void samsung_debugfs_exit(struct samsung_laptop *samsung) debugfs_remove_recursive(samsung->debug.root); } -static int samsung_debugfs_init(struct samsung_laptop *samsung) +static void samsung_debugfs_init(struct samsung_laptop *samsung) { - struct dentry *dent; + struct dentry *root; - samsung->debug.root = debugfs_create_dir("samsung-laptop", NULL); - if (!samsung->debug.root) { - pr_err("failed to create debugfs directory"); - goto error_debugfs; - } + root = debugfs_create_dir("samsung-laptop", NULL); + samsung->debug.root = root; samsung->debug.f0000_wrapper.data = samsung->f0000_segment; samsung->debug.f0000_wrapper.size = 0xffff; @@ -1299,60 +1292,24 @@ static int samsung_debugfs_init(struct samsung_laptop *samsung) samsung->debug.sdiag_wrapper.data = samsung->sdiag; samsung->debug.sdiag_wrapper.size = strlen(samsung->sdiag); - dent = debugfs_create_u16("command", S_IRUGO | S_IWUSR, - samsung->debug.root, &samsung->debug.command); - if (!dent) - goto error_debugfs; - - dent = debugfs_create_u32("d0", S_IRUGO | S_IWUSR, samsung->debug.root, - &samsung->debug.data.d0); - if (!dent) - goto error_debugfs; - - dent = debugfs_create_u32("d1", S_IRUGO | S_IWUSR, samsung->debug.root, - &samsung->debug.data.d1); - if (!dent) - goto error_debugfs; - - dent = debugfs_create_u16("d2", S_IRUGO | S_IWUSR, samsung->debug.root, - &samsung->debug.data.d2); - if (!dent) - goto error_debugfs; - - dent = debugfs_create_u8("d3", S_IRUGO | S_IWUSR, samsung->debug.root, - &samsung->debug.data.d3); - if (!dent) - goto error_debugfs; - - dent = debugfs_create_blob("data", S_IRUGO | S_IWUSR, - samsung->debug.root, - &samsung->debug.data_wrapper); - if (!dent) - goto error_debugfs; - - dent = debugfs_create_blob("f0000_segment", S_IRUSR | S_IWUSR, - samsung->debug.root, - &samsung->debug.f0000_wrapper); - if (!dent) - goto error_debugfs; - - dent = debugfs_create_file("call", S_IFREG | S_IRUGO, - samsung->debug.root, samsung, - &samsung_laptop_call_fops); - if (!dent) - goto error_debugfs; - - dent = debugfs_create_blob("sdiag", S_IRUGO | S_IWUSR, - samsung->debug.root, - &samsung->debug.sdiag_wrapper); - if (!dent) - goto error_debugfs; - - return 0; - -error_debugfs: - samsung_debugfs_exit(samsung); - return -ENOMEM; + debugfs_create_u16("command", S_IRUGO | S_IWUSR, root, + &samsung->debug.command); + debugfs_create_u32("d0", S_IRUGO | S_IWUSR, root, + &samsung->debug.data.d0); + debugfs_create_u32("d1", S_IRUGO | S_IWUSR, root, + &samsung->debug.data.d1); + debugfs_create_u16("d2", S_IRUGO | S_IWUSR, root, + &samsung->debug.data.d2); + debugfs_create_u8("d3", S_IRUGO | S_IWUSR, root, + &samsung->debug.data.d3); + debugfs_create_blob("data", S_IRUGO | S_IWUSR, root, + &samsung->debug.data_wrapper); + debugfs_create_blob("f0000_segment", S_IRUSR | S_IWUSR, root, + &samsung->debug.f0000_wrapper); + debugfs_create_file("call", S_IFREG | S_IRUGO, root, samsung, + &samsung_laptop_call_fops); + debugfs_create_blob("sdiag", S_IRUGO | S_IWUSR, root, + &samsung->debug.sdiag_wrapper); } static void samsung_sabi_exit(struct samsung_laptop *samsung) @@ -1745,9 +1702,7 @@ static int __init samsung_init(void) if (ret) goto error_lid_handling; - ret = samsung_debugfs_init(samsung); - if (ret) - goto error_debugfs; + samsung_debugfs_init(samsung); samsung->pm_nb.notifier_call = samsung_pm_notification; register_pm_notifier(&samsung->pm_nb); @@ -1755,8 +1710,6 @@ static int __init samsung_init(void) samsung_platform_device = samsung->platform_device; return ret; -error_debugfs: - samsung_lid_handling_exit(samsung); error_lid_handling: samsung_leds_exit(samsung); error_leds: diff --git a/drivers/platform/x86/samsung-q10.c b/drivers/platform/x86/samsung-q10.c index a2fb7fbc3273..6eb08b539311 100644 --- a/drivers/platform/x86/samsung-q10.c +++ b/drivers/platform/x86/samsung-q10.c @@ -1,12 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Driver for Samsung Q10 and related laptops: controls the backlight * * Copyright (c) 2011 Frederick van der Wyck <fvanderwyck@gmail.com> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * */ #include <linux/module.h> diff --git a/drivers/platform/x86/sony-laptop.c b/drivers/platform/x86/sony-laptop.c index 4bfbfa3f78e6..fb088dd8529e 100644 --- a/drivers/platform/x86/sony-laptop.c +++ b/drivers/platform/x86/sony-laptop.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * ACPI Sony Notebook Control Driver (SNC and SPIC) * @@ -25,21 +26,6 @@ * Copyright (C) 2000 Andrew Tridgell <tridge@valinux.com> * * Earlier work by Werner Almesberger, Paul `Rusty' Russell and Paul Mackerras. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - * */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt @@ -4424,14 +4410,16 @@ sony_pic_read_possible_resource(struct acpi_resource *resource, void *context) } return AE_OK; } + + case ACPI_RESOURCE_TYPE_END_TAG: + return AE_OK; + default: dprintk("Resource %d isn't an IRQ nor an IO port\n", resource->type); + return AE_CTRL_TERMINATE; - case ACPI_RESOURCE_TYPE_END_TAG: - return AE_OK; } - return AE_CTRL_TERMINATE; } static int sony_pic_possible_resources(struct acpi_device *device) diff --git a/drivers/platform/x86/surface3-wmi.c b/drivers/platform/x86/surface3-wmi.c index 25b176996cb7..130b6f52a600 100644 --- a/drivers/platform/x86/surface3-wmi.c +++ b/drivers/platform/x86/surface3-wmi.c @@ -1,14 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Driver for the LID cover switch of the Surface 3 * * Copyright (c) 2016 Red Hat Inc. */ -/* - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation; version 2 of the License. - */ #include <linux/kernel.h> #include <linux/module.h> diff --git a/drivers/platform/x86/surface3_button.c b/drivers/platform/x86/surface3_button.c index 57f51476bb65..48d77e7aae76 100644 --- a/drivers/platform/x86/surface3_button.c +++ b/drivers/platform/x86/surface3_button.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Supports for the button array on the Surface tablets. * @@ -6,11 +7,6 @@ * Based on soc_button_array.c: * * {C} Copyright 2014 Intel Corporation - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; version 2 - * of the License. */ #include <linux/module.h> diff --git a/drivers/platform/x86/surfacepro3_button.c b/drivers/platform/x86/surfacepro3_button.c index 1b491690ce07..ec515223f654 100644 --- a/drivers/platform/x86/surfacepro3_button.c +++ b/drivers/platform/x86/surfacepro3_button.c @@ -1,14 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * power/home/volume button support for * Microsoft Surface Pro 3/4 tablet. * * Copyright (c) 2015 Intel Corporation. * All rights reserved. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; version 2 - * of the License. */ #include <linux/kernel.h> @@ -24,6 +20,12 @@ #define SURFACE_BUTTON_OBJ_NAME "VGBI" #define SURFACE_BUTTON_DEVICE_NAME "Surface Pro 3/4 Buttons" +#define MSHW0040_DSM_REVISION 0x01 +#define MSHW0040_DSM_GET_OMPR 0x02 // get OEM Platform Revision +static const guid_t MSHW0040_DSM_UUID = + GUID_INIT(0x6fd05c69, 0xcde3, 0x49f4, 0x95, 0xed, 0xab, 0x16, 0x65, + 0x49, 0x80, 0x35); + #define SURFACE_BUTTON_NOTIFY_TABLET_MODE 0xc8 #define SURFACE_BUTTON_NOTIFY_PRESS_POWER 0xc6 @@ -146,6 +148,44 @@ static int surface_button_resume(struct device *dev) } #endif +/* + * Surface Pro 4 and Surface Book 2 / Surface Pro 2017 use the same device + * ID (MSHW0040) for the power/volume buttons. Make sure this is the right + * device by checking for the _DSM method and OEM Platform Revision. + * + * Returns true if the driver should bind to this device, i.e. the device is + * either MSWH0028 (Pro 3) or MSHW0040 on a Pro 4 or Book 1. + */ +static bool surface_button_check_MSHW0040(struct acpi_device *dev) +{ + acpi_handle handle = dev->handle; + union acpi_object *result; + u64 oem_platform_rev = 0; // valid revisions are nonzero + + // get OEM platform revision + result = acpi_evaluate_dsm_typed(handle, &MSHW0040_DSM_UUID, + MSHW0040_DSM_REVISION, + MSHW0040_DSM_GET_OMPR, + NULL, ACPI_TYPE_INTEGER); + + /* + * If evaluating the _DSM fails, the method is not present. This means + * that we have either MSHW0028 or MSHW0040 on Pro 4 or Book 1, so we + * should use this driver. We use revision 0 indicating it is + * unavailable. + */ + + if (result) { + oem_platform_rev = result->integer.value; + ACPI_FREE(result); + } + + dev_dbg(&dev->dev, "OEM Platform Revision %llu\n", oem_platform_rev); + + return oem_platform_rev == 0; +} + + static int surface_button_add(struct acpi_device *device) { struct surface_button *button; @@ -158,6 +198,9 @@ static int surface_button_add(struct acpi_device *device) strlen(SURFACE_BUTTON_OBJ_NAME))) return -ENODEV; + if (!surface_button_check_MSHW0040(device)) + return -ENODEV; + button = kzalloc(sizeof(struct surface_button), GFP_KERNEL); if (!button) return -ENOMEM; diff --git a/drivers/platform/x86/system76_acpi.c b/drivers/platform/x86/system76_acpi.c new file mode 100644 index 000000000000..4f6e4c342382 --- /dev/null +++ b/drivers/platform/x86/system76_acpi.c @@ -0,0 +1,384 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * System76 ACPI Driver + * + * Copyright (C) 2019 System76 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/acpi.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/leds.h> +#include <linux/module.h> +#include <linux/pci_ids.h> +#include <linux/types.h> + +struct system76_data { + struct acpi_device *acpi_dev; + struct led_classdev ap_led; + struct led_classdev kb_led; + enum led_brightness kb_brightness; + enum led_brightness kb_toggle_brightness; + int kb_color; +}; + +static const struct acpi_device_id device_ids[] = { + {"17761776", 0}, + {"", 0}, +}; +MODULE_DEVICE_TABLE(acpi, device_ids); + +// Array of keyboard LED brightness levels +static const enum led_brightness kb_levels[] = { + 48, + 72, + 96, + 144, + 192, + 255 +}; + +// Array of keyboard LED colors in 24-bit RGB format +static const int kb_colors[] = { + 0xFFFFFF, + 0x0000FF, + 0xFF0000, + 0xFF00FF, + 0x00FF00, + 0x00FFFF, + 0xFFFF00 +}; + +// Get a System76 ACPI device value by name +static int system76_get(struct system76_data *data, char *method) +{ + acpi_handle handle; + acpi_status status; + unsigned long long ret = 0; + + handle = acpi_device_handle(data->acpi_dev); + status = acpi_evaluate_integer(handle, method, NULL, &ret); + if (ACPI_SUCCESS(status)) + return (int)ret; + else + return -1; +} + +// Set a System76 ACPI device value by name +static int system76_set(struct system76_data *data, char *method, int value) +{ + union acpi_object obj; + struct acpi_object_list obj_list; + acpi_handle handle; + acpi_status status; + + obj.type = ACPI_TYPE_INTEGER; + obj.integer.value = value; + obj_list.count = 1; + obj_list.pointer = &obj; + handle = acpi_device_handle(data->acpi_dev); + status = acpi_evaluate_object(handle, method, &obj_list, NULL); + if (ACPI_SUCCESS(status)) + return 0; + else + return -1; +} + +// Get the airplane mode LED brightness +static enum led_brightness ap_led_get(struct led_classdev *led) +{ + struct system76_data *data; + int value; + + data = container_of(led, struct system76_data, ap_led); + value = system76_get(data, "GAPL"); + if (value > 0) + return (enum led_brightness)value; + else + return LED_OFF; +} + +// Set the airplane mode LED brightness +static void ap_led_set(struct led_classdev *led, enum led_brightness value) +{ + struct system76_data *data; + + data = container_of(led, struct system76_data, ap_led); + system76_set(data, "SAPL", value == LED_OFF ? 0 : 1); +} + +// Get the last set keyboard LED brightness +static enum led_brightness kb_led_get(struct led_classdev *led) +{ + struct system76_data *data; + + data = container_of(led, struct system76_data, kb_led); + return data->kb_brightness; +} + +// Set the keyboard LED brightness +static void kb_led_set(struct led_classdev *led, enum led_brightness value) +{ + struct system76_data *data; + + data = container_of(led, struct system76_data, kb_led); + data->kb_brightness = value; + system76_set(data, "SKBL", (int)data->kb_brightness); +} + +// Get the last set keyboard LED color +static ssize_t kb_led_color_show( + struct device *dev, + struct device_attribute *dev_attr, + char *buf) +{ + struct led_classdev *led; + struct system76_data *data; + + led = (struct led_classdev *)dev->driver_data; + data = container_of(led, struct system76_data, kb_led); + return sprintf(buf, "%06X\n", data->kb_color); +} + +// Set the keyboard LED color +static ssize_t kb_led_color_store( + struct device *dev, + struct device_attribute *dev_attr, + const char *buf, + size_t size) +{ + struct led_classdev *led; + struct system76_data *data; + unsigned int val; + int ret; + + led = (struct led_classdev *)dev->driver_data; + data = container_of(led, struct system76_data, kb_led); + ret = kstrtouint(buf, 16, &val); + if (ret) + return ret; + if (val > 0xFFFFFF) + return -EINVAL; + data->kb_color = (int)val; + system76_set(data, "SKBC", data->kb_color); + + return size; +} + +static const struct device_attribute kb_led_color_dev_attr = { + .attr = { + .name = "color", + .mode = 0644, + }, + .show = kb_led_color_show, + .store = kb_led_color_store, +}; + +// Notify that the keyboard LED was changed by hardware +static void kb_led_notify(struct system76_data *data) +{ + led_classdev_notify_brightness_hw_changed( + &data->kb_led, + data->kb_brightness + ); +} + +// Read keyboard LED brightness as set by hardware +static void kb_led_hotkey_hardware(struct system76_data *data) +{ + int value; + + value = system76_get(data, "GKBL"); + if (value < 0) + return; + data->kb_brightness = value; + kb_led_notify(data); +} + +// Toggle the keyboard LED +static void kb_led_hotkey_toggle(struct system76_data *data) +{ + if (data->kb_brightness > 0) { + data->kb_toggle_brightness = data->kb_brightness; + kb_led_set(&data->kb_led, 0); + } else { + kb_led_set(&data->kb_led, data->kb_toggle_brightness); + } + kb_led_notify(data); +} + +// Decrease the keyboard LED brightness +static void kb_led_hotkey_down(struct system76_data *data) +{ + int i; + + if (data->kb_brightness > 0) { + for (i = ARRAY_SIZE(kb_levels); i > 0; i--) { + if (kb_levels[i - 1] < data->kb_brightness) { + kb_led_set(&data->kb_led, kb_levels[i - 1]); + break; + } + } + } else { + kb_led_set(&data->kb_led, data->kb_toggle_brightness); + } + kb_led_notify(data); +} + +// Increase the keyboard LED brightness +static void kb_led_hotkey_up(struct system76_data *data) +{ + int i; + + if (data->kb_brightness > 0) { + for (i = 0; i < ARRAY_SIZE(kb_levels); i++) { + if (kb_levels[i] > data->kb_brightness) { + kb_led_set(&data->kb_led, kb_levels[i]); + break; + } + } + } else { + kb_led_set(&data->kb_led, data->kb_toggle_brightness); + } + kb_led_notify(data); +} + +// Cycle the keyboard LED color +static void kb_led_hotkey_color(struct system76_data *data) +{ + int i; + + if (data->kb_color < 0) + return; + if (data->kb_brightness > 0) { + for (i = 0; i < ARRAY_SIZE(kb_colors); i++) { + if (kb_colors[i] == data->kb_color) + break; + } + i += 1; + if (i >= ARRAY_SIZE(kb_colors)) + i = 0; + data->kb_color = kb_colors[i]; + system76_set(data, "SKBC", data->kb_color); + } else { + kb_led_set(&data->kb_led, data->kb_toggle_brightness); + } + kb_led_notify(data); +} + +// Handle ACPI notification +static void system76_notify(struct acpi_device *acpi_dev, u32 event) +{ + struct system76_data *data; + + data = acpi_driver_data(acpi_dev); + switch (event) { + case 0x80: + kb_led_hotkey_hardware(data); + break; + case 0x81: + kb_led_hotkey_toggle(data); + break; + case 0x82: + kb_led_hotkey_down(data); + break; + case 0x83: + kb_led_hotkey_up(data); + break; + case 0x84: + kb_led_hotkey_color(data); + break; + } +} + +// Add a System76 ACPI device +static int system76_add(struct acpi_device *acpi_dev) +{ + struct system76_data *data; + int err; + + data = devm_kzalloc(&acpi_dev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + acpi_dev->driver_data = data; + data->acpi_dev = acpi_dev; + + err = system76_get(data, "INIT"); + if (err) + return err; + data->ap_led.name = "system76_acpi::airplane"; + data->ap_led.flags = LED_CORE_SUSPENDRESUME; + data->ap_led.brightness_get = ap_led_get; + data->ap_led.brightness_set = ap_led_set; + data->ap_led.max_brightness = 1; + data->ap_led.default_trigger = "rfkill-none"; + err = devm_led_classdev_register(&acpi_dev->dev, &data->ap_led); + if (err) + return err; + + data->kb_led.name = "system76_acpi::kbd_backlight"; + data->kb_led.flags = LED_BRIGHT_HW_CHANGED | LED_CORE_SUSPENDRESUME; + data->kb_led.brightness_get = kb_led_get; + data->kb_led.brightness_set = kb_led_set; + if (acpi_has_method(acpi_device_handle(data->acpi_dev), "SKBC")) { + data->kb_led.max_brightness = 255; + data->kb_toggle_brightness = 72; + data->kb_color = 0xffffff; + system76_set(data, "SKBC", data->kb_color); + } else { + data->kb_led.max_brightness = 5; + data->kb_color = -1; + } + err = devm_led_classdev_register(&acpi_dev->dev, &data->kb_led); + if (err) + return err; + + if (data->kb_color >= 0) { + err = device_create_file( + data->kb_led.dev, + &kb_led_color_dev_attr + ); + if (err) + return err; + } + + return 0; +} + +// Remove a System76 ACPI device +static int system76_remove(struct acpi_device *acpi_dev) +{ + struct system76_data *data; + + data = acpi_driver_data(acpi_dev); + if (data->kb_color >= 0) + device_remove_file(data->kb_led.dev, &kb_led_color_dev_attr); + + devm_led_classdev_unregister(&acpi_dev->dev, &data->ap_led); + + devm_led_classdev_unregister(&acpi_dev->dev, &data->kb_led); + + system76_get(data, "FINI"); + + return 0; +} + +static struct acpi_driver system76_driver = { + .name = "System76 ACPI Driver", + .class = "hotkey", + .ids = device_ids, + .ops = { + .add = system76_add, + .remove = system76_remove, + .notify = system76_notify, + }, +}; +module_acpi_driver(system76_driver); + +MODULE_DESCRIPTION("System76 ACPI Driver"); +MODULE_AUTHOR("Jeremy Soller <jeremy@system76.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/tc1100-wmi.c b/drivers/platform/x86/tc1100-wmi.c index 65b0a4845ddd..803920b6f01d 100644 --- a/drivers/platform/x86/tc1100-wmi.c +++ b/drivers/platform/x86/tc1100-wmi.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * HP Compaq TC1100 Tablet WMI Extras Driver * @@ -5,24 +6,6 @@ * Copyright (C) 2004 Jamey Hicks <jamey.hicks@hp.com> * Copyright (C) 2001, 2002 Andy Grover <andrew.grover@intel.com> * Copyright (C) 2001, 2002 Paul Diefenbaugh <paul.s.diefenbaugh@intel.com> - * - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or (at - * your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. - * - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index 726341f2b638..da794dcfdd92 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -1,24 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * thinkpad_acpi.c - ThinkPad ACPI Extras * - * * Copyright (C) 2004-2005 Borislav Deianov <borislav@users.sf.net> * Copyright (C) 2006-2009 Henrique de Moraes Holschuh <hmh@hmh.eng.br> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt @@ -79,7 +64,7 @@ #include <linux/jiffies.h> #include <linux/workqueue.h> #include <linux/acpi.h> -#include <linux/pci_ids.h> +#include <linux/pci.h> #include <linux/power_supply.h> #include <sound/core.h> #include <sound/control.h> @@ -3662,22 +3647,19 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) goto err_exit; /* Set up key map */ - hotkey_keycode_map = kmalloc(TPACPI_HOTKEY_MAP_SIZE, - GFP_KERNEL); - if (!hotkey_keycode_map) { - pr_err("failed to allocate memory for key map\n"); - res = -ENOMEM; - goto err_exit; - } - keymap_id = tpacpi_check_quirks(tpacpi_keymap_qtable, ARRAY_SIZE(tpacpi_keymap_qtable)); BUG_ON(keymap_id >= ARRAY_SIZE(tpacpi_keymaps)); dbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_HKEY, "using keymap number %lu\n", keymap_id); - memcpy(hotkey_keycode_map, &tpacpi_keymaps[keymap_id], - TPACPI_HOTKEY_MAP_SIZE); + hotkey_keycode_map = kmemdup(&tpacpi_keymaps[keymap_id], + TPACPI_HOTKEY_MAP_SIZE, GFP_KERNEL); + if (!hotkey_keycode_map) { + pr_err("failed to allocate memory for key map\n"); + res = -ENOMEM; + goto err_exit; + } input_set_capability(tpacpi_inputdev, EV_MSC, MSC_SCAN); tpacpi_inputdev->keycodesize = TPACPI_HOTKEY_MAP_TYPESIZE; @@ -4212,7 +4194,7 @@ static void hotkey_notify(struct ibm_struct *ibm, u32 event) known_ev = true; break; } - /* fallthrough to default */ + /* fallthrough - to default */ default: known_ev = false; } @@ -4501,6 +4483,74 @@ static void bluetooth_exit(void) bluetooth_shutdown(); } +static const struct dmi_system_id bt_fwbug_list[] __initconst = { + { + .ident = "ThinkPad E485", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_BOARD_NAME, "20KU"), + }, + }, + { + .ident = "ThinkPad E585", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_BOARD_NAME, "20KV"), + }, + }, + { + .ident = "ThinkPad A285 - 20MW", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_BOARD_NAME, "20MW"), + }, + }, + { + .ident = "ThinkPad A285 - 20MX", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_BOARD_NAME, "20MX"), + }, + }, + { + .ident = "ThinkPad A485 - 20MU", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_BOARD_NAME, "20MU"), + }, + }, + { + .ident = "ThinkPad A485 - 20MV", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_BOARD_NAME, "20MV"), + }, + }, + {} +}; + +static const struct pci_device_id fwbug_cards_ids[] __initconst = { + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x24F3) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x24FD) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x2526) }, + {} +}; + + +static int __init have_bt_fwbug(void) +{ + /* + * Some AMD based ThinkPads have a firmware bug that calling + * "GBDC" will cause bluetooth on Intel wireless cards blocked + */ + if (dmi_check_system(bt_fwbug_list) && pci_dev_present(fwbug_cards_ids)) { + vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_RFKILL, + FW_BUG "disable bluetooth subdriver for Intel cards\n"); + return 1; + } else + return 0; +} + static int __init bluetooth_init(struct ibm_init_struct *iibm) { int res; @@ -4513,7 +4563,7 @@ static int __init bluetooth_init(struct ibm_init_struct *iibm) /* bluetooth not supported on 570, 600e/x, 770e, 770x, A21e, A2xm/p, G4x, R30, R31, R40e, R50e, T20-22, X20-21 */ - tp_features.bluetooth = hkey_handle && + tp_features.bluetooth = !have_bt_fwbug() && hkey_handle && acpi_evalf(hkey_handle, &status, "GBDC", "qd"); vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_RFKILL, @@ -5808,7 +5858,7 @@ static int led_set_status(const unsigned int led, return -EPERM; if (!acpi_evalf(led_handle, NULL, NULL, "vdd", (1 << led), led_sled_arg1[ledstatus])) - rc = -EIO; + return -EIO; break; case TPACPI_LED_OLD: /* 600e/x, 770e, 770x, A21e, A2xm/p, T20-22, X20 */ @@ -5832,10 +5882,10 @@ static int led_set_status(const unsigned int led, return -EPERM; if (!acpi_evalf(led_handle, NULL, NULL, "vdd", led, led_led_arg1[ledstatus])) - rc = -EIO; + return -EIO; break; default: - rc = -ENXIO; + return -ENXIO; } if (!rc) @@ -6249,8 +6299,8 @@ static int thermal_get_sensor(int idx, s32 *value) t = TP_EC_THERMAL_TMP8; idx -= 8; } - /* fallthrough */ #endif + /* fallthrough */ case TPACPI_THERMAL_TPEC_8: if (idx <= 7) { if (!acpi_ec_read(t + idx, &tmp)) @@ -9661,6 +9711,107 @@ static struct ibm_struct battery_driver_data = { .exit = tpacpi_battery_exit, }; +/************************************************************************* + * LCD Shadow subdriver, for the Lenovo PrivacyGuard feature + */ + +static int lcdshadow_state; + +static int lcdshadow_on_off(bool state) +{ + acpi_handle set_shadow_handle; + int output; + + if (ACPI_FAILURE(acpi_get_handle(hkey_handle, "SSSS", &set_shadow_handle))) { + pr_warn("Thinkpad ACPI has no %s interface.\n", "SSSS"); + return -EIO; + } + + if (!acpi_evalf(set_shadow_handle, &output, NULL, "dd", (int)state)) + return -EIO; + + lcdshadow_state = state; + return 0; +} + +static int lcdshadow_set(bool on) +{ + if (lcdshadow_state < 0) + return lcdshadow_state; + if (lcdshadow_state == on) + return 0; + return lcdshadow_on_off(on); +} + +static int tpacpi_lcdshadow_init(struct ibm_init_struct *iibm) +{ + acpi_handle get_shadow_handle; + int output; + + if (ACPI_FAILURE(acpi_get_handle(hkey_handle, "GSSS", &get_shadow_handle))) { + lcdshadow_state = -ENODEV; + return 0; + } + + if (!acpi_evalf(get_shadow_handle, &output, NULL, "dd", 0)) { + lcdshadow_state = -EIO; + return -EIO; + } + if (!(output & 0x10000)) { + lcdshadow_state = -ENODEV; + return 0; + } + lcdshadow_state = output & 0x1; + + return 0; +} + +static void lcdshadow_resume(void) +{ + if (lcdshadow_state >= 0) + lcdshadow_on_off(lcdshadow_state); +} + +static int lcdshadow_read(struct seq_file *m) +{ + if (lcdshadow_state < 0) { + seq_puts(m, "status:\t\tnot supported\n"); + } else { + seq_printf(m, "status:\t\t%d\n", lcdshadow_state); + seq_puts(m, "commands:\t0, 1\n"); + } + + return 0; +} + +static int lcdshadow_write(char *buf) +{ + char *cmd; + int state = -1; + + if (lcdshadow_state < 0) + return -ENODEV; + + while ((cmd = next_cmd(&buf))) { + if (strlencmp(cmd, "0") == 0) + state = 0; + else if (strlencmp(cmd, "1") == 0) + state = 1; + } + + if (state == -1) + return -EINVAL; + + return lcdshadow_set(state); +} + +static struct ibm_struct lcdshadow_driver_data = { + .name = "lcdshadow", + .resume = lcdshadow_resume, + .read = lcdshadow_read, + .write = lcdshadow_write, +}; + /**************************************************************************** **************************************************************************** * @@ -9890,6 +10041,37 @@ invalid: return '\0'; } +static void find_new_ec_fwstr(const struct dmi_header *dm, void *private) +{ + char *ec_fw_string = (char *) private; + const char *dmi_data = (const char *)dm; + /* + * ThinkPad Embedded Controller Program Table on newer models + * + * Offset | Name | Width | Description + * ---------------------------------------------------- + * 0x00 | Type | BYTE | 0x8C + * 0x01 | Length | BYTE | + * 0x02 | Handle | WORD | Varies + * 0x04 | Signature | BYTEx6 | ASCII for "LENOVO" + * 0x0A | OEM struct offset | BYTE | 0x0B + * 0x0B | OEM struct number | BYTE | 0x07, for this structure + * 0x0C | OEM struct revision | BYTE | 0x01, for this format + * 0x0D | ECP version ID | STR ID | + * 0x0E | ECP release date | STR ID | + */ + + /* Return if data structure not match */ + if (dm->type != 140 || dm->length < 0x0F || + memcmp(dmi_data + 4, "LENOVO", 6) != 0 || + dmi_data[0x0A] != 0x0B || dmi_data[0x0B] != 0x07 || + dmi_data[0x0C] != 0x01) + return; + + /* fwstr is the first 8byte string */ + strncpy(ec_fw_string, dmi_data + 0x0F, 8); +} + /* returns 0 - probe ok, or < 0 - probe error. * Probe ok doesn't mean thinkpad found. * On error, kfree() cleanup on tp->* is not performed, caller must do it */ @@ -9897,7 +10079,7 @@ static int __must_check __init get_thinkpad_model_data( struct thinkpad_id_data *tp) { const struct dmi_device *dev = NULL; - char ec_fw_string[18]; + char ec_fw_string[18] = {0}; char const *s; char t; @@ -9937,20 +10119,25 @@ static int __must_check __init get_thinkpad_model_data( ec_fw_string) == 1) { ec_fw_string[sizeof(ec_fw_string) - 1] = 0; ec_fw_string[strcspn(ec_fw_string, " ]")] = 0; + break; + } + } - tp->ec_version_str = kstrdup(ec_fw_string, GFP_KERNEL); - if (!tp->ec_version_str) - return -ENOMEM; + /* Newer ThinkPads have different EC program info table */ + if (!ec_fw_string[0]) + dmi_walk(find_new_ec_fwstr, &ec_fw_string); - t = tpacpi_parse_fw_id(ec_fw_string, - &tp->ec_model, &tp->ec_release); - if (t != 'H') { - pr_notice("ThinkPad firmware release %s doesn't match the known patterns\n", - ec_fw_string); - pr_notice("please report this to %s\n", - TPACPI_MAIL); - } - break; + if (ec_fw_string[0]) { + tp->ec_version_str = kstrdup(ec_fw_string, GFP_KERNEL); + if (!tp->ec_version_str) + return -ENOMEM; + + t = tpacpi_parse_fw_id(ec_fw_string, + &tp->ec_model, &tp->ec_release); + if (t != 'H') { + pr_notice("ThinkPad firmware release %s doesn't match the known patterns\n", + ec_fw_string); + pr_notice("please report this to %s\n", TPACPI_MAIL); } } @@ -10106,6 +10293,10 @@ static struct ibm_init_struct ibms_init[] __initdata = { .init = tpacpi_battery_init, .data = &battery_driver_data, }, + { + .init = tpacpi_lcdshadow_init, + .data = &lcdshadow_driver_data, + }, }; static int __init set_ibm_param(const char *val, const struct kernel_param *kp) @@ -10165,7 +10356,7 @@ MODULE_PARM_DESC(volume_mode, module_param_named(volume_capabilities, volume_capabilities, uint, 0444); MODULE_PARM_DESC(volume_capabilities, - "Selects the mixer capabilites: 0=auto, 1=volume and mute, 2=mute only"); + "Selects the mixer capabilities: 0=auto, 1=volume and mute, 2=mute only"); module_param_named(volume_control, volume_control_allowed, bool, 0444); MODULE_PARM_DESC(volume_control, diff --git a/drivers/platform/x86/toshiba-wmi.c b/drivers/platform/x86/toshiba-wmi.c index 03d7620cd6d7..77c35529ab6f 100644 --- a/drivers/platform/x86/toshiba-wmi.c +++ b/drivers/platform/x86/toshiba-wmi.c @@ -1,18 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * toshiba_wmi.c - Toshiba WMI Hotkey Driver * * Copyright (C) 2015 Azael Avalos <coproscefalo@gmail.com> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt diff --git a/drivers/platform/x86/toshiba_acpi.c b/drivers/platform/x86/toshiba_acpi.c index e366977bda41..a1e6569427c3 100644 --- a/drivers/platform/x86/toshiba_acpi.c +++ b/drivers/platform/x86/toshiba_acpi.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * toshiba_acpi.c - Toshiba Laptop ACPI Extras * @@ -6,19 +7,6 @@ * Copyright (C) 2010 Pierre Ducroquet * Copyright (C) 2014-2016 Azael Avalos * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * The full GNU General Public License is included in this distribution in - * the file called "COPYING". - * * The devolpment page for this driver is located at * http://memebeam.org/toys/ToshibaAcpiDriver. * diff --git a/drivers/platform/x86/toshiba_bluetooth.c b/drivers/platform/x86/toshiba_bluetooth.c index be1d137c6079..57a5dc60c58a 100644 --- a/drivers/platform/x86/toshiba_bluetooth.c +++ b/drivers/platform/x86/toshiba_bluetooth.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * Toshiba Bluetooth Enable Driver * @@ -6,10 +7,6 @@ * * Thanks to Matthew Garrett for background info on ACPI innards which * normal people aren't meant to understand :-) - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt diff --git a/drivers/platform/x86/toshiba_haps.c b/drivers/platform/x86/toshiba_haps.c index fb2736602558..b237bd6b1ee5 100644 --- a/drivers/platform/x86/toshiba_haps.c +++ b/drivers/platform/x86/toshiba_haps.c @@ -1,18 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * Toshiba HDD Active Protection Sensor (HAPS) driver * * Copyright (C) 2014 Azael Avalos <coproscefalo@gmail.com> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt diff --git a/drivers/platform/x86/touchscreen_dmi.c b/drivers/platform/x86/touchscreen_dmi.c index 2d56ff7c8230..72205771d03d 100644 --- a/drivers/platform/x86/touchscreen_dmi.c +++ b/drivers/platform/x86/touchscreen_dmi.c @@ -1,13 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * Touchscreen driver DMI based configuration code * * Copyright (c) 2017 Red Hat Inc. * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * * Red Hat authors: * Hans de Goede <hdegoede@redhat.com> */ @@ -91,6 +87,22 @@ static const struct ts_dmi_data chuwi_hi10_air_data = { .properties = chuwi_hi10_air_props, }; +static const struct property_entry chuwi_hi10_plus_props[] = { + PROPERTY_ENTRY_U32("touchscreen-min-x", 0), + PROPERTY_ENTRY_U32("touchscreen-min-y", 5), + PROPERTY_ENTRY_U32("touchscreen-size-x", 1914), + PROPERTY_ENTRY_U32("touchscreen-size-y", 1283), + PROPERTY_ENTRY_STRING("firmware-name", "gsl1680-chuwi-hi10plus.fw"), + PROPERTY_ENTRY_U32("silead,max-fingers", 10), + PROPERTY_ENTRY_BOOL("silead,home-button"), + { } +}; + +static const struct ts_dmi_data chuwi_hi10_plus_data = { + .acpi_name = "MSSL0017:00", + .properties = chuwi_hi10_plus_props, +}; + static const struct property_entry chuwi_vi8_props[] = { PROPERTY_ENTRY_U32("touchscreen-min-x", 4), PROPERTY_ENTRY_U32("touchscreen-min-y", 6), @@ -124,6 +136,22 @@ static const struct ts_dmi_data chuwi_vi10_data = { .properties = chuwi_vi10_props, }; +static const struct property_entry chuwi_surbook_mini_props[] = { + PROPERTY_ENTRY_U32("touchscreen-min-x", 88), + PROPERTY_ENTRY_U32("touchscreen-min-y", 13), + PROPERTY_ENTRY_U32("touchscreen-size-x", 2040), + PROPERTY_ENTRY_U32("touchscreen-size-y", 1524), + PROPERTY_ENTRY_STRING("firmware-name", "gsl1680-chuwi-surbook-mini.fw"), + PROPERTY_ENTRY_U32("silead,max-fingers", 10), + PROPERTY_ENTRY_BOOL("touchscreen-inverted-y"), + { } +}; + +static const struct ts_dmi_data chuwi_surbook_mini_data = { + .acpi_name = "MSSL1680:00", + .properties = chuwi_surbook_mini_props, +}; + static const struct property_entry connect_tablet9_props[] = { PROPERTY_ENTRY_U32("touchscreen-min-x", 9), PROPERTY_ENTRY_U32("touchscreen-min-y", 10), @@ -218,6 +246,24 @@ static const struct ts_dmi_data gp_electronic_t701_data = { .properties = gp_electronic_t701_props, }; +static const struct property_entry irbis_tw90_props[] = { + PROPERTY_ENTRY_U32("touchscreen-size-x", 1720), + PROPERTY_ENTRY_U32("touchscreen-size-y", 1138), + PROPERTY_ENTRY_U32("touchscreen-min-x", 8), + PROPERTY_ENTRY_U32("touchscreen-min-y", 14), + PROPERTY_ENTRY_BOOL("touchscreen-inverted-y"), + PROPERTY_ENTRY_BOOL("touchscreen-swapped-x-y"), + PROPERTY_ENTRY_STRING("firmware-name", "gsl3680-irbis_tw90.fw"), + PROPERTY_ENTRY_U32("silead,max-fingers", 10), + PROPERTY_ENTRY_BOOL("silead,home-button"), + { } +}; + +static const struct ts_dmi_data irbis_tw90_data = { + .acpi_name = "MSSL1680:00", + .properties = irbis_tw90_props, +}; + static const struct property_entry itworks_tw891_props[] = { PROPERTY_ENTRY_U32("touchscreen-min-x", 1), PROPERTY_ENTRY_U32("touchscreen-min-y", 5), @@ -249,6 +295,37 @@ static const struct ts_dmi_data jumper_ezpad_6_pro_data = { .properties = jumper_ezpad_6_pro_props, }; +static const struct property_entry jumper_ezpad_6_pro_b_props[] = { + PROPERTY_ENTRY_U32("touchscreen-size-x", 1980), + PROPERTY_ENTRY_U32("touchscreen-size-y", 1500), + PROPERTY_ENTRY_STRING("firmware-name", "gsl3692-jumper-ezpad-6-pro-b.fw"), + PROPERTY_ENTRY_BOOL("touchscreen-inverted-y"), + PROPERTY_ENTRY_U32("silead,max-fingers", 10), + PROPERTY_ENTRY_BOOL("silead,home-button"), + { } +}; + +static const struct ts_dmi_data jumper_ezpad_6_pro_b_data = { + .acpi_name = "MSSL1680:00", + .properties = jumper_ezpad_6_pro_b_props, +}; + +static const struct property_entry jumper_ezpad_6_m4_props[] = { + PROPERTY_ENTRY_U32("touchscreen-min-x", 35), + PROPERTY_ENTRY_U32("touchscreen-min-y", 15), + PROPERTY_ENTRY_U32("touchscreen-size-x", 1950), + PROPERTY_ENTRY_U32("touchscreen-size-y", 1525), + PROPERTY_ENTRY_STRING("firmware-name", "gsl3692-jumper-ezpad-6-m4.fw"), + PROPERTY_ENTRY_U32("silead,max-fingers", 10), + PROPERTY_ENTRY_BOOL("silead,home-button"), + { } +}; + +static const struct ts_dmi_data jumper_ezpad_6_m4_data = { + .acpi_name = "MSSL1680:00", + .properties = jumper_ezpad_6_m4_props, +}; + static const struct property_entry jumper_ezpad_mini3_props[] = { PROPERTY_ENTRY_U32("touchscreen-min-x", 23), PROPERTY_ENTRY_U32("touchscreen-min-y", 16), @@ -265,6 +342,23 @@ static const struct ts_dmi_data jumper_ezpad_mini3_data = { .properties = jumper_ezpad_mini3_props, }; +static const struct property_entry myria_my8307_props[] = { + PROPERTY_ENTRY_U32("touchscreen-size-x", 1720), + PROPERTY_ENTRY_U32("touchscreen-size-y", 1140), + PROPERTY_ENTRY_BOOL("touchscreen-inverted-x"), + PROPERTY_ENTRY_BOOL("touchscreen-inverted-y"), + PROPERTY_ENTRY_BOOL("touchscreen-swapped-x-y"), + PROPERTY_ENTRY_STRING("firmware-name", "gsl1680-myria-my8307.fw"), + PROPERTY_ENTRY_U32("silead,max-fingers", 10), + PROPERTY_ENTRY_BOOL("silead,home-button"), + { } +}; + +static const struct ts_dmi_data myria_my8307_data = { + .acpi_name = "MSSL1680:00", + .properties = myria_my8307_props, +}; + static const struct property_entry onda_obook_20_plus_props[] = { PROPERTY_ENTRY_U32("touchscreen-size-x", 1728), PROPERTY_ENTRY_U32("touchscreen-size-y", 1148), @@ -420,6 +514,24 @@ static const struct ts_dmi_data pov_mobii_wintab_p1006w_v10_data = { .properties = pov_mobii_wintab_p1006w_v10_props, }; +static const struct property_entry schneider_sct101ctm_props[] = { + PROPERTY_ENTRY_U32("touchscreen-size-x", 1715), + PROPERTY_ENTRY_U32("touchscreen-size-y", 1140), + PROPERTY_ENTRY_BOOL("touchscreen-inverted-x"), + PROPERTY_ENTRY_BOOL("touchscreen-inverted-y"), + PROPERTY_ENTRY_BOOL("touchscreen-swapped-x-y"), + PROPERTY_ENTRY_STRING("firmware-name", + "gsl1680-schneider-sct101ctm.fw"), + PROPERTY_ENTRY_U32("silead,max-fingers", 10), + PROPERTY_ENTRY_BOOL("silead,home-button"), + { } +}; + +static const struct ts_dmi_data schneider_sct101ctm_data = { + .acpi_name = "MSSL1680:00", + .properties = schneider_sct101ctm_props, +}; + static const struct property_entry teclast_x3_plus_props[] = { PROPERTY_ENTRY_U32("touchscreen-size-x", 1980), PROPERTY_ENTRY_U32("touchscreen-size-y", 1500), @@ -569,11 +681,21 @@ static const struct dmi_system_id touchscreen_dmi_table[] = { /* Chuwi Hi10 Air */ .driver_data = (void *)&chuwi_hi10_air_data, .matches = { - DMI_MATCH(DMI_BOARD_VENDOR, "Hampoo"), + DMI_MATCH(DMI_SYS_VENDOR, "CHUWI INNOVATION AND TECHNOLOGY(SHENZHEN)CO.LTD"), + DMI_MATCH(DMI_BOARD_NAME, "Cherry Trail CR"), DMI_MATCH(DMI_PRODUCT_SKU, "P1W6_C109D_B"), }, }, { + /* Chuwi Hi10 Plus (CWI527) */ + .driver_data = (void *)&chuwi_hi10_plus_data, + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Hampoo"), + DMI_MATCH(DMI_PRODUCT_NAME, "Hi10 plus tablet"), + DMI_MATCH(DMI_BOARD_NAME, "Cherry Trail CR"), + }, + }, + { /* Chuwi Vi8 (CWI506) */ .driver_data = (void *)&chuwi_vi8_data, .matches = { @@ -593,6 +715,14 @@ static const struct dmi_system_id touchscreen_dmi_table[] = { }, }, { + /* Chuwi Surbook Mini (CWI540) */ + .driver_data = (void *)&chuwi_surbook_mini_data, + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Hampoo"), + DMI_MATCH(DMI_PRODUCT_NAME, "C3W6_AP108_4G"), + }, + }, + { /* Connect Tablet 9 */ .driver_data = (void *)&connect_tablet9_data, .matches = { @@ -655,6 +785,14 @@ static const struct dmi_system_id touchscreen_dmi_table[] = { }, }, { + /* Irbis TW90 */ + .driver_data = (void *)&irbis_tw90_data, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "IRBIS"), + DMI_MATCH(DMI_PRODUCT_NAME, "TW90"), + }, + }, + { /* I.T.Works TW891 */ .driver_data = (void *)&itworks_tw891_data, .matches = { @@ -674,6 +812,27 @@ static const struct dmi_system_id touchscreen_dmi_table[] = { }, }, { + /* Jumper EZpad 6 Pro B */ + .driver_data = (void *)&jumper_ezpad_6_pro_b_data, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Jumper"), + DMI_MATCH(DMI_PRODUCT_NAME, "EZpad"), + DMI_MATCH(DMI_BIOS_VERSION, "5.12"), + /* Above matches are too generic, add bios-date match */ + DMI_MATCH(DMI_BIOS_DATE, "04/24/2018"), + }, + }, + { + /* Jumper EZpad 6 m4 */ + .driver_data = (void *)&jumper_ezpad_6_m4_data, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "jumper"), + DMI_MATCH(DMI_PRODUCT_NAME, "EZpad"), + /* Jumper8.S106x.A00C.1066 with the version dropped */ + DMI_MATCH(DMI_BIOS_VERSION, "Jumper8.S106x"), + }, + }, + { /* Jumper EZpad mini3 */ .driver_data = (void *)&jumper_ezpad_mini3_data, .matches = { @@ -691,6 +850,14 @@ static const struct dmi_system_id touchscreen_dmi_table[] = { }, }, { + /* Myria MY8307 */ + .driver_data = (void *)&myria_my8307_data, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Complet Electro Serv"), + DMI_MATCH(DMI_PRODUCT_NAME, "MY8307"), + }, + }, + { /* Onda oBook 20 Plus */ .driver_data = (void *)&onda_obook_20_plus_data, .matches = { @@ -786,6 +953,14 @@ static const struct dmi_system_id touchscreen_dmi_table[] = { }, }, { + /* Schneider SCT101CTM */ + .driver_data = (void *)&schneider_sct101ctm_data, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Default string"), + DMI_MATCH(DMI_PRODUCT_NAME, "SCT101CTM"), + }, + }, + { /* Teclast X3 Plus */ .driver_data = (void *)&teclast_x3_plus_data, .matches = { @@ -811,6 +986,14 @@ static const struct dmi_system_id touchscreen_dmi_table[] = { }, }, { + /* Trekstor Primebook C11B (same touchscreen as the C11) */ + .driver_data = (void *)&trekstor_primebook_c11_data, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "TREKSTOR"), + DMI_MATCH(DMI_PRODUCT_NAME, "PRIMEBOOK C11B"), + }, + }, + { /* Trekstor Primebook C13 */ .driver_data = (void *)&trekstor_primebook_c13_data, .matches = { diff --git a/drivers/platform/x86/wmi-bmof.c b/drivers/platform/x86/wmi-bmof.c index 8751a13134be..66b434d6307f 100644 --- a/drivers/platform/x86/wmi-bmof.c +++ b/drivers/platform/x86/wmi-bmof.c @@ -1,17 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * WMI embedded Binary MOF driver * * Copyright (c) 2015 Andrew Lutomirski * Copyright (C) 2017 VMware, Inc. All Rights Reserved. - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 as published - * by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt @@ -54,7 +46,7 @@ read_bmof(struct file *filp, struct kobject *kobj, return count; } -static int wmi_bmof_probe(struct wmi_device *wdev) +static int wmi_bmof_probe(struct wmi_device *wdev, const void *context) { struct bmof_priv *priv; int ret; diff --git a/drivers/platform/x86/wmi.c b/drivers/platform/x86/wmi.c index 7b26b6ccf1a0..dc2e966a5c25 100644 --- a/drivers/platform/x86/wmi.c +++ b/drivers/platform/x86/wmi.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * ACPI-WMI mapping driver * @@ -11,24 +12,6 @@ * WMI bus infrastructure by Andrew Lutomirski and Darren Hart: * Copyright (C) 2015 Andrew Lutomirski * Copyright (C) 2017 VMware, Inc. All Rights Reserved. - * - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or (at - * your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. - * - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt @@ -146,6 +129,28 @@ static bool find_guid(const char *guid_string, struct wmi_block **out) return false; } +static const void *find_guid_context(struct wmi_block *wblock, + struct wmi_driver *wdriver) +{ + const struct wmi_device_id *id; + uuid_le guid_input; + + if (wblock == NULL || wdriver == NULL) + return NULL; + if (wdriver->id_table == NULL) + return NULL; + + id = wdriver->id_table; + while (*id->guid_string) { + if (uuid_le_to_bin(id->guid_string, &guid_input)) + continue; + if (!memcmp(wblock->gblock.guid, &guid_input, 16)) + return id->context; + id++; + } + return NULL; +} + static int get_subobj_info(acpi_handle handle, const char *pathname, struct acpi_device_info **info) { @@ -335,9 +340,7 @@ static acpi_status __query_block(struct wmi_block *wblock, u8 instance, * expensive, but have no corresponding WCxx method. So we * should not fail if this happens. */ - if (acpi_has_method(handle, wc_method)) - wc_status = acpi_execute_simple_method(handle, - wc_method, 1); + wc_status = acpi_execute_simple_method(handle, wc_method, 1); } strcpy(method, "WQ"); @@ -635,6 +638,25 @@ bool wmi_has_guid(const char *guid_string) } EXPORT_SYMBOL_GPL(wmi_has_guid); +/** + * wmi_get_acpi_device_uid() - Get _UID name of ACPI device that defines GUID + * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba + * + * Find the _UID of ACPI device associated with this WMI GUID. + * + * Return: The ACPI _UID field value or NULL if the WMI GUID was not found + */ +char *wmi_get_acpi_device_uid(const char *guid_string) +{ + struct wmi_block *wblock = NULL; + + if (!find_guid(guid_string, &wblock)) + return NULL; + + return acpi_device_uid(wblock->acpi_device); +} +EXPORT_SYMBOL_GPL(wmi_get_acpi_device_uid); + static struct wmi_block *dev_to_wblock(struct device *dev) { return container_of(dev, struct wmi_block, dev.dev); @@ -889,7 +911,7 @@ static const struct file_operations wmi_fops = { .read = wmi_char_read, .open = wmi_char_open, .unlocked_ioctl = wmi_ioctl, - .compat_ioctl = wmi_ioctl, + .compat_ioctl = compat_ptr_ioctl, }; static int wmi_dev_probe(struct device *dev) @@ -904,7 +926,8 @@ static int wmi_dev_probe(struct device *dev) dev_warn(dev, "failed to enable device -- probing anyway\n"); if (wdriver->probe) { - ret = wdriver->probe(dev_to_wdev(dev)); + ret = wdriver->probe(dev_to_wdev(dev), + find_guid_context(wblock, wdriver)); if (ret != 0) goto probe_failure; } diff --git a/drivers/platform/x86/xiaomi-wmi.c b/drivers/platform/x86/xiaomi-wmi.c new file mode 100644 index 000000000000..601cbb282f54 --- /dev/null +++ b/drivers/platform/x86/xiaomi-wmi.c @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: GPL-2.0 +/* WMI driver for Xiaomi Laptops */ + +#include <linux/acpi.h> +#include <linux/input.h> +#include <linux/module.h> +#include <linux/wmi.h> + +#include <uapi/linux/input-event-codes.h> + +#define XIAOMI_KEY_FN_ESC_0 "A2095CCE-0491-44E7-BA27-F8ED8F88AA86" +#define XIAOMI_KEY_FN_ESC_1 "7BBE8E39-B486-473D-BA13-66F75C5805CD" +#define XIAOMI_KEY_FN_FN "409B028D-F06B-4C7C-8BBB-EE133A6BD87E" +#define XIAOMI_KEY_CAPSLOCK "83FE7607-053A-4644-822A-21532C621FC7" +#define XIAOMI_KEY_FN_F7 "76E9027C-95D0-4180-8692-DA6747DD1C2D" + +#define XIAOMI_DEVICE(guid, key) \ + .guid_string = (guid), \ + .context = &(const unsigned int){key} + +struct xiaomi_wmi { + struct input_dev *input_dev; + unsigned int key_code; +}; + +int xiaomi_wmi_probe(struct wmi_device *wdev, const void *context) +{ + struct xiaomi_wmi *data; + + if (wdev == NULL || context == NULL) + return -EINVAL; + + data = devm_kzalloc(&wdev->dev, sizeof(struct xiaomi_wmi), GFP_KERNEL); + if (data == NULL) + return -ENOMEM; + dev_set_drvdata(&wdev->dev, data); + + data->input_dev = devm_input_allocate_device(&wdev->dev); + if (data->input_dev == NULL) + return -ENOMEM; + data->input_dev->name = "Xiaomi WMI keys"; + data->input_dev->phys = "wmi/input0"; + + data->key_code = *((const unsigned int *)context); + set_bit(EV_KEY, data->input_dev->evbit); + set_bit(data->key_code, data->input_dev->keybit); + + return input_register_device(data->input_dev); +} + +void xiaomi_wmi_notify(struct wmi_device *wdev, union acpi_object *dummy) +{ + struct xiaomi_wmi *data; + + if (wdev == NULL) + return; + + data = dev_get_drvdata(&wdev->dev); + if (data == NULL) + return; + + input_report_key(data->input_dev, data->key_code, 1); + input_sync(data->input_dev); + input_report_key(data->input_dev, data->key_code, 0); + input_sync(data->input_dev); +} + +static const struct wmi_device_id xiaomi_wmi_id_table[] = { + // { XIAOMI_DEVICE(XIAOMI_KEY_FN_ESC_0, KEY_FN_ESC) }, + // { XIAOMI_DEVICE(XIAOMI_KEY_FN_ESC_1, KEY_FN_ESC) }, + { XIAOMI_DEVICE(XIAOMI_KEY_FN_FN, KEY_PROG1) }, + // { XIAOMI_DEVICE(XIAOMI_KEY_CAPSLOCK, KEY_CAPSLOCK) }, + { XIAOMI_DEVICE(XIAOMI_KEY_FN_F7, KEY_CUT) }, + + /* Terminating entry */ + { } +}; + +static struct wmi_driver xiaomi_wmi_driver = { + .driver = { + .name = "xiaomi-wmi", + }, + .id_table = xiaomi_wmi_id_table, + .probe = xiaomi_wmi_probe, + .notify = xiaomi_wmi_notify, +}; +module_wmi_driver(xiaomi_wmi_driver); + +MODULE_DEVICE_TABLE(wmi, xiaomi_wmi_id_table); +MODULE_AUTHOR("Mattias Jacobsson"); +MODULE_DESCRIPTION("Xiaomi WMI driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/platform/x86/xo1-rfkill.c b/drivers/platform/x86/xo1-rfkill.c index e46fa9cebc7d..cb3253c10ef3 100644 --- a/drivers/platform/x86/xo1-rfkill.c +++ b/drivers/platform/x86/xo1-rfkill.c @@ -1,12 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * Support for rfkill through the OLPC XO-1 laptop embedded controller * * Copyright (C) 2010 One Laptop per Child - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. */ #include <linux/module.h> diff --git a/drivers/platform/x86/xo15-ebook.c b/drivers/platform/x86/xo15-ebook.c index 49cbccec6e2d..8337c99d2ce2 100644 --- a/drivers/platform/x86/xo15-ebook.c +++ b/drivers/platform/x86/xo15-ebook.c @@ -1,14 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0-or-later /* * OLPC XO-1.5 ebook switch driver * (based on generic ACPI button driver) * * Copyright (C) 2009 Paul Fox <pgf@laptop.org> * Copyright (C) 2010 One Laptop per Child - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or (at - * your option) any later version. */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |