diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2018-02-07 02:30:52 +0300 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2018-02-07 02:30:52 +0300 |
commit | cbd7b8a76b79a2ff6112ef2e77031b694843b8a1 (patch) | |
tree | 41a7e9bae92abe001c36a094b1135f0b93375c31 /drivers | |
parent | 3f551e3cefcf119c1d397ed8b5633d9fa73fca0a (diff) | |
parent | 8a0f5b6f33275eb0251e0e1b0716744f9d95415f (diff) | |
download | linux-cbd7b8a76b79a2ff6112ef2e77031b694843b8a1.tar.xz |
Merge tag 'platform-drivers-x86-v4.16-1' of git://git.infradead.org/linux-platform-drivers-x86
Pull x86 platform-driver updates from Darren Hart:
"New model support added for Dell, Ideapad, Acer, Asus, Thinkpad, and
GPD laptops. Improvements to the common intel-vbtn driver, including
tablet mode, rotate, and front button support. Intel CPU support added
for Cannonlake and platform support for Dollar Cove power button.
Overhaul of the mellanox platform driver, creating a new
platform/mellanox directory for the newly multi-architecture regmap
interface.
Significant Intel PMC update with CannonLake support, Coffeelake
update, CPUID enumeration, module support, new read64 API, refactoring
and cleanups.
Revert the apple-gmux iGP IO lock, addressing reported issues with
non-binary drivers, leaving Nvidia binary driver users to comment out
conflicting code.
Miscellaneous fixes and cleanups"
* tag 'platform-drivers-x86-v4.16-1' of git://git.infradead.org/linux-platform-drivers-x86: (81 commits)
platform/x86: mlx-platform: Fix an ERR_PTR vs NULL issue
platform/x86: intel_pmc_core: Special case for Coffeelake
platform/x86: intel_pmc_core: Add CannonLake PCH support
x86/cpu: Add Cannonlake to Intel family
platform/x86: intel_pmc_core: Read base address from LPIT
ACPI / LPIT: Export lpit_read_residency_count_address()
platform/x86: intel-vbtn: Replace License by SDPX identifier
platform/x86: intel-vbtn: Remove redundant inclusions
platform/x86: intel-vbtn: Support tablet mode switch
platform/x86: dell-laptop: Allocate buffer on heap rather than globally
platform/x86: intel_pmc_core: Remove unused header file
platform/x86: mlx-platform: Add hotplug device unregister to error path
platform/x86: mlx-platform: fix module aliases
platform/mellanox: mlxreg-hotplug: Add check for negative adapter number
platform/x86: mlx-platform: Add IO access verification callbacks
platform/x86: mlx-platform: Document pdev_hotplug field
platform/x86: mlx-platform: Allow compilation for 32 bit arch
platform/mellanox: mlxreg-hotplug: Enable building for ARM
platform/mellanox: mlxreg-hotplug: Modify to use a regmap interface
platform/mellanox: Group create/destroy with attribute functions
...
Diffstat (limited to 'drivers')
30 files changed, 2132 insertions, 980 deletions
diff --git a/drivers/acpi/acpi_lpit.c b/drivers/acpi/acpi_lpit.c index e94e478dd18b..cf4fc0161164 100644 --- a/drivers/acpi/acpi_lpit.c +++ b/drivers/acpi/acpi_lpit.c @@ -100,6 +100,7 @@ int lpit_read_residency_count_address(u64 *address) return 0; } +EXPORT_SYMBOL_GPL(lpit_read_residency_count_address); static void lpit_update_residency(struct lpit_residency_info *info, struct acpi_lpit_native *lpit_native) diff --git a/drivers/platform/Kconfig b/drivers/platform/Kconfig index c11db8bceea1..d4c2e424a700 100644 --- a/drivers/platform/Kconfig +++ b/drivers/platform/Kconfig @@ -8,3 +8,5 @@ endif source "drivers/platform/goldfish/Kconfig" source "drivers/platform/chrome/Kconfig" + +source "drivers/platform/mellanox/Kconfig" diff --git a/drivers/platform/Makefile b/drivers/platform/Makefile index d3a6630266a0..4b2ce58bcd9c 100644 --- a/drivers/platform/Makefile +++ b/drivers/platform/Makefile @@ -4,6 +4,7 @@ # obj-$(CONFIG_X86) += x86/ +obj-$(CONFIG_MELLANOX_PLATFORM) += mellanox/ obj-$(CONFIG_MIPS) += mips/ obj-$(CONFIG_OLPC) += olpc/ obj-$(CONFIG_GOLDFISH) += goldfish/ diff --git a/drivers/platform/mellanox/Kconfig b/drivers/platform/mellanox/Kconfig new file mode 100644 index 000000000000..591bccdeaff9 --- /dev/null +++ b/drivers/platform/mellanox/Kconfig @@ -0,0 +1,26 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Platform support for Mellanox hardware +# + +menuconfig MELLANOX_PLATFORM + bool "Platform support for Mellanox hardware" + depends on X86 || ARM || COMPILE_TEST + ---help--- + Say Y here to get to see options for platform support for + Mellanox systems. This option alone does not add any kernel code. + + If you say N, all options in this submenu will be skipped and disabled. + +if MELLANOX_PLATFORM + +config MLXREG_HOTPLUG + tristate "Mellanox platform hotplug driver support" + depends on REGMAP + depends on HWMON + depends on 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. + +endif # MELLANOX_PLATFORM diff --git a/drivers/platform/mellanox/Makefile b/drivers/platform/mellanox/Makefile new file mode 100644 index 000000000000..7c8385e497a8 --- /dev/null +++ b/drivers/platform/mellanox/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for linux/drivers/platform/mellanox +# Mellanox Platform-Specific Drivers +# +obj-$(CONFIG_MLXREG_HOTPLUG) += mlxreg-hotplug.o diff --git a/drivers/platform/mellanox/mlxreg-hotplug.c b/drivers/platform/mellanox/mlxreg-hotplug.c new file mode 100644 index 000000000000..0dfa1ca0d05b --- /dev/null +++ b/drivers/platform/mellanox/mlxreg-hotplug.c @@ -0,0 +1,638 @@ +/* + * Copyright (c) 2016-2018 Mellanox Technologies. All rights reserved. + * Copyright (c) 2016-2018 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/module.h> +#include <linux/of_device.h> +#include <linux/platform_data/mlxreg.h> +#include <linux/platform_device.h> +#include <linux/spinlock.h> +#include <linux/regmap.h> +#include <linux/workqueue.h> + +/* Offset of event and mask registers from status register. */ +#define MLXREG_HOTPLUG_EVENT_OFF 1 +#define MLXREG_HOTPLUG_MASK_OFF 2 +#define MLXREG_HOTPLUG_AGGR_MASK_OFF 1 + +/* ASIC health parameters. */ +#define MLXREG_HOTPLUG_HEALTH_MASK 0x02 +#define MLXREG_HOTPLUG_RST_CNTR 3 + +#define MLXREG_HOTPLUG_ATTRS_MAX 24 + +/** + * struct mlxreg_hotplug_priv_data - platform private data: + * @irq: platform device interrupt number; + * @pdev: platform device; + * @plat: platform data; + * @dwork: delayed work template; + * @lock: spin lock; + * @hwmon: hwmon device; + * @mlxreg_hotplug_attr: sysfs attributes array; + * @mlxreg_hotplug_dev_attr: sysfs sensor device attribute array; + * @group: sysfs attribute group; + * @groups: list of sysfs attribute group for hwmon registration; + * @cell: location of top aggregation interrupt register; + * @mask: top aggregation interrupt common mask; + * @aggr_cache: last value of aggregation register status; + */ +struct mlxreg_hotplug_priv_data { + int irq; + struct device *dev; + struct platform_device *pdev; + struct mlxreg_hotplug_platform_data *plat; + struct regmap *regmap; + struct delayed_work dwork_irq; + struct delayed_work dwork; + spinlock_t lock; /* sync with interrupt */ + struct device *hwmon; + struct attribute *mlxreg_hotplug_attr[MLXREG_HOTPLUG_ATTRS_MAX + 1]; + struct sensor_device_attribute_2 + mlxreg_hotplug_dev_attr[MLXREG_HOTPLUG_ATTRS_MAX]; + struct attribute_group group; + const struct attribute_group *groups[2]; + u32 cell; + u32 mask; + u32 aggr_cache; + bool after_probe; +}; + +static int mlxreg_hotplug_device_create(struct device *dev, + struct mlxreg_core_data *data) +{ + /* + * Return if adapter number is negative. It could be in case hotplug + * event is not associated with hotplug device. + */ + if (data->hpdev.nr < 0) + return 0; + + data->hpdev.adapter = i2c_get_adapter(data->hpdev.nr); + if (!data->hpdev.adapter) { + dev_err(dev, "Failed to get adapter for bus %d\n", + data->hpdev.nr); + return -EFAULT; + } + + data->hpdev.client = i2c_new_device(data->hpdev.adapter, + data->hpdev.brdinfo); + if (!data->hpdev.client) { + dev_err(dev, "Failed to create client %s at bus %d at addr 0x%02x\n", + data->hpdev.brdinfo->type, data->hpdev.nr, + data->hpdev.brdinfo->addr); + + i2c_put_adapter(data->hpdev.adapter); + data->hpdev.adapter = NULL; + return -EFAULT; + } + + return 0; +} + +static void mlxreg_hotplug_device_destroy(struct mlxreg_core_data *data) +{ + if (data->hpdev.client) { + i2c_unregister_device(data->hpdev.client); + data->hpdev.client = NULL; + } + + if (data->hpdev.adapter) { + i2c_put_adapter(data->hpdev.adapter); + data->hpdev.adapter = NULL; + } +} + +static ssize_t mlxreg_hotplug_attr_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct mlxreg_hotplug_priv_data *priv = dev_get_drvdata(dev); + struct mlxreg_core_hotplug_platform_data *pdata; + int index = to_sensor_dev_attr_2(attr)->index; + int nr = to_sensor_dev_attr_2(attr)->nr; + struct mlxreg_core_item *item; + struct mlxreg_core_data *data; + u32 regval; + int ret; + + pdata = dev_get_platdata(&priv->pdev->dev); + item = pdata->items + nr; + data = item->data + index; + + ret = regmap_read(priv->regmap, data->reg, ®val); + if (ret) + return ret; + + if (item->health) { + regval &= data->mask; + } else { + /* Bit = 0 : functional if item->inversed is true. */ + if (item->inversed) + regval = !(regval & data->mask); + else + regval = !!(regval & data->mask); + } + + return sprintf(buf, "%u\n", regval); +} + +#define PRIV_ATTR(i) priv->mlxreg_hotplug_attr[i] +#define PRIV_DEV_ATTR(i) priv->mlxreg_hotplug_dev_attr[i] + +static int mlxreg_hotplug_attr_init(struct mlxreg_hotplug_priv_data *priv) +{ + struct mlxreg_core_hotplug_platform_data *pdata; + struct mlxreg_core_item *item; + struct mlxreg_core_data *data; + int num_attrs = 0, id = 0, i, j; + + pdata = dev_get_platdata(&priv->pdev->dev); + item = pdata->items; + + /* Go over all kinds of items - psu, pwr, fan. */ + for (i = 0; i < pdata->counter; i++, item++) { + num_attrs += item->count; + data = item->data; + /* Go over all units within the item. */ + for (j = 0; j < item->count; j++, data++, id++) { + PRIV_ATTR(id) = &PRIV_DEV_ATTR(id).dev_attr.attr; + PRIV_ATTR(id)->name = devm_kasprintf(&priv->pdev->dev, + GFP_KERNEL, + data->label); + + if (!PRIV_ATTR(id)->name) { + dev_err(priv->dev, "Memory allocation failed for attr %d.\n", + id); + return -ENOMEM; + } + + PRIV_DEV_ATTR(id).dev_attr.attr.name = + PRIV_ATTR(id)->name; + PRIV_DEV_ATTR(id).dev_attr.attr.mode = 0444; + PRIV_DEV_ATTR(id).dev_attr.show = + mlxreg_hotplug_attr_show; + PRIV_DEV_ATTR(id).nr = i; + PRIV_DEV_ATTR(id).index = j; + sysfs_attr_init(&PRIV_DEV_ATTR(id).dev_attr.attr); + } + } + + priv->group.attrs = devm_kzalloc(&priv->pdev->dev, num_attrs * + sizeof(struct attribute *), + GFP_KERNEL); + if (!priv->group.attrs) + return -ENOMEM; + + priv->group.attrs = priv->mlxreg_hotplug_attr; + priv->groups[0] = &priv->group; + priv->groups[1] = NULL; + + return 0; +} + +static void +mlxreg_hotplug_work_helper(struct mlxreg_hotplug_priv_data *priv, + struct mlxreg_core_item *item) +{ + struct mlxreg_core_data *data; + u32 asserted, regval, bit; + int ret; + + /* + * 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(priv->dev, "False signal: at offset:mask 0x%02x:0x%02x.\n", + item->reg, item->mask); + + return; + } + + /* Mask event. */ + ret = regmap_write(priv->regmap, item->reg + MLXREG_HOTPLUG_MASK_OFF, + 0); + if (ret) + goto out; + + /* Read status. */ + ret = regmap_read(priv->regmap, item->reg, ®val); + if (ret) + goto out; + + /* Set asserted bits and save last status. */ + regval &= item->mask; + asserted = item->cache ^ regval; + item->cache = regval; + + for_each_set_bit(bit, (unsigned long *)&asserted, 8) { + data = item->data + bit; + if (regval & BIT(bit)) { + if (item->inversed) + mlxreg_hotplug_device_destroy(data); + else + mlxreg_hotplug_device_create(priv->dev, data); + } else { + if (item->inversed) + mlxreg_hotplug_device_create(priv->dev, data); + else + mlxreg_hotplug_device_destroy(data); + } + } + + /* Acknowledge event. */ + ret = regmap_write(priv->regmap, item->reg + MLXREG_HOTPLUG_EVENT_OFF, + 0); + if (ret) + goto out; + + /* Unmask event. */ + ret = regmap_write(priv->regmap, item->reg + MLXREG_HOTPLUG_MASK_OFF, + item->mask); + + out: + if (ret) + dev_err(priv->dev, "Failed to complete workqueue.\n"); +} + +static void +mlxreg_hotplug_health_work_helper(struct mlxreg_hotplug_priv_data *priv, + struct mlxreg_core_item *item) +{ + struct mlxreg_core_data *data = item->data; + u32 regval; + int i, ret; + + for (i = 0; i < item->count; i++, data++) { + /* Mask event. */ + ret = regmap_write(priv->regmap, data->reg + + MLXREG_HOTPLUG_MASK_OFF, 0); + if (ret) + goto out; + + /* Read status. */ + ret = regmap_read(priv->regmap, data->reg, ®val); + if (ret) + goto out; + + regval &= data->mask; + item->cache = regval; + if (regval == MLXREG_HOTPLUG_HEALTH_MASK) { + if ((data->health_cntr++ == MLXREG_HOTPLUG_RST_CNTR) || + !priv->after_probe) { + mlxreg_hotplug_device_create(priv->dev, data); + data->attached = true; + } + } else { + if (data->attached) { + mlxreg_hotplug_device_destroy(data); + data->attached = false; + data->health_cntr = 0; + } + } + + /* Acknowledge event. */ + ret = regmap_write(priv->regmap, data->reg + + MLXREG_HOTPLUG_EVENT_OFF, 0); + if (ret) + goto out; + + /* Unmask event. */ + ret = regmap_write(priv->regmap, data->reg + + MLXREG_HOTPLUG_MASK_OFF, data->mask); + if (ret) + goto out; + } + + out: + if (ret) + dev_err(priv->dev, "Failed to complete workqueue.\n"); +} + +/* + * mlxreg_hotplug_work_handler - performs traversing of device interrupt + * registers according to the below hierarchy schema: + * + * Aggregation registers (status/mask) + * PSU registers: *---* + * *-----------------* | | + * |status/event/mask|-----> | * | + * *-----------------* | | + * Power registers: | | + * *-----------------* | | + * |status/event/mask|-----> | * | + * *-----------------* | | + * FAN registers: | |--> CPU + * *-----------------* | | + * |status/event/mask|-----> | * | + * *-----------------* | | + * ASIC registers: | | + * *-----------------* | | + * |status/event/mask|-----> | * | + * *-----------------* | | + * *---* + * + * In case some system changed are detected: FAN in/out, PSU in/out, power + * cable attached/detached, ASIC health good/bad, relevant device is created + * or destroyed. + */ +static void mlxreg_hotplug_work_handler(struct work_struct *work) +{ + struct mlxreg_core_hotplug_platform_data *pdata; + struct mlxreg_hotplug_priv_data *priv; + struct mlxreg_core_item *item; + u32 regval, aggr_asserted; + unsigned long flags; + int i, ret; + + priv = container_of(work, struct mlxreg_hotplug_priv_data, + dwork_irq.work); + pdata = dev_get_platdata(&priv->pdev->dev); + item = pdata->items; + + /* Mask aggregation event. */ + ret = regmap_write(priv->regmap, pdata->cell + + MLXREG_HOTPLUG_AGGR_MASK_OFF, 0); + if (ret < 0) + goto out; + + /* Read aggregation status. */ + ret = regmap_read(priv->regmap, pdata->cell, ®val); + if (ret) + goto out; + + regval &= pdata->mask; + aggr_asserted = priv->aggr_cache ^ regval; + priv->aggr_cache = regval; + + /* Handle topology and health configuration changes. */ + for (i = 0; i < pdata->counter; i++, item++) { + if (aggr_asserted & item->aggr_mask) { + if (item->health) + mlxreg_hotplug_health_work_helper(priv, item); + else + mlxreg_hotplug_work_helper(priv, item); + } + } + + if (aggr_asserted) { + spin_lock_irqsave(&priv->lock, flags); + + /* + * It is possible, that some signals have been inserted, while + * interrupt has been masked by mlxreg_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_irq); + schedule_delayed_work(&priv->dwork_irq, 0); + + spin_unlock_irqrestore(&priv->lock, flags); + + return; + } + + /* Unmask aggregation event (no need acknowledge). */ + ret = regmap_write(priv->regmap, pdata->cell + + MLXREG_HOTPLUG_AGGR_MASK_OFF, pdata->mask); + + out: + if (ret) + dev_err(priv->dev, "Failed to complete workqueue.\n"); +} + +static int mlxreg_hotplug_set_irq(struct mlxreg_hotplug_priv_data *priv) +{ + struct mlxreg_core_hotplug_platform_data *pdata; + struct mlxreg_core_item *item; + int i, ret; + + pdata = dev_get_platdata(&priv->pdev->dev); + item = pdata->items; + + for (i = 0; i < pdata->counter; i++, item++) { + /* Clear group presense event. */ + ret = regmap_write(priv->regmap, item->reg + + MLXREG_HOTPLUG_EVENT_OFF, 0); + if (ret) + goto out; + + /* Set group initial status as mask and unmask group event. */ + if (item->inversed) { + item->cache = item->mask; + ret = regmap_write(priv->regmap, item->reg + + MLXREG_HOTPLUG_MASK_OFF, + item->mask); + if (ret) + goto out; + } + } + + /* Keep aggregation initial status as zero and unmask events. */ + ret = regmap_write(priv->regmap, pdata->cell + + MLXREG_HOTPLUG_AGGR_MASK_OFF, pdata->mask); + if (ret) + goto out; + + /* Keep low aggregation initial status as zero and unmask events. */ + if (pdata->cell_low) { + ret = regmap_write(priv->regmap, pdata->cell_low + + MLXREG_HOTPLUG_AGGR_MASK_OFF, + pdata->mask_low); + if (ret) + goto out; + } + + /* Invoke work handler for initializing hot plug devices setting. */ + mlxreg_hotplug_work_handler(&priv->dwork_irq.work); + + out: + if (ret) + dev_err(priv->dev, "Failed to set interrupts.\n"); + enable_irq(priv->irq); + return ret; +} + +static void mlxreg_hotplug_unset_irq(struct mlxreg_hotplug_priv_data *priv) +{ + struct mlxreg_core_hotplug_platform_data *pdata; + struct mlxreg_core_item *item; + struct mlxreg_core_data *data; + int count, i, j; + + pdata = dev_get_platdata(&priv->pdev->dev); + item = pdata->items; + disable_irq(priv->irq); + cancel_delayed_work_sync(&priv->dwork_irq); + + /* Mask low aggregation event, if defined. */ + if (pdata->cell_low) + regmap_write(priv->regmap, pdata->cell_low + + MLXREG_HOTPLUG_AGGR_MASK_OFF, 0); + + /* Mask aggregation event. */ + regmap_write(priv->regmap, pdata->cell + MLXREG_HOTPLUG_AGGR_MASK_OFF, + 0); + + /* Clear topology configurations. */ + for (i = 0; i < pdata->counter; i++, item++) { + data = item->data; + /* Mask group presense event. */ + regmap_write(priv->regmap, data->reg + MLXREG_HOTPLUG_MASK_OFF, + 0); + /* Clear group presense event. */ + regmap_write(priv->regmap, data->reg + + MLXREG_HOTPLUG_EVENT_OFF, 0); + + /* Remove all the attached devices in group. */ + count = item->count; + for (j = 0; j < count; j++, data++) + mlxreg_hotplug_device_destroy(data); + } +} + +static irqreturn_t mlxreg_hotplug_irq_handler(int irq, void *dev) +{ + struct mlxreg_hotplug_priv_data *priv; + + priv = (struct mlxreg_hotplug_priv_data *)dev; + + /* Schedule work task for immediate execution.*/ + schedule_delayed_work(&priv->dwork_irq, 0); + + return IRQ_HANDLED; +} + +static int mlxreg_hotplug_probe(struct platform_device *pdev) +{ + struct mlxreg_core_hotplug_platform_data *pdata; + struct mlxreg_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; + + if (pdata->irq) { + priv->irq = pdata->irq; + } else { + priv->irq = platform_get_irq(pdev, 0); + if (priv->irq < 0) { + dev_err(&pdev->dev, "Failed to get platform irq: %d\n", + priv->irq); + return priv->irq; + } + } + + priv->regmap = pdata->regmap; + priv->dev = pdev->dev.parent; + priv->pdev = pdev; + + err = devm_request_irq(&pdev->dev, priv->irq, + mlxreg_hotplug_irq_handler, IRQF_TRIGGER_FALLING + | IRQF_SHARED, "mlxreg-hotplug", priv); + if (err) { + dev_err(&pdev->dev, "Failed to request irq: %d\n", err); + return err; + } + + disable_irq(priv->irq); + spin_lock_init(&priv->lock); + INIT_DELAYED_WORK(&priv->dwork_irq, mlxreg_hotplug_work_handler); + /* Perform initial interrupts setup. */ + mlxreg_hotplug_set_irq(priv); + + priv->after_probe = true; + dev_set_drvdata(&pdev->dev, priv); + + err = mlxreg_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, + "mlxreg_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); + } + + return 0; +} + +static int mlxreg_hotplug_remove(struct platform_device *pdev) +{ + struct mlxreg_hotplug_priv_data *priv = dev_get_drvdata(&pdev->dev); + + /* Clean interrupts setup. */ + mlxreg_hotplug_unset_irq(priv); + + return 0; +} + +static struct platform_driver mlxreg_hotplug_driver = { + .driver = { + .name = "mlxreg-hotplug", + }, + .probe = mlxreg_hotplug_probe, + .remove = mlxreg_hotplug_remove, +}; + +module_platform_driver(mlxreg_hotplug_driver); + +MODULE_AUTHOR("Vadim Pasternak <vadimp@mellanox.com>"); +MODULE_DESCRIPTION("Mellanox regmap hotplug platform driver"); +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_ALIAS("platform:mlxreg-hotplug"); 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; |