diff options
Diffstat (limited to 'drivers/platform')
-rw-r--r-- | drivers/platform/x86/Kconfig | 40 | ||||
-rw-r--r-- | drivers/platform/x86/Makefile | 4 | ||||
-rw-r--r-- | drivers/platform/x86/ideapad-laptop.c | 7 | ||||
-rw-r--r-- | drivers/platform/x86/intel_bxtwc_tmu.c | 162 | ||||
-rw-r--r-- | drivers/platform/x86/mlx-platform.c | 361 | ||||
-rw-r--r-- | drivers/platform/x86/surface3-wmi.c | 297 | ||||
-rw-r--r-- | drivers/platform/x86/surface3_button.c | 250 |
7 files changed, 1120 insertions, 1 deletions
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 185376901d9c..5fe8be089b8b 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -363,6 +363,18 @@ config IDEAPAD_LAPTOP This is a driver for Lenovo IdeaPad netbooks contains drivers for rfkill switch, hotkey, fan control and backlight control. +config SURFACE3_WMI + tristate "Surface 3 WMI Driver" + depends on ACPI_WMI + depends on DMI + depends on INPUT + depends on SPI + ---help--- + Say Y here if you have a Surface 3. + + To compile this driver as a module, choose M here: the module will + be called surface3-wmi. + config THINKPAD_ACPI tristate "ThinkPad ACPI Laptop Extras" depends on ACPI @@ -1005,12 +1017,27 @@ config INTEL_PMC_IPC The PMC is an ARC processor which defines IPC commands for communication with other entities in the CPU. +config INTEL_BXTWC_PMIC_TMU + tristate "Intel BXT Whiskey Cove TMU Driver" + depends on REGMAP + depends on INTEL_SOC_PMIC && INTEL_PMC_IPC + ---help--- + Select this driver to use Intel BXT Whiskey Cove PMIC TMU feature. + This driver enables the alarm wakeup functionality in the TMU unit + of Whiskey Cove PMIC. + config SURFACE_PRO3_BUTTON tristate "Power/home/volume buttons driver for Microsoft Surface Pro 3/4 tablet" depends on ACPI && INPUT ---help--- This driver handles the power/home/volume buttons on the Microsoft Surface Pro 3/4 tablet. +config SURFACE_3_BUTTON + tristate "Power/home/volume buttons driver for Microsoft Surface 3 tablet" + depends on ACPI && KEYBOARD_GPIO + ---help--- + This driver handles the power/home/volume buttons on the Microsoft Surface 3 tablet. + config INTEL_PUNIT_IPC tristate "Intel P-Unit IPC Driver" ---help--- @@ -1028,10 +1055,21 @@ config INTEL_TELEMETRY directly via debugfs files. Various tools may use this interface for SoC state monitoring. +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 + solutions based on Virtual Protocol Interconnect (VPI) technology + enable seamless connectivity to 56/100Gb/s InfiniBand or 10/40/56GbE + connection. + + If you have a Mellanox system, say Y or M here. + config MLX_CPLD_PLATFORM tristate "Mellanox platform hotplug driver support" default n - depends on MLX_PLATFORM select HWMON select I2C ---help--- diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 1f06b6339cf7..d4111f0f8a78 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -34,6 +34,7 @@ obj-$(CONFIG_PANASONIC_LAPTOP) += panasonic-laptop.o obj-$(CONFIG_INTEL_MENLOW) += intel_menlow.o obj-$(CONFIG_ACPI_WMI) += wmi.o obj-$(CONFIG_MSI_WMI) += msi-wmi.o +obj-$(CONFIG_SURFACE3_WMI) += surface3-wmi.o obj-$(CONFIG_TOPSTAR_LAPTOP) += topstar-laptop.o # toshiba_acpi must link after wmi to ensure that wmi devices are found @@ -66,9 +67,12 @@ obj-$(CONFIG_PVPANIC) += pvpanic.o obj-$(CONFIG_ALIENWARE_WMI) += alienware-wmi.o obj-$(CONFIG_INTEL_PMC_IPC) += intel_pmc_ipc.o obj-$(CONFIG_SURFACE_PRO3_BUTTON) += surfacepro3_button.o +obj-$(CONFIG_SURFACE_3_BUTTON) += surface3_button.o obj-$(CONFIG_INTEL_PUNIT_IPC) += intel_punit_ipc.o +obj-$(CONFIG_INTEL_BXTWC_PMIC_TMU) += intel_bxtwc_tmu.o obj-$(CONFIG_INTEL_TELEMETRY) += intel_telemetry_core.o \ intel_telemetry_pltdrv.o \ intel_telemetry_debugfs.o obj-$(CONFIG_INTEL_PMC_CORE) += intel_pmc_core.o +obj-$(CONFIG_MLX_PLATFORM) += mlx-platform.o obj-$(CONFIG_MLX_CPLD_PLATFORM) += mlxcpld-hotplug.o diff --git a/drivers/platform/x86/ideapad-laptop.c b/drivers/platform/x86/ideapad-laptop.c index a7614fc542b5..410741acb3c9 100644 --- a/drivers/platform/x86/ideapad-laptop.c +++ b/drivers/platform/x86/ideapad-laptop.c @@ -871,6 +871,13 @@ static const struct dmi_system_id no_hw_rfkill_list[] = { }, }, { + .ident = "Lenovo ideapad Y700-15ACZ", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo ideapad Y700-15ACZ"), + }, + }, + { .ident = "Lenovo ideapad Y700-15ISK", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), diff --git a/drivers/platform/x86/intel_bxtwc_tmu.c b/drivers/platform/x86/intel_bxtwc_tmu.c new file mode 100644 index 000000000000..e202abd5b0df --- /dev/null +++ b/drivers/platform/x86/intel_bxtwc_tmu.c @@ -0,0 +1,162 @@ +/* + * intel_bxtwc_tmu.c - Intel BXT Whiskey Cove PMIC TMU driver + * + * Copyright (C) 2016 Intel Corporation. All rights reserved. + * + * This driver adds TMU (Time Management Unit) support for Intel BXT platform. + * It enables the alarm wake-up functionality in the TMU unit of Whiskey Cove + * PMIC. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version + * 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/mfd/intel_soc_pmic.h> + +#define BXTWC_TMUIRQ 0x4fb6 +#define BXTWC_MIRQLVL1 0x4e0e +#define BXTWC_MTMUIRQ_REG 0x4fb7 +#define BXTWC_MIRQLVL1_MTMU BIT(1) +#define BXTWC_TMU_WK_ALRM BIT(1) +#define BXTWC_TMU_SYS_ALRM BIT(2) +#define BXTWC_TMU_ALRM_MASK (BXTWC_TMU_WK_ALRM | BXTWC_TMU_SYS_ALRM) +#define BXTWC_TMU_ALRM_IRQ (BXTWC_TMU_WK_ALRM | BXTWC_TMU_SYS_ALRM) + +struct wcove_tmu { + int irq; + struct device *dev; + struct regmap *regmap; +}; + +static irqreturn_t bxt_wcove_tmu_irq_handler(int irq, void *data) +{ + struct wcove_tmu *wctmu = data; + unsigned int tmu_irq; + + /* Read TMU interrupt reg */ + regmap_read(wctmu->regmap, BXTWC_TMUIRQ, &tmu_irq); + if (tmu_irq & BXTWC_TMU_ALRM_IRQ) { + /* clear TMU irq */ + regmap_write(wctmu->regmap, BXTWC_TMUIRQ, tmu_irq); + return IRQ_HANDLED; + } + return IRQ_NONE; +} + +static int bxt_wcove_tmu_probe(struct platform_device *pdev) +{ + struct intel_soc_pmic *pmic = dev_get_drvdata(pdev->dev.parent); + struct regmap_irq_chip_data *regmap_irq_chip; + struct wcove_tmu *wctmu; + int ret, virq, irq; + + wctmu = devm_kzalloc(&pdev->dev, sizeof(*wctmu), GFP_KERNEL); + if (!wctmu) + return -ENOMEM; + + wctmu->dev = &pdev->dev; + wctmu->regmap = pmic->regmap; + + irq = platform_get_irq(pdev, 0); + + if (irq < 0) { + dev_err(&pdev->dev, "invalid irq %d\n", irq); + return irq; + } + + regmap_irq_chip = pmic->irq_chip_data_tmu; + virq = regmap_irq_get_virq(regmap_irq_chip, irq); + if (virq < 0) { + dev_err(&pdev->dev, + "failed to get virtual interrupt=%d\n", irq); + return virq; + } + + ret = devm_request_threaded_irq(&pdev->dev, virq, + NULL, bxt_wcove_tmu_irq_handler, + IRQF_ONESHOT, "bxt_wcove_tmu", wctmu); + if (ret) { + dev_err(&pdev->dev, "request irq failed: %d,virq: %d\n", + ret, virq); + return ret; + } + wctmu->irq = virq; + + /* Enable TMU interrupts */ + regmap_update_bits(wctmu->regmap, BXTWC_MIRQLVL1, + BXTWC_MIRQLVL1_MTMU, 0); + + /* Unmask TMU second level Wake & System alarm */ + regmap_update_bits(wctmu->regmap, BXTWC_MTMUIRQ_REG, + BXTWC_TMU_ALRM_MASK, 0); + + platform_set_drvdata(pdev, wctmu); + return 0; +} + +static int bxt_wcove_tmu_remove(struct platform_device *pdev) +{ + struct wcove_tmu *wctmu = platform_get_drvdata(pdev); + unsigned int val; + + /* Mask TMU interrupts */ + regmap_read(wctmu->regmap, BXTWC_MIRQLVL1, &val); + regmap_write(wctmu->regmap, BXTWC_MIRQLVL1, + val | BXTWC_MIRQLVL1_MTMU); + regmap_read(wctmu->regmap, BXTWC_MTMUIRQ_REG, &val); + regmap_write(wctmu->regmap, BXTWC_MTMUIRQ_REG, + val | BXTWC_TMU_ALRM_MASK); + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int bxtwc_tmu_suspend(struct device *dev) +{ + struct wcove_tmu *wctmu = dev_get_drvdata(dev); + + enable_irq_wake(wctmu->irq); + return 0; +} + +static int bxtwc_tmu_resume(struct device *dev) +{ + struct wcove_tmu *wctmu = dev_get_drvdata(dev); + + disable_irq_wake(wctmu->irq); + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(bxtwc_tmu_pm_ops, bxtwc_tmu_suspend, bxtwc_tmu_resume); + +static const struct platform_device_id bxt_wcove_tmu_id_table[] = { + { .name = "bxt_wcove_tmu" }, + {}, +}; +MODULE_DEVICE_TABLE(platform, bxt_wcove_tmu_id_table); + +static struct platform_driver bxt_wcove_tmu_driver = { + .probe = bxt_wcove_tmu_probe, + .remove = bxt_wcove_tmu_remove, + .driver = { + .name = "bxt_wcove_tmu", + .pm = &bxtwc_tmu_pm_ops, + }, + .id_table = bxt_wcove_tmu_id_table, +}; + +module_platform_driver(bxt_wcove_tmu_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Nilesh Bacchewar <nilesh.bacchewar@intel.com>"); +MODULE_DESCRIPTION("BXT Whiskey Cove TMU Driver"); diff --git a/drivers/platform/x86/mlx-platform.c b/drivers/platform/x86/mlx-platform.c new file mode 100644 index 000000000000..97b4c3a219c0 --- /dev/null +++ b/drivers/platform/x86/mlx-platform.c @@ -0,0 +1,361 @@ +/* + * 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/device.h> +#include <linux/dmi.h> +#include <linux/i2c.h> +#include <linux/i2c-mux.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> + +#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_IO_RANGE 0x100 +#define MLXPLAT_CPLD_LPC_I2C_CH1_OFF 0xdb +#define MLXPLAT_CPLD_LPC_I2C_CH2_OFF 0xda +#define MLXPLAT_CPLD_LPC_PIO_OFFSET 0x10000UL +#define MLXPLAT_CPLD_LPC_REG1 ((MLXPLAT_CPLD_LPC_REG_BASE_ADRR + \ + MLXPLAT_CPLD_LPC_I2C_CH1_OFF) | \ + MLXPLAT_CPLD_LPC_PIO_OFFSET) +#define MLXPLAT_CPLD_LPC_REG2 ((MLXPLAT_CPLD_LPC_REG_BASE_ADRR + \ + MLXPLAT_CPLD_LPC_I2C_CH2_OFF) | \ + MLXPLAT_CPLD_LPC_PIO_OFFSET) + +/* Start channel numbers */ +#define MLXPLAT_CPLD_CH1 2 +#define MLXPLAT_CPLD_CH2 10 + +/* Number of LPC attached MUX platform devices */ +#define MLXPLAT_CPLD_LPC_MUX_DEVS 2 + +/* mlxplat_priv - platform private data + * @pdev_i2c - i2c controller platform device + * @pdev_mux - array of mux platform devices + */ +struct mlxplat_priv { + struct platform_device *pdev_i2c; + struct platform_device *pdev_mux[MLXPLAT_CPLD_LPC_MUX_DEVS]; + struct platform_device *pdev_hotplug; +}; + +/* Regions for LPC I2C controller and LPC base register space */ +static const struct resource mlxplat_lpc_resources[] = { + [0] = DEFINE_RES_NAMED(MLXPLAT_CPLD_LPC_I2C_BASE_ADRR, + MLXPLAT_CPLD_LPC_IO_RANGE, + "mlxplat_cpld_lpc_i2c_ctrl", IORESOURCE_IO), + [1] = DEFINE_RES_NAMED(MLXPLAT_CPLD_LPC_REG_BASE_ADRR, + MLXPLAT_CPLD_LPC_IO_RANGE, + "mlxplat_cpld_lpc_regs", + IORESOURCE_IO), +}; + +/* Platform default channels */ +static const int mlxplat_default_channels[][8] = { + { + MLXPLAT_CPLD_CH1, MLXPLAT_CPLD_CH1 + 1, MLXPLAT_CPLD_CH1 + 2, + MLXPLAT_CPLD_CH1 + 3, MLXPLAT_CPLD_CH1 + 4, MLXPLAT_CPLD_CH1 + + 5, MLXPLAT_CPLD_CH1 + 6, MLXPLAT_CPLD_CH1 + 7 + }, + { + MLXPLAT_CPLD_CH2, MLXPLAT_CPLD_CH2 + 1, MLXPLAT_CPLD_CH2 + 2, + MLXPLAT_CPLD_CH2 + 3, MLXPLAT_CPLD_CH2 + 4, MLXPLAT_CPLD_CH2 + + 5, MLXPLAT_CPLD_CH2 + 6, MLXPLAT_CPLD_CH2 + 7 + }, +}; + +/* Platform channels for MSN21xx system family */ +static const int mlxplat_msn21xx_channels[] = { 1, 2, 3, 4, 5, 6, 7, 8 }; + +/* Platform mux data */ +static struct i2c_mux_reg_platform_data mlxplat_mux_data[] = { + { + .parent = 1, + .base_nr = MLXPLAT_CPLD_CH1, + .write_only = 1, + .reg = (void __iomem *)MLXPLAT_CPLD_LPC_REG1, + .reg_size = 1, + .idle_in_use = 1, + }, + { + .parent = 1, + .base_nr = MLXPLAT_CPLD_CH2, + .write_only = 1, + .reg = (void __iomem *)MLXPLAT_CPLD_LPC_REG2, + .reg_size = 1, + .idle_in_use = 1, + }, + +}; + +/* Platform hotplug devices */ +static struct mlxcpld_hotplug_device mlxplat_mlxcpld_hotplug_psu[] = { + { + .brdinfo = { I2C_BOARD_INFO("24c02", 0x51) }, + .bus = 10, + }, + { + .brdinfo = { I2C_BOARD_INFO("24c02", 0x50) }, + .bus = 10, + }, +}; + +static struct mlxcpld_hotplug_device mlxplat_mlxcpld_hotplug_pwr[] = { + { + .brdinfo = { I2C_BOARD_INFO("dps460", 0x59) }, + .bus = 10, + }, + { + .brdinfo = { I2C_BOARD_INFO("dps460", 0x58) }, + .bus = 10, + }, +}; + +static struct mlxcpld_hotplug_device mlxplat_mlxcpld_hotplug_fan[] = { + { + .brdinfo = { I2C_BOARD_INFO("24c32", 0x50) }, + .bus = 11, + }, + { + .brdinfo = { I2C_BOARD_INFO("24c32", 0x50) }, + .bus = 12, + }, + { + .brdinfo = { I2C_BOARD_INFO("24c32", 0x50) }, + .bus = 13, + }, + { + .brdinfo = { I2C_BOARD_INFO("24c32", 0x50) }, + .bus = 14, + }, +}; + +/* Platform hotplug default data */ +static +struct mlxcpld_hotplug_platform_data mlxplat_mlxcpld_hotplug_default_data = { + .top_aggr_offset = (MLXPLAT_CPLD_LPC_REG_BASE_ADRR | 0x3a), + .top_aggr_mask = 0x48, + .top_aggr_psu_mask = 0x08, + .psu_reg_offset = (MLXPLAT_CPLD_LPC_REG_BASE_ADRR | 0x58), + .psu_mask = 0x03, + .psu_count = ARRAY_SIZE(mlxplat_mlxcpld_hotplug_psu), + .psu = mlxplat_mlxcpld_hotplug_psu, + .top_aggr_pwr_mask = 0x08, + .pwr_reg_offset = (MLXPLAT_CPLD_LPC_REG_BASE_ADRR | 0x64), + .pwr_mask = 0x03, + .pwr_count = ARRAY_SIZE(mlxplat_mlxcpld_hotplug_pwr), + .pwr = mlxplat_mlxcpld_hotplug_pwr, + .top_aggr_fan_mask = 0x40, + .fan_reg_offset = (MLXPLAT_CPLD_LPC_REG_BASE_ADRR | 0x88), + .fan_mask = 0x0f, + .fan_count = ARRAY_SIZE(mlxplat_mlxcpld_hotplug_fan), + .fan = mlxplat_mlxcpld_hotplug_fan, +}; + +/* Platform hotplug MSN21xx system family data */ +static +struct mlxcpld_hotplug_platform_data mlxplat_mlxcpld_hotplug_msn21xx_data = { + .top_aggr_offset = (MLXPLAT_CPLD_LPC_REG_BASE_ADRR | 0x3a), + .top_aggr_mask = 0x04, + .top_aggr_pwr_mask = 0x04, + .pwr_reg_offset = (MLXPLAT_CPLD_LPC_REG_BASE_ADRR | 0x64), + .pwr_mask = 0x03, + .pwr_count = ARRAY_SIZE(mlxplat_mlxcpld_hotplug_pwr), +}; + +static struct resource mlxplat_mlxcpld_hotplug_resources[] = { + [0] = DEFINE_RES_IRQ_NAMED(17, "mlxcpld-hotplug"), +}; + +struct platform_device *mlxplat_dev; +struct mlxcpld_hotplug_platform_data *mlxplat_hotplug; + +static int __init mlxplat_dmi_default_matched(const struct dmi_system_id *dmi) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(mlxplat_mux_data); i++) { + mlxplat_mux_data[i].values = mlxplat_default_channels[i]; + mlxplat_mux_data[i].n_values = + ARRAY_SIZE(mlxplat_default_channels[i]); + } + mlxplat_hotplug = &mlxplat_mlxcpld_hotplug_default_data; + + return 1; +}; + +static int __init mlxplat_dmi_msn21xx_matched(const struct dmi_system_id *dmi) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(mlxplat_mux_data); i++) { + mlxplat_mux_data[i].values = mlxplat_msn21xx_channels; + mlxplat_mux_data[i].n_values = + ARRAY_SIZE(mlxplat_msn21xx_channels); + } + mlxplat_hotplug = &mlxplat_mlxcpld_hotplug_msn21xx_data; + + return 1; +}; + +static struct dmi_system_id mlxplat_dmi_table[] __initdata = { + { + .callback = mlxplat_dmi_default_matched, + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Mellanox Technologies"), + DMI_MATCH(DMI_PRODUCT_NAME, "MSN24"), + }, + }, + { + .callback = mlxplat_dmi_default_matched, + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Mellanox Technologies"), + DMI_MATCH(DMI_PRODUCT_NAME, "MSN27"), + }, + }, + { + .callback = mlxplat_dmi_default_matched, + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Mellanox Technologies"), + DMI_MATCH(DMI_PRODUCT_NAME, "MSB"), + }, + }, + { + .callback = mlxplat_dmi_default_matched, + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Mellanox Technologies"), + DMI_MATCH(DMI_PRODUCT_NAME, "MSX"), + }, + }, + { + .callback = mlxplat_dmi_msn21xx_matched, + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Mellanox Technologies"), + DMI_MATCH(DMI_PRODUCT_NAME, "MSN21"), + }, + }, + { } +}; + +static int __init mlxplat_init(void) +{ + struct mlxplat_priv *priv; + int i, err; + + if (!dmi_check_system(mlxplat_dmi_table)) + return -ENODEV; + + mlxplat_dev = platform_device_register_simple(MLX_PLAT_DEVICE_NAME, -1, + mlxplat_lpc_resources, + ARRAY_SIZE(mlxplat_lpc_resources)); + + if (IS_ERR(mlxplat_dev)) + return PTR_ERR(mlxplat_dev); + + priv = devm_kzalloc(&mlxplat_dev->dev, sizeof(struct mlxplat_priv), + GFP_KERNEL); + if (!priv) { + err = -ENOMEM; + goto fail_alloc; + } + platform_set_drvdata(mlxplat_dev, priv); + + priv->pdev_i2c = platform_device_register_simple("i2c_mlxcpld", -1, + NULL, 0); + if (IS_ERR(priv->pdev_i2c)) { + err = PTR_ERR(priv->pdev_i2c); + goto fail_alloc; + } + + for (i = 0; i < ARRAY_SIZE(mlxplat_mux_data); i++) { + priv->pdev_mux[i] = platform_device_register_resndata( + &mlxplat_dev->dev, + "i2c-mux-reg", i, NULL, + 0, &mlxplat_mux_data[i], + sizeof(mlxplat_mux_data[i])); + if (IS_ERR(priv->pdev_mux[i])) { + err = PTR_ERR(priv->pdev_mux[i]); + goto fail_platform_mux_register; + } + } + + priv->pdev_hotplug = platform_device_register_resndata( + &mlxplat_dev->dev, "mlxcpld-hotplug", -1, + mlxplat_mlxcpld_hotplug_resources, + ARRAY_SIZE(mlxplat_mlxcpld_hotplug_resources), + mlxplat_hotplug, sizeof(*mlxplat_hotplug)); + if (IS_ERR(priv->pdev_hotplug)) { + err = PTR_ERR(priv->pdev_hotplug); + goto fail_platform_mux_register; + } + + return 0; + +fail_platform_mux_register: + for (i--; i > 0 ; i--) + platform_device_unregister(priv->pdev_mux[i]); + platform_device_unregister(priv->pdev_i2c); +fail_alloc: + platform_device_unregister(mlxplat_dev); + + return err; +} +module_init(mlxplat_init); + +static void __exit mlxplat_exit(void) +{ + struct mlxplat_priv *priv = platform_get_drvdata(mlxplat_dev); + int i; + + platform_device_unregister(priv->pdev_hotplug); + + for (i = ARRAY_SIZE(mlxplat_mux_data) - 1; i >= 0 ; i--) + platform_device_unregister(priv->pdev_mux[i]); + + platform_device_unregister(priv->pdev_i2c); + platform_device_unregister(mlxplat_dev); +} +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/surface3-wmi.c b/drivers/platform/x86/surface3-wmi.c new file mode 100644 index 000000000000..cbf4d83a7271 --- /dev/null +++ b/drivers/platform/x86/surface3-wmi.c @@ -0,0 +1,297 @@ +/* + * Driver for the LID cover switch of the Surface 3 + * + * Copyright (c) 2016 Red Hat Inc. + */ + +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; version 2 of the License. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> + +#include <linux/acpi.h> +#include <linux/dmi.h> +#include <linux/input.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/spi/spi.h> + +MODULE_AUTHOR("Benjamin Tissoires <benjamin.tissoires@redhat.com>"); +MODULE_DESCRIPTION("Surface 3 platform driver"); +MODULE_LICENSE("GPL"); + +#define ACPI_BUTTON_HID_LID "PNP0C0D" +#define SPI_CTL_OBJ_NAME "SPI" +#define SPI_TS_OBJ_NAME "NTRG" + +#define SURFACE3_LID_GUID "F7CC25EC-D20B-404C-8903-0ED4359C18AE" + +MODULE_ALIAS("wmi:" SURFACE3_LID_GUID); + +static const struct dmi_system_id surface3_dmi_table[] = { +#if defined(CONFIG_X86) + { + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_MATCH(DMI_PRODUCT_NAME, "Surface 3"), + }, + }, +#endif + { } +}; + +struct surface3_wmi { + struct acpi_device *touchscreen_adev; + struct acpi_device *pnp0c0d_adev; + struct acpi_hotplug_context hp; + struct input_dev *input; +}; + +static struct platform_device *s3_wmi_pdev; + +static struct surface3_wmi s3_wmi; + +static DEFINE_MUTEX(s3_wmi_lock); + +static int s3_wmi_query_block(const char *guid, int instance, int *ret) +{ + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; + acpi_status status; + union acpi_object *obj; + int error = 0; + + mutex_lock(&s3_wmi_lock); + status = wmi_query_block(guid, instance, &output); + + obj = output.pointer; + + if (!obj || obj->type != ACPI_TYPE_INTEGER) { + if (obj) { + pr_err("query block returned object type: %d - buffer length:%d\n", + obj->type, + obj->type == ACPI_TYPE_BUFFER ? + obj->buffer.length : 0); + } + error = -EINVAL; + goto out_free_unlock; + } + *ret = obj->integer.value; + out_free_unlock: + kfree(obj); + mutex_unlock(&s3_wmi_lock); + return error; +} + +static inline int s3_wmi_query_lid(int *ret) +{ + return s3_wmi_query_block(SURFACE3_LID_GUID, 0, ret); +} + +static int s3_wmi_send_lid_state(void) +{ + int ret, lid_sw; + + ret = s3_wmi_query_lid(&lid_sw); + if (ret) + return ret; + + input_report_switch(s3_wmi.input, SW_LID, lid_sw); + input_sync(s3_wmi.input); + + return 0; +} + +static int s3_wmi_hp_notify(struct acpi_device *adev, u32 value) +{ + return s3_wmi_send_lid_state(); +} + +static acpi_status s3_wmi_attach_spi_device(acpi_handle handle, + u32 level, + void *data, + void **return_value) +{ + struct acpi_device *adev, **ts_adev; + + if (acpi_bus_get_device(handle, &adev)) + return AE_OK; + + ts_adev = data; + + if (strncmp(acpi_device_bid(adev), SPI_TS_OBJ_NAME, + strlen(SPI_TS_OBJ_NAME))) + return AE_OK; + + if (*ts_adev) { + pr_err("duplicate entry %s\n", SPI_TS_OBJ_NAME); + return AE_OK; + } + + *ts_adev = adev; + + return AE_OK; +} + +static int s3_wmi_check_platform_device(struct device *dev, void *data) +{ + struct acpi_device *adev, *ts_adev; + acpi_handle handle; + acpi_status status; + + /* ignore non ACPI devices */ + handle = ACPI_HANDLE(dev); + if (!handle || acpi_bus_get_device(handle, &adev)) + return 0; + + /* check for LID ACPI switch */ + if (!strcmp(ACPI_BUTTON_HID_LID, acpi_device_hid(adev))) { + s3_wmi.pnp0c0d_adev = adev; + return 0; + } + + /* ignore non SPI controllers */ + if (strncmp(acpi_device_bid(adev), SPI_CTL_OBJ_NAME, + strlen(SPI_CTL_OBJ_NAME))) + return 0; + + status = acpi_walk_namespace(ACPI_TYPE_DEVICE, handle, 1, + s3_wmi_attach_spi_device, NULL, + &ts_adev, NULL); + if (ACPI_FAILURE(status)) + dev_warn(dev, "failed to enumerate SPI slaves\n"); + + if (!ts_adev) + return 0; + + s3_wmi.touchscreen_adev = ts_adev; + + return 0; +} + +static int s3_wmi_create_and_register_input(struct platform_device *pdev) +{ + struct input_dev *input; + int error; + + input = devm_input_allocate_device(&pdev->dev); + if (!input) + return -ENOMEM; + + input->name = "Lid Switch"; + input->phys = "button/input0"; + input->id.bustype = BUS_HOST; + input->id.product = 0x0005; + + input_set_capability(input, EV_SW, SW_LID); + + error = input_register_device(input); + if (error) + goto out_err; + + s3_wmi.input = input; + + return 0; + out_err: + input_free_device(s3_wmi.input); + return error; +} + +static int __init s3_wmi_probe(struct platform_device *pdev) +{ + int error; + + if (!dmi_check_system(surface3_dmi_table)) + return -ENODEV; + + memset(&s3_wmi, 0, sizeof(s3_wmi)); + + bus_for_each_dev(&platform_bus_type, NULL, NULL, + s3_wmi_check_platform_device); + + if (!s3_wmi.touchscreen_adev) + return -ENODEV; + + acpi_bus_trim(s3_wmi.pnp0c0d_adev); + + error = s3_wmi_create_and_register_input(pdev); + if (error) + goto restore_acpi_lid; + + acpi_initialize_hp_context(s3_wmi.touchscreen_adev, &s3_wmi.hp, + s3_wmi_hp_notify, NULL); + + s3_wmi_send_lid_state(); + + return 0; + + restore_acpi_lid: + acpi_bus_scan(s3_wmi.pnp0c0d_adev->handle); + return error; +} + +static int s3_wmi_remove(struct platform_device *device) +{ + /* remove the hotplug context from the acpi device */ + s3_wmi.touchscreen_adev->hp = NULL; + + /* reinstall the actual PNPC0C0D LID default handle */ + acpi_bus_scan(s3_wmi.pnp0c0d_adev->handle); + return 0; +} + +#ifdef CONFIG_PM +static int s3_wmi_resume(struct device *dev) +{ + s3_wmi_send_lid_state(); + return 0; +} +#endif +static SIMPLE_DEV_PM_OPS(s3_wmi_pm, NULL, s3_wmi_resume); + +static struct platform_driver s3_wmi_driver = { + .driver = { + .name = "surface3-wmi", + .pm = &s3_wmi_pm, + }, + .remove = s3_wmi_remove, +}; + +static int __init s3_wmi_init(void) +{ + int error; + + s3_wmi_pdev = platform_device_alloc("surface3-wmi", -1); + if (!s3_wmi_pdev) + return -ENOMEM; + + error = platform_device_add(s3_wmi_pdev); + if (error) + goto err_device_put; + + error = platform_driver_probe(&s3_wmi_driver, s3_wmi_probe); + if (error) + goto err_device_del; + + pr_info("Surface 3 WMI Extras loaded\n"); + return 0; + + err_device_del: + platform_device_del(s3_wmi_pdev); + err_device_put: + platform_device_put(s3_wmi_pdev); + return error; +} + +static void __exit s3_wmi_exit(void) +{ + platform_device_unregister(s3_wmi_pdev); + platform_driver_unregister(&s3_wmi_driver); +} + +module_init(s3_wmi_init); +module_exit(s3_wmi_exit); diff --git a/drivers/platform/x86/surface3_button.c b/drivers/platform/x86/surface3_button.c new file mode 100644 index 000000000000..8bfd7f613d36 --- /dev/null +++ b/drivers/platform/x86/surface3_button.c @@ -0,0 +1,250 @@ +/* + * Supports for the button array on the Surface tablets. + * + * (C) Copyright 2016 Red Hat, Inc + * + * Based on soc_button_array.c: + * + * {C} Copyright 2014 Intel Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; version 2 + * of the License. + */ + +#include <linux/module.h> +#include <linux/input.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/i2c.h> +#include <linux/slab.h> +#include <linux/acpi.h> +#include <linux/gpio/consumer.h> +#include <linux/gpio_keys.h> +#include <linux/gpio.h> +#include <linux/platform_device.h> + + +#define SURFACE_BUTTON_OBJ_NAME "TEV2" +#define MAX_NBUTTONS 4 + +/* + * Some of the buttons like volume up/down are auto repeat, while others + * are not. To support both, we register two platform devices, and put + * buttons into them based on whether the key should be auto repeat. + */ +#define BUTTON_TYPES 2 + +/* + * Power button, Home button, Volume buttons support is supposed to + * be covered by drivers/input/misc/soc_button_array.c, which is implemented + * according to "Windows ACPI Design Guide for SoC Platforms". + * However surface 3 seems not to obey the specs, instead it uses + * device TEV2(MSHW0028) for declaring the GPIOs. The gpios are also slightly + * different in which the Home button is active high. + * Compared to surfacepro3_button.c which also handles MSHW0028, the Surface 3 + * is a reduce platform and thus uses GPIOs, not ACPI events. + * We choose an I2C driver here because we need to access the resources + * declared under the device node, while surfacepro3_button.c only needs + * the ACPI companion node. + */ +static const struct acpi_device_id surface3_acpi_match[] = { + { "MSHW0028", 0 }, + { } +}; +MODULE_DEVICE_TABLE(acpi, surface3_acpi_match); + +struct surface3_button_info { + const char *name; + int acpi_index; + unsigned int event_type; + unsigned int event_code; + bool autorepeat; + bool wakeup; + bool active_low; +}; + +struct surface3_button_data { + struct platform_device *children[BUTTON_TYPES]; +}; + +/* + * Get the Nth GPIO number from the ACPI object. + */ +static int surface3_button_lookup_gpio(struct device *dev, int acpi_index) +{ + struct gpio_desc *desc; + int gpio; + + desc = gpiod_get_index(dev, NULL, acpi_index, GPIOD_ASIS); + if (IS_ERR(desc)) + return PTR_ERR(desc); + + gpio = desc_to_gpio(desc); + + gpiod_put(desc); + + return gpio; +} + +static struct platform_device * +surface3_button_device_create(struct i2c_client *client, + const struct surface3_button_info *button_info, + bool autorepeat) +{ + const struct surface3_button_info *info; + struct platform_device *pd; + struct gpio_keys_button *gpio_keys; + struct gpio_keys_platform_data *gpio_keys_pdata; + int n_buttons = 0; + int gpio; + int error; + + gpio_keys_pdata = devm_kzalloc(&client->dev, + sizeof(*gpio_keys_pdata) + + sizeof(*gpio_keys) * MAX_NBUTTONS, + GFP_KERNEL); + if (!gpio_keys_pdata) + return ERR_PTR(-ENOMEM); + + gpio_keys = (void *)(gpio_keys_pdata + 1); + + for (info = button_info; info->name; info++) { + if (info->autorepeat != autorepeat) + continue; + + gpio = surface3_button_lookup_gpio(&client->dev, + info->acpi_index); + if (!gpio_is_valid(gpio)) + continue; + + gpio_keys[n_buttons].type = info->event_type; + gpio_keys[n_buttons].code = info->event_code; + gpio_keys[n_buttons].gpio = gpio; + gpio_keys[n_buttons].active_low = info->active_low; + gpio_keys[n_buttons].desc = info->name; + gpio_keys[n_buttons].wakeup = info->wakeup; + n_buttons++; + } + + if (n_buttons == 0) { + error = -ENODEV; + goto err_free_mem; + } + + gpio_keys_pdata->buttons = gpio_keys; + gpio_keys_pdata->nbuttons = n_buttons; + gpio_keys_pdata->rep = autorepeat; + + pd = platform_device_alloc("gpio-keys", PLATFORM_DEVID_AUTO); + if (!pd) { + error = -ENOMEM; + goto err_free_mem; + } + + error = platform_device_add_data(pd, gpio_keys_pdata, + sizeof(*gpio_keys_pdata)); + if (error) + goto err_free_pdev; + + error = platform_device_add(pd); + if (error) + goto err_free_pdev; + + return pd; + +err_free_pdev: + platform_device_put(pd); +err_free_mem: + devm_kfree(&client->dev, gpio_keys_pdata); + return ERR_PTR(error); +} + +static int surface3_button_remove(struct i2c_client *client) +{ + struct surface3_button_data *priv = i2c_get_clientdata(client); + + int i; + + for (i = 0; i < BUTTON_TYPES; i++) + if (priv->children[i]) + platform_device_unregister(priv->children[i]); + + return 0; +} + +static struct surface3_button_info surface3_button_surface3[] = { + { "power", 0, EV_KEY, KEY_POWER, false, true, true }, + { "home", 1, EV_KEY, KEY_LEFTMETA, false, true, false }, + { "volume_up", 2, EV_KEY, KEY_VOLUMEUP, true, false, true }, + { "volume_down", 3, EV_KEY, KEY_VOLUMEDOWN, true, false, true }, + { } +}; + +static int surface3_button_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct device *dev = &client->dev; + struct surface3_button_data *priv; + struct platform_device *pd; + int i; + int error; + + if (strncmp(acpi_device_bid(ACPI_COMPANION(&client->dev)), + SURFACE_BUTTON_OBJ_NAME, + strlen(SURFACE_BUTTON_OBJ_NAME))) + return -ENODEV; + + if (gpiod_count(dev, KBUILD_MODNAME) <= 0) { + dev_dbg(dev, "no GPIO attached, ignoring...\n"); + return -ENODEV; + } + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + i2c_set_clientdata(client, priv); + + for (i = 0; i < BUTTON_TYPES; i++) { + pd = surface3_button_device_create(client, + surface3_button_surface3, + i == 0); + if (IS_ERR(pd)) { + error = PTR_ERR(pd); + if (error != -ENODEV) { + surface3_button_remove(client); + return error; + } + continue; + } + + priv->children[i] = pd; + } + + if (!priv->children[0] && !priv->children[1]) + return -ENODEV; + + return 0; +} + +static const struct i2c_device_id surface3_id[] = { + { } +}; +MODULE_DEVICE_TABLE(i2c, surface3_id); + +static struct i2c_driver surface3_driver = { + .probe = surface3_button_probe, + .remove = surface3_button_remove, + .id_table = surface3_id, + .driver = { + .name = "surface3", + .acpi_match_table = ACPI_PTR(surface3_acpi_match), + }, +}; +module_i2c_driver(surface3_driver); + +MODULE_AUTHOR("Benjamin Tissoires <benjamin.tissoires@gmail.com>"); +MODULE_DESCRIPTION("surface3 button array driver"); +MODULE_LICENSE("GPL v2"); |