From 960652046899ff2fb4bd37e1e38836e3a67c27bf Mon Sep 17 00:00:00 2001 From: "Darren Hart (VMware)" Date: Fri, 8 Dec 2017 15:52:49 -0800 Subject: MAINTAINERS: Update tree for platform-drivers-x86 Update the tree listed for X86 PLATFORM DRIVERS to its new top level reposority at infradead. The old one is an alias to the new one, but we prefer to remove the "user/dvhart" from the URL. Signed-off-by: Darren Hart (VMware) --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index aa71ab52fd76..825acf165c2d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14863,7 +14863,7 @@ X86 PLATFORM DRIVERS M: Darren Hart M: Andy Shevchenko L: platform-driver-x86@vger.kernel.org -T: git git://git.infradead.org/users/dvhart/linux-platform-drivers-x86.git +T: git git://git.infradead.org/linux-platform-drivers-x86.git S: Maintained F: drivers/platform/x86/ F: drivers/platform/olpc/ -- cgit v1.2.3 From 1b46f17d687a431c423f970a5d292c6fcd478afb Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 12 Dec 2017 17:40:54 +0100 Subject: platform/x86: Add driver for GPD pocket custom fan controller Add a driver for the GPD pocket device's custom fan controller, which gets controlled through 2 GPIOs listed in a FAN02501 ACPI device. Cc: James Suggested-by: James Signed-off-by: Hans de Goede Signed-off-by: Andy Shevchenko --- MAINTAINERS | 6 ++ drivers/platform/x86/Kconfig | 12 +++ drivers/platform/x86/Makefile | 1 + drivers/platform/x86/gpd-pocket-fan.c | 193 ++++++++++++++++++++++++++++++++++ 4 files changed, 212 insertions(+) create mode 100644 drivers/platform/x86/gpd-pocket-fan.c (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 825acf165c2d..bc4d77695918 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5947,6 +5947,12 @@ L: linux-input@vger.kernel.org S: Maintained F: drivers/input/touchscreen/goodix.c +GPD POCKET FAN DRIVER +M: Hans de Goede +L: platform-driver-x86@vger.kernel.org +S: Maintained +F: drivers/platform/x86/gpd-pocket-fan.c + GPIO ACPI SUPPORT M: Mika Westerberg M: Andy Shevchenko diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 88bf2f46b7a4..8a07df54a67e 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -258,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 diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 4f92dbf30900..57f37e55786a 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -29,6 +29,7 @@ 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 diff --git a/drivers/platform/x86/gpd-pocket-fan.c b/drivers/platform/x86/gpd-pocket-fan.c new file mode 100644 index 000000000000..c6f4d89b1437 --- /dev/null +++ b/drivers/platform/x86/gpd-pocket-fan.c @@ -0,0 +1,193 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * GPD Pocket fan controller driver + * + * Copyright (C) 2017 Hans de Goede + */ + +#include +#include +#include +#include +#include +#include +#include + +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"); + +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 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, i; + + if (thermal_zone_get_temp(fan->dts0, &t0) || + thermal_zone_get_temp(fan->dts1, &t1)) { + dev_warn(fan->dev, "Error getting temperature\n"); + queue_delayed_work(system_wq, &fan->work, + msecs_to_jiffies(1000)); + return; + } + + temp = max(t0, t1); + + speed = fan->last_speed; + + /* Determine minimum speed */ + for (i = 0; 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 = 0; 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 = 3; /* kick start motor */ + + 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; + } + + 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); + + gpd_pocket_fan_set_speed(fan, 0); + 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 Date: Wed, 17 Jan 2018 18:21:53 +0000 Subject: platform/x86: Move Mellanox platform hotplug driver to platform/mellanox In preparation for making the hotplug driver build for different architectures, move mlxcpld-hotplug.c to platform/mellanox and the header to include/linux/platform_data as mlxreg.h to reflect the new interface changes to come. Replace references to CPLD with REG throughout the files, consistent with the new name. Signed-off-by: Vadim Pasternak Acked-by: Andy Shevchenko [dvhart: update copyright, rewrite commit message] Signed-off-by: Darren Hart (VMware) --- MAINTAINERS | 7 +- drivers/platform/Kconfig | 2 + drivers/platform/Makefile | 1 + drivers/platform/mellanox/Kconfig | 25 ++ drivers/platform/mellanox/Makefile | 6 + drivers/platform/mellanox/mlxreg-hotplug.c | 514 +++++++++++++++++++++++++ drivers/platform/x86/Kconfig | 8 - drivers/platform/x86/Makefile | 1 - drivers/platform/x86/mlx-platform.c | 18 +- drivers/platform/x86/mlxcpld-hotplug.c | 515 -------------------------- include/linux/platform_data/mlxcpld-hotplug.h | 99 ----- include/linux/platform_data/mlxreg.h | 98 +++++ 12 files changed, 659 insertions(+), 635 deletions(-) create mode 100644 drivers/platform/mellanox/Kconfig create mode 100644 drivers/platform/mellanox/Makefile create mode 100644 drivers/platform/mellanox/mlxreg-hotplug.c delete mode 100644 drivers/platform/x86/mlxcpld-hotplug.c delete mode 100644 include/linux/platform_data/mlxcpld-hotplug.h create mode 100644 include/linux/platform_data/mlxreg.h (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index bc4d77695918..c07e1144688a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -8860,12 +8860,13 @@ W: http://www.mellanox.com Q: http://patchwork.ozlabs.org/project/netdev/list/ F: drivers/net/ethernet/mellanox/mlxfw/ -MELLANOX MLX CPLD HOTPLUG DRIVER +MELLANOX HARDWARE PLATFORM SUPPORT +M: Andy Shevchenko +M: Darren Hart M: Vadim Pasternak L: platform-driver-x86@vger.kernel.org S: Supported -F: drivers/platform/x86/mlxcpld-hotplug.c -F: include/linux/platform_data/mlxcpld-hotplug.h +F: drivers/platform/mellanox/ MELLANOX MLX4 core VPI driver M: Tariq Toukan 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..d73529203327 --- /dev/null +++ b/drivers/platform/mellanox/Kconfig @@ -0,0 +1,25 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Platform support for Mellanox hardware +# + +menuconfig MELLANOX_PLATFORM + bool "Platform support for Mellanox hardware" + depends on X86 || 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 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..5cfc82ba34be --- /dev/null +++ b/drivers/platform/mellanox/mlxreg-hotplug.c @@ -0,0 +1,514 @@ +/* + * Copyright (c) 2016-2018 Mellanox Technologies. All rights reserved. + * Copyright (c) 2016-2018 Vadim Pasternak + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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 + +#define MLXREG_HOTPLUG_ATTRS_NUM 8 + +/** + * enum mlxreg_hotplug_attr_type - sysfs attributes for hotplug events: + * @MLXREG_HOTPLUG_ATTR_TYPE_PSU: power supply unit attribute; + * @MLXREG_HOTPLUG_ATTR_TYPE_PWR: power cable attribute; + * @MLXREG_HOTPLUG_ATTR_TYPE_FAN: FAN drawer attribute; + */ +enum mlxreg_hotplug_attr_type { + MLXREG_HOTPLUG_ATTR_TYPE_PSU, + MLXREG_HOTPLUG_ATTR_TYPE_PWR, + MLXREG_HOTPLUG_ATTR_TYPE_FAN, +}; + +/** + * struct mlxreg_hotplug_priv_data - platform private data: + * @irq: platform interrupt number; + * @pdev: platform device; + * @plat: platform data; + * @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; + * @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 mlxreg_hotplug_priv_data { + int irq; + struct platform_device *pdev; + struct mlxreg_hotplug_platform_data *plat; + struct device *hwmon; + struct attribute *mlxreg_hotplug_attr[MLXREG_HOTPLUG_ATTRS_NUM + 1]; + struct sensor_device_attribute_2 + mlxreg_hotplug_dev_attr[MLXREG_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 mlxreg_hotplug_attr_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct mlxreg_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 MLXREG_HOTPLUG_ATTR_TYPE_PSU: + /* Bit = 0 : PSU is present. */ + reg_val = !!!(inb(priv->plat->psu_reg_offset) & BIT(index)); + break; + + case MLXREG_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 MLXREG_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->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) +{ + 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 = MLXREG_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 = MLXREG_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 = MLXREG_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 = mlxreg_hotplug_attr_show; + PRIV_DEV_ATTR(i).index = i; + sysfs_attr_init(&PRIV_DEV_ATTR(i).dev_attr.attr); + } + + priv->group.attrs = priv->mlxreg_hotplug_attr; + priv->groups[0] = &priv->group; + priv->groups[1] = NULL; + + return 0; +} + +static int mlxreg_hotplug_device_create(struct device *dev, + struct mlxreg_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 mlxreg_hotplug_device_destroy(struct mlxreg_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 +mlxreg_hotplug_work_helper(struct device *dev, + struct mlxreg_hotplug_device *item, u8 is_inverse, + u16 offset, u8 mask, u8 *cache) +{ + u8 val, asserted; + int bit; + + /* Mask event. */ + outb(0, offset + MLXREG_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) + mlxreg_hotplug_device_destroy(item + bit); + else + mlxreg_hotplug_device_create(dev, item + bit); + } else { + if (is_inverse) + mlxreg_hotplug_device_create(dev, item + bit); + else + mlxreg_hotplug_device_destroy(item + bit); + } + } + + /* Acknowledge event. */ + outb(0, offset + MLXREG_HOTPLUG_EVENT_OFF); + /* Unmask event. */ + outb(mask, offset + MLXREG_HOTPLUG_MASK_OFF); +} + +/* + * mlxreg_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 mlxreg_hotplug_work_handler(struct work_struct *work) +{ + struct mlxreg_hotplug_priv_data *priv = container_of(work, + struct mlxreg_hotplug_priv_data, dwork.work); + u8 val, aggr_asserted; + unsigned long flags; + + /* Mask aggregation event. */ + outb(0, priv->plat->top_aggr_offset + MLXREG_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) + mlxreg_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) + mlxreg_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) + mlxreg_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 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); + 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 + + MLXREG_HOTPLUG_AGGR_MASK_OFF); +} + +static void mlxreg_hotplug_set_irq(struct mlxreg_hotplug_priv_data *priv) +{ + /* Clear psu presense event. */ + outb(0, priv->plat->psu_reg_offset + MLXREG_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 + + MLXREG_HOTPLUG_MASK_OFF); + + /* Clear power cable event. */ + outb(0, priv->plat->pwr_reg_offset + MLXREG_HOTPLUG_EVENT_OFF); + /* Keep power initial status as zero and unmask power event. */ + outb(priv->plat->pwr_mask, priv->plat->pwr_reg_offset + + MLXREG_HOTPLUG_MASK_OFF); + + /* Clear fan presense event. */ + outb(0, priv->plat->fan_reg_offset + MLXREG_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 + + MLXREG_HOTPLUG_MASK_OFF); + + /* Keep aggregation initial status as zero and unmask events. */ + outb(priv->plat->top_aggr_mask, priv->plat->top_aggr_offset + + MLXREG_HOTPLUG_AGGR_MASK_OFF); + + /* Invoke work handler for initializing hot plug devices setting. */ + mlxreg_hotplug_work_handler(&priv->dwork.work); + + enable_irq(priv->irq); +} + +static void mlxreg_hotplug_unset_irq(struct mlxreg_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 + MLXREG_HOTPLUG_AGGR_MASK_OFF); + + /* Mask psu presense event. */ + outb(0, priv->plat->psu_reg_offset + MLXREG_HOTPLUG_MASK_OFF); + /* Clear psu presense event. */ + outb(0, priv->plat->psu_reg_offset + MLXREG_HOTPLUG_EVENT_OFF); + + /* Mask power cable event. */ + outb(0, priv->plat->pwr_reg_offset + MLXREG_HOTPLUG_MASK_OFF); + /* Clear power cable event. */ + outb(0, priv->plat->pwr_reg_offset + MLXREG_HOTPLUG_EVENT_OFF); + + /* Mask fan presense event. */ + outb(0, priv->plat->fan_reg_offset + MLXREG_HOTPLUG_MASK_OFF); + /* Clear fan presense event. */ + outb(0, priv->plat->fan_reg_offset + MLXREG_HOTPLUG_EVENT_OFF); + + /* Remove all the attached devices. */ + for (i = 0; i < priv->plat->psu_count; i++) + mlxreg_hotplug_device_destroy(priv->plat->psu + i); + + for (i = 0; i < priv->plat->pwr_count; i++) + mlxreg_hotplug_device_destroy(priv->plat->pwr + i); + + for (i = 0; i < priv->plat->fan_count; i++) + mlxreg_hotplug_device_destroy(priv->plat->fan + i); +} + +static irqreturn_t mlxreg_hotplug_irq_handler(int irq, void *dev) +{ + struct mlxreg_hotplug_priv_data *priv = + (struct mlxreg_hotplug_priv_data *)dev; + + /* Schedule work task for immediate execution.*/ + schedule_delayed_work(&priv->dwork, 0); + + return IRQ_HANDLED; +} + +static int mlxreg_hotplug_probe(struct platform_device *pdev) +{ + struct mlxreg_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; + + 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, + mlxreg_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, mlxreg_hotplug_work_handler); + spin_lock_init(&priv->lock); + + 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); + } + + platform_set_drvdata(pdev, priv); + + /* Perform initial interrupts setup. */ + mlxreg_hotplug_set_irq(priv); + + return 0; +} + +static int mlxreg_hotplug_remove(struct platform_device *pdev) +{ + struct mlxreg_hotplug_priv_data *priv = platform_get_drvdata(pdev); + + /* 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 "); +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 aaca22fb45b9..53f52f9df2c5 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -1169,14 +1169,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 diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 57f37e55786a..c388608ad2a3 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -88,6 +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/mlx-platform.c b/drivers/platform/x86/mlx-platform.c index 504256c3660d..0fbec1f1ca40 100644 --- a/drivers/platform/x86/mlx-platform.c +++ b/drivers/platform/x86/mlx-platform.c @@ -38,7 +38,7 @@ #include #include #include -#include +#include #define MLX_PLAT_DEVICE_NAME "mlxplat" @@ -138,7 +138,7 @@ static struct i2c_mux_reg_platform_data mlxplat_mux_data[] = { }; /* Platform hotplug devices */ -static struct mlxcpld_hotplug_device mlxplat_mlxcpld_psu[] = { +static struct mlxreg_hotplug_device mlxplat_mlxcpld_psu[] = { { .brdinfo = { I2C_BOARD_INFO("24c02", 0x51) }, .bus = 10, @@ -149,7 +149,7 @@ static struct mlxcpld_hotplug_device mlxplat_mlxcpld_psu[] = { }, }; -static struct mlxcpld_hotplug_device mlxplat_mlxcpld_pwr[] = { +static struct mlxreg_hotplug_device mlxplat_mlxcpld_pwr[] = { { .brdinfo = { I2C_BOARD_INFO("dps460", 0x59) }, .bus = 10, @@ -160,7 +160,7 @@ static struct mlxcpld_hotplug_device mlxplat_mlxcpld_pwr[] = { }, }; -static struct mlxcpld_hotplug_device mlxplat_mlxcpld_fan[] = { +static struct mlxreg_hotplug_device mlxplat_mlxcpld_fan[] = { { .brdinfo = { I2C_BOARD_INFO("24c32", 0x50) }, .bus = 11, @@ -181,7 +181,7 @@ static struct mlxcpld_hotplug_device mlxplat_mlxcpld_fan[] = { /* Platform hotplug default data */ static -struct mlxcpld_hotplug_platform_data mlxplat_mlxcpld_default_data = { +struct mlxreg_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, @@ -203,7 +203,7 @@ struct mlxcpld_hotplug_platform_data mlxplat_mlxcpld_default_data = { /* Platform hotplug MSN21xx system family data */ static -struct mlxcpld_hotplug_platform_data mlxplat_mlxcpld_msn21xx_data = { +struct mlxreg_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, @@ -213,11 +213,11 @@ struct mlxcpld_hotplug_platform_data mlxplat_mlxcpld_msn21xx_data = { }; 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_hotplug_platform_data *mlxplat_hotplug; static int __init mlxplat_dmi_default_matched(const struct dmi_system_id *dmi) { @@ -329,7 +329,7 @@ static int __init mlxplat_init(void) } 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), 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 - * - * 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* 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 "); -MODULE_DESCRIPTION("Mellanox CPLD hotplug platform driver"); -MODULE_LICENSE("Dual BSD/GPL"); -MODULE_ALIAS("platform:mlxcpld-hotplug"); diff --git a/include/linux/platform_data/mlxcpld-hotplug.h b/include/linux/platform_data/mlxcpld-hotplug.h deleted file mode 100644 index e4cfcffaa6f4..000000000000 --- a/include/linux/platform_data/mlxcpld-hotplug.h +++ /dev/null @@ -1,99 +0,0 @@ -/* - * include/linux/platform_data/mlxcpld-hotplug.h - * Copyright (c) 2016 Mellanox Technologies. All rights reserved. - * Copyright (c) 2016 Vadim Pasternak - * - * 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. - */ - -#ifndef __LINUX_PLATFORM_DATA_MLXCPLD_HOTPLUG_H -#define __LINUX_PLATFORM_DATA_MLXCPLD_HOTPLUG_H - -/** - * struct mlxcpld_hotplug_device - I2C device data: - * @adapter: I2C device adapter; - * @client: I2C device client; - * @brdinfo: device board information; - * @bus: I2C bus, where device is attached; - * - * Structure represents I2C hotplug device static data (board topology) and - * dynamic data (related kernel objects handles). - */ -struct mlxcpld_hotplug_device { - struct i2c_adapter *adapter; - struct i2c_client *client; - struct i2c_board_info brdinfo; - u16 bus; -}; - -/** - * struct mlxcpld_hotplug_platform_data - device platform data: - * @top_aggr_offset: offset of top aggregation interrupt register; - * @top_aggr_mask: top aggregation interrupt common mask; - * @top_aggr_psu_mask: top aggregation interrupt PSU mask; - * @psu_reg_offset: offset of PSU interrupt register; - * @psu_mask: PSU interrupt mask; - * @psu_count: number of equipped replaceable PSUs; - * @psu: pointer to PSU devices data array; - * @top_aggr_pwr_mask: top aggregation interrupt power mask; - * @pwr_reg_offset: offset of power interrupt register - * @pwr_mask: power interrupt mask; - * @pwr_count: number of power sources; - * @pwr: pointer to power devices data array; - * @top_aggr_fan_mask: top aggregation interrupt FAN mask; - * @fan_reg_offset: offset of FAN interrupt register; - * @fan_mask: FAN interrupt mask; - * @fan_count: number of equipped replaceable FANs; - * @fan: pointer to FAN devices data array; - * - * Structure represents board platform data, related to system hotplug events, - * like FAN, PSU, power cable insertion and removing. This data provides the - * number of hot-pluggable devices and hardware description for event handling. - */ -struct mlxcpld_hotplug_platform_data { - u16 top_aggr_offset; - u8 top_aggr_mask; - u8 top_aggr_psu_mask; - u16 psu_reg_offset; - u8 psu_mask; - u8 psu_count; - struct mlxcpld_hotplug_device *psu; - u8 top_aggr_pwr_mask; - u16 pwr_reg_offset; - u8 pwr_mask; - u8 pwr_count; - struct mlxcpld_hotplug_device *pwr; - u8 top_aggr_fan_mask; - u16 fan_reg_offset; - u8 fan_mask; - u8 fan_count; - struct mlxcpld_hotplug_device *fan; -}; - -#endif /* __LINUX_PLATFORM_DATA_MLXCPLD_HOTPLUG_H */ diff --git a/include/linux/platform_data/mlxreg.h b/include/linux/platform_data/mlxreg.h new file mode 100644 index 000000000000..8dcbb8e21ee2 --- /dev/null +++ b/include/linux/platform_data/mlxreg.h @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2017 Mellanox Technologies. All rights reserved. + * Copyright (c) 2017 Vadim Pasternak + * + * 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. + */ + +#ifndef __LINUX_PLATFORM_DATA_MLXREG_H +#define __LINUX_PLATFORM_DATA_MLXREG_H + +/** + * struct mlxreg_hotplug_device - I2C device data: + * @adapter: I2C device adapter; + * @client: I2C device client; + * @brdinfo: device board information; + * @bus: I2C bus, where device is attached; + * + * Structure represents I2C hotplug device static data (board topology) and + * dynamic data (related kernel objects handles). + */ +struct mlxreg_hotplug_device { + struct i2c_adapter *adapter; + struct i2c_client *client; + struct i2c_board_info brdinfo; + u16 bus; +}; + +/** + * struct mlxreg_hotplug_platform_data - device platform data: + * @top_aggr_offset: offset of top aggregation interrupt register; + * @top_aggr_mask: top aggregation interrupt common mask; + * @top_aggr_psu_mask: top aggregation interrupt PSU mask; + * @psu_reg_offset: offset of PSU interrupt register; + * @psu_mask: PSU interrupt mask; + * @psu_count: number of equipped replaceable PSUs; + * @psu: pointer to PSU devices data array; + * @top_aggr_pwr_mask: top aggregation interrupt power mask; + * @pwr_reg_offset: offset of power interrupt register + * @pwr_mask: power interrupt mask; + * @pwr_count: number of power sources; + * @pwr: pointer to power devices data array; + * @top_aggr_fan_mask: top aggregation interrupt FAN mask; + * @fan_reg_offset: offset of FAN interrupt register; + * @fan_mask: FAN interrupt mask; + * @fan_count: number of equipped replaceable FANs; + * @fan: pointer to FAN devices data array; + * + * Structure represents board platform data, related to system hotplug events, + * like FAN, PSU, power cable insertion and removing. This data provides the + * number of hot-pluggable devices and hardware description for event handling. + */ +struct mlxreg_hotplug_platform_data { + u16 top_aggr_offset; + u8 top_aggr_mask; + u8 top_aggr_psu_mask; + u16 psu_reg_offset; + u8 psu_mask; + u8 psu_count; + struct mlxreg_hotplug_device *psu; + u8 top_aggr_pwr_mask; + u16 pwr_reg_offset; + u8 pwr_mask; + u8 pwr_count; + struct mlxreg_hotplug_device *pwr; + u8 top_aggr_fan_mask; + u16 fan_reg_offset; + u8 fan_mask; + u8 fan_count; + struct mlxreg_hotplug_device *fan; +}; + +#endif /* __LINUX_PLATFORM_DATA_MLXREG_H */ -- cgit v1.2.3