diff options
Diffstat (limited to 'drivers/platform/x86')
24 files changed, 1458 insertions, 980 deletions
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 2c745e8ccad6..9a8f96465cdc 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -36,6 +36,20 @@ config ACER_WMI If you have an ACPI-WMI compatible Acer/ Wistron laptop, say Y or M here. +config ACER_WIRELESS + tristate "Acer Wireless Radio Control Driver" + depends on ACPI + depends on INPUT + ---help--- + The Acer Wireless Radio Control handles the airplane mode hotkey + present on new Acer laptops. + + Say Y or M here if you have an Acer notebook with an airplane mode + hotkey. + + If you choose to compile this driver as a module the module will be + called acer-wireless. + config ACERHDF tristate "Acer Aspire One temperature and fan driver" depends on ACPI && THERMAL @@ -244,6 +258,18 @@ config AMILO_RFKILL This is a driver for enabling wifi on some Fujitsu-Siemens Amilo laptops. +config GPD_POCKET_FAN + tristate "GPD Pocket Fan Controller support" + depends on ACPI + depends on THERMAL + ---help--- + Driver for the GPD Pocket vendor specific FAN02501 ACPI device + which controls the fan speed on the GPD Pocket. + + Without this driver the fan on the Pocket will stay off independent + of the CPU temperature. Say Y or M if the kernel may be used on a + GPD pocket. + config TC1100_WMI tristate "HP Compaq TC1100 Tablet WMI Extras" depends on !X86_64 @@ -812,9 +838,8 @@ config TOSHIBA_WMI config ACPI_CMPC tristate "CMPC Laptop Extras" - depends on ACPI + depends on ACPI && INPUT depends on RFKILL || RFKILL=n - select INPUT select BACKLIGHT_CLASS_DEVICE help Support for Intel Classmate PC ACPI devices, including some @@ -949,7 +974,7 @@ config INTEL_IMR If you are running on a Galileo/Quark say Y here. config INTEL_PMC_CORE - bool "Intel PMC Core driver" + tristate "Intel PMC Core driver" depends on PCI ---help--- The Intel Platform Controller Hub for Intel Core SoCs provides access @@ -958,7 +983,10 @@ config INTEL_PMC_CORE exposed by the Power Management Controller. Supported features: - - SLP_S0_RESIDENCY counter. + - SLP_S0_RESIDENCY counter + - PCH IP Power Gating status + - LTR Ignore + - MPHY/PLL gating status (Sunrisepoint PCH only) config IBM_RTL tristate "Device driver to enable PRTL support" @@ -1131,7 +1159,6 @@ config INTEL_TELEMETRY config MLX_PLATFORM tristate "Mellanox Technologies platform support" - depends on X86_64 ---help--- This option enables system support for the Mellanox Technologies platform. The Mellanox systems provide data center networking @@ -1141,14 +1168,6 @@ config MLX_PLATFORM If you have a Mellanox system, say Y or M here. -config MLX_CPLD_PLATFORM - tristate "Mellanox platform hotplug driver support" - select HWMON - select I2C - ---help--- - This driver handles hot-plug events for the power suppliers, power - cables and fans on the wide range Mellanox IB and Ethernet systems. - config INTEL_TURBO_MAX_3 bool "Intel Turbo Boost Max Technology 3.0 enumeration driver" depends on X86_64 && SCHED_MC_PRIO @@ -1170,6 +1189,17 @@ config SILEAD_DMI with the OS-image for the device. This option supplies the missing information. Enable this for x86 tablets with Silead touchscreens. +config INTEL_CHTDC_TI_PWRBTN + tristate "Intel Cherry Trail Dollar Cove TI power button driver" + depends on INTEL_SOC_PMIC_CHTDC_TI + depends on INPUT + ---help--- + This option adds a power button driver driver for Dollar Cove TI + PMIC on Intel Cherry Trail devices. + + To compile this driver as a module, choose M here: the module + will be called intel_chtdc_ti_pwrbtn. + endif # X86_PLATFORM_DEVICES config PMC_ATOM diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index c32b34a72467..c388608ad2a3 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -23,11 +23,13 @@ obj-$(CONFIG_DELL_WMI_LED) += dell-wmi-led.o obj-$(CONFIG_DELL_SMO8800) += dell-smo8800.o obj-$(CONFIG_DELL_RBTN) += dell-rbtn.o obj-$(CONFIG_ACER_WMI) += acer-wmi.o +obj-$(CONFIG_ACER_WIRELESS) += acer-wireless.o obj-$(CONFIG_ACERHDF) += acerhdf.o obj-$(CONFIG_HP_ACCEL) += hp_accel.o obj-$(CONFIG_HP_WIRELESS) += hp-wireless.o obj-$(CONFIG_HP_WMI) += hp-wmi.o obj-$(CONFIG_AMILO_RFKILL) += amilo-rfkill.o +obj-$(CONFIG_GPD_POCKET_FAN) += gpd-pocket-fan.o obj-$(CONFIG_TC1100_WMI) += tc1100-wmi.o obj-$(CONFIG_SONY_LAPTOP) += sony-laptop.o obj-$(CONFIG_IDEAPAD_LAPTOP) += ideapad-laptop.o @@ -86,5 +88,5 @@ obj-$(CONFIG_INTEL_TELEMETRY) += intel_telemetry_core.o \ obj-$(CONFIG_INTEL_PMC_CORE) += intel_pmc_core.o obj-$(CONFIG_PMC_ATOM) += pmc_atom.o obj-$(CONFIG_MLX_PLATFORM) += mlx-platform.o -obj-$(CONFIG_MLX_CPLD_PLATFORM) += mlxcpld-hotplug.o obj-$(CONFIG_INTEL_TURBO_MAX_3) += intel_turbo_max_3.o +obj-$(CONFIG_INTEL_CHTDC_TI_PWRBTN) += intel_chtdc_ti_pwrbtn.o diff --git a/drivers/platform/x86/acer-wireless.c b/drivers/platform/x86/acer-wireless.c new file mode 100644 index 000000000000..858037987b33 --- /dev/null +++ b/drivers/platform/x86/acer-wireless.c @@ -0,0 +1,71 @@ +/* + * 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> +#include <linux/input.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/pci_ids.h> +#include <linux/types.h> + +static const struct acpi_device_id acer_wireless_acpi_ids[] = { + {"10251229", 0}, + {"", 0}, +}; +MODULE_DEVICE_TABLE(acpi, acer_wireless_acpi_ids); + +static void acer_wireless_notify(struct acpi_device *adev, u32 event) +{ + struct input_dev *idev = acpi_driver_data(adev); + + dev_dbg(&adev->dev, "event=%#x\n", event); + if (event != 0x80) { + dev_notice(&adev->dev, "Unknown SMKB event: %#x\n", event); + return; + } + input_report_key(idev, KEY_RFKILL, 1); + input_report_key(idev, KEY_RFKILL, 0); + input_sync(idev); +} + +static int acer_wireless_add(struct acpi_device *adev) +{ + struct input_dev *idev; + + idev = devm_input_allocate_device(&adev->dev); + if (!idev) + return -ENOMEM; + + adev->driver_data = idev; + idev->name = "Acer Wireless Radio Control"; + idev->phys = "acer-wireless/input0"; + idev->id.bustype = BUS_HOST; + idev->id.vendor = PCI_VENDOR_ID_AI; + idev->id.product = 0x1229; + set_bit(EV_KEY, idev->evbit); + set_bit(KEY_RFKILL, idev->keybit); + + return input_register_device(idev); +} + +static struct acpi_driver acer_wireless_driver = { + .name = "Acer Wireless Radio Control Driver", + .class = "hotkey", + .ids = acer_wireless_acpi_ids, + .ops = { + .add = acer_wireless_add, + .notify = acer_wireless_notify, + }, +}; +module_acpi_driver(acer_wireless_driver); + +MODULE_DESCRIPTION("Acer Wireless Radio Control Driver"); +MODULE_AUTHOR("Chris Chiu <chiu@gmail.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/platform/x86/alienware-wmi.c b/drivers/platform/x86/alienware-wmi.c index 4eb8e1a472b2..9d7dbd925065 100644 --- a/drivers/platform/x86/alienware-wmi.c +++ b/drivers/platform/x86/alienware-wmi.c @@ -68,6 +68,14 @@ struct quirk_entry { static struct quirk_entry *quirks; + +static struct quirk_entry quirk_inspiron5675 = { + .num_zones = 2, + .hdmi_mux = 0, + .amplifier = 0, + .deepslp = 0, +}; + static struct quirk_entry quirk_unknown = { .num_zones = 2, .hdmi_mux = 0, @@ -171,6 +179,15 @@ static const struct dmi_system_id alienware_quirks[] __initconst = { }, .driver_data = &quirk_asm201, }, + { + .callback = dmi_matched, + .ident = "Dell Inc. Inspiron 5675", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron 5675"), + }, + .driver_data = &quirk_inspiron5675, + }, {} }; diff --git a/drivers/platform/x86/apple-gmux.c b/drivers/platform/x86/apple-gmux.c index 623d322447a2..7c4eb86c851e 100644 --- a/drivers/platform/x86/apple-gmux.c +++ b/drivers/platform/x86/apple-gmux.c @@ -24,7 +24,6 @@ #include <linux/delay.h> #include <linux/pci.h> #include <linux/vga_switcheroo.h> -#include <linux/vgaarb.h> #include <acpi/video.h> #include <asm/io.h> @@ -54,7 +53,6 @@ struct apple_gmux_data { bool indexed; struct mutex index_lock; - struct pci_dev *pdev; struct backlight_device *bdev; /* switcheroo data */ @@ -599,23 +597,6 @@ static int gmux_resume(struct device *dev) return 0; } -static struct pci_dev *gmux_get_io_pdev(void) -{ - struct pci_dev *pdev = NULL; - - while ((pdev = pci_get_class(PCI_CLASS_DISPLAY_VGA << 8, pdev))) { - u16 cmd; - - pci_read_config_word(pdev, PCI_COMMAND, &cmd); - if (!(cmd & PCI_COMMAND_IO)) - continue; - - return pdev; - } - - return NULL; -} - static int is_thunderbolt(struct device *dev, void *data) { return to_pci_dev(dev)->is_thunderbolt; @@ -631,7 +612,6 @@ static int gmux_probe(struct pnp_dev *pnp, const struct pnp_device_id *id) int ret = -ENXIO; acpi_status status; unsigned long long gpe; - struct pci_dev *pdev = NULL; if (apple_gmux_data) return -EBUSY; @@ -682,7 +662,7 @@ static int gmux_probe(struct pnp_dev *pnp, const struct pnp_device_id *id) ver_minor = (version >> 16) & 0xff; ver_release = (version >> 8) & 0xff; } else { - pr_info("gmux device not present or IO disabled\n"); + pr_info("gmux device not present\n"); ret = -ENODEV; goto err_release; } @@ -690,23 +670,6 @@ static int gmux_probe(struct pnp_dev *pnp, const struct pnp_device_id *id) pr_info("Found gmux version %d.%d.%d [%s]\n", ver_major, ver_minor, ver_release, (gmux_data->indexed ? "indexed" : "classic")); - /* - * Apple systems with gmux are EFI based and normally don't use - * VGA. In addition changing IO+MEM ownership between IGP and dGPU - * disables IO/MEM used for backlight control on some systems. - * Lock IO+MEM to GPU with active IO to prevent switch. - */ - pdev = gmux_get_io_pdev(); - if (pdev && vga_tryget(pdev, - VGA_RSRC_NORMAL_IO | VGA_RSRC_NORMAL_MEM)) { - pr_err("IO+MEM vgaarb-locking for PCI:%s failed\n", - pci_name(pdev)); - ret = -EBUSY; - goto err_release; - } else if (pdev) - pr_info("locked IO for PCI:%s\n", pci_name(pdev)); - gmux_data->pdev = pdev; - memset(&props, 0, sizeof(props)); props.type = BACKLIGHT_PLATFORM; props.max_brightness = gmux_read32(gmux_data, GMUX_PORT_MAX_BRIGHTNESS); @@ -822,10 +785,6 @@ err_enable_gpe: err_notify: backlight_device_unregister(bdev); err_release: - if (gmux_data->pdev) - vga_put(gmux_data->pdev, - VGA_RSRC_NORMAL_IO | VGA_RSRC_NORMAL_MEM); - pci_dev_put(pdev); release_region(gmux_data->iostart, gmux_data->iolen); err_free: kfree(gmux_data); @@ -845,11 +804,6 @@ static void gmux_remove(struct pnp_dev *pnp) &gmux_notify_handler); } - if (gmux_data->pdev) { - vga_put(gmux_data->pdev, - VGA_RSRC_NORMAL_IO | VGA_RSRC_NORMAL_MEM); - pci_dev_put(gmux_data->pdev); - } backlight_device_unregister(gmux_data->bdev); release_region(gmux_data->iostart, gmux_data->iolen); diff --git a/drivers/platform/x86/asus-nb-wmi.c b/drivers/platform/x86/asus-nb-wmi.c index 5269a01d9bdd..136ff2b4cce5 100644 --- a/drivers/platform/x86/asus-nb-wmi.c +++ b/drivers/platform/x86/asus-nb-wmi.c @@ -111,7 +111,7 @@ static struct quirk_entry quirk_asus_x550lb = { .xusb2pr = 0x01D9, }; -static struct quirk_entry quirk_asus_ux330uak = { +static struct quirk_entry quirk_asus_forceals = { .wmi_force_als_set = true, }; @@ -387,7 +387,7 @@ static const struct dmi_system_id asus_quirks[] = { DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), DMI_MATCH(DMI_PRODUCT_NAME, "UX330UAK"), }, - .driver_data = &quirk_asus_ux330uak, + .driver_data = &quirk_asus_forceals, }, { .callback = dmi_matched, @@ -398,6 +398,15 @@ static const struct dmi_system_id asus_quirks[] = { }, .driver_data = &quirk_asus_x550lb, }, + { + .callback = dmi_matched, + .ident = "ASUSTeK COMPUTER INC. UX430UQ", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), + DMI_MATCH(DMI_PRODUCT_NAME, "UX430UQ"), + }, + .driver_data = &quirk_asus_forceals, + }, {}, }; diff --git a/drivers/platform/x86/dell-laptop.c b/drivers/platform/x86/dell-laptop.c index cd4725e7e0b5..a7b141992cb3 100644 --- a/drivers/platform/x86/dell-laptop.c +++ b/drivers/platform/x86/dell-laptop.c @@ -36,10 +36,10 @@ #include "dell-smbios.h" struct quirk_entry { - u8 touchpad_led; - u8 kbd_led_levels_off_1; + bool touchpad_led; + bool kbd_led_levels_off_1; - int needs_kbd_timeouts; + bool needs_kbd_timeouts; /* * Ordered list of timeouts expressed in seconds. * The list must end with -1 @@ -50,7 +50,7 @@ struct quirk_entry { static struct quirk_entry *quirks; static struct quirk_entry quirk_dell_vostro_v130 = { - .touchpad_led = 1, + .touchpad_led = true, }; static int __init dmi_matched(const struct dmi_system_id *dmi) @@ -64,12 +64,12 @@ static int __init dmi_matched(const struct dmi_system_id *dmi) * is used then BIOS silently set timeout to 0 without any error message. */ static struct quirk_entry quirk_dell_xps13_9333 = { - .needs_kbd_timeouts = 1, + .needs_kbd_timeouts = true, .kbd_timeouts = { 0, 5, 15, 60, 5 * 60, 15 * 60, -1 }, }; static struct quirk_entry quirk_dell_latitude_e6410 = { - .kbd_led_levels_off_1 = 1, + .kbd_led_levels_off_1 = true, }; static struct platform_driver platform_driver = { @@ -78,7 +78,6 @@ static struct platform_driver platform_driver = { } }; -static struct calling_interface_buffer *buffer; static struct platform_device *platform_device; static struct backlight_device *dell_backlight_device; static struct rfkill *wifi_rfkill; @@ -110,6 +109,42 @@ static const struct dmi_system_id dell_device_table[] __initconst = { }, }, { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_CHASSIS_TYPE, "30"), /*Tablet*/ + }, + }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_CHASSIS_TYPE, "31"), /*Convertible*/ + }, + }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_CHASSIS_TYPE, "32"), /*Detachable*/ + }, + }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_CHASSIS_TYPE, "30"), /*Tablet*/ + }, + }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_CHASSIS_TYPE, "31"), /*Convertible*/ + }, + }, + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_CHASSIS_TYPE, "32"), /*Detachable*/ + }, + }, + { .ident = "Dell Computer Corporation", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer Corporation"), @@ -286,7 +321,8 @@ static const struct dmi_system_id dell_quirks[] __initconst = { { } }; -void dell_set_arguments(u32 arg0, u32 arg1, u32 arg2, u32 arg3) +static void dell_fill_request(struct calling_interface_buffer *buffer, + u32 arg0, u32 arg1, u32 arg2, u32 arg3) { memset(buffer, 0, sizeof(struct calling_interface_buffer)); buffer->input[0] = arg0; @@ -295,7 +331,8 @@ void dell_set_arguments(u32 arg0, u32 arg1, u32 arg2, u32 arg3) buffer->input[3] = arg3; } -int dell_send_request(u16 class, u16 select) +static int dell_send_request(struct calling_interface_buffer *buffer, + u16 class, u16 select) { int ret; @@ -432,21 +469,22 @@ static int dell_rfkill_set(void *data, bool blocked) int disable = blocked ? 1 : 0; unsigned long radio = (unsigned long)data; int hwswitch_bit = (unsigned long)data - 1; + struct calling_interface_buffer buffer; int hwswitch; int status; int ret; - dell_set_arguments(0, 0, 0, 0); - ret = dell_send_request(CLASS_INFO, SELECT_RFKILL); + dell_fill_request(&buffer, 0, 0, 0, 0); + ret = dell_send_request(&buffer, CLASS_INFO, SELECT_RFKILL); if (ret) return ret; - status = buffer->output[1]; + status = buffer.output[1]; - dell_set_arguments(0x2, 0, 0, 0); - ret = dell_send_request(CLASS_INFO, SELECT_RFKILL); + dell_fill_request(&buffer, 0x2, 0, 0, 0); + ret = dell_send_request(&buffer, CLASS_INFO, SELECT_RFKILL); if (ret) return ret; - hwswitch = buffer->output[1]; + hwswitch = buffer.output[1]; /* If the hardware switch controls this radio, and the hardware switch is disabled, always disable the radio */ @@ -454,8 +492,8 @@ static int dell_rfkill_set(void *data, bool blocked) (status & BIT(0)) && !(status & BIT(16))) disable = 1; - dell_set_arguments(1 | (radio<<8) | (disable << 16), 0, 0, 0); - ret = dell_send_request(CLASS_INFO, SELECT_RFKILL); + dell_fill_request(&buffer, 1 | (radio<<8) | (disable << 16), 0, 0, 0); + ret = dell_send_request(&buffer, CLASS_INFO, SELECT_RFKILL); return ret; } @@ -464,9 +502,11 @@ static void dell_rfkill_update_sw_state(struct rfkill *rfkill, int radio, { if (status & BIT(0)) { /* Has hw-switch, sync sw_state to BIOS */ + struct calling_interface_buffer buffer; int block = rfkill_blocked(rfkill); - dell_set_arguments(1 | (radio << 8) | (block << 16), 0, 0, 0); - dell_send_request(CLASS_INFO, SELECT_RFKILL); + dell_fill_request(&buffer, + 1 | (radio << 8) | (block << 16), 0, 0, 0); + dell_send_request(&buffer, CLASS_INFO, SELECT_RFKILL); } else { /* No hw-switch, sync BIOS state to sw_state */ rfkill_set_sw_state(rfkill, !!(status & BIT(radio + 16))); @@ -483,21 +523,22 @@ static void dell_rfkill_update_hw_state(struct rfkill *rfkill, int radio, static void dell_rfkill_query(struct rfkill *rfkill, void *data) { int radio = ((unsigned long)data & 0xF); + struct calling_interface_buffer buffer; int hwswitch; int status; int ret; - dell_set_arguments(0, 0, 0, 0); - ret = dell_send_request(CLASS_INFO, SELECT_RFKILL); - status = buffer->output[1]; + dell_fill_request(&buffer, 0, 0, 0, 0); + ret = dell_send_request(&buffer, CLASS_INFO, SELECT_RFKILL); + status = buffer.output[1]; if (ret != 0 || !(status & BIT(0))) { return; } - dell_set_arguments(0, 0x2, 0, 0); - ret = dell_send_request(CLASS_INFO, SELECT_RFKILL); - hwswitch = buffer->output[1]; + dell_fill_request(&buffer, 0, 0x2, 0, 0); + ret = dell_send_request(&buffer, CLASS_INFO, SELECT_RFKILL); + hwswitch = buffer.output[1]; if (ret != 0) return; @@ -514,22 +555,23 @@ static struct dentry *dell_laptop_dir; static int dell_debugfs_show(struct seq_file *s, void *data) { + struct calling_interface_buffer buffer; int hwswitch_state; int hwswitch_ret; int status; int ret; - dell_set_arguments(0, 0, 0, 0); - ret = dell_send_request(CLASS_INFO, SELECT_RFKILL); + dell_fill_request(&buffer, 0, 0, 0, 0); + ret = dell_send_request(&buffer, CLASS_INFO, SELECT_RFKILL); if (ret) return ret; - status = buffer->output[1]; + status = buffer.output[1]; - dell_set_arguments(0, 0x2, 0, 0); - hwswitch_ret = dell_send_request(CLASS_INFO, SELECT_RFKILL); + dell_fill_request(&buffer, 0, 0x2, 0, 0); + hwswitch_ret = dell_send_request(&buffer, CLASS_INFO, SELECT_RFKILL); if (hwswitch_ret) return hwswitch_ret; - hwswitch_state = buffer->output[1]; + hwswitch_state = buffer.output[1]; seq_printf(s, "return:\t%d\n", ret); seq_printf(s, "status:\t0x%X\n", status); @@ -610,22 +652,23 @@ static const struct file_operations dell_debugfs_fops = { static void dell_update_rfkill(struct work_struct *ignored) { + struct calling_interface_buffer buffer; int hwswitch = 0; int status; int ret; - dell_set_arguments(0, 0, 0, 0); - ret = dell_send_request(CLASS_INFO, SELECT_RFKILL); - status = buffer->output[1]; + dell_fill_request(&buffer, 0, 0, 0, 0); + ret = dell_send_request(&buffer, CLASS_INFO, SELECT_RFKILL); + status = buffer.output[1]; if (ret != 0) return; - dell_set_arguments(0, 0x2, 0, 0); - ret = dell_send_request(CLASS_INFO, SELECT_RFKILL); + dell_fill_request(&buffer, 0, 0x2, 0, 0); + ret = dell_send_request(&buffer, CLASS_INFO, SELECT_RFKILL); if (ret == 0 && (status & BIT(0))) - hwswitch = buffer->output[1]; + hwswitch = buffer.output[1]; if (wifi_rfkill) { dell_rfkill_update_hw_state(wifi_rfkill, 1, status, hwswitch); @@ -683,6 +726,7 @@ static struct notifier_block dell_laptop_rbtn_notifier = { static int __init dell_setup_rfkill(void) { + struct calling_interface_buffer buffer; int status, ret, whitelisted; const char *product; @@ -698,9 +742,9 @@ static int __init dell_setup_rfkill(void) if (!force_rfkill && !whitelisted) return 0; - dell_set_arguments(0, 0, 0, 0); - ret = dell_send_request(CLASS_INFO, SELECT_RFKILL); - status = buffer->output[1]; + dell_fill_request(&buffer, 0, 0, 0, 0); + ret = dell_send_request(&buffer, CLASS_INFO, SELECT_RFKILL); + status = buffer.output[1]; /* dell wireless info smbios call is not supported */ if (ret != 0) @@ -853,6 +897,7 @@ static void dell_cleanup_rfkill(void) static int dell_send_intensity(struct backlight_device *bd) { + struct calling_interface_buffer buffer; struct calling_interface_token *token; int ret; @@ -860,17 +905,21 @@ static int dell_send_intensity(struct backlight_device *bd) if (!token) return -ENODEV; - dell_set_arguments(token->location, bd->props.brightness, 0, 0); + dell_fill_request(&buffer, + token->location, bd->props.brightness, 0, 0); if (power_supply_is_system_supplied() > 0) - ret = dell_send_request(CLASS_TOKEN_WRITE, SELECT_TOKEN_AC); + ret = dell_send_request(&buffer, + CLASS_TOKEN_WRITE, SELECT_TOKEN_AC); else - ret = dell_send_request(CLASS_TOKEN_WRITE, SELECT_TOKEN_BAT); + ret = dell_send_request(&buffer, + CLASS_TOKEN_WRITE, SELECT_TOKEN_BAT); return ret; } static int dell_get_intensity(struct backlight_device *bd) { + struct calling_interface_buffer buffer; struct calling_interface_token *token; int ret; @@ -878,14 +927,17 @@ static int dell_get_intensity(struct backlight_device *bd) if (!token) return -ENODEV; - dell_set_arguments(token->location, 0, 0, 0); + dell_fill_request(&buffer, token->location, 0, 0, 0); if (power_supply_is_system_supplied() > 0) - ret = dell_send_request(CLASS_TOKEN_READ, SELECT_TOKEN_AC); + ret = dell_send_request(&buffer, + CLASS_TOKEN_READ, SELECT_TOKEN_AC); else - ret = dell_send_request(CLASS_TOKEN_READ, SELECT_TOKEN_BAT); + ret = dell_send_request(&buffer, + CLASS_TOKEN_READ, SELECT_TOKEN_BAT); if (ret == 0) - ret = buffer->output[1]; + ret = buffer.output[1]; + return ret; } @@ -1133,6 +1185,7 @@ static u8 kbd_previous_mode_bit; static bool kbd_led_present; static DEFINE_MUTEX(kbd_led_mutex); +static enum led_brightness kbd_led_level; /* * NOTE: there are three ways to set the keyboard backlight level. @@ -1149,31 +1202,33 @@ static DEFINE_MUTEX(kbd_led_mutex); static int kbd_get_info(struct kbd_info *info) { + struct calling_interface_buffer buffer; u8 units; int ret; - dell_set_arguments(0, 0, 0, 0); - ret = dell_send_request(CLASS_KBD_BACKLIGHT, SELECT_KBD_BACKLIGHT); + dell_fill_request(&buffer, 0, 0, 0, 0); + ret = dell_send_request(&buffer, + CLASS_KBD_BACKLIGHT, SELECT_KBD_BACKLIGHT); if (ret) return ret; - info->modes = buffer->output[1] & 0xFFFF; - info->type = (buffer->output[1] >> 24) & 0xFF; - info->triggers = buffer->output[2] & 0xFF; - units = (buffer->output[2] >> 8) & 0xFF; - info->levels = (buffer->output[2] >> 16) & 0xFF; + info->modes = buffer.output[1] & 0xFFFF; + info->type = (buffer.output[1] >> 24) & 0xFF; + info->triggers = buffer.output[2] & 0xFF; + units = (buffer.output[2] >> 8) & 0xFF; + info->levels = (buffer.output[2] >> 16) & 0xFF; if (quirks && quirks->kbd_led_levels_off_1 && info->levels) info->levels--; if (units & BIT(0)) - info->seconds = (buffer->output[3] >> 0) & 0xFF; + info->seconds = (buffer.output[3] >> 0) & 0xFF; if (units & BIT(1)) - info->minutes = (buffer->output[3] >> 8) & 0xFF; + info->minutes = (buffer.output[3] >> 8) & 0xFF; if (units & BIT(2)) - info->hours = (buffer->output[3] >> 16) & 0xFF; + info->hours = (buffer.output[3] >> 16) & 0xFF; if (units & BIT(3)) - info->days = (buffer->output[3] >> 24) & 0xFF; + info->days = (buffer.output[3] >> 24) & 0xFF; return ret; } @@ -1233,31 +1288,34 @@ static int kbd_set_level(struct kbd_state *state, u8 level) static int kbd_get_state(struct kbd_state *state) { + struct calling_interface_buffer buffer; int ret; - dell_set_arguments(0x1, 0, 0, 0); - ret = dell_send_request(CLASS_KBD_BACKLIGHT, SELECT_KBD_BACKLIGHT); + dell_fill_request(&buffer, 0, 0, 0, 0); + ret = dell_send_request(&buffer, + CLASS_KBD_BACKLIGHT, SELECT_KBD_BACKLIGHT); if (ret) return ret; - state->mode_bit = ffs(buffer->output[1] & 0xFFFF); + state->mode_bit = ffs(buffer.output[1] & 0xFFFF); if (state->mode_bit != 0) state->mode_bit--; - state->triggers = (buffer->output[1] >> 16) & 0xFF; - state->timeout_value = (buffer->output[1] >> 24) & 0x3F; - state->timeout_unit = (buffer->output[1] >> 30) & 0x3; - state->als_setting = buffer->output[2] & 0xFF; - state->als_value = (buffer->output[2] >> 8) & 0xFF; - state->level = (buffer->output[2] >> 16) & 0xFF; - state->timeout_value_ac = (buffer->output[2] >> 24) & 0x3F; - state->timeout_unit_ac = (buffer->output[2] >> 30) & 0x3; + state->triggers = (buffer.output[1] >> 16) & 0xFF; + state->timeout_value = (buffer.output[1] >> 24) & 0x3F; + state->timeout_unit = (buffer.output[1] >> 30) & 0x3; + state->als_setting = buffer.output[2] & 0xFF; + state->als_value = (buffer.output[2] >> 8) & 0xFF; + state->level = (buffer.output[2] >> 16) & 0xFF; + state->timeout_value_ac = (buffer.output[2] >> 24) & 0x3F; + state->timeout_unit_ac = (buffer.output[2] >> 30) & 0x3; return ret; } static int kbd_set_state(struct kbd_state *state) { + struct calling_interface_buffer buffer; int ret; u32 input1; u32 input2; @@ -1270,8 +1328,9 @@ static int kbd_set_state(struct kbd_state *state) input2 |= (state->level & 0xFF) << 16; input2 |= (state->timeout_value_ac & 0x3F) << 24; input2 |= (state->timeout_unit_ac & 0x3) << 30; - dell_set_arguments(0x2, input1, input2, 0); - ret = dell_send_request(CLASS_KBD_BACKLIGHT, SELECT_KBD_BACKLIGHT); + dell_fill_request(&buffer, 0x2, input1, input2, 0); + ret = dell_send_request(&buffer, + CLASS_KBD_BACKLIGHT, SELECT_KBD_BACKLIGHT); return ret; } @@ -1298,6 +1357,7 @@ static int kbd_set_state_safe(struct kbd_state *state, struct kbd_state *old) static int kbd_set_token_bit(u8 bit) { + struct calling_interface_buffer buffer; struct calling_interface_token *token; int ret; @@ -1308,14 +1368,15 @@ static int kbd_set_token_bit(u8 bit) if (!token) return -EINVAL; - dell_set_arguments(token->location, token->value, 0, 0); - ret = dell_send_request(CLASS_TOKEN_WRITE, SELECT_TOKEN_STD); + dell_fill_request(&buffer, token->location, token->value, 0, 0); + ret = dell_send_request(&buffer, CLASS_TOKEN_WRITE, SELECT_TOKEN_STD); return ret; } static int kbd_get_token_bit(u8 bit) { + struct calling_interface_buffer buffer; struct calling_interface_token *token; int ret; int val; @@ -1327,9 +1388,9 @@ static int kbd_get_token_bit(u8 bit) if (!token) return -EINVAL; - dell_set_arguments(token->location, 0, 0, 0); - ret = dell_send_request(CLASS_TOKEN_READ, SELECT_TOKEN_STD); - val = buffer->output[1]; + dell_fill_request(&buffer, token->location, 0, 0, 0); + ret = dell_send_request(&buffer, CLASS_TOKEN_READ, SELECT_TOKEN_STD); + val = buffer.output[1]; if (ret) return ret; @@ -1947,6 +2008,7 @@ static enum led_brightness kbd_led_level_get(struct led_classdev *led_cdev) static int kbd_led_level_set(struct led_classdev *led_cdev, enum led_brightness value) { + enum led_brightness new_value = value; struct kbd_state state; struct kbd_state new_state; u16 num; @@ -1976,6 +2038,9 @@ static int kbd_led_level_set(struct led_classdev *led_cdev, } out: + if (ret == 0) + kbd_led_level = new_value; + mutex_unlock(&kbd_led_mutex); return ret; } @@ -2003,6 +2068,9 @@ static int __init kbd_led_init(struct device *dev) if (kbd_led.max_brightness) kbd_led.max_brightness--; } + + kbd_led_level = kbd_led_level_get(NULL); + ret = led_classdev_register(dev, &kbd_led); if (ret) kbd_led_present = false; @@ -2027,13 +2095,25 @@ static void kbd_led_exit(void) static int dell_laptop_notifier_call(struct notifier_block *nb, unsigned long action, void *data) { + bool changed = false; + enum led_brightness new_kbd_led_level; + switch (action) { case DELL_LAPTOP_KBD_BACKLIGHT_BRIGHTNESS_CHANGED: if (!kbd_led_present) break; - led_classdev_notify_brightness_hw_changed(&kbd_led, - kbd_led_level_get(&kbd_led)); + mutex_lock(&kbd_led_mutex); + new_kbd_led_level = kbd_led_level_get(&kbd_led); + if (kbd_led_level != new_kbd_led_level) { + kbd_led_level = new_kbd_led_level; + changed = true; + } + mutex_unlock(&kbd_led_mutex); + + if (changed) + led_classdev_notify_brightness_hw_changed(&kbd_led, + kbd_led_level); break; } @@ -2046,6 +2126,7 @@ static struct notifier_block dell_laptop_notifier = { int dell_micmute_led_set(int state) { + struct calling_interface_buffer buffer; struct calling_interface_token *token; if (state == 0) @@ -2058,8 +2139,8 @@ int dell_micmute_led_set(int state) if (!token) return -ENODEV; - dell_set_arguments(token->location, token->value, 0, 0); - dell_send_request(CLASS_TOKEN_WRITE, SELECT_TOKEN_STD); + dell_fill_request(&buffer, token->location, token->value, 0, 0); + dell_send_request(&buffer, CLASS_TOKEN_WRITE, SELECT_TOKEN_STD); return state; } @@ -2090,13 +2171,6 @@ static int __init dell_init(void) if (ret) goto fail_platform_device2; - buffer = kzalloc(sizeof(struct calling_interface_buffer), GFP_KERNEL); - if (!buffer) { - ret = -ENOMEM; - goto fail_buffer; - } - - ret = dell_setup_rfkill(); if (ret) { @@ -2121,10 +2195,13 @@ static int __init dell_init(void) token = dell_smbios_find_token(BRIGHTNESS_TOKEN); if (token) { - dell_set_arguments(token->location, 0, 0, 0); - ret = dell_send_request(CLASS_TOKEN_READ, SELECT_TOKEN_AC); + struct calling_interface_buffer buffer; + + dell_fill_request(&buffer, token->location, 0, 0, 0); + ret = dell_send_request(&buffer, + CLASS_TOKEN_READ, SELECT_TOKEN_AC); if (ret) - max_intensity = buffer->output[3]; + max_intensity = buffer.output[3]; } if (max_intensity) { @@ -2158,8 +2235,6 @@ static int __init dell_init(void) fail_get_brightness: backlight_device_unregister(dell_backlight_device); fail_backlight: - kfree(buffer); -fail_buffer: dell_cleanup_rfkill(); fail_rfkill: platform_device_del(platform_device); @@ -2179,7 +2254,6 @@ static void __exit dell_exit(void) touchpad_led_exit(); kbd_led_exit(); backlight_device_unregister(dell_backlight_device); - kfree(buffer); dell_cleanup_rfkill(); if (platform_device) { platform_device_unregister(platform_device); diff --git a/drivers/platform/x86/dell-smbios.c b/drivers/platform/x86/dell-smbios.c index 6a60db515bda..8541cde4cb7d 100644 --- a/drivers/platform/x86/dell-smbios.c +++ b/drivers/platform/x86/dell-smbios.c @@ -65,10 +65,10 @@ static struct smbios_call call_whitelist[] = { /* calls that are explicitly blacklisted */ static struct smbios_call call_blacklist[] = { - {0x0000, 01, 07}, /* manufacturing use */ - {0x0000, 06, 05}, /* manufacturing use */ - {0x0000, 11, 03}, /* write once */ - {0x0000, 11, 07}, /* write once */ + {0x0000, 1, 7}, /* manufacturing use */ + {0x0000, 6, 5}, /* manufacturing use */ + {0x0000, 11, 3}, /* write once */ + {0x0000, 11, 7}, /* write once */ {0x0000, 11, 11}, /* write once */ {0x0000, 19, -1}, /* diagnostics */ /* handled by kernel: dell-laptop */ diff --git a/drivers/platform/x86/dell-wmi.c b/drivers/platform/x86/dell-wmi.c index fb25b20df316..2c9927430d85 100644 --- a/drivers/platform/x86/dell-wmi.c +++ b/drivers/platform/x86/dell-wmi.c @@ -261,6 +261,9 @@ static const u16 bios_to_linux_keycode[256] = { * override them. */ static const struct key_entry dell_wmi_keymap_type_0010[] = { + /* Mic mute */ + { KE_KEY, 0x150, { KEY_MICMUTE } }, + /* Fn-lock */ { KE_IGNORE, 0x151, { KEY_RESERVED } }, diff --git a/drivers/platform/x86/gpd-pocket-fan.c b/drivers/platform/x86/gpd-pocket-fan.c new file mode 100644 index 000000000000..2d645c505f81 --- /dev/null +++ b/drivers/platform/x86/gpd-pocket-fan.c @@ -0,0 +1,216 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * GPD Pocket fan controller driver + * + * Copyright (C) 2017 Hans de Goede <hdegoede@redhat.com> + */ + +#include <linux/acpi.h> +#include <linux/gpio/consumer.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/thermal.h> +#include <linux/workqueue.h> + +#define MAX_SPEED 3 + +static int temp_limits[3] = { 55000, 60000, 65000 }; +module_param_array(temp_limits, int, NULL, 0444); +MODULE_PARM_DESC(temp_limits, + "Milli-celcius values above which the fan speed increases"); + +static int hysteresis = 3000; +module_param(hysteresis, int, 0444); +MODULE_PARM_DESC(hysteresis, + "Hysteresis in milli-celcius before lowering the fan speed"); + +static int speed_on_ac = 2; +module_param(speed_on_ac, int, 0444); +MODULE_PARM_DESC(speed_on_ac, + "minimum fan speed to allow when system is powered by AC"); + +struct gpd_pocket_fan_data { + struct device *dev; + struct thermal_zone_device *dts0; + struct thermal_zone_device *dts1; + struct gpio_desc *gpio0; + struct gpio_desc *gpio1; + struct delayed_work work; + int last_speed; +}; + +static void gpd_pocket_fan_set_speed(struct gpd_pocket_fan_data *fan, int speed) +{ + if (speed == fan->last_speed) + return; + + gpiod_direction_output(fan->gpio0, !!(speed & 1)); + gpiod_direction_output(fan->gpio1, !!(speed & 2)); + + fan->last_speed = speed; +} + +static int gpd_pocket_fan_min_speed(void) +{ + if (power_supply_is_system_supplied()) + return speed_on_ac; + else + return 0; +} + +static void gpd_pocket_fan_worker(struct work_struct *work) +{ + struct gpd_pocket_fan_data *fan = + container_of(work, struct gpd_pocket_fan_data, work.work); + int t0, t1, temp, speed, min_speed, i; + + if (thermal_zone_get_temp(fan->dts0, &t0) || + thermal_zone_get_temp(fan->dts1, &t1)) { + dev_warn(fan->dev, "Error getting temperature\n"); + speed = MAX_SPEED; + goto set_speed; + } + + temp = max(t0, t1); + + speed = fan->last_speed; + min_speed = gpd_pocket_fan_min_speed(); + + /* Determine minimum speed */ + for (i = min_speed; i < ARRAY_SIZE(temp_limits); i++) { + if (temp < temp_limits[i]) + break; + } + if (speed < i) + speed = i; + + /* Use hysteresis before lowering speed again */ + for (i = min_speed; i < ARRAY_SIZE(temp_limits); i++) { + if (temp <= (temp_limits[i] - hysteresis)) + break; + } + if (speed > i) + speed = i; + + if (fan->last_speed <= 0 && speed) + speed = MAX_SPEED; /* kick start motor */ + +set_speed: + gpd_pocket_fan_set_speed(fan, speed); + + /* When mostly idle (low temp/speed), slow down the poll interval. */ + queue_delayed_work(system_wq, &fan->work, + msecs_to_jiffies(4000 / (speed + 1))); +} + +static void gpd_pocket_fan_force_update(struct gpd_pocket_fan_data *fan) +{ + fan->last_speed = -1; + mod_delayed_work(system_wq, &fan->work, 0); +} + +static int gpd_pocket_fan_probe(struct platform_device *pdev) +{ + struct gpd_pocket_fan_data *fan; + int i; + + for (i = 0; i < ARRAY_SIZE(temp_limits); i++) { + if (temp_limits[i] < 40000 || temp_limits[i] > 70000) { + dev_err(&pdev->dev, "Invalid temp-limit %d (must be between 40000 and 70000)\n", + temp_limits[i]); + return -EINVAL; + } + } + if (hysteresis < 1000 || hysteresis > 10000) { + dev_err(&pdev->dev, "Invalid hysteresis %d (must be between 1000 and 10000)\n", + hysteresis); + return -EINVAL; + } + if (speed_on_ac < 0 || speed_on_ac > MAX_SPEED) { + dev_err(&pdev->dev, "Invalid speed_on_ac %d (must be between 0 and 3)\n", + speed_on_ac); + return -EINVAL; + } + + fan = devm_kzalloc(&pdev->dev, sizeof(*fan), GFP_KERNEL); + if (!fan) + return -ENOMEM; + + fan->dev = &pdev->dev; + INIT_DELAYED_WORK(&fan->work, gpd_pocket_fan_worker); + + /* Note this returns a "weak" reference which we don't need to free */ + fan->dts0 = thermal_zone_get_zone_by_name("soc_dts0"); + if (IS_ERR(fan->dts0)) + return -EPROBE_DEFER; + + fan->dts1 = thermal_zone_get_zone_by_name("soc_dts1"); + if (IS_ERR(fan->dts1)) + return -EPROBE_DEFER; + + fan->gpio0 = devm_gpiod_get_index(fan->dev, NULL, 0, GPIOD_ASIS); + if (IS_ERR(fan->gpio0)) + return PTR_ERR(fan->gpio0); + + fan->gpio1 = devm_gpiod_get_index(fan->dev, NULL, 1, GPIOD_ASIS); + if (IS_ERR(fan->gpio1)) + return PTR_ERR(fan->gpio1); + + gpd_pocket_fan_force_update(fan); + + platform_set_drvdata(pdev, fan); + return 0; +} + +static int gpd_pocket_fan_remove(struct platform_device *pdev) +{ + struct gpd_pocket_fan_data *fan = platform_get_drvdata(pdev); + + cancel_delayed_work_sync(&fan->work); + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int gpd_pocket_fan_suspend(struct device *dev) +{ + struct gpd_pocket_fan_data *fan = dev_get_drvdata(dev); + + cancel_delayed_work_sync(&fan->work); + gpd_pocket_fan_set_speed(fan, gpd_pocket_fan_min_speed()); + return 0; +} + +static int gpd_pocket_fan_resume(struct device *dev) +{ + struct gpd_pocket_fan_data *fan = dev_get_drvdata(dev); + + gpd_pocket_fan_force_update(fan); + return 0; +} +#endif +static SIMPLE_DEV_PM_OPS(gpd_pocket_fan_pm_ops, + gpd_pocket_fan_suspend, + gpd_pocket_fan_resume); + +static struct acpi_device_id gpd_pocket_fan_acpi_match[] = { + { "FAN02501" }, + {}, +}; +MODULE_DEVICE_TABLE(acpi, gpd_pocket_fan_acpi_match); + +static struct platform_driver gpd_pocket_fan_driver = { + .probe = gpd_pocket_fan_probe, + .remove = gpd_pocket_fan_remove, + .driver = { + .name = "gpd_pocket_fan", + .acpi_match_table = gpd_pocket_fan_acpi_match, + .pm = &gpd_pocket_fan_pm_ops, + }, +}; + +module_platform_driver(gpd_pocket_fan_driver); +MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com"); +MODULE_DESCRIPTION("GPD pocket fan driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/ideapad-laptop.c b/drivers/platform/x86/ideapad-laptop.c index 53ab4e0f8962..b2bbddd09a52 100644 --- a/drivers/platform/x86/ideapad-laptop.c +++ b/drivers/platform/x86/ideapad-laptop.c @@ -124,10 +124,10 @@ static int read_method_int(acpi_handle handle, const char *method, int *val) if (ACPI_FAILURE(status)) { *val = -1; return -1; - } else { - *val = result; - return 0; } + *val = result; + return 0; + } static int method_gbmd(acpi_handle handle, unsigned long *ret) @@ -164,10 +164,10 @@ static int method_vpcr(acpi_handle handle, int cmd, int *ret) if (ACPI_FAILURE(status)) { *ret = -1; return -1; - } else { - *ret = result; - return 0; } + *ret = result; + return 0; + } static int method_vpcw(acpi_handle handle, int cmd, int data) @@ -231,7 +231,7 @@ static int write_ec_cmd(acpi_handle handle, int cmd, unsigned long data) if (val == 0) return 0; } - pr_err("timeout in write_ec_cmd\n"); + pr_err("timeout in %s\n", __func__); return -1; } @@ -964,6 +964,13 @@ static void ideapad_wmi_notify(u32 value, void *context) */ 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_BOARD_NAME, "80WW"), + }, + }, + { .ident = "Lenovo G40-30", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), @@ -1104,6 +1111,13 @@ static const struct dmi_system_id no_hw_rfkill_list[] = { }, }, { + .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"), diff --git a/drivers/platform/x86/intel-hid.c b/drivers/platform/x86/intel-hid.c index f470279c4c10..d1a01311c1a2 100644 --- a/drivers/platform/x86/intel-hid.c +++ b/drivers/platform/x86/intel-hid.c @@ -25,6 +25,7 @@ #include <linux/acpi.h> #include <linux/suspend.h> #include <acpi/acpi_bus.h> +#include <linux/dmi.h> MODULE_LICENSE("GPL"); MODULE_AUTHOR("Alex Hung"); @@ -73,6 +74,24 @@ static const struct key_entry intel_array_keymap[] = { { KE_END }, }; +static const struct dmi_system_id button_array_table[] = { + { + .ident = "Wacom MobileStudio Pro 13", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Wacom Co.,Ltd"), + DMI_MATCH(DMI_PRODUCT_NAME, "Wacom MobileStudio Pro 13"), + }, + }, + { + .ident = "Wacom MobileStudio Pro 16", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Wacom Co.,Ltd"), + DMI_MATCH(DMI_PRODUCT_NAME, "Wacom MobileStudio Pro 16"), + }, + }, + { } +}; + struct intel_hid_priv { struct input_dev *input_dev; struct input_dev *array; @@ -263,10 +282,27 @@ wakeup: ev_index); } +static bool button_array_present(struct platform_device *device) +{ + acpi_handle handle = ACPI_HANDLE(&device->dev); + unsigned long long event_cap; + acpi_status status; + bool supported = false; + + status = acpi_evaluate_integer(handle, "HEBC", NULL, &event_cap); + if (ACPI_SUCCESS(status) && (event_cap & 0x20000)) + supported = true; + + if (dmi_check_system(button_array_table)) + supported = true; + + return supported; +} + static int intel_hid_probe(struct platform_device *device) { acpi_handle handle = ACPI_HANDLE(&device->dev); - unsigned long long event_cap, mode; + unsigned long long mode; struct intel_hid_priv *priv; acpi_status status; int err; @@ -299,8 +335,7 @@ static int intel_hid_probe(struct platform_device *device) } /* Setup 5 button array */ - status = acpi_evaluate_integer(handle, "HEBC", NULL, &event_cap); - if (ACPI_SUCCESS(status) && (event_cap & 0x20000)) { + if (button_array_present(device)) { dev_info(&device->dev, "platform supports 5 button array\n"); err = intel_button_array_input_setup(device); if (err) diff --git a/drivers/platform/x86/intel-vbtn.c b/drivers/platform/x86/intel-vbtn.c index 58c5ff36523a..b703d6f5b099 100644 --- a/drivers/platform/x86/intel-vbtn.c +++ b/drivers/platform/x86/intel-vbtn.c @@ -1,30 +1,21 @@ +// SPDX-License-Identifier: GPL-2.0+ /* * Intel Virtual Button driver for Windows 8.1+ * * Copyright (C) 2016 AceLan Kao <acelan.kao@canonical.com> * Copyright (C) 2016 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. - * */ +#include <linux/acpi.h> +#include <linux/input.h> +#include <linux/input/sparse-keymap.h> #include <linux/kernel.h> #include <linux/module.h> -#include <linux/init.h> -#include <linux/input.h> #include <linux/platform_device.h> -#include <linux/input/sparse-keymap.h> -#include <linux/acpi.h> #include <linux/suspend.h> -#include <acpi/acpi_bus.h> + +/* When NOT in tablet mode, VGBS returns with the flag 0x40 */ +#define TABLET_MODE_FLAG 0x40 MODULE_LICENSE("GPL"); MODULE_AUTHOR("AceLan Kao"); @@ -38,10 +29,16 @@ static const struct acpi_device_id intel_vbtn_ids[] = { static const struct key_entry intel_vbtn_keymap[] = { { KE_KEY, 0xC0, { KEY_POWER } }, /* power key press */ { KE_IGNORE, 0xC1, { KEY_POWER } }, /* power key release */ + { KE_KEY, 0xC2, { KEY_LEFTMETA } }, /* 'Windows' key press */ + { KE_KEY, 0xC3, { KEY_LEFTMETA } }, /* 'Windows' key release */ { KE_KEY, 0xC4, { KEY_VOLUMEUP } }, /* volume-up key press */ { KE_IGNORE, 0xC5, { KEY_VOLUMEUP } }, /* volume-up key release */ { KE_KEY, 0xC6, { KEY_VOLUMEDOWN } }, /* volume-down key press */ { KE_IGNORE, 0xC7, { KEY_VOLUMEDOWN } }, /* volume-down key release */ + { KE_KEY, 0xC8, { KEY_ROTATE_LOCK_TOGGLE } }, /* rotate-lock key press */ + { KE_KEY, 0xC9, { KEY_ROTATE_LOCK_TOGGLE } }, /* rotate-lock key release */ + { KE_SW, 0xCC, { .sw = { SW_TABLET_MODE, 1 } } }, /* Tablet */ + { KE_SW, 0xCD, { .sw = { SW_TABLET_MODE, 0 } } }, /* Laptop */ { KE_END }, }; @@ -74,20 +71,35 @@ 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; + bool autorelease; if (priv->wakeup_mode) { if (sparse_keymap_entry_from_scancode(priv->input_dev, event)) { pm_wakeup_hard_event(&device->dev); return; } - } else if (sparse_keymap_report_event(priv->input_dev, event, 1, true)) { - return; + goto out_unknown; } + + /* + * Even press events are autorelease if there is no corresponding odd + * release event, or if the odd event is KE_IGNORE. + */ + ke_rel = sparse_keymap_entry_from_scancode(priv->input_dev, event | 1); + autorelease = val && (!ke_rel || ke_rel->type == KE_IGNORE); + + if (sparse_keymap_report_event(priv->input_dev, event, val, autorelease)) + return; + +out_unknown: dev_dbg(&device->dev, "unknown event index 0x%x\n", event); } static int intel_vbtn_probe(struct platform_device *device) { + struct acpi_buffer vgbs_output = { ACPI_ALLOCATE_BUFFER, NULL }; acpi_handle handle = ACPI_HANDLE(&device->dev); struct intel_vbtn_priv *priv; acpi_status status; @@ -110,6 +122,23 @@ static int intel_vbtn_probe(struct platform_device *device) return err; } + /* + * VGBS being present and returning something means we have + * a tablet mode switch. + */ + status = acpi_evaluate_object(handle, "VGBS", NULL, &vgbs_output); + if (ACPI_SUCCESS(status)) { + union acpi_object *obj = vgbs_output.pointer; + + if (obj && obj->type == ACPI_TYPE_INTEGER) { + int m = !(obj->integer.value & TABLET_MODE_FLAG); + + input_report_switch(priv->input_dev, SW_TABLET_MODE, m); + } + } + + kfree(vgbs_output.pointer); + status = acpi_install_notify_handler(handle, ACPI_DEVICE_NOTIFY, notify_handler, diff --git a/drivers/platform/x86/intel_chtdc_ti_pwrbtn.c b/drivers/platform/x86/intel_chtdc_ti_pwrbtn.c new file mode 100644 index 000000000000..38b8e7cfe88c --- /dev/null +++ b/drivers/platform/x86/intel_chtdc_ti_pwrbtn.c @@ -0,0 +1,93 @@ +/* + * Power-button driver for Dollar Cove TI PMIC + * Copyright (C) 2014 Intel Corp + * Copyright (c) 2017 Takashi Iwai <tiwai@suse.de> + */ + +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/device.h> +#include <linux/mfd/intel_soc_pmic.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/pm_wakeirq.h> +#include <linux/slab.h> + +#define CHTDC_TI_SIRQ_REG 0x3 +#define SIRQ_PWRBTN_REL BIT(0) + +static irqreturn_t chtdc_ti_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); + int state; + + if (!regmap_read(regmap, CHTDC_TI_SIRQ_REG, &state)) { + dev_dbg(dev, "SIRQ_REG=0x%x\n", state); + input_report_key(input, KEY_POWER, !(state & SIRQ_PWRBTN_REL)); + input_sync(input); + } + + return IRQ_HANDLED; +} + +static int chtdc_ti_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, err; + + 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_set_capability(input, EV_KEY, KEY_POWER); + err = input_register_device(input); + if (err) + return err; + + dev_set_drvdata(dev, pmic->regmap); + + err = devm_request_threaded_irq(dev, irq, NULL, + chtdc_ti_pwrbtn_interrupt, + 0, KBUILD_MODNAME, input); + if (err) + return err; + + device_init_wakeup(dev, true); + dev_pm_set_wake_irq(dev, irq); + return 0; +} + +static int chtdc_ti_pwrbtn_remove(struct platform_device *pdev) +{ + dev_pm_clear_wake_irq(&pdev->dev); + device_init_wakeup(&pdev->dev, false); + return 0; +} + +static const struct platform_device_id chtdc_ti_pwrbtn_id_table[] = { + { .name = "chtdc_ti_pwrbtn" }, + {}, +}; +MODULE_DEVICE_TABLE(platform, chtdc_ti_pwrbtn_id_table); + +static struct platform_driver chtdc_ti_pwrbtn_driver = { + .driver = { + .name = KBUILD_MODNAME, + }, + .probe = chtdc_ti_pwrbtn_probe, + .remove = chtdc_ti_pwrbtn_remove, + .id_table = chtdc_ti_pwrbtn_id_table, +}; +module_platform_driver(chtdc_ti_pwrbtn_driver); + +MODULE_DESCRIPTION("Power-button driver for Dollar Cove TI PMIC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/platform/x86/intel_int0002_vgpio.c b/drivers/platform/x86/intel_int0002_vgpio.c index f7b67e898abc..a473dc51b18d 100644 --- a/drivers/platform/x86/intel_int0002_vgpio.c +++ b/drivers/platform/x86/intel_int0002_vgpio.c @@ -180,7 +180,7 @@ static int int0002_probe(struct platform_device *pdev) * to gpiochip_set_chained_irqchip, because the irq is shared. */ ret = devm_request_irq(dev, irq, int0002_irq, - IRQF_SHARED | IRQF_NO_THREAD, "INT0002", chip); + IRQF_SHARED, "INT0002", chip); if (ret) { dev_err(dev, "Error requesting IRQ %d: %d\n", irq, ret); return ret; diff --git a/drivers/platform/x86/intel_pmc_core.c b/drivers/platform/x86/intel_pmc_core.c index 17e08b42b0a9..43bbe74743d9 100644 --- a/drivers/platform/x86/intel_pmc_core.c +++ b/drivers/platform/x86/intel_pmc_core.c @@ -18,20 +18,24 @@ * */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/acpi.h> #include <linux/debugfs.h> #include <linux/delay.h> -#include <linux/device.h> -#include <linux/init.h> #include <linux/io.h> +#include <linux/module.h> #include <linux/pci.h> #include <linux/uaccess.h> #include <asm/cpu_device_id.h> #include <asm/intel-family.h> -#include <asm/pmc_core.h> #include "intel_pmc_core.h" +#define ICPU(model, data) \ + { X86_VENDOR_INTEL, 6, model, X86_FEATURE_MWAIT, (kernel_ulong_t)data } + static struct pmc_dev pmc; static const struct pmc_bit_map spt_pll_map[] = { @@ -119,10 +123,88 @@ static const struct pmc_reg_map spt_reg_map = { .pm_read_disable_bit = SPT_PMC_READ_DISABLE_BIT, }; -static const struct pci_device_id pmc_pci_ids[] = { - { PCI_VDEVICE(INTEL, SPT_PMC_PCI_DEVICE_ID), - (kernel_ulong_t)&spt_reg_map }, - { 0, }, +/* Cannonlake: PGD PFET Enable Ack Status Register(s) bitmap */ +static const struct pmc_bit_map cnp_pfear_map[] = { + {"PMC", BIT(0)}, + {"OPI-DMI", BIT(1)}, + {"SPI/eSPI", BIT(2)}, + {"XHCI", BIT(3)}, + {"SPA", BIT(4)}, + {"SPB", BIT(5)}, + {"SPC", BIT(6)}, + {"GBE", BIT(7)}, + + {"SATA", BIT(0)}, + {"HDA_PGD0", BIT(1)}, + {"HDA_PGD1", BIT(2)}, + {"HDA_PGD2", BIT(3)}, + {"HDA_PGD3", BIT(4)}, + {"SPD", BIT(5)}, + {"LPSS", BIT(6)}, + {"LPC", BIT(7)}, + + {"SMB", BIT(0)}, + {"ISH", BIT(1)}, + {"P2SB", BIT(2)}, + {"NPK_VNN", BIT(3)}, + {"SDX", BIT(4)}, + {"SPE", BIT(5)}, + {"Fuse", BIT(6)}, + {"Res_23", BIT(7)}, + + {"CSME_FSC", BIT(0)}, + {"USB3_OTG", BIT(1)}, + {"EXI", BIT(2)}, + {"CSE", BIT(3)}, + {"csme_kvm", BIT(4)}, + {"csme_pmt", BIT(5)}, + {"csme_clink", BIT(6)}, + {"csme_ptio", BIT(7)}, + + {"csme_usbr", BIT(0)}, + {"csme_susram", BIT(1)}, + {"csme_smt1", BIT(2)}, + {"CSME_SMT4", BIT(3)}, + {"csme_sms2", BIT(4)}, + {"csme_sms1", BIT(5)}, + {"csme_rtc", BIT(6)}, + {"csme_psf", BIT(7)}, + + {"SBR0", BIT(0)}, + {"SBR1", BIT(1)}, + {"SBR2", BIT(2)}, + {"SBR3", BIT(3)}, + {"SBR4", BIT(4)}, + {"SBR5", BIT(5)}, + {"CSME_PECI", BIT(6)}, + {"PSF1", BIT(7)}, + + {"PSF2", BIT(0)}, + {"PSF3", BIT(1)}, + {"PSF4", BIT(2)}, + {"CNVI", BIT(3)}, + {"UFS0", BIT(4)}, + {"EMMC", BIT(5)}, + {"Res_6", BIT(6)}, + {"SBR6", BIT(7)}, + + {"SBR7", BIT(0)}, + {"NPK_AON", BIT(1)}, + {"HDA_PGD4", BIT(2)}, + {"HDA_PGD5", BIT(3)}, + {"HDA_PGD6", BIT(4)}, + {} +}; + +static const struct pmc_reg_map cnp_reg_map = { + .pfear_sts = cnp_pfear_map, + .slp_s0_offset = CNP_PMC_SLP_S0_RES_COUNTER_OFFSET, + .ltr_ignore_offset = CNP_PMC_LTR_IGNORE_OFFSET, + .regmap_length = CNP_PMC_MMIO_REG_LEN, + .ppfear0_offset = CNP_PMC_HOST_PPFEAR0A, + .ppfear_buckets = CNP_PPFEAR_NUM_ENTRIES, + .pm_cfg_offset = CNP_PMC_PM_CFG_OFFSET, + .pm_read_disable_bit = CNP_PMC_READ_DISABLE_BIT, }; static inline u8 pmc_core_reg_read_byte(struct pmc_dev *pmcdev, int offset) @@ -146,37 +228,6 @@ static inline u32 pmc_core_adjust_slp_s0_step(u32 value) return value * SPT_PMC_SLP_S0_RES_COUNTER_STEP; } -/** - * intel_pmc_slp_s0_counter_read() - Read SLP_S0 residency. - * @data: Out param that contains current SLP_S0 count. - * - * This API currently supports Intel Skylake SoC and Sunrise - * Point Platform Controller Hub. Future platform support - * should be added for platforms that support low power modes - * beyond Package C10 state. - * - * SLP_S0_RESIDENCY counter counts in 100 us granularity per - * step hence function populates the multiplied value in out - * parameter @data. - * - * Return: an error code or 0 on success. - */ -int intel_pmc_slp_s0_counter_read(u32 *data) -{ - struct pmc_dev *pmcdev = &pmc; - const struct pmc_reg_map *map = pmcdev->map; - u32 value; - - if (!pmcdev->has_slp_s0_res) - return -EACCES; - - value = pmc_core_reg_read(pmcdev, map->slp_s0_offset); - *data = pmc_core_adjust_slp_s0_step(value); - - return 0; -} -EXPORT_SYMBOL_GPL(intel_pmc_slp_s0_counter_read); - static int pmc_core_dev_state_get(void *data, u64 *val) { struct pmc_dev *pmcdev = data; @@ -437,47 +488,33 @@ static void pmc_core_dbgfs_unregister(struct pmc_dev *pmcdev) static int pmc_core_dbgfs_register(struct pmc_dev *pmcdev) { - struct dentry *dir, *file; + struct dentry *dir; dir = debugfs_create_dir("pmc_core", NULL); if (!dir) return -ENOMEM; pmcdev->dbgfs_dir = dir; - file = debugfs_create_file("slp_s0_residency_usec", S_IFREG | S_IRUGO, - dir, pmcdev, &pmc_core_dev_state); - if (!file) - goto err; - - file = debugfs_create_file("pch_ip_power_gating_status", - S_IFREG | S_IRUGO, dir, pmcdev, - &pmc_core_ppfear_ops); - if (!file) - goto err; - - file = debugfs_create_file("mphy_core_lanes_power_gating_status", - S_IFREG | S_IRUGO, dir, pmcdev, - &pmc_core_mphy_pg_ops); - if (!file) - goto err; - - file = debugfs_create_file("pll_status", - S_IFREG | S_IRUGO, dir, pmcdev, - &pmc_core_pll_ops); - if (!file) - goto err; - - file = debugfs_create_file("ltr_ignore", - S_IFREG | S_IRUGO, dir, pmcdev, - &pmc_core_ltr_ignore_ops); - - if (!file) - goto err; + + debugfs_create_file("slp_s0_residency_usec", 0444, dir, pmcdev, + &pmc_core_dev_state); + + debugfs_create_file("pch_ip_power_gating_status", 0444, dir, pmcdev, + &pmc_core_ppfear_ops); + + debugfs_create_file("ltr_ignore", 0644, dir, pmcdev, + &pmc_core_ltr_ignore_ops); + + if (pmcdev->map->pll_sts) + debugfs_create_file("pll_status", 0444, dir, pmcdev, + &pmc_core_pll_ops); + + if (pmcdev->map->mphy_sts) + debugfs_create_file("mphy_core_lanes_power_gating_status", + 0444, dir, pmcdev, + &pmc_core_mphy_pg_ops); return 0; -err: - pmc_core_dbgfs_unregister(pmcdev); - return -ENODEV; } #else static inline int pmc_core_dbgfs_register(struct pmc_dev *pmcdev) @@ -491,71 +528,76 @@ 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[] = { - { X86_VENDOR_INTEL, 6, INTEL_FAM6_SKYLAKE_MOBILE, X86_FEATURE_MWAIT, - (kernel_ulong_t)NULL}, - { X86_VENDOR_INTEL, 6, INTEL_FAM6_SKYLAKE_DESKTOP, X86_FEATURE_MWAIT, - (kernel_ulong_t)NULL}, - { X86_VENDOR_INTEL, 6, INTEL_FAM6_KABYLAKE_MOBILE, X86_FEATURE_MWAIT, - (kernel_ulong_t)NULL}, - { X86_VENDOR_INTEL, 6, INTEL_FAM6_KABYLAKE_DESKTOP, X86_FEATURE_MWAIT, - (kernel_ulong_t)NULL}, + ICPU(INTEL_FAM6_SKYLAKE_MOBILE, &spt_reg_map), + ICPU(INTEL_FAM6_SKYLAKE_DESKTOP, &spt_reg_map), + ICPU(INTEL_FAM6_KABYLAKE_MOBILE, &spt_reg_map), + ICPU(INTEL_FAM6_KABYLAKE_DESKTOP, &spt_reg_map), + ICPU(INTEL_FAM6_CANNONLAKE_MOBILE, &cnp_reg_map), {} }; -static int pmc_core_probe(struct pci_dev *dev, const struct pci_device_id *id) +MODULE_DEVICE_TABLE(x86cpu, intel_pmc_core_ids); + +static const struct pci_device_id pmc_pci_ids[] = { + { PCI_VDEVICE(INTEL, SPT_PMC_PCI_DEVICE_ID), 0}, + { 0, }, +}; + +static int __init pmc_core_probe(void) { - struct device *ptr_dev = &dev->dev; struct pmc_dev *pmcdev = &pmc; const struct x86_cpu_id *cpu_id; - const struct pmc_reg_map *map = (struct pmc_reg_map *)id->driver_data; + u64 slp_s0_addr; int err; cpu_id = x86_match_cpu(intel_pmc_core_ids); - if (!cpu_id) { - dev_dbg(&dev->dev, "PMC Core: cpuid mismatch.\n"); - return -EINVAL; - } - - err = pcim_enable_device(dev); - if (err < 0) { - dev_dbg(&dev->dev, "PMC Core: failed to enable Power Management Controller.\n"); - return err; - } - - err = pci_read_config_dword(dev, - SPT_PMC_BASE_ADDR_OFFSET, - &pmcdev->base_addr); - if (err < 0) { - dev_dbg(&dev->dev, "PMC Core: failed to read PCI config space.\n"); - return err; - } - pmcdev->base_addr &= PMC_BASE_ADDR_MASK; - dev_dbg(&dev->dev, "PMC Core: PWRMBASE is %#x\n", pmcdev->base_addr); - - pmcdev->regbase = devm_ioremap_nocache(ptr_dev, - pmcdev->base_addr, - SPT_PMC_MMIO_REG_LEN); - if (!pmcdev->regbase) { - dev_dbg(&dev->dev, "PMC Core: ioremap failed.\n"); + if (!cpu_id) + return -ENODEV; + + 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 + * in this case. + */ + if (!pci_dev_present(pmc_pci_ids)) + pmcdev->map = &cnp_reg_map; + + if (lpit_read_residency_count_address(&slp_s0_addr)) + pmcdev->base_addr = PMC_BASE_ADDR_DEFAULT; + else + pmcdev->base_addr = slp_s0_addr - pmcdev->map->slp_s0_offset; + + pmcdev->regbase = ioremap(pmcdev->base_addr, + pmcdev->map->regmap_length); + if (!pmcdev->regbase) return -ENOMEM; - } mutex_init(&pmcdev->lock); - pmcdev->map = map; pmcdev->pmc_xram_read_bit = pmc_core_check_read_lock_bit(); err = pmc_core_dbgfs_register(pmcdev); - if (err < 0) - dev_warn(&dev->dev, "PMC Core: debugfs register failed.\n"); + if (err < 0) { + pr_warn(" debugfs register failed.\n"); + iounmap(pmcdev->regbase); + return err; + } - pmc.has_slp_s0_res = true; + pr_info(" initialized\n"); return 0; } +module_init(pmc_core_probe) -static struct pci_driver intel_pmc_core_driver = { - .name = "intel_pmc_core", - .id_table = pmc_pci_ids, - .probe = pmc_core_probe, -}; +static void __exit pmc_core_remove(void) +{ + struct pmc_dev *pmcdev = &pmc; + + pmc_core_dbgfs_unregister(pmcdev); + mutex_destroy(&pmcdev->lock); + iounmap(pmcdev->regbase); +} +module_exit(pmc_core_remove) -builtin_pci_driver(intel_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 3d225a9cc09f..5fa5f97870aa 100644 --- a/drivers/platform/x86/intel_pmc_core.h +++ b/drivers/platform/x86/intel_pmc_core.h @@ -21,9 +21,10 @@ #ifndef PMC_CORE_H #define PMC_CORE_H +#define PMC_BASE_ADDR_DEFAULT 0xFE000000 + /* Sunrise Point Power Management Controller PCI Device ID */ #define SPT_PMC_PCI_DEVICE_ID 0x9d21 - #define SPT_PMC_BASE_ADDR_OFFSET 0x48 #define SPT_PMC_SLP_S0_RES_COUNTER_OFFSET 0x13c #define SPT_PMC_PM_CFG_OFFSET 0x18 @@ -122,6 +123,17 @@ enum ppfear_regs { #define SPT_PMC_BIT_MPHY_CMN_LANE2 BIT(2) #define SPT_PMC_BIT_MPHY_CMN_LANE3 BIT(3) +/* Cannonlake Power Management Controller register offsets */ +#define CNP_PMC_SLP_S0_RES_COUNTER_OFFSET 0x193C +#define CNP_PMC_LTR_IGNORE_OFFSET 0x1B0C +#define CNP_PMC_PM_CFG_OFFSET 0x1818 +/* Cannonlake: PGD PFET Enable Ack Status Register(s) start */ +#define CNP_PMC_HOST_PPFEAR0A 0x1D90 + +#define CNP_PMC_MMIO_REG_LEN 0x2000 +#define CNP_PPFEAR_NUM_ENTRIES 8 +#define CNP_PMC_READ_DISABLE_BIT 22 + struct pmc_bit_map { const char *name; u32 bit_mask; @@ -135,7 +147,6 @@ struct pmc_bit_map { * @pll_sts: Maps name of PLL to corresponding bit status * @slp_s0_offset: PWRMBASE offset to read SLP_S0 residency * @ltr_ignore_offset: PWRMBASE offset to read/write LTR ignore bit - * @base_address: Base address of PWRMBASE defined in BIOS writer guide * @regmap_length: Length of memory to map from PWRMBASE address to access * @ppfear0_offset: PWRMBASE offset to to read PPFEAR* * @ppfear_buckets: Number of 8 bits blocks to read all IP blocks from @@ -152,7 +163,6 @@ struct pmc_reg_map { const struct pmc_bit_map *pll_sts; const u32 slp_s0_offset; const u32 ltr_ignore_offset; - const u32 base_address; const int regmap_length; const u32 ppfear0_offset; const int ppfear_buckets; @@ -162,12 +172,14 @@ struct pmc_reg_map { /** * struct pmc_dev - pmc device structure - * @base_addr: comtains pmc base address + * @base_addr: contains pmc base address * @regbase: pointer to io-remapped memory location - * @dbgfs_dir: path to debug fs interface - * @feature_available: flag to indicate whether - * the feature is available - * on a particular platform or not. + * @map: pointer to pmc_reg_map struct that contains platform + * specific attributes + * @dbgfs_dir: path to debugfs interface + * @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 * * pmc_dev contains info about power management controller device. */ @@ -178,7 +190,6 @@ struct pmc_dev { #if IS_ENABLED(CONFIG_DEBUG_FS) struct dentry *dbgfs_dir; #endif /* CONFIG_DEBUG_FS */ - bool has_slp_s0_res; int pmc_xram_read_bit; struct mutex lock; /* generic mutex lock for PMC Core */ }; diff --git a/drivers/platform/x86/intel_pmc_ipc.c b/drivers/platform/x86/intel_pmc_ipc.c index e03fa31446ca..e7edc8c63936 100644 --- a/drivers/platform/x86/intel_pmc_ipc.c +++ b/drivers/platform/x86/intel_pmc_ipc.c @@ -215,11 +215,11 @@ static inline int is_gcr_valid(u32 offset) } /** - * intel_pmc_gcr_read() - Read PMC GCR register + * intel_pmc_gcr_read() - Read a 32-bit PMC GCR register * @offset: offset of GCR register from GCR address base * @data: data pointer for storing the register output * - * Reads the PMC GCR register of given offset. + * Reads the 32-bit PMC GCR register at given offset. * * Return: negative value on error or 0 on success. */ @@ -244,6 +244,35 @@ int intel_pmc_gcr_read(u32 offset, u32 *data) EXPORT_SYMBOL_GPL(intel_pmc_gcr_read); /** + * intel_pmc_gcr_read64() - Read a 64-bit PMC GCR register + * @offset: offset of GCR register from GCR address base + * @data: data pointer for storing the register output + * + * Reads the 64-bit PMC GCR register at given offset. + * + * Return: negative value on error or 0 on success. + */ +int intel_pmc_gcr_read64(u32 offset, u64 *data) +{ + int ret; + + spin_lock(&ipcdev.gcr_lock); + + ret = is_gcr_valid(offset); + if (ret < 0) { + spin_unlock(&ipcdev.gcr_lock); + return ret; + } + + *data = readq(ipcdev.gcr_mem_base + offset); + + spin_unlock(&ipcdev.gcr_lock); + + return 0; +} +EXPORT_SYMBOL_GPL(intel_pmc_gcr_read64); + +/** * intel_pmc_gcr_write() - Write PMC GCR register * @offset: offset of GCR register from GCR address base * @data: register update value diff --git a/drivers/platform/x86/intel_telemetry_debugfs.c b/drivers/platform/x86/intel_telemetry_debugfs.c index 4249e8267bbc..ffd0474b0531 100644 --- a/drivers/platform/x86/intel_telemetry_debugfs.c +++ b/drivers/platform/x86/intel_telemetry_debugfs.c @@ -23,7 +23,6 @@ */ #include <linux/debugfs.h> #include <linux/device.h> -#include <linux/io.h> #include <linux/module.h> #include <linux/pci.h> #include <linux/seq_file.h> @@ -32,11 +31,10 @@ #include <asm/cpu_device_id.h> #include <asm/intel-family.h> #include <asm/intel_pmc_ipc.h> -#include <asm/intel_punit_ipc.h> #include <asm/intel_telemetry.h> -#define DRIVER_NAME "telemetry_soc_debugfs" -#define DRIVER_VERSION "1.0.0" +#define DRIVER_NAME "telemetry_soc_debugfs" +#define DRIVER_VERSION "1.0.0" /* ApolloLake SoC Event-IDs */ #define TELEM_APL_PSS_PSTATES_ID 0x2802 @@ -98,10 +96,6 @@ static u32 suspend_shlw_ctr_temp, suspend_deep_ctr_temp; static u64 suspend_shlw_res_temp, suspend_deep_res_temp; struct telemetry_susp_stats { - u32 shlw_swake_ctr; - u32 deep_swake_ctr; - u64 shlw_swake_res; - u64 deep_swake_res; u32 shlw_ctr; u32 deep_ctr; u64 shlw_res; @@ -250,7 +244,6 @@ static struct telem_ioss_pg_info telem_apl_ioss_pg_data[] = { {"PRTC", 25}, }; - struct telemetry_debugfs_conf { struct telemetry_susp_stats suspend_stats; struct dentry *telemetry_dbg_dir; @@ -385,7 +378,6 @@ static int telem_pss_states_show(struct seq_file *s, void *unused) TELEM_APL_MASK_PCS_STATE; } - TELEM_CHECK_AND_PARSE_EVTS(conf->pss_idle_id, conf->pss_idle_evts - 1, pss_idle, evtlog[index].telem_evtlog, @@ -405,7 +397,6 @@ static int telem_pss_states_show(struct seq_file *s, void *unused) conf->pcs_s0ix_blkd_data, TELEM_MASK_BYTE); - TELEM_CHECK_AND_PARSE_EVTS(conf->pss_wakeup_id, conf->pss_wakeup_evts, pss_s0ix_wakeup, @@ -498,7 +489,6 @@ static const struct file_operations telem_pss_ops = { .release = single_release, }; - static int telem_ioss_states_show(struct seq_file *s, void *unused) { struct telemetry_evtlog evtlog[TELEM_MAX_OS_ALLOCATED_EVENTS]; @@ -598,19 +588,15 @@ static int telem_soc_states_show(struct seq_file *s, void *unused) seq_printf(s, "S0IX Shallow\t\t\t %10u\t %10llu\n", s0ix_shlw_ctr - - conf->suspend_stats.shlw_ctr - - conf->suspend_stats.shlw_swake_ctr, + conf->suspend_stats.shlw_ctr, (u64)((s0ix_shlw_res - - conf->suspend_stats.shlw_res - - conf->suspend_stats.shlw_swake_res)*10/192)); + conf->suspend_stats.shlw_res)*10/192)); seq_printf(s, "S0IX Deep\t\t\t %10u\t %10llu\n", s0ix_deep_ctr - - conf->suspend_stats.deep_ctr - - conf->suspend_stats.deep_swake_ctr, + conf->suspend_stats.deep_ctr, (u64)((s0ix_deep_res - - conf->suspend_stats.deep_res - - conf->suspend_stats.deep_swake_res)*10/192)); + conf->suspend_stats.deep_res)*10/192)); seq_printf(s, "Suspend(With S0ixShallow)\t %10u\t %10llu\n", conf->suspend_stats.shlw_ctr, @@ -620,14 +606,8 @@ static int telem_soc_states_show(struct seq_file *s, void *unused) conf->suspend_stats.deep_ctr, (u64)(conf->suspend_stats.deep_res*10)/192); - seq_printf(s, "Suspend(With Shallow-Wakes)\t %10u\t %10llu\n", - conf->suspend_stats.shlw_swake_ctr + - conf->suspend_stats.deep_swake_ctr, - (u64)((conf->suspend_stats.shlw_swake_res + - conf->suspend_stats.deep_swake_res)*10/192)); - - seq_printf(s, "S0IX+Suspend Total\t\t %10u\t %10llu\n", s0ix_total_ctr, - (u64)(s0ix_total_res*10/192)); + seq_printf(s, "TOTAL S0IX\t\t\t %10u\t %10llu\n", s0ix_total_ctr, + (u64)(s0ix_total_res*10/192)); seq_puts(s, "\n-------------------------------------------------\n"); seq_puts(s, "\t\tDEVICE STATES\n"); seq_puts(s, "-------------------------------------------------\n"); @@ -772,7 +752,6 @@ static const struct file_operations telem_pss_trc_verb_ops = { .release = single_release, }; - static int telem_ioss_trc_verb_show(struct seq_file *s, void *unused) { u32 verbosity; @@ -890,28 +869,45 @@ static int pm_suspend_exit_cb(void) goto out; } + /* + * Due to some design limitations in the firmware, sometimes the + * counters do not get updated by the time we reach here. As a + * workaround, we try to see if this was a genuine case of sleep + * failure or not by cross-checking from PMC GCR registers directly. + */ + if (suspend_shlw_ctr_exit == suspend_shlw_ctr_temp && + suspend_deep_ctr_exit == suspend_deep_ctr_temp) { + ret = intel_pmc_gcr_read64(PMC_GCR_TELEM_SHLW_S0IX_REG, + &suspend_shlw_res_exit); + if (ret < 0) + goto out; + + ret = intel_pmc_gcr_read64(PMC_GCR_TELEM_DEEP_S0IX_REG, + &suspend_deep_res_exit); + if (ret < 0) + goto out; + + if (suspend_shlw_res_exit > suspend_shlw_res_temp) + suspend_shlw_ctr_exit++; + + if (suspend_deep_res_exit > suspend_deep_res_temp) + suspend_deep_ctr_exit++; + } + suspend_shlw_ctr_exit -= suspend_shlw_ctr_temp; suspend_deep_ctr_exit -= suspend_deep_ctr_temp; suspend_shlw_res_exit -= suspend_shlw_res_temp; suspend_deep_res_exit -= suspend_deep_res_temp; - if (suspend_shlw_ctr_exit == 1) { + if (suspend_shlw_ctr_exit != 0) { conf->suspend_stats.shlw_ctr += suspend_shlw_ctr_exit; conf->suspend_stats.shlw_res += suspend_shlw_res_exit; } - /* Shallow Wakes Case */ - else if (suspend_shlw_ctr_exit > 1) { - conf->suspend_stats.shlw_swake_ctr += - suspend_shlw_ctr_exit; - conf->suspend_stats.shlw_swake_res += - suspend_shlw_res_exit; - } - - if (suspend_deep_ctr_exit == 1) { + if (suspend_deep_ctr_exit != 0) { conf->suspend_stats.deep_ctr += suspend_deep_ctr_exit; @@ -919,15 +915,6 @@ static int pm_suspend_exit_cb(void) suspend_deep_res_exit; } - /* Shallow Wakes Case */ - else if (suspend_deep_ctr_exit > 1) { - conf->suspend_stats.deep_swake_ctr += - suspend_deep_ctr_exit; - - conf->suspend_stats.deep_swake_res += - suspend_deep_res_exit; - } - out: suspend_prep_ok = 0; return NOTIFY_OK; diff --git a/drivers/platform/x86/mlx-platform.c b/drivers/platform/x86/mlx-platform.c index 504256c3660d..27de29961f5e 100644 --- a/drivers/platform/x86/mlx-platform.c +++ b/drivers/platform/x86/mlx-platform.c @@ -35,20 +35,31 @@ #include <linux/dmi.h> #include <linux/i2c.h> #include <linux/i2c-mux.h> +#include <linux/io.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/platform_data/i2c-mux-reg.h> -#include <linux/platform_data/mlxcpld-hotplug.h> +#include <linux/platform_data/mlxreg.h> +#include <linux/regmap.h> #define MLX_PLAT_DEVICE_NAME "mlxplat" /* LPC bus IO offsets */ #define MLXPLAT_CPLD_LPC_I2C_BASE_ADRR 0x2000 #define MLXPLAT_CPLD_LPC_REG_BASE_ADRR 0x2500 -#define MLXPLAT_CPLD_LPC_REG_AGGR_ADRR 0x253a -#define MLXPLAT_CPLD_LPC_REG_PSU_ADRR 0x2558 -#define MLXPLAT_CPLD_LPC_REG_PWR_ADRR 0x2564 -#define MLXPLAT_CPLD_LPC_REG_FAN_ADRR 0x2588 +#define MLXPLAT_CPLD_LPC_REG_AGGR_OFFSET 0x3a +#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_PSU_OFFSET 0x58 +#define MLXPLAT_CPLD_LPC_REG_PSU_EVENT_OFFSET 0x59 +#define MLXPLAT_CPLD_LPC_REG_PSU_MASK_OFFSET 0x5a +#define MLXPLAT_CPLD_LPC_REG_PWR_OFFSET 0x64 +#define MLXPLAT_CPLD_LPC_REG_PWR_EVENT_OFFSET 0x65 +#define MLXPLAT_CPLD_LPC_REG_PWR_MASK_OFFSET 0x66 +#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_IO_RANGE 0x100 #define MLXPLAT_CPLD_LPC_I2C_CH1_OFF 0xdb #define MLXPLAT_CPLD_LPC_I2C_CH2_OFF 0xda @@ -81,6 +92,7 @@ /* mlxplat_priv - platform private data * @pdev_i2c - i2c controller platform device * @pdev_mux - array of mux platform devices + * @pdev_hotplug - hotplug platform devices */ struct mlxplat_priv { struct platform_device *pdev_i2c; @@ -138,86 +150,264 @@ static struct i2c_mux_reg_platform_data mlxplat_mux_data[] = { }; /* Platform hotplug devices */ -static struct mlxcpld_hotplug_device mlxplat_mlxcpld_psu[] = { +static struct i2c_board_info mlxplat_mlxcpld_psu[] = { { - .brdinfo = { I2C_BOARD_INFO("24c02", 0x51) }, - .bus = 10, + I2C_BOARD_INFO("24c02", 0x51), }, { - .brdinfo = { I2C_BOARD_INFO("24c02", 0x50) }, - .bus = 10, + I2C_BOARD_INFO("24c02", 0x50), }, }; -static struct mlxcpld_hotplug_device mlxplat_mlxcpld_pwr[] = { +static struct i2c_board_info mlxplat_mlxcpld_pwr[] = { { - .brdinfo = { I2C_BOARD_INFO("dps460", 0x59) }, - .bus = 10, + I2C_BOARD_INFO("dps460", 0x59), }, { - .brdinfo = { I2C_BOARD_INFO("dps460", 0x58) }, - .bus = 10, + I2C_BOARD_INFO("dps460", 0x58), }, }; -static struct mlxcpld_hotplug_device mlxplat_mlxcpld_fan[] = { +static struct i2c_board_info mlxplat_mlxcpld_fan[] = { { - .brdinfo = { I2C_BOARD_INFO("24c32", 0x50) }, - .bus = 11, + I2C_BOARD_INFO("24c32", 0x50), }, { - .brdinfo = { I2C_BOARD_INFO("24c32", 0x50) }, - .bus = 12, + I2C_BOARD_INFO("24c32", 0x50), }, { - .brdinfo = { I2C_BOARD_INFO("24c32", 0x50) }, - .bus = 13, + I2C_BOARD_INFO("24c32", 0x50), }, { - .brdinfo = { I2C_BOARD_INFO("24c32", 0x50) }, - .bus = 14, + I2C_BOARD_INFO("24c32", 0x50), }, }; /* Platform hotplug default data */ +static struct mlxreg_core_data mlxplat_mlxcpld_default_psu_items_data[] = { + { + .label = "psu1", + .reg = MLXPLAT_CPLD_LPC_REG_PSU_OFFSET, + .mask = BIT(0), + .hpdev.brdinfo = &mlxplat_mlxcpld_psu[0], + .hpdev.nr = 10, + }, + { + .label = "psu2", + .reg = MLXPLAT_CPLD_LPC_REG_PSU_OFFSET, + .mask = BIT(1), + .hpdev.brdinfo = &mlxplat_mlxcpld_psu[1], + .hpdev.nr = 10, + }, +}; + +static struct mlxreg_core_data mlxplat_mlxcpld_default_pwr_items_data[] = { + { + .label = "pwr1", + .reg = MLXPLAT_CPLD_LPC_REG_PWR_OFFSET, + .mask = BIT(0), + .hpdev.brdinfo = &mlxplat_mlxcpld_pwr[0], + .hpdev.nr = 10, + }, + { + .label = "pwr2", + .reg = MLXPLAT_CPLD_LPC_REG_PWR_OFFSET, + .mask = BIT(1), + .hpdev.brdinfo = &mlxplat_mlxcpld_pwr[1], + .hpdev.nr = 10, + }, +}; + +static struct mlxreg_core_data mlxplat_mlxcpld_default_fan_items_data[] = { + { + .label = "fan1", + .reg = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET, + .mask = BIT(0), + .hpdev.brdinfo = &mlxplat_mlxcpld_fan[0], + .hpdev.nr = 11, + }, + { + .label = "fan2", + .reg = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET, + .mask = BIT(1), + .hpdev.brdinfo = &mlxplat_mlxcpld_fan[1], + .hpdev.nr = 12, + }, + { + .label = "fan3", + .reg = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET, + .mask = BIT(2), + .hpdev.brdinfo = &mlxplat_mlxcpld_fan[2], + .hpdev.nr = 13, + }, + { + .label = "fan4", + .reg = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET, + .mask = BIT(3), + .hpdev.brdinfo = &mlxplat_mlxcpld_fan[3], + .hpdev.nr = 14, + }, +}; + +static struct mlxreg_core_item mlxplat_mlxcpld_default_items[] = { + { + .data = mlxplat_mlxcpld_default_psu_items_data, + .aggr_mask = MLXPLAT_CPLD_AGGR_PSU_MASK_DEF, + .reg = MLXPLAT_CPLD_LPC_REG_PSU_OFFSET, + .mask = MLXPLAT_CPLD_PSU_MASK, + .count = ARRAY_SIZE(mlxplat_mlxcpld_psu), + .inversed = 1, + .health = false, + }, + { + .data = mlxplat_mlxcpld_default_pwr_items_data, + .aggr_mask = MLXPLAT_CPLD_AGGR_PWR_MASK_DEF, + .reg = MLXPLAT_CPLD_LPC_REG_PWR_OFFSET, + .mask = MLXPLAT_CPLD_PWR_MASK, + .count = ARRAY_SIZE(mlxplat_mlxcpld_pwr), + .inversed = 0, + .health = false, + }, + { + .data = mlxplat_mlxcpld_default_fan_items_data, + .aggr_mask = MLXPLAT_CPLD_AGGR_FAN_MASK_DEF, + .reg = MLXPLAT_CPLD_LPC_REG_FAN_OFFSET, + .mask = MLXPLAT_CPLD_FAN_MASK, + .count = ARRAY_SIZE(mlxplat_mlxcpld_fan), + .inversed = 1, + .health = false, + }, +}; + static -struct mlxcpld_hotplug_platform_data mlxplat_mlxcpld_default_data = { - .top_aggr_offset = MLXPLAT_CPLD_LPC_REG_AGGR_ADRR, - .top_aggr_mask = MLXPLAT_CPLD_AGGR_MASK_DEF, - .top_aggr_psu_mask = MLXPLAT_CPLD_AGGR_PSU_MASK_DEF, - .psu_reg_offset = MLXPLAT_CPLD_LPC_REG_PSU_ADRR, - .psu_mask = MLXPLAT_CPLD_PSU_MASK, - .psu_count = ARRAY_SIZE(mlxplat_mlxcpld_psu), - .psu = mlxplat_mlxcpld_psu, - .top_aggr_pwr_mask = MLXPLAT_CPLD_AGGR_PWR_MASK_DEF, - .pwr_reg_offset = MLXPLAT_CPLD_LPC_REG_PWR_ADRR, - .pwr_mask = MLXPLAT_CPLD_PWR_MASK, - .pwr_count = ARRAY_SIZE(mlxplat_mlxcpld_pwr), - .pwr = mlxplat_mlxcpld_pwr, - .top_aggr_fan_mask = MLXPLAT_CPLD_AGGR_FAN_MASK_DEF, - .fan_reg_offset = MLXPLAT_CPLD_LPC_REG_FAN_ADRR, - .fan_mask = MLXPLAT_CPLD_FAN_MASK, - .fan_count = ARRAY_SIZE(mlxplat_mlxcpld_fan), - .fan = mlxplat_mlxcpld_fan, +struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_default_data = { + .items = mlxplat_mlxcpld_default_items, + .counter = ARRAY_SIZE(mlxplat_mlxcpld_default_items), + .cell = MLXPLAT_CPLD_LPC_REG_AGGR_OFFSET, + .mask = MLXPLAT_CPLD_AGGR_MASK_DEF, }; /* Platform hotplug MSN21xx system family data */ +static struct mlxreg_core_item mlxplat_mlxcpld_msn21xx_items[] = { + { + .data = mlxplat_mlxcpld_default_pwr_items_data, + .aggr_mask = MLXPLAT_CPLD_AGGR_PWR_MASK_DEF, + .reg = MLXPLAT_CPLD_LPC_REG_PWR_OFFSET, + .mask = MLXPLAT_CPLD_PWR_MASK, + .count = ARRAY_SIZE(mlxplat_mlxcpld_pwr), + .inversed = 0, + .health = false, + }, +}; + static -struct mlxcpld_hotplug_platform_data mlxplat_mlxcpld_msn21xx_data = { - .top_aggr_offset = MLXPLAT_CPLD_LPC_REG_AGGR_ADRR, - .top_aggr_mask = MLXPLAT_CPLD_AGGR_MASK_MSN21XX, - .top_aggr_pwr_mask = MLXPLAT_CPLD_AGGR_MASK_MSN21XX, - .pwr_reg_offset = MLXPLAT_CPLD_LPC_REG_PWR_ADRR, - .pwr_mask = MLXPLAT_CPLD_PWR_MASK, - .pwr_count = ARRAY_SIZE(mlxplat_mlxcpld_pwr), +struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_msn21xx_data = { + .items = mlxplat_mlxcpld_msn21xx_items, + .counter = ARRAY_SIZE(mlxplat_mlxcpld_msn21xx_items), + .cell = MLXPLAT_CPLD_LPC_REG_AGGR_OFFSET, + .mask = MLXPLAT_CPLD_AGGR_MASK_DEF, +}; + +static bool mlxplat_mlxcpld_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MLXPLAT_CPLD_LPC_REG_AGGR_MASK_OFFSET: + case MLXPLAT_CPLD_LPC_REG_AGGRLO_MASK_OFFSET: + case MLXPLAT_CPLD_LPC_REG_PSU_EVENT_OFFSET: + case MLXPLAT_CPLD_LPC_REG_PSU_MASK_OFFSET: + case MLXPLAT_CPLD_LPC_REG_PWR_EVENT_OFFSET: + case MLXPLAT_CPLD_LPC_REG_PWR_MASK_OFFSET: + case MLXPLAT_CPLD_LPC_REG_FAN_EVENT_OFFSET: + case MLXPLAT_CPLD_LPC_REG_FAN_MASK_OFFSET: + return true; + } + return false; +} + +static bool mlxplat_mlxcpld_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MLXPLAT_CPLD_LPC_REG_AGGR_OFFSET: + 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_PSU_OFFSET: + case MLXPLAT_CPLD_LPC_REG_PSU_EVENT_OFFSET: + case MLXPLAT_CPLD_LPC_REG_PSU_MASK_OFFSET: + case MLXPLAT_CPLD_LPC_REG_PWR_OFFSET: + case MLXPLAT_CPLD_LPC_REG_PWR_EVENT_OFFSET: + case MLXPLAT_CPLD_LPC_REG_PWR_MASK_OFFSET: + case MLXPLAT_CPLD_LPC_REG_FAN_OFFSET: + case MLXPLAT_CPLD_LPC_REG_FAN_EVENT_OFFSET: + case MLXPLAT_CPLD_LPC_REG_FAN_MASK_OFFSET: + return true; + } + return false; +} + +static bool mlxplat_mlxcpld_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case MLXPLAT_CPLD_LPC_REG_AGGR_OFFSET: + 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_PSU_OFFSET: + case MLXPLAT_CPLD_LPC_REG_PSU_EVENT_OFFSET: + case MLXPLAT_CPLD_LPC_REG_PSU_MASK_OFFSET: + case MLXPLAT_CPLD_LPC_REG_PWR_OFFSET: + case MLXPLAT_CPLD_LPC_REG_PWR_EVENT_OFFSET: + case MLXPLAT_CPLD_LPC_REG_PWR_MASK_OFFSET: + case MLXPLAT_CPLD_LPC_REG_FAN_OFFSET: + case MLXPLAT_CPLD_LPC_REG_FAN_EVENT_OFFSET: + case MLXPLAT_CPLD_LPC_REG_FAN_MASK_OFFSET: + return true; + } + return false; +} + +struct mlxplat_mlxcpld_regmap_context { + void __iomem *base; +}; + +static struct mlxplat_mlxcpld_regmap_context mlxplat_mlxcpld_regmap_ctx; + +static int +mlxplat_mlxcpld_reg_read(void *context, unsigned int reg, unsigned int *val) +{ + struct mlxplat_mlxcpld_regmap_context *ctx = context; + + *val = ioread8(ctx->base + reg); + return 0; +} + +static int +mlxplat_mlxcpld_reg_write(void *context, unsigned int reg, unsigned int val) +{ + struct mlxplat_mlxcpld_regmap_context *ctx = context; + + iowrite8(val, ctx->base + reg); + return 0; +} + +static const struct regmap_config mlxplat_mlxcpld_regmap_config = { + .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_read = mlxplat_mlxcpld_reg_read, + .reg_write = mlxplat_mlxcpld_reg_write, }; static struct resource mlxplat_mlxcpld_resources[] = { - [0] = DEFINE_RES_IRQ_NAMED(17, "mlxcpld-hotplug"), + [0] = DEFINE_RES_IRQ_NAMED(17, "mlxreg-hotplug"), }; static struct platform_device *mlxplat_dev; -static struct mlxcpld_hotplug_platform_data *mlxplat_hotplug; +static struct mlxreg_core_hotplug_platform_data *mlxplat_hotplug; static int __init mlxplat_dmi_default_matched(const struct dmi_system_id *dmi) { @@ -286,6 +476,8 @@ static const struct dmi_system_id mlxplat_dmi_table[] __initconst = { { } }; +MODULE_DEVICE_TABLE(dmi, mlxplat_dmi_table); + static int __init mlxplat_init(void) { struct mlxplat_priv *priv; @@ -328,8 +520,23 @@ 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; + } + priv->pdev_hotplug = platform_device_register_resndata( - &mlxplat_dev->dev, "mlxcpld-hotplug", + &mlxplat_dev->dev, "mlxreg-hotplug", PLATFORM_DEVID_NONE, mlxplat_mlxcpld_resources, ARRAY_SIZE(mlxplat_mlxcpld_resources), @@ -339,8 +546,16 @@ static int __init mlxplat_init(void) goto fail_platform_mux_register; } + /* Sync registers with hardware. */ + regcache_mark_dirty(mlxplat_hotplug->regmap); + err = regcache_sync(mlxplat_hotplug->regmap); + if (err) + goto fail_platform_hotplug_register; + return 0; +fail_platform_hotplug_register: + platform_device_unregister(priv->pdev_hotplug); fail_platform_mux_register: while (--i >= 0) platform_device_unregister(priv->pdev_mux[i]); @@ -370,8 +585,3 @@ module_exit(mlxplat_exit); MODULE_AUTHOR("Vadim Pasternak (vadimp@mellanox.com)"); MODULE_DESCRIPTION("Mellanox platform driver"); MODULE_LICENSE("Dual BSD/GPL"); -MODULE_ALIAS("dmi:*:*Mellanox*:MSN24*:"); -MODULE_ALIAS("dmi:*:*Mellanox*:MSN27*:"); -MODULE_ALIAS("dmi:*:*Mellanox*:MSB*:"); -MODULE_ALIAS("dmi:*:*Mellanox*:MSX*:"); -MODULE_ALIAS("dmi:*:*Mellanox*:MSN21*:"); diff --git a/drivers/platform/x86/mlxcpld-hotplug.c b/drivers/platform/x86/mlxcpld-hotplug.c deleted file mode 100644 index aff3686b3b37..000000000000 --- a/drivers/platform/x86/mlxcpld-hotplug.c +++ /dev/null @@ -1,515 +0,0 @@ -/* - * drivers/platform/x86/mlxcpld-hotplug.c - * Copyright (c) 2016 Mellanox Technologies. All rights reserved. - * Copyright (c) 2016 Vadim Pasternak <vadimp@mellanox.com> - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the names of the copyright holders nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * Alternatively, this software may be distributed under the terms of the - * GNU General Public License ("GPL") version 2 as published by the Free - * Software Foundation. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -#include <linux/bitops.h> -#include <linux/device.h> -#include <linux/hwmon.h> -#include <linux/hwmon-sysfs.h> -#include <linux/i2c.h> -#include <linux/interrupt.h> -#include <linux/io.h> -#include <linux/module.h> -#include <linux/platform_data/mlxcpld-hotplug.h> -#include <linux/platform_device.h> -#include <linux/spinlock.h> -#include <linux/wait.h> -#include <linux/workqueue.h> - -/* Offset of event and mask registers from status register */ -#define MLXCPLD_HOTPLUG_EVENT_OFF 1 -#define MLXCPLD_HOTPLUG_MASK_OFF 2 -#define MLXCPLD_HOTPLUG_AGGR_MASK_OFF 1 - -#define MLXCPLD_HOTPLUG_ATTRS_NUM 8 - -/** - * enum mlxcpld_hotplug_attr_type - sysfs attributes for hotplug events: - * @MLXCPLD_HOTPLUG_ATTR_TYPE_PSU: power supply unit attribute; - * @MLXCPLD_HOTPLUG_ATTR_TYPE_PWR: power cable attribute; - * @MLXCPLD_HOTPLUG_ATTR_TYPE_FAN: FAN drawer attribute; - */ -enum mlxcpld_hotplug_attr_type { - MLXCPLD_HOTPLUG_ATTR_TYPE_PSU, - MLXCPLD_HOTPLUG_ATTR_TYPE_PWR, - MLXCPLD_HOTPLUG_ATTR_TYPE_FAN, -}; - -/** - * struct mlxcpld_hotplug_priv_data - platform private data: - * @irq: platform interrupt number; - * @pdev: platform device; - * @plat: platform data; - * @hwmon: hwmon device; - * @mlxcpld_hotplug_attr: sysfs attributes array; - * @mlxcpld_hotplug_dev_attr: sysfs sensor device attribute array; - * @group: sysfs attribute group; - * @groups: list of sysfs attribute group for hwmon registration; - * @dwork: delayed work template; - * @lock: spin lock; - * @aggr_cache: last value of aggregation register status; - * @psu_cache: last value of PSU register status; - * @pwr_cache: last value of power register status; - * @fan_cache: last value of FAN register status; - */ -struct mlxcpld_hotplug_priv_data { - int irq; - struct platform_device *pdev; - struct mlxcpld_hotplug_platform_data *plat; - struct device *hwmon; - struct attribute *mlxcpld_hotplug_attr[MLXCPLD_HOTPLUG_ATTRS_NUM + 1]; - struct sensor_device_attribute_2 - mlxcpld_hotplug_dev_attr[MLXCPLD_HOTPLUG_ATTRS_NUM]; - struct attribute_group group; - const struct attribute_group *groups[2]; - struct delayed_work dwork; - spinlock_t lock; - u8 aggr_cache; - u8 psu_cache; - u8 pwr_cache; - u8 fan_cache; -}; - -static ssize_t mlxcpld_hotplug_attr_show(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - struct platform_device *pdev = to_platform_device(dev); - struct mlxcpld_hotplug_priv_data *priv = platform_get_drvdata(pdev); - int index = to_sensor_dev_attr_2(attr)->index; - int nr = to_sensor_dev_attr_2(attr)->nr; - u8 reg_val = 0; - - switch (nr) { - case MLXCPLD_HOTPLUG_ATTR_TYPE_PSU: - /* Bit = 0 : PSU is present. */ - reg_val = !!!(inb(priv->plat->psu_reg_offset) & BIT(index)); - break; - - case MLXCPLD_HOTPLUG_ATTR_TYPE_PWR: - /* Bit = 1 : power cable is attached. */ - reg_val = !!(inb(priv->plat->pwr_reg_offset) & BIT(index % - priv->plat->pwr_count)); - break; - - case MLXCPLD_HOTPLUG_ATTR_TYPE_FAN: - /* Bit = 0 : FAN is present. */ - reg_val = !!!(inb(priv->plat->fan_reg_offset) & BIT(index % - priv->plat->fan_count)); - break; - } - - return sprintf(buf, "%u\n", reg_val); -} - -#define PRIV_ATTR(i) priv->mlxcpld_hotplug_attr[i] -#define PRIV_DEV_ATTR(i) priv->mlxcpld_hotplug_dev_attr[i] -static int mlxcpld_hotplug_attr_init(struct mlxcpld_hotplug_priv_data *priv) -{ - int num_attrs = priv->plat->psu_count + priv->plat->pwr_count + - priv->plat->fan_count; - int i; - - priv->group.attrs = devm_kzalloc(&priv->pdev->dev, num_attrs * - sizeof(struct attribute *), - GFP_KERNEL); - if (!priv->group.attrs) - return -ENOMEM; - - for (i = 0; i < num_attrs; i++) { - PRIV_ATTR(i) = &PRIV_DEV_ATTR(i).dev_attr.attr; - - if (i < priv->plat->psu_count) { - PRIV_ATTR(i)->name = devm_kasprintf(&priv->pdev->dev, - GFP_KERNEL, "psu%u", i + 1); - PRIV_DEV_ATTR(i).nr = MLXCPLD_HOTPLUG_ATTR_TYPE_PSU; - } else if (i < priv->plat->psu_count + priv->plat->pwr_count) { - PRIV_ATTR(i)->name = devm_kasprintf(&priv->pdev->dev, - GFP_KERNEL, "pwr%u", i % - priv->plat->pwr_count + 1); - PRIV_DEV_ATTR(i).nr = MLXCPLD_HOTPLUG_ATTR_TYPE_PWR; - } else { - PRIV_ATTR(i)->name = devm_kasprintf(&priv->pdev->dev, - GFP_KERNEL, "fan%u", i % - priv->plat->fan_count + 1); - PRIV_DEV_ATTR(i).nr = MLXCPLD_HOTPLUG_ATTR_TYPE_FAN; - } - - if (!PRIV_ATTR(i)->name) { - dev_err(&priv->pdev->dev, "Memory allocation failed for sysfs attribute %d.\n", - i + 1); - return -ENOMEM; - } - - PRIV_DEV_ATTR(i).dev_attr.attr.name = PRIV_ATTR(i)->name; - PRIV_DEV_ATTR(i).dev_attr.attr.mode = S_IRUGO; - PRIV_DEV_ATTR(i).dev_attr.show = mlxcpld_hotplug_attr_show; - PRIV_DEV_ATTR(i).index = i; - sysfs_attr_init(&PRIV_DEV_ATTR(i).dev_attr.attr); - } - - priv->group.attrs = priv->mlxcpld_hotplug_attr; - priv->groups[0] = &priv->group; - priv->groups[1] = NULL; - - return 0; -} - -static int mlxcpld_hotplug_device_create(struct device *dev, - struct mlxcpld_hotplug_device *item) -{ - item->adapter = i2c_get_adapter(item->bus); - if (!item->adapter) { - dev_err(dev, "Failed to get adapter for bus %d\n", - item->bus); - return -EFAULT; - } - - item->client = i2c_new_device(item->adapter, &item->brdinfo); - if (!item->client) { - dev_err(dev, "Failed to create client %s at bus %d at addr 0x%02x\n", - item->brdinfo.type, item->bus, item->brdinfo.addr); - i2c_put_adapter(item->adapter); - item->adapter = NULL; - return -EFAULT; - } - - return 0; -} - -static void mlxcpld_hotplug_device_destroy(struct mlxcpld_hotplug_device *item) -{ - if (item->client) { - i2c_unregister_device(item->client); - item->client = NULL; - } - - if (item->adapter) { - i2c_put_adapter(item->adapter); - item->adapter = NULL; - } -} - -static inline void -mlxcpld_hotplug_work_helper(struct device *dev, - struct mlxcpld_hotplug_device *item, u8 is_inverse, - u16 offset, u8 mask, u8 *cache) -{ - u8 val, asserted; - int bit; - - /* Mask event. */ - outb(0, offset + MLXCPLD_HOTPLUG_MASK_OFF); - /* Read status. */ - val = inb(offset) & mask; - asserted = *cache ^ val; - *cache = val; - - /* - * Validate if item related to received signal type is valid. - * It should never happen, excepted the situation when some - * piece of hardware is broken. In such situation just produce - * error message and return. Caller must continue to handle the - * signals from other devices if any. - */ - if (unlikely(!item)) { - dev_err(dev, "False signal is received: register at offset 0x%02x, mask 0x%02x.\n", - offset, mask); - return; - } - - for_each_set_bit(bit, (unsigned long *)&asserted, 8) { - if (val & BIT(bit)) { - if (is_inverse) - mlxcpld_hotplug_device_destroy(item + bit); - else - mlxcpld_hotplug_device_create(dev, item + bit); - } else { - if (is_inverse) - mlxcpld_hotplug_device_create(dev, item + bit); - else - mlxcpld_hotplug_device_destroy(item + bit); - } - } - - /* Acknowledge event. */ - outb(0, offset + MLXCPLD_HOTPLUG_EVENT_OFF); - /* Unmask event. */ - outb(mask, offset + MLXCPLD_HOTPLUG_MASK_OFF); -} - -/* - * mlxcpld_hotplug_work_handler - performs traversing of CPLD interrupt - * registers according to the below hierarchy schema: - * - * Aggregation registers (status/mask) - * PSU registers: *---* - * *-----------------* | | - * |status/event/mask|----->| * | - * *-----------------* | | - * Power registers: | | - * *-----------------* | | - * |status/event/mask|----->| * |---> CPU - * *-----------------* | | - * FAN registers: - * *-----------------* | | - * |status/event/mask|----->| * | - * *-----------------* | | - * *---* - * In case some system changed are detected: FAN in/out, PSU in/out, power - * cable attached/detached, relevant device is created or destroyed. - */ -static void mlxcpld_hotplug_work_handler(struct work_struct *work) -{ - struct mlxcpld_hotplug_priv_data *priv = container_of(work, - struct mlxcpld_hotplug_priv_data, dwork.work); - u8 val, aggr_asserted; - unsigned long flags; - - /* Mask aggregation event. */ - outb(0, priv->plat->top_aggr_offset + MLXCPLD_HOTPLUG_AGGR_MASK_OFF); - /* Read aggregation status. */ - val = inb(priv->plat->top_aggr_offset) & priv->plat->top_aggr_mask; - aggr_asserted = priv->aggr_cache ^ val; - priv->aggr_cache = val; - - /* Handle PSU configuration changes. */ - if (aggr_asserted & priv->plat->top_aggr_psu_mask) - mlxcpld_hotplug_work_helper(&priv->pdev->dev, priv->plat->psu, - 1, priv->plat->psu_reg_offset, - priv->plat->psu_mask, - &priv->psu_cache); - - /* Handle power cable configuration changes. */ - if (aggr_asserted & priv->plat->top_aggr_pwr_mask) - mlxcpld_hotplug_work_helper(&priv->pdev->dev, priv->plat->pwr, - 0, priv->plat->pwr_reg_offset, - priv->plat->pwr_mask, - &priv->pwr_cache); - - /* Handle FAN configuration changes. */ - if (aggr_asserted & priv->plat->top_aggr_fan_mask) - mlxcpld_hotplug_work_helper(&priv->pdev->dev, priv->plat->fan, - 1, priv->plat->fan_reg_offset, - priv->plat->fan_mask, - &priv->fan_cache); - - if (aggr_asserted) { - spin_lock_irqsave(&priv->lock, flags); - - /* - * It is possible, that some signals have been inserted, while - * interrupt has been masked by mlxcpld_hotplug_work_handler. - * In this case such signals will be missed. In order to handle - * these signals delayed work is canceled and work task - * re-scheduled for immediate execution. It allows to handle - * missed signals, if any. In other case work handler just - * validates that no new signals have been received during - * masking. - */ - cancel_delayed_work(&priv->dwork); - schedule_delayed_work(&priv->dwork, 0); - - spin_unlock_irqrestore(&priv->lock, flags); - - return; - } - - /* Unmask aggregation event (no need acknowledge). */ - outb(priv->plat->top_aggr_mask, priv->plat->top_aggr_offset + - MLXCPLD_HOTPLUG_AGGR_MASK_OFF); -} - -static void mlxcpld_hotplug_set_irq(struct mlxcpld_hotplug_priv_data *priv) -{ - /* Clear psu presense event. */ - outb(0, priv->plat->psu_reg_offset + MLXCPLD_HOTPLUG_EVENT_OFF); - /* Set psu initial status as mask and unmask psu event. */ - priv->psu_cache = priv->plat->psu_mask; - outb(priv->plat->psu_mask, priv->plat->psu_reg_offset + - MLXCPLD_HOTPLUG_MASK_OFF); - - /* Clear power cable event. */ - outb(0, priv->plat->pwr_reg_offset + MLXCPLD_HOTPLUG_EVENT_OFF); - /* Keep power initial status as zero and unmask power event. */ - outb(priv->plat->pwr_mask, priv->plat->pwr_reg_offset + - MLXCPLD_HOTPLUG_MASK_OFF); - - /* Clear fan presense event. */ - outb(0, priv->plat->fan_reg_offset + MLXCPLD_HOTPLUG_EVENT_OFF); - /* Set fan initial status as mask and unmask fan event. */ - priv->fan_cache = priv->plat->fan_mask; - outb(priv->plat->fan_mask, priv->plat->fan_reg_offset + - MLXCPLD_HOTPLUG_MASK_OFF); - - /* Keep aggregation initial status as zero and unmask events. */ - outb(priv->plat->top_aggr_mask, priv->plat->top_aggr_offset + - MLXCPLD_HOTPLUG_AGGR_MASK_OFF); - - /* Invoke work handler for initializing hot plug devices setting. */ - mlxcpld_hotplug_work_handler(&priv->dwork.work); - - enable_irq(priv->irq); -} - -static void mlxcpld_hotplug_unset_irq(struct mlxcpld_hotplug_priv_data *priv) -{ - int i; - - disable_irq(priv->irq); - cancel_delayed_work_sync(&priv->dwork); - - /* Mask aggregation event. */ - outb(0, priv->plat->top_aggr_offset + MLXCPLD_HOTPLUG_AGGR_MASK_OFF); - - /* Mask psu presense event. */ - outb(0, priv->plat->psu_reg_offset + MLXCPLD_HOTPLUG_MASK_OFF); - /* Clear psu presense event. */ - outb(0, priv->plat->psu_reg_offset + MLXCPLD_HOTPLUG_EVENT_OFF); - - /* Mask power cable event. */ - outb(0, priv->plat->pwr_reg_offset + MLXCPLD_HOTPLUG_MASK_OFF); - /* Clear power cable event. */ - outb(0, priv->plat->pwr_reg_offset + MLXCPLD_HOTPLUG_EVENT_OFF); - - /* Mask fan presense event. */ - outb(0, priv->plat->fan_reg_offset + MLXCPLD_HOTPLUG_MASK_OFF); - /* Clear fan presense event. */ - outb(0, priv->plat->fan_reg_offset + MLXCPLD_HOTPLUG_EVENT_OFF); - - /* Remove all the attached devices. */ - for (i = 0; i < priv->plat->psu_count; i++) - mlxcpld_hotplug_device_destroy(priv->plat->psu + i); - - for (i = 0; i < priv->plat->pwr_count; i++) - mlxcpld_hotplug_device_destroy(priv->plat->pwr + i); - - for (i = 0; i < priv->plat->fan_count; i++) - mlxcpld_hotplug_device_destroy(priv->plat->fan + i); -} - -static irqreturn_t mlxcpld_hotplug_irq_handler(int irq, void *dev) -{ - struct mlxcpld_hotplug_priv_data *priv = - (struct mlxcpld_hotplug_priv_data *)dev; - - /* Schedule work task for immediate execution.*/ - schedule_delayed_work(&priv->dwork, 0); - - return IRQ_HANDLED; -} - -static int mlxcpld_hotplug_probe(struct platform_device *pdev) -{ - struct mlxcpld_hotplug_platform_data *pdata; - struct mlxcpld_hotplug_priv_data *priv; - int err; - - pdata = dev_get_platdata(&pdev->dev); - if (!pdata) { - dev_err(&pdev->dev, "Failed to get platform data.\n"); - return -EINVAL; - } - - priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); - if (!priv) - return -ENOMEM; - - priv->pdev = pdev; - priv->plat = pdata; - - priv->irq = platform_get_irq(pdev, 0); - if (priv->irq < 0) { - dev_err(&pdev->dev, "Failed to get platform irq: %d\n", - priv->irq); - return priv->irq; - } - - err = devm_request_irq(&pdev->dev, priv->irq, - mlxcpld_hotplug_irq_handler, 0, pdev->name, - priv); - if (err) { - dev_err(&pdev->dev, "Failed to request irq: %d\n", err); - return err; - } - disable_irq(priv->irq); - - INIT_DELAYED_WORK(&priv->dwork, mlxcpld_hotplug_work_handler); - spin_lock_init(&priv->lock); - - err = mlxcpld_hotplug_attr_init(priv); - if (err) { - dev_err(&pdev->dev, "Failed to allocate attributes: %d\n", err); - return err; - } - - priv->hwmon = devm_hwmon_device_register_with_groups(&pdev->dev, - "mlxcpld_hotplug", priv, priv->groups); - if (IS_ERR(priv->hwmon)) { - dev_err(&pdev->dev, "Failed to register hwmon device %ld\n", - PTR_ERR(priv->hwmon)); - return PTR_ERR(priv->hwmon); - } - - platform_set_drvdata(pdev, priv); - - /* Perform initial interrupts setup. */ - mlxcpld_hotplug_set_irq(priv); - - return 0; -} - -static int mlxcpld_hotplug_remove(struct platform_device *pdev) -{ - struct mlxcpld_hotplug_priv_data *priv = platform_get_drvdata(pdev); - - /* Clean interrupts setup. */ - mlxcpld_hotplug_unset_irq(priv); - - return 0; -} - -static struct platform_driver mlxcpld_hotplug_driver = { - .driver = { - .name = "mlxcpld-hotplug", - }, - .probe = mlxcpld_hotplug_probe, - .remove = mlxcpld_hotplug_remove, -}; - -module_platform_driver(mlxcpld_hotplug_driver); - -MODULE_AUTHOR("Vadim Pasternak <vadimp@mellanox.com>"); -MODULE_DESCRIPTION("Mellanox CPLD hotplug platform driver"); -MODULE_LICENSE("Dual BSD/GPL"); -MODULE_ALIAS("platform:mlxcpld-hotplug"); diff --git a/drivers/platform/x86/pmc_atom.c b/drivers/platform/x86/pmc_atom.c index 77bac859342d..4b3c37b6288c 100644 --- a/drivers/platform/x86/pmc_atom.c +++ b/drivers/platform/x86/pmc_atom.c @@ -208,6 +208,20 @@ static const struct pmc_data cht_data = { .clks = cht_clks, }; +#define DEFINE_SHOW_ATTRIBUTE(__name) \ +static int __name ## _open(struct inode *inode, struct file *file) \ +{ \ + return single_open(file, __name ## _show, inode->i_private); \ +} \ + \ +static const struct file_operations __name ## _fops = { \ + .owner = THIS_MODULE, \ + .open = __name ## _open, \ + .read = seq_read, \ + .llseek = seq_lseek, \ + .release = single_release, \ +} + static inline u32 pmc_reg_read(struct pmc_dev *pmc, int reg_offset) { return readl(pmc->regmap + reg_offset); @@ -309,17 +323,7 @@ static int pmc_dev_state_show(struct seq_file *s, void *unused) return 0; } -static int pmc_dev_state_open(struct inode *inode, struct file *file) -{ - return single_open(file, pmc_dev_state_show, inode->i_private); -} - -static const struct file_operations pmc_dev_state_ops = { - .open = pmc_dev_state_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, -}; +DEFINE_SHOW_ATTRIBUTE(pmc_dev_state); static int pmc_pss_state_show(struct seq_file *s, void *unused) { @@ -336,17 +340,7 @@ static int pmc_pss_state_show(struct seq_file *s, void *unused) return 0; } -static int pmc_pss_state_open(struct inode *inode, struct file *file) -{ - return single_open(file, pmc_pss_state_show, inode->i_private); -} - -static const struct file_operations pmc_pss_state_ops = { - .open = pmc_pss_state_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, -}; +DEFINE_SHOW_ATTRIBUTE(pmc_pss_state); static int pmc_sleep_tmr_show(struct seq_file *s, void *unused) { @@ -367,17 +361,7 @@ static int pmc_sleep_tmr_show(struct seq_file *s, void *unused) return 0; } -static int pmc_sleep_tmr_open(struct inode *inode, struct file *file) -{ - return single_open(file, pmc_sleep_tmr_show, inode->i_private); -} - -static const struct file_operations pmc_sleep_tmr_ops = { - .open = pmc_sleep_tmr_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, -}; +DEFINE_SHOW_ATTRIBUTE(pmc_sleep_tmr); static void pmc_dbgfs_unregister(struct pmc_dev *pmc) { @@ -395,17 +379,17 @@ static int pmc_dbgfs_register(struct pmc_dev *pmc) pmc->dbgfs_dir = dir; f = debugfs_create_file("dev_state", S_IFREG | S_IRUGO, - dir, pmc, &pmc_dev_state_ops); + 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_ops); + 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_ops); + dir, pmc, &pmc_sleep_tmr_fops); if (!f) goto err; diff --git a/drivers/platform/x86/silead_dmi.c b/drivers/platform/x86/silead_dmi.c index 266535c2a72f..3a624090191d 100644 --- a/drivers/platform/x86/silead_dmi.c +++ b/drivers/platform/x86/silead_dmi.c @@ -67,6 +67,21 @@ static const struct silead_ts_dmi_data dexp_ursus_7w_data = { .properties = dexp_ursus_7w_props, }; +static const struct property_entry surftab_twin_10_1_st10432_8_props[] = { + PROPERTY_ENTRY_U32("touchscreen-size-x", 1900), + PROPERTY_ENTRY_U32("touchscreen-size-y", 1280), + PROPERTY_ENTRY_U32("touchscreen-inverted-y", 1), + PROPERTY_ENTRY_STRING("firmware-name", + "gsl3670-surftab-twin-10-1-st10432-8.fw"), + PROPERTY_ENTRY_U32("silead,max-fingers", 10), + { } +}; + +static const struct silead_ts_dmi_data surftab_twin_10_1_st10432_8_data = { + .acpi_name = "MSSL1680:00", + .properties = surftab_twin_10_1_st10432_8_props, +}; + static const struct property_entry surftab_wintron70_st70416_6_props[] = { PROPERTY_ENTRY_U32("touchscreen-size-x", 884), PROPERTY_ENTRY_U32("touchscreen-size-y", 632), @@ -171,6 +186,97 @@ static const struct silead_ts_dmi_data digma_citi_e200_data = { .properties = digma_citi_e200_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), + PROPERTY_ENTRY_BOOL("touchscreen-inverted-x"), + PROPERTY_ENTRY_BOOL("touchscreen-inverted-y"), + PROPERTY_ENTRY_BOOL("touchscreen-swapped-x-y"), + PROPERTY_ENTRY_STRING("firmware-name", "gsl3676-onda-obook-20-plus.fw"), + PROPERTY_ENTRY_U32("silead,max-fingers", 10), + PROPERTY_ENTRY_BOOL("silead,home-button"), + { } +}; + +static const struct silead_ts_dmi_data onda_obook_20_plus_data = { + .acpi_name = "MSSL1680:00", + .properties = onda_obook_20_plus_props, +}; + +static const struct property_entry chuwi_hi8_props[] = { + PROPERTY_ENTRY_U32("touchscreen-size-x", 1665), + PROPERTY_ENTRY_U32("touchscreen-size-y", 1140), + PROPERTY_ENTRY_BOOL("touchscreen-swapped-x-y"), + PROPERTY_ENTRY_BOOL("silead,home-button"), + PROPERTY_ENTRY_STRING("firmware-name", "gsl1680-chuwi-hi8.fw"), + { } +}; + +static const struct silead_ts_dmi_data chuwi_hi8_data = { + .acpi_name = "MSSL0001:00", + .properties = chuwi_hi8_props, +}; + +static const struct property_entry chuwi_vi8_props[] = { + PROPERTY_ENTRY_U32("touchscreen-size-x", 1724), + PROPERTY_ENTRY_U32("touchscreen-size-y", 1140), + PROPERTY_ENTRY_BOOL("touchscreen-swapped-x-y"), + PROPERTY_ENTRY_STRING("firmware-name", "gsl3676-chuwi-vi8.fw"), + PROPERTY_ENTRY_U32("silead,max-fingers", 10), + PROPERTY_ENTRY_BOOL("silead,home-button"), + { } +}; + +static const struct silead_ts_dmi_data chuwi_vi8_data = { + .acpi_name = "MSSL1680:00", + .properties = chuwi_vi8_props, +}; + +static const struct property_entry trekstor_primebook_c13_props[] = { + PROPERTY_ENTRY_U32("touchscreen-size-x", 2624), + PROPERTY_ENTRY_U32("touchscreen-size-y", 1920), + PROPERTY_ENTRY_STRING("firmware-name", + "gsl1680-trekstor-primebook-c13.fw"), + PROPERTY_ENTRY_U32("silead,max-fingers", 10), + PROPERTY_ENTRY_BOOL("silead,home-button"), + { } +}; + +static const struct silead_ts_dmi_data trekstor_primebook_c13_data = { + .acpi_name = "MSSL1680:00", + .properties = trekstor_primebook_c13_props, +}; + +static const struct property_entry teclast_x98plus2_props[] = { + PROPERTY_ENTRY_U32("touchscreen-size-x", 2048), + PROPERTY_ENTRY_U32("touchscreen-size-y", 1280), + PROPERTY_ENTRY_BOOL("touchscreen-inverted-x"), + PROPERTY_ENTRY_BOOL("touchscreen-inverted-y"), + PROPERTY_ENTRY_STRING("firmware-name", + "gsl1686-teclast_x98plus2.fw"), + PROPERTY_ENTRY_U32("silead,max-fingers", 10), + { } +}; + +static const struct silead_ts_dmi_data teclast_x98plus2_data = { + .acpi_name = "MSSL1680:00", + .properties = teclast_x98plus2_props, +}; + +static const struct property_entry teclast_x3_plus_props[] = { + PROPERTY_ENTRY_U32("touchscreen-size-x", 1980), + PROPERTY_ENTRY_U32("touchscreen-size-y", 1500), + PROPERTY_ENTRY_STRING("firmware-name", "gsl1680-teclast-x3-plus.fw"), + PROPERTY_ENTRY_U32("silead,max-fingers", 10), + PROPERTY_ENTRY_BOOL("silead,home-button"), + { } +}; + +static const struct silead_ts_dmi_data teclast_x3_plus_data = { + .acpi_name = "MSSL1680:00", + .properties = teclast_x3_plus_props, +}; + static const struct dmi_system_id silead_ts_dmi_table[] = { { /* CUBE iwork8 Air */ @@ -199,6 +305,14 @@ static const struct dmi_system_id silead_ts_dmi_table[] = { }, }, { + /* TrekStor SurfTab twin 10.1 ST10432-8 */ + .driver_data = (void *)&surftab_twin_10_1_st10432_8_data, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "TrekStor"), + DMI_MATCH(DMI_PRODUCT_NAME, "SurfTab twin 10.1"), + }, + }, + { /* Trekstor Surftab Wintron 7.0 ST70416-6 */ .driver_data = (void *)&surftab_wintron70_st70416_6_data, .matches = { @@ -209,6 +323,17 @@ static const struct dmi_system_id silead_ts_dmi_table[] = { }, }, { + /* Trekstor Surftab Wintron 7.0 ST70416-6, newer BIOS */ + .driver_data = (void *)&surftab_wintron70_st70416_6_data, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "TrekStor"), + DMI_MATCH(DMI_PRODUCT_NAME, + "SurfTab wintron 7.0 ST70416-6"), + /* Exact match, different versions need different fw */ + DMI_MATCH(DMI_BIOS_VERSION, "TREK.G.WI71C.JGBMRBA05"), + }, + }, + { /* Ployer Momo7w (same hardware as the Trekstor ST70416-6) */ .driver_data = (void *)&surftab_wintron70_st70416_6_data, .matches = { @@ -271,6 +396,56 @@ static const struct dmi_system_id silead_ts_dmi_table[] = { DMI_MATCH(DMI_BOARD_NAME, "Cherry Trail CR"), }, }, + { + /* Onda oBook 20 Plus */ + .driver_data = (void *)&onda_obook_20_plus_data, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "ONDA"), + DMI_MATCH(DMI_PRODUCT_NAME, "OBOOK 20 PLUS"), + }, + }, + { + /* Chuwi Hi8 */ + .driver_data = (void *)&chuwi_hi8_data, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "ilife"), + DMI_MATCH(DMI_PRODUCT_NAME, "S806"), + }, + }, + { + /* Chuwi Vi8 (CWI506) */ + .driver_data = (void *)&chuwi_vi8_data, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Insyde"), + DMI_MATCH(DMI_PRODUCT_NAME, "i86"), + DMI_MATCH(DMI_BIOS_VERSION, "CHUWI.D86JLBNR"), + }, + }, + { + /* Trekstor Primebook C13 */ + .driver_data = (void *)&trekstor_primebook_c13_data, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "TREKSTOR"), + DMI_MATCH(DMI_PRODUCT_NAME, "Primebook C13"), + }, + }, + { + /* Teclast X98 Plus II */ + .driver_data = (void *)&teclast_x98plus2_data, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "TECLAST"), + DMI_MATCH(DMI_PRODUCT_NAME, "X98 Plus II"), + }, + }, + { + /* Teclast X3 Plus */ + .driver_data = (void *)&teclast_x3_plus_data, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "TECLAST"), + DMI_MATCH(DMI_PRODUCT_NAME, "X3 Plus"), + DMI_MATCH(DMI_BOARD_NAME, "X3 Plus"), + }, + }, { }, }; diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index 117be48ff4de..d5eaf3b1edba 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -214,6 +214,10 @@ enum tpacpi_hkey_event_t { /* AC-related events */ TP_HKEY_EV_AC_CHANGED = 0x6040, /* AC status changed */ + /* Further user-interface events */ + TP_HKEY_EV_PALM_DETECTED = 0x60b0, /* palm hoveres keyboard */ + TP_HKEY_EV_PALM_UNDETECTED = 0x60b1, /* palm removed */ + /* Misc */ TP_HKEY_EV_RFKILL_CHANGED = 0x7000, /* rfkill switch changed */ }; @@ -2113,12 +2117,10 @@ static int hotkey_gmms_get_tablet_mode(int s, int *has_tablet_mode) TP_ACPI_MULTI_MODE_FLAT; break; case 4: - valid_modes = TP_ACPI_MULTI_MODE_LAPTOP | - TP_ACPI_MULTI_MODE_TABLET | - TP_ACPI_MULTI_MODE_STAND | - TP_ACPI_MULTI_MODE_TENT; - break; case 5: + /* In mode 4, FLAT is not specified as a valid mode. However, + * it can be seen at least on the X1 Yoga 2nd Generation. + */ valid_modes = TP_ACPI_MULTI_MODE_LAPTOP | TP_ACPI_MULTI_MODE_FLAT | TP_ACPI_MULTI_MODE_TABLET | @@ -4079,6 +4081,12 @@ static bool hotkey_notify_6xxx(const u32 hkey, *send_acpi_ev = false; break; + case TP_HKEY_EV_PALM_DETECTED: + case TP_HKEY_EV_PALM_UNDETECTED: + /* palm detected hovering the keyboard, forward to user-space + * via netlink for consumption */ + return true; + default: pr_warn("unknown possible thermal alarm or keyboard event received\n"); known = false; |