Commit 5f084819 authored by Linus Torvalds's avatar Linus Torvalds

Merge tag 'platform-drivers-x86-v6.0-1' of...

Merge tag 'platform-drivers-x86-v6.0-1' of git://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86

Pull x86 platform driver updates from Hans de Goede:

 - Microsoft Surface:
     - SSAM hot unplug support
     - Surface Pro 8 keyboard cover support
     - Tablet mode switch support for Surface Pro 8 and Surface Laptop
       Studio

 - thinkpad_acpi:
     - AMD Automatice Mode Transitions (AMT) support

 - Mellanox:
     - Vulcan chassis COMe NVSwitch management support
     - XH3000 support

 - New generic/shared Intel P2SB (Primary to Sideband) support

 - Lots of small cleanups

 - Various small bugfixes

 - Various new hardware ids / quirks additions

* tag 'platform-drivers-x86-v6.0-1' of git://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86: (105 commits)
  platform/x86/intel/vsec: Fix wrong type for local status variables
  platform/x86: p2sb: Move out of X86_PLATFORM_DEVICES dependency
  platform/x86: pmc_atom: Fix comment typo
  platform/surface: gpe: Add support for 13" Intel version of Surface Laptop 4
  platform/olpc: Fix uninitialized data in debugfs write
  platform/mellanox: mlxreg-lc: Fix error flow and extend verbosity
  platform/x86: pmc_atom: Match all Lex BayTrail boards with critclk_systems DMI table
  platform/x86: sony-laptop: Remove useless comparisons in sony_pic_read_possible_resource()
  tools/power/x86/intel-speed-select: Remove unneeded semicolon
  tools/power/x86/intel-speed-select: Fix off by one check
  platform/surface: tabletsw: Fix __le32 integer access
  Documentation/ABI: Add new attributes for mlxreg-io sysfs interfaces
  Documentation/ABI: mlxreg-io: Fix contact info
  platform/mellanox: mlxreg-io: Add locking for io operations
  platform/x86: mlx-platform: Add COME board revision register
  platform/x86: mlx-platform: Add support for new system XH3000
  platform/x86: mlx-platform: Introduce support for COMe NVSwitch management module for Vulcan chassis
  platform/x86: mlx-platform: Add support for systems equipped with two ASICs
  platform/x86: mlx-platform: Add cosmetic changes for alignment
  platform/x86: mlx-platform: Make activation of some drivers conditional
  ...
parents 5bb3bf24 3d46d784
What: /sys/bus/surface_aggregator/devices/01:0e:01:00:01/state
Date: July 2022
KernelVersion: 5.20
Contact: Maximilian Luz <luzmaximilian@gmail.com>
Description:
This attribute returns a string with the current type-cover
or device posture, as indicated by the embedded controller.
Currently returned posture states are:
- "disconnected": The type-cover has been disconnected.
- "closed": The type-cover has been folded closed and lies on
top of the display.
- "laptop": The type-cover is open and in laptop-mode, i.e.,
ready for normal use.
- "folded-canvas": The type-cover has been folded back
part-ways, but does not lie flush with the back side of the
device. In general, this means that the kick-stand is used
and extended atop of the cover.
- "folded-back": The type cover has been fully folded back and
lies flush with the back side of the device.
- "<unknown>": The current state is unknown to the driver, for
example due to newer as-of-yet unsupported hardware.
New states may be introduced with new hardware. Users therefore
must not rely on this list of states being exhaustive and
gracefully handle unknown states.
What: /sys/bus/surface_aggregator/devices/01:26:01:00:01/state
Date: July 2022
KernelVersion: 5.20
Contact: Maximilian Luz <luzmaximilian@gmail.com>
Description:
This attribute returns a string with the current device posture, as indicated by the embedded controller. Currently
returned posture states are:
- "closed": The lid of the device is closed.
- "laptop": The lid of the device is opened and the device
operates as a normal laptop.
- "slate": The screen covers the keyboard or has been flipped
back and the device operates mainly based on touch input.
- "tablet": The device operates as tablet and exclusively
relies on touch input (or external peripherals).
- "<unknown>": The current state is unknown to the driver, for
example due to newer as-of-yet unsupported hardware.
New states may be introduced with new hardware. Users therefore
must not rely on this list of states being exhaustive and
gracefully handle unknown states.
......@@ -17,6 +17,8 @@
.. |SSAM_DEVICE| replace:: :c:func:`SSAM_DEVICE`
.. |ssam_notifier_register| replace:: :c:func:`ssam_notifier_register`
.. |ssam_notifier_unregister| replace:: :c:func:`ssam_notifier_unregister`
.. |ssam_device_notifier_register| replace:: :c:func:`ssam_device_notifier_register`
.. |ssam_device_notifier_unregister| replace:: :c:func:`ssam_device_notifier_unregister`
.. |ssam_request_sync| replace:: :c:func:`ssam_request_sync`
.. |ssam_event_mask| replace:: :c:type:`enum ssam_event_mask <ssam_event_mask>`
......@@ -312,7 +314,9 @@ Handling Events
To receive events from the SAM EC, an event notifier must be registered for
the desired event via |ssam_notifier_register|. The notifier must be
unregistered via |ssam_notifier_unregister| once it is not required any
more.
more. For |ssam_device| type clients, the |ssam_device_notifier_register| and
|ssam_device_notifier_unregister| wrappers should be preferred as they properly
handle hot-removal of client devices.
Event notifiers are registered by providing (at minimum) a callback to call
in case an event has been received, the registry specifying how the event
......
......@@ -1007,7 +1007,7 @@ AMD PMC DRIVER
M: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
L: platform-driver-x86@vger.kernel.org
S: Maintained
F: drivers/platform/x86/amd-pmc.*
F: drivers/platform/x86/amd/pmc.c
AMD HSMP DRIVER
M: Naveen Krishna Chatradhi <naveenkrishna.chatradhi@amd.com>
......@@ -1017,7 +1017,7 @@ S: Maintained
F: Documentation/x86/amd_hsmp.rst
F: arch/x86/include/asm/amd_hsmp.h
F: arch/x86/include/uapi/asm/amd_hsmp.h
F: drivers/platform/x86/amd_hsmp.c
F: drivers/platform/x86/amd/hsmp.c
AMD POWERPLAY AND SWSMU
M: Evan Quan <evan.quan@amd.com>
......@@ -13467,6 +13467,12 @@ F: drivers/scsi/smartpqi/smartpqi*.[ch]
F: include/linux/cciss*.h
F: include/uapi/linux/cciss*.h
MICROSOFT SURFACE AGGREGATOR TABLET-MODE SWITCH
M: Maximilian Luz <luzmaximilian@gmail.com>
L: platform-driver-x86@vger.kernel.org
S: Maintained
F: drivers/platform/surface/surface_aggregator_tabletsw.c
MICROSOFT SURFACE BATTERY AND AC DRIVERS
M: Maximilian Luz <luzmaximilian@gmail.com>
L: linux-pm@vger.kernel.org
......@@ -13538,6 +13544,12 @@ F: include/linux/surface_acpi_notify.h
F: include/linux/surface_aggregator/
F: include/uapi/linux/surface_aggregator/
MICROSOFT SURFACE SYSTEM AGGREGATOR HUB DRIVER
M: Maximilian Luz <luzmaximilian@gmail.com>
L: platform-driver-x86@vger.kernel.org
S: Maintained
F: drivers/platform/surface/surface_aggregator_hub.c
MICROTEK X6 SCANNER
M: Oliver Neukum <oliver@neukum.org>
S: Maintained
......
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_PMC_ATOM) += clk-pmc-atom.o
obj-$(CONFIG_X86_AMD_PLATFORM_DEVICE) += clk-fch.o
clk-x86-lpss-y := clk-lpss-atom.o
obj-$(CONFIG_X86_INTEL_LPSS) += clk-x86-lpss.o
obj-$(CONFIG_X86_INTEL_LPSS) += clk-lpss-atom.o clk-pmc-atom.o
obj-$(CONFIG_CLK_LGM_CGU) += clk-cgu.o clk-cgu-pll.o clk-lgm.o
......@@ -263,6 +263,7 @@ config EDAC_I10NM
config EDAC_PND2
tristate "Intel Pondicherry2"
depends on PCI && X86_64 && X86_MCE_INTEL
select P2SB if X86
help
Support for error detection and correction on the Intel
Pondicherry2 Integrated Memory Controller. This SoC IP is
......
......@@ -28,6 +28,8 @@
#include <linux/bitmap.h>
#include <linux/math64.h>
#include <linux/mod_devicetable.h>
#include <linux/platform_data/x86/p2sb.h>
#include <asm/cpu_device_id.h>
#include <asm/intel-family.h>
#include <asm/processor.h>
......@@ -232,42 +234,14 @@ static u64 get_mem_ctrl_hub_base_addr(void)
return U64_LSHIFT(hi.base, 32) | U64_LSHIFT(lo.base, 15);
}
static u64 get_sideband_reg_base_addr(void)
{
struct pci_dev *pdev;
u32 hi, lo;
u8 hidden;
pdev = pci_get_device(PCI_VENDOR_ID_INTEL, 0x19dd, NULL);
if (pdev) {
/* Unhide the P2SB device, if it's hidden */
pci_read_config_byte(pdev, 0xe1, &hidden);
if (hidden)
pci_write_config_byte(pdev, 0xe1, 0);
pci_read_config_dword(pdev, 0x10, &lo);
pci_read_config_dword(pdev, 0x14, &hi);
lo &= 0xfffffff0;
/* Hide the P2SB device, if it was hidden before */
if (hidden)
pci_write_config_byte(pdev, 0xe1, hidden);
pci_dev_put(pdev);
return (U64_LSHIFT(hi, 32) | U64_LSHIFT(lo, 0));
} else {
return 0xfd000000;
}
}
#define DNV_MCHBAR_SIZE 0x8000
#define DNV_SB_PORT_SIZE 0x10000
static int dnv_rd_reg(int port, int off, int op, void *data, size_t sz, char *name)
{
struct pci_dev *pdev;
char *base;
u64 addr;
unsigned long size;
void __iomem *base;
struct resource r;
int ret;
if (op == 4) {
pdev = pci_get_device(PCI_VENDOR_ID_INTEL, 0x1980, NULL);
......@@ -279,26 +253,30 @@ static int dnv_rd_reg(int port, int off, int op, void *data, size_t sz, char *na
} else {
/* MMIO via memory controller hub base address */
if (op == 0 && port == 0x4c) {
addr = get_mem_ctrl_hub_base_addr();
if (!addr)
memset(&r, 0, sizeof(r));
r.start = get_mem_ctrl_hub_base_addr();
if (!r.start)
return -ENODEV;
size = DNV_MCHBAR_SIZE;
r.end = r.start + DNV_MCHBAR_SIZE - 1;
} else {
/* MMIO via sideband register base address */
addr = get_sideband_reg_base_addr();
if (!addr)
return -ENODEV;
addr += (port << 16);
size = DNV_SB_PORT_SIZE;
ret = p2sb_bar(NULL, 0, &r);
if (ret)
return ret;
r.start += (port << 16);
r.end = r.start + DNV_SB_PORT_SIZE - 1;
}
base = ioremap((resource_size_t)addr, size);
base = ioremap(r.start, resource_size(&r));
if (!base)
return -ENODEV;
if (sz == 8)
*(u32 *)(data + 4) = *(u32 *)(base + off + 4);
*(u32 *)data = *(u32 *)(base + off);
*(u64 *)data = readq(base + off);
else
*(u32 *)data = readl(base + off);
iounmap(base);
}
......
......@@ -19,12 +19,30 @@
#include "surface_hid_core.h"
/* -- Utility functions. ---------------------------------------------------- */
static bool surface_hid_is_hot_removed(struct surface_hid_device *shid)
{
/*
* Non-ssam client devices, i.e. platform client devices, cannot be
* hot-removed.
*/
if (!is_ssam_device(shid->dev))
return false;
return ssam_device_is_hot_removed(to_ssam_device(shid->dev));
}
/* -- Device descriptor access. --------------------------------------------- */
static int surface_hid_load_hid_descriptor(struct surface_hid_device *shid)
{
int status;
if (surface_hid_is_hot_removed(shid))
return -ENODEV;
status = shid->ops.get_descriptor(shid, SURFACE_HID_DESC_HID,
(u8 *)&shid->hid_desc, sizeof(shid->hid_desc));
if (status)
......@@ -61,6 +79,9 @@ static int surface_hid_load_device_attributes(struct surface_hid_device *shid)
{
int status;
if (surface_hid_is_hot_removed(shid))
return -ENODEV;
status = shid->ops.get_descriptor(shid, SURFACE_HID_DESC_ATTRS,
(u8 *)&shid->attrs, sizeof(shid->attrs));
if (status)
......@@ -88,9 +109,18 @@ static int surface_hid_start(struct hid_device *hid)
static void surface_hid_stop(struct hid_device *hid)
{
struct surface_hid_device *shid = hid->driver_data;
bool hot_removed;
/*
* Communication may fail for devices that have been hot-removed. This
* also includes unregistration of HID events, so we need to check this
* here. Only if the device has not been marked as hot-removed, we can
* safely disable events.
*/
hot_removed = surface_hid_is_hot_removed(shid);
/* Note: This call will log errors for us, so ignore them here. */
ssam_notifier_unregister(shid->ctrl, &shid->notif);
__ssam_notifier_unregister(shid->ctrl, &shid->notif, !hot_removed);
}
static int surface_hid_open(struct hid_device *hid)
......@@ -109,6 +139,9 @@ static int surface_hid_parse(struct hid_device *hid)
u8 *buf;
int status;
if (surface_hid_is_hot_removed(shid))
return -ENODEV;
buf = kzalloc(len, GFP_KERNEL);
if (!buf)
return -ENOMEM;
......@@ -126,6 +159,9 @@ static int surface_hid_raw_request(struct hid_device *hid, unsigned char reportn
{
struct surface_hid_device *shid = hid->driver_data;
if (surface_hid_is_hot_removed(shid))
return -ENODEV;
if (rtype == HID_OUTPUT_REPORT && reqtype == HID_REQ_SET_REPORT)
return shid->ops.output_report(shid, reportnum, buf, len);
......
......@@ -108,6 +108,7 @@ config I2C_HIX5HD2
config I2C_I801
tristate "Intel 82801 (ICH/PCH)"
depends on PCI
select P2SB if X86
select CHECK_SIGNATURE if X86 && DMI
select I2C_SMBUS
help
......
......@@ -112,6 +112,7 @@
#include <linux/err.h>
#include <linux/platform_device.h>
#include <linux/platform_data/itco_wdt.h>
#include <linux/platform_data/x86/p2sb.h>
#include <linux/pm_runtime.h>
#include <linux/mutex.h>
......@@ -141,7 +142,6 @@
#define TCOBASE 0x050
#define TCOCTL 0x054
#define SBREG_BAR 0x10
#define SBREG_SMBCTRL 0xc6000c
#define SBREG_SMBCTRL_DNV 0xcf000c
......@@ -1485,45 +1485,24 @@ i801_add_tco_spt(struct i801_priv *priv, struct pci_dev *pci_dev,
.version = 4,
};
struct resource *res;
unsigned int devfn;
u64 base64_addr;
u32 base_addr;
u8 hidden;
int ret;
/*
* We must access the NO_REBOOT bit over the Primary to Sideband
* bridge (P2SB). The BIOS prevents the P2SB device from being
* enumerated by the PCI subsystem, so we need to unhide/hide it
* to lookup the P2SB BAR.
* (P2SB) bridge.
*/
pci_lock_rescan_remove();
devfn = PCI_DEVFN(PCI_SLOT(pci_dev->devfn), 1);
/* Unhide the P2SB device, if it is hidden */
pci_bus_read_config_byte(pci_dev->bus, devfn, 0xe1, &hidden);
if (hidden)
pci_bus_write_config_byte(pci_dev->bus, devfn, 0xe1, 0x0);
pci_bus_read_config_dword(pci_dev->bus, devfn, SBREG_BAR, &base_addr);
base64_addr = base_addr & 0xfffffff0;
pci_bus_read_config_dword(pci_dev->bus, devfn, SBREG_BAR + 0x4, &base_addr);
base64_addr |= (u64)base_addr << 32;
/* Hide the P2SB device, if it was hidden before */
if (hidden)
pci_bus_write_config_byte(pci_dev->bus, devfn, 0xe1, hidden);
pci_unlock_rescan_remove();
res = &tco_res[1];
ret = p2sb_bar(pci_dev->bus, 0, res);
if (ret)
return ERR_PTR(ret);
if (pci_dev->device == PCI_DEVICE_ID_INTEL_DNV_SMBUS)
res->start = (resource_size_t)base64_addr + SBREG_SMBCTRL_DNV;
res->start += SBREG_SMBCTRL_DNV;
else
res->start = (resource_size_t)base64_addr + SBREG_SMBCTRL;
res->start += SBREG_SMBCTRL;
res->end = res->start + 3;
res->flags = IORESOURCE_MEM;
return platform_device_register_resndata(&pci_dev->dev, "iTCO_wdt", -1,
tco_res, 2, &pldata, sizeof(pldata));
......
# SPDX-License-Identifier: GPL-2.0-only
config LEDS_SIEMENS_SIMATIC_IPC
tristate "LED driver for Siemens Simatic IPCs"
depends on LEDS_CLASS
depends on LEDS_GPIO
depends on SIEMENS_SIMATIC_IPC
help
This option enables support for the LEDs of several Industrial PCs
from Siemens.
To compile this driver as a module, choose M here: the module
will be called simatic-ipc-leds.
To compile this driver as a module, choose M here: the modules
will be called simatic-ipc-leds and simatic-ipc-leds-gpio.
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_LEDS_SIEMENS_SIMATIC_IPC) += simatic-ipc-leds.o
obj-$(CONFIG_LEDS_SIEMENS_SIMATIC_IPC) += simatic-ipc-leds-gpio.o
// SPDX-License-Identifier: GPL-2.0
/*
* Siemens SIMATIC IPC driver for GPIO based LEDs
*
* Copyright (c) Siemens AG, 2022
*
* Authors:
* Henning Schild <henning.schild@siemens.com>
*/
#include <linux/gpio/machine.h>
#include <linux/gpio/consumer.h>
#include <linux/leds.h>
#include <linux/module.h>
#include <linux/platform_device.h>
static struct gpiod_lookup_table simatic_ipc_led_gpio_table = {
.dev_id = "leds-gpio",
.table = {
GPIO_LOOKUP_IDX("apollolake-pinctrl.0", 51, NULL, 0, GPIO_ACTIVE_LOW),
GPIO_LOOKUP_IDX("apollolake-pinctrl.0", 52, NULL, 1, GPIO_ACTIVE_LOW),
GPIO_LOOKUP_IDX("apollolake-pinctrl.0", 53, NULL, 2, GPIO_ACTIVE_LOW),
GPIO_LOOKUP_IDX("apollolake-pinctrl.0", 57, NULL, 3, GPIO_ACTIVE_LOW),
GPIO_LOOKUP_IDX("apollolake-pinctrl.0", 58, NULL, 4, GPIO_ACTIVE_LOW),
GPIO_LOOKUP_IDX("apollolake-pinctrl.0", 60, NULL, 5, GPIO_ACTIVE_LOW),
GPIO_LOOKUP_IDX("apollolake-pinctrl.0", 56, NULL, 6, GPIO_ACTIVE_LOW),
GPIO_LOOKUP_IDX("apollolake-pinctrl.0", 59, NULL, 7, GPIO_ACTIVE_HIGH),
},
};
static const struct gpio_led simatic_ipc_gpio_leds[] = {
{ .name = "green:" LED_FUNCTION_STATUS "-3" },
{ .name = "red:" LED_FUNCTION_STATUS "-1" },
{ .name = "green:" LED_FUNCTION_STATUS "-1" },
{ .name = "red:" LED_FUNCTION_STATUS "-2" },
{ .name = "green:" LED_FUNCTION_STATUS "-2" },
{ .name = "red:" LED_FUNCTION_STATUS "-3" },
};
static const struct gpio_led_platform_data simatic_ipc_gpio_leds_pdata = {
.num_leds = ARRAY_SIZE(simatic_ipc_gpio_leds),
.leds = simatic_ipc_gpio_leds,
};
static struct platform_device *simatic_leds_pdev;
static int simatic_ipc_leds_gpio_remove(struct platform_device *pdev)
{
gpiod_remove_lookup_table(&simatic_ipc_led_gpio_table);
platform_device_unregister(simatic_leds_pdev);
return 0;
}
static int simatic_ipc_leds_gpio_probe(struct platform_device *pdev)
{
struct gpio_desc *gpiod;
int err;
gpiod_add_lookup_table(&simatic_ipc_led_gpio_table);
simatic_leds_pdev = platform_device_register_resndata(NULL,
"leds-gpio", PLATFORM_DEVID_NONE, NULL, 0,
&simatic_ipc_gpio_leds_pdata,
sizeof(simatic_ipc_gpio_leds_pdata));
if (IS_ERR(simatic_leds_pdev)) {
err = PTR_ERR(simatic_leds_pdev);
goto out;
}
/* PM_BIOS_BOOT_N */
gpiod = gpiod_get_index(&simatic_leds_pdev->dev, NULL, 6, GPIOD_OUT_LOW);
if (IS_ERR(gpiod)) {
err = PTR_ERR(gpiod);
goto out;
}
gpiod_put(gpiod);
/* PM_WDT_OUT */
gpiod = gpiod_get_index(&simatic_leds_pdev->dev, NULL, 7, GPIOD_OUT_LOW);
if (IS_ERR(gpiod)) {
err = PTR_ERR(gpiod);
goto out;
}
gpiod_put(gpiod);
return 0;
out:
simatic_ipc_leds_gpio_remove(pdev);
return err;
}
static struct platform_driver simatic_ipc_led_gpio_driver = {
.probe = simatic_ipc_leds_gpio_probe,
.remove = simatic_ipc_leds_gpio_remove,
.driver = {
.name = KBUILD_MODNAME,
}
};
module_platform_driver(simatic_ipc_led_gpio_driver);
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:" KBUILD_MODNAME);
MODULE_SOFTDEP("pre: platform:leds-gpio");
MODULE_AUTHOR("Henning Schild <henning.schild@siemens.com>");
......@@ -23,7 +23,7 @@
#define SIMATIC_IPC_LED_PORT_BASE 0x404E
struct simatic_ipc_led {
unsigned int value; /* mask for io and offset for mem */
unsigned int value; /* mask for io */
char *name;
struct led_classdev cdev;
};
......@@ -38,21 +38,6 @@ static struct simatic_ipc_led simatic_ipc_leds_io[] = {
{ }
};
/* the actual start will be discovered with PCI, 0 is a placeholder */
static struct resource simatic_ipc_led_mem_res = DEFINE_RES_MEM_NAMED(0, SZ_4K, KBUILD_MODNAME);
static void __iomem *simatic_ipc_led_memory;
static struct simatic_ipc_led simatic_ipc_leds_mem[] = {
{0x500 + 0x1A0, "red:" LED_FUNCTION_STATUS "-1"},
{0x500 + 0x1A8, "green:" LED_FUNCTION_STATUS "-1"},
{0x500 + 0x1C8, "red:" LED_FUNCTION_STATUS "-2"},
{0x500 + 0x1D0, "green:" LED_FUNCTION_STATUS "-2"},
{0x500 + 0x1E0, "red:" LED_FUNCTION_STATUS "-3"},
{0x500 + 0x198, "green:" LED_FUNCTION_STATUS "-3"},
{ }
};
static struct resource simatic_ipc_led_io_res =
DEFINE_RES_IO_NAMED(SIMATIC_IPC_LED_PORT_BASE, SZ_2, KBUILD_MODNAME);
......@@ -88,28 +73,6 @@ static enum led_brightness simatic_ipc_led_get_io(struct led_classdev *led_cd)
return inw(SIMATIC_IPC_LED_PORT_BASE) & led->value ? LED_OFF : led_cd->max_brightness;
}
static void simatic_ipc_led_set_mem(struct led_classdev *led_cd,
enum led_brightness brightness)
{
struct simatic_ipc_led *led = cdev_to_led(led_cd);
void __iomem *reg = simatic_ipc_led_memory + led->value;
u32 val;
val = readl(reg);
val = (val & ~1) | (brightness == LED_OFF);
writel(val, reg);
}
static enum led_brightness simatic_ipc_led_get_mem(struct led_classdev *led_cd)
{
struct simatic_ipc_led *led = cdev_to_led(led_cd);
void __iomem *reg = simatic_ipc_led_memory + led->value;
u32 val;
val = readl(reg);
return (val & 1) ? LED_OFF : led_cd->max_brightness;
}
static int simatic_ipc_leds_probe(struct platform_device *pdev)
{
const struct simatic_ipc_platform *plat = pdev->dev.platform_data;
......@@ -117,9 +80,7 @@ static int simatic_ipc_leds_probe(struct platform_device *pdev)
struct simatic_ipc_led *ipcled;
struct led_classdev *cdev;
struct resource *res;
void __iomem *reg;
int err, type;
u32 val;
int err;
switch (plat->devmode) {
case SIMATIC_IPC_DEVICE_227D:
......@@ -134,52 +95,19 @@ static int simatic_ipc_leds_probe(struct platform_device *pdev)
}
ipcled = simatic_ipc_leds_io;
}
type = IORESOURCE_IO;
if (!devm_request_region(dev, res->start, resource_size(res), KBUILD_MODNAME)) {
dev_err(dev, "Unable to register IO resource at %pR\n", res);
return -EBUSY;
}
break;
case SIMATIC_IPC_DEVICE_127E:
res = &simatic_ipc_led_mem_res;
ipcled = simatic_ipc_leds_mem;
type = IORESOURCE_MEM;
/* get GPIO base from PCI */
res->start = simatic_ipc_get_membase0(PCI_DEVFN(13, 0));
if (res->start == 0)
return -ENODEV;
/* do the final address calculation */
res->start = res->start + (0xC5 << 16);
res->end += res->start;
simatic_ipc_led_memory = devm_ioremap_resource(dev, res);
if (IS_ERR(simatic_ipc_led_memory))
return PTR_ERR(simatic_ipc_led_memory);
/* initialize power/watchdog LED */
reg = simatic_ipc_led_memory + 0x500 + 0x1D8; /* PM_WDT_OUT */
val = readl(reg);
writel(val & ~1, reg);
reg = simatic_ipc_led_memory + 0x500 + 0x1C0; /* PM_BIOS_BOOT_N */
val = readl(reg);
writel(val | 1, reg);
break;
default:
return -ENODEV;
}
while (ipcled->value) {
cdev = &ipcled->cdev;
if (type == IORESOURCE_MEM) {
cdev->brightness_set = simatic_ipc_led_set_mem;
cdev->brightness_get = simatic_ipc_led_get_mem;
} else {
cdev->brightness_set = simatic_ipc_led_set_io;
cdev->brightness_get = simatic_ipc_led_get_io;
}
cdev->brightness_set = simatic_ipc_led_set_io;
cdev->brightness_get = simatic_ipc_led_get_io;
cdev->max_brightness = LED_ON;
cdev->name = ipcled->name;
......
......@@ -572,6 +572,7 @@ config LPC_ICH
tristate "Intel ICH LPC"
depends on PCI
select MFD_CORE
select P2SB if X86
help
The LPC bridge function of the Intel ICH provides support for
many functional units. This driver provides needed support for
......
......@@ -8,7 +8,8 @@
* Configuration Registers.
*
* This driver is derived from lpc_sch.
*
* Copyright (c) 2017, 2021-2022 Intel Corporation
* Copyright (c) 2011 Extreme Engineering Solution, Inc.
* Author: Aaron Sierra <asierra@xes-inc.com>
*
......@@ -42,9 +43,11 @@
#include <linux/errno.h>
#include <linux/acpi.h>
#include <linux/pci.h>
#include <linux/pinctrl/pinctrl.h>
#include <linux/mfd/core.h>
#include <linux/mfd/lpc_ich.h>
#include <linux/platform_data/itco_wdt.h>
#include <linux/platform_data/x86/p2sb.h>
#define ACPIBASE 0x40
#define ACPIBASE_GPE_OFF 0x28
......@@ -71,8 +74,6 @@
#define BCR 0xdc
#define BCR_WPD BIT(0)
#define SPIBASE_APL_SZ 4096
#define GPIOBASE_ICH0 0x58
#define GPIOCTRL_ICH0 0x5C
#define GPIOBASE_ICH6 0x48
......@@ -143,6 +144,73 @@ static struct mfd_cell lpc_ich_gpio_cell = {
.ignore_resource_conflicts = true,
};
#define APL_GPIO_NORTH 0
#define APL_GPIO_NORTHWEST 1
#define APL_GPIO_WEST 2
#define APL_GPIO_SOUTHWEST 3
#define APL_GPIO_NR_DEVICES 4
/* Offset data for Apollo Lake GPIO controllers */
static resource_size_t apl_gpio_offsets[APL_GPIO_NR_DEVICES] = {
[APL_GPIO_NORTH] = 0xc50000,
[APL_GPIO_NORTHWEST] = 0xc40000,
[APL_GPIO_WEST] = 0xc70000,
[APL_GPIO_SOUTHWEST] = 0xc00000,
};
#define APL_GPIO_RESOURCE_SIZE 0x1000
#define APL_GPIO_IRQ 14
static struct resource apl_gpio_resources[APL_GPIO_NR_DEVICES][2] = {
[APL_GPIO_NORTH] = {
DEFINE_RES_MEM(0, 0),
DEFINE_RES_IRQ(APL_GPIO_IRQ),
},
[APL_GPIO_NORTHWEST] = {
DEFINE_RES_MEM(0, 0),
DEFINE_RES_IRQ(APL_GPIO_IRQ),
},
[APL_GPIO_WEST] = {
DEFINE_RES_MEM(0, 0),
DEFINE_RES_IRQ(APL_GPIO_IRQ),
},
[APL_GPIO_SOUTHWEST] = {
DEFINE_RES_MEM(0, 0),
DEFINE_RES_IRQ(APL_GPIO_IRQ),
},
};
static const struct mfd_cell apl_gpio_devices[APL_GPIO_NR_DEVICES] = {
[APL_GPIO_NORTH] = {
.name = "apollolake-pinctrl",
.id = APL_GPIO_NORTH,
.num_resources = ARRAY_SIZE(apl_gpio_resources[APL_GPIO_NORTH]),
.resources = apl_gpio_resources[APL_GPIO_NORTH],
.ignore_resource_conflicts = true,
},
[APL_GPIO_NORTHWEST] = {
.name = "apollolake-pinctrl",
.id = APL_GPIO_NORTHWEST,
.num_resources = ARRAY_SIZE(apl_gpio_resources[APL_GPIO_NORTHWEST]),
.resources = apl_gpio_resources[APL_GPIO_NORTHWEST],
.ignore_resource_conflicts = true,
},
[APL_GPIO_WEST] = {
.name = "apollolake-pinctrl",
.id = APL_GPIO_WEST,
.num_resources = ARRAY_SIZE(apl_gpio_resources[APL_GPIO_WEST]),
.resources = apl_gpio_resources[APL_GPIO_WEST],
.ignore_resource_conflicts = true,
},
[APL_GPIO_SOUTHWEST] = {
.name = "apollolake-pinctrl",
.id = APL_GPIO_SOUTHWEST,
.num_resources = ARRAY_SIZE(apl_gpio_resources[APL_GPIO_SOUTHWEST]),
.resources = apl_gpio_resources[APL_GPIO_SOUTHWEST],
.ignore_resource_conflicts = true,
},
};
static struct mfd_cell lpc_ich_spi_cell = {
.name = "intel-spi",
......@@ -1086,6 +1154,34 @@ static int lpc_ich_init_wdt(struct pci_dev *dev)
return ret;
}
static int lpc_ich_init_pinctrl(struct pci_dev *dev)
{
struct resource base;
unsigned int i;
int ret;
/* Check, if GPIO has been exported as an ACPI device */
if (acpi_dev_present("INT3452", NULL, -1))
return -EEXIST;
ret = p2sb_bar(dev->bus, 0, &base);
if (ret)
return ret;
for (i = 0; i < ARRAY_SIZE(apl_gpio_devices); i++) {
struct resource *mem = &apl_gpio_resources[i][0];
resource_size_t offset = apl_gpio_offsets[i];
/* Fill MEM resource */
mem->start = base.start + offset;
mem->end = base.start + offset + APL_GPIO_RESOURCE_SIZE - 1;
mem->flags = base.flags;
}
return mfd_add_devices(&dev->dev, 0, apl_gpio_devices,
ARRAY_SIZE(apl_gpio_devices), NULL, 0, NULL);
}
static bool lpc_ich_byt_set_writeable(void __iomem *base, void *data)
{
u32 val;
......@@ -1100,35 +1196,32 @@ static bool lpc_ich_byt_set_writeable(void __iomem *base, void *data)
return val & BYT_BCR_WPD;
}
static bool lpc_ich_lpt_set_writeable(void __iomem *base, void *data)
static bool lpc_ich_set_writeable(struct pci_bus *bus, unsigned int devfn)
{
struct pci_dev *pdev = data;
u32 bcr;
pci_read_config_dword(pdev, BCR, &bcr);
pci_bus_read_config_dword(bus, devfn, BCR, &bcr);
if (!(bcr & BCR_WPD)) {
bcr |= BCR_WPD;
pci_write_config_dword(pdev, BCR, bcr);
pci_read_config_dword(pdev, BCR, &bcr);
pci_bus_write_config_dword(bus, devfn, BCR, bcr);
pci_bus_read_config_dword(bus, devfn, BCR, &bcr);
}
return bcr & BCR_WPD;
}
static bool lpc_ich_bxt_set_writeable(void __iomem *base, void *data)
static bool lpc_ich_lpt_set_writeable(void __iomem *base, void *data)
{
unsigned int spi = PCI_DEVFN(13, 2);
struct pci_bus *bus = data;
u32 bcr;
struct pci_dev *pdev = data;
pci_bus_read_config_dword(bus, spi, BCR, &bcr);
if (!(bcr & BCR_WPD)) {
bcr |= BCR_WPD;
pci_bus_write_config_dword(bus, spi, BCR, bcr);
pci_bus_read_config_dword(bus, spi, BCR, &bcr);
}
return lpc_ich_set_writeable(pdev->bus, pdev->devfn);
}
return bcr & BCR_WPD;
static bool lpc_ich_bxt_set_writeable(void __iomem *base, void *data)
{
struct pci_dev *pdev = data;
return lpc_ich_set_writeable(pdev->bus, PCI_DEVFN(13, 2));
}
static int lpc_ich_init_spi(struct pci_dev *dev)
......@@ -1137,6 +1230,7 @@ static int lpc_ich_init_spi(struct pci_dev *dev)
struct resource *res = &intel_spi_res[0];
struct intel_spi_boardinfo *info;
u32 spi_base, rcba;
int ret;
info = devm_kzalloc(&dev->dev, sizeof(*info), GFP_KERNEL);
if (!info)
......@@ -1167,30 +1261,19 @@ static int lpc_ich_init_spi(struct pci_dev *dev)
}
break;
case INTEL_SPI_BXT: {
unsigned int p2sb = PCI_DEVFN(13, 0);
unsigned int spi = PCI_DEVFN(13, 2);
struct pci_bus *bus = dev->bus;
case INTEL_SPI_BXT:
/*
* The P2SB is hidden by BIOS and we need to unhide it in
* order to read BAR of the SPI flash device. Once that is
* done we hide it again.
*/
pci_bus_write_config_byte(bus, p2sb, 0xe1, 0x0);
pci_bus_read_config_dword(bus, spi, PCI_BASE_ADDRESS_0,
&spi_base);
if (spi_base != ~0) {
res->start = spi_base & 0xfffffff0;
res->end = res->start + SPIBASE_APL_SZ - 1;
info->set_writeable = lpc_ich_bxt_set_writeable;
info->data = bus;
}
ret = p2sb_bar(dev->bus, PCI_DEVFN(13, 2), res);
if (ret)
return ret;
pci_bus_write_config_byte(bus, p2sb, 0xe1, 0x1);
info->set_writeable = lpc_ich_bxt_set_writeable;
info->data = dev;
break;
}
default:
return -EINVAL;
......@@ -1249,6 +1332,12 @@ static int lpc_ich_probe(struct pci_dev *dev,
cell_added = true;
}
if (priv->chipset == LPC_APL) {
ret = lpc_ich_init_pinctrl(dev);
if (!ret)
cell_added = true;
}
if (lpc_chipset_info[priv->chipset].spi_type) {
ret = lpc_ich_init_spi(dev);
if (!ret)
......
......@@ -1641,16 +1641,14 @@ EXPORT_SYMBOL_GPL(intel_pinctrl_probe_by_uid);
const struct intel_pinctrl_soc_data *intel_pinctrl_get_soc_data(struct platform_device *pdev)
{
const struct intel_pinctrl_soc_data * const *table;
const struct intel_pinctrl_soc_data *data = NULL;
const struct intel_pinctrl_soc_data **table;
struct acpi_device *adev;
unsigned int i;
adev = ACPI_COMPANION(&pdev->dev);
if (adev) {
const void *match = device_get_match_data(&pdev->dev);
table = device_get_match_data(&pdev->dev);
if (table) {
struct acpi_device *adev = ACPI_COMPANION(&pdev->dev);
unsigned int i;
table = (const struct intel_pinctrl_soc_data **)match;
for (i = 0; table[i]; i++) {
if (!strcmp(adev->pnp.unique_id, table[i]->uid)) {
data = table[i];
......@@ -1664,7 +1662,7 @@ const struct intel_pinctrl_soc_data *intel_pinctrl_get_soc_data(struct platform_
if (!id)
return ERR_PTR(-ENODEV);
table = (const struct intel_pinctrl_soc_data **)id->driver_data;
table = (const struct intel_pinctrl_soc_data * const *)id->driver_data;
data = table[pdev->id];
}
......
# SPDX-License-Identifier: GPL-2.0-only
if X86
source "drivers/platform/x86/Kconfig"
endif
if MIPS
source "drivers/platform/mips/Kconfig"
endif
......@@ -15,3 +12,5 @@ source "drivers/platform/mellanox/Kconfig"
source "drivers/platform/olpc/Kconfig"
source "drivers/platform/surface/Kconfig"
source "drivers/platform/x86/Kconfig"
......@@ -31,6 +31,7 @@
* @group: sysfs attribute group;
* @groups: list of sysfs attribute group for hwmon registration;
* @regsize: size of a register value;
* @io_lock: user access locking;
*/
struct mlxreg_io_priv_data {
struct platform_device *pdev;
......@@ -41,6 +42,7 @@ struct mlxreg_io_priv_data {
struct attribute_group group;
const struct attribute_group *groups[2];
int regsize;
struct mutex io_lock; /* Protects user access. */
};
static int
......@@ -116,14 +118,19 @@ mlxreg_io_attr_show(struct device *dev, struct device_attribute *attr,
u32 regval = 0;
int ret;
mutex_lock(&priv->io_lock);
ret = mlxreg_io_get_reg(priv->pdata->regmap, data, 0, true,
priv->regsize, &regval);
if (ret)
goto access_error;
mutex_unlock(&priv->io_lock);
return sprintf(buf, "%u\n", regval);
access_error:
mutex_unlock(&priv->io_lock);
return ret;
}
......@@ -145,6 +152,8 @@ mlxreg_io_attr_store(struct device *dev, struct device_attribute *attr,
if (ret)
return ret;
mutex_lock(&priv->io_lock);
ret = mlxreg_io_get_reg(priv->pdata->regmap, data, input_val, false,
priv->regsize, &regval);
if (ret)
......@@ -154,9 +163,12 @@ mlxreg_io_attr_store(struct device *dev, struct device_attribute *attr,
if (ret)
goto access_error;
mutex_unlock(&priv->io_lock);
return len;
access_error:
mutex_unlock(&priv->io_lock);
dev_err(&priv->pdev->dev, "Bus access error\n");
return ret;
}
......@@ -246,16 +258,27 @@ static int mlxreg_io_probe(struct platform_device *pdev)
return PTR_ERR(priv->hwmon);
}
mutex_init(&priv->io_lock);
dev_set_drvdata(&pdev->dev, priv);
return 0;
}
static int mlxreg_io_remove(struct platform_device *pdev)
{
struct mlxreg_io_priv_data *priv = dev_get_drvdata(&pdev->dev);
mutex_destroy(&priv->io_lock);
return 0;
}
static struct platform_driver mlxreg_io_driver = {
.driver = {
.name = "mlxreg-io",
},
.probe = mlxreg_io_probe,
.remove = mlxreg_io_remove,
};
module_platform_driver(mlxreg_io_driver);
......
......@@ -716,8 +716,12 @@ mlxreg_lc_config_init(struct mlxreg_lc *mlxreg_lc, void *regmap,
switch (regval) {
case MLXREG_LC_SN4800_C16:
err = mlxreg_lc_sn4800_c16_config_init(mlxreg_lc, regmap, data);
if (err)
if (err) {
dev_err(dev, "Failed to config client %s at bus %d at addr 0x%02x\n",
data->hpdev.brdinfo->type, data->hpdev.nr,
data->hpdev.brdinfo->addr);
return err;
}
break;
default:
return -ENODEV;
......@@ -730,8 +734,11 @@ mlxreg_lc_config_init(struct mlxreg_lc *mlxreg_lc, void *regmap,
mlxreg_lc->mux = platform_device_register_resndata(dev, "i2c-mux-mlxcpld", data->hpdev.nr,
NULL, 0, mlxreg_lc->mux_data,
sizeof(*mlxreg_lc->mux_data));
if (IS_ERR(mlxreg_lc->mux))
if (IS_ERR(mlxreg_lc->mux)) {
dev_err(dev, "Failed to create mux infra for client %s at bus %d at addr 0x%02x\n",
data->hpdev.brdinfo->type, data->hpdev.nr, data->hpdev.brdinfo->addr);
return PTR_ERR(mlxreg_lc->mux);
}
/* Register IO access driver. */
if (mlxreg_lc->io_data) {
......@@ -740,6 +747,9 @@ mlxreg_lc_config_init(struct mlxreg_lc *mlxreg_lc, void *regmap,
platform_device_register_resndata(dev, "mlxreg-io", data->hpdev.nr, NULL, 0,
mlxreg_lc->io_data, sizeof(*mlxreg_lc->io_data));
if (IS_ERR(mlxreg_lc->io_regs)) {
dev_err(dev, "Failed to create regio for client %s at bus %d at addr 0x%02x\n",
data->hpdev.brdinfo->type, data->hpdev.nr,
data->hpdev.brdinfo->addr);
err = PTR_ERR(mlxreg_lc->io_regs);
goto fail_register_io;
}
......@@ -753,6 +763,9 @@ mlxreg_lc_config_init(struct mlxreg_lc *mlxreg_lc, void *regmap,
mlxreg_lc->led_data,
sizeof(*mlxreg_lc->led_data));
if (IS_ERR(mlxreg_lc->led)) {
dev_err(dev, "Failed to create LED objects for client %s at bus %d at addr 0x%02x\n",
data->hpdev.brdinfo->type, data->hpdev.nr,
data->hpdev.brdinfo->addr);
err = PTR_ERR(mlxreg_lc->led);
goto fail_register_led;
}
......@@ -809,7 +822,8 @@ static int mlxreg_lc_probe(struct platform_device *pdev)
if (!data->hpdev.adapter) {
dev_err(&pdev->dev, "Failed to get adapter for bus %d\n",
data->hpdev.nr);
return -EFAULT;
err = -EFAULT;
goto i2c_get_adapter_fail;
}
/* Create device at the top of line card I2C tree.*/
......@@ -818,32 +832,40 @@ static int mlxreg_lc_probe(struct platform_device *pdev)
if (IS_ERR(data->hpdev.client)) {
dev_err(&pdev->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 PTR_ERR(data->hpdev.client);
err = PTR_ERR(data->hpdev.client);
goto i2c_new_device_fail;
}
regmap = devm_regmap_init_i2c(data->hpdev.client,
&mlxreg_lc_regmap_conf);
if (IS_ERR(regmap)) {
dev_err(&pdev->dev, "Failed to create regmap for client %s at bus %d at addr 0x%02x\n",
data->hpdev.brdinfo->type, data->hpdev.nr, data->hpdev.brdinfo->addr);
err = PTR_ERR(regmap);
goto mlxreg_lc_probe_fail;
goto devm_regmap_init_i2c_fail;
}
/* Set default registers. */
for (i = 0; i < mlxreg_lc_regmap_conf.num_reg_defaults; i++) {
err = regmap_write(regmap, mlxreg_lc_regmap_default[i].reg,
mlxreg_lc_regmap_default[i].def);
if (err)
goto mlxreg_lc_probe_fail;
if (err) {
dev_err(&pdev->dev, "Failed to set default regmap %d for client %s at bus %d at addr 0x%02x\n",
i, data->hpdev.brdinfo->type, data->hpdev.nr,
data->hpdev.brdinfo->addr);
goto regmap_write_fail;
}
}
/* Sync registers with hardware. */
regcache_mark_dirty(regmap);
err = regcache_sync(regmap);
if (err)
goto mlxreg_lc_probe_fail;
if (err) {
dev_err(&pdev->dev, "Failed to sync regmap for client %s at bus %d at addr 0x%02x\n",
data->hpdev.brdinfo->type, data->hpdev.nr, data->hpdev.brdinfo->addr);
err = PTR_ERR(regmap);
goto regcache_sync_fail;
}
par_pdata = data->hpdev.brdinfo->platform_data;
mlxreg_lc->par_regmap = par_pdata->regmap;
......@@ -854,12 +876,27 @@ static int mlxreg_lc_probe(struct platform_device *pdev)
/* Configure line card. */
err = mlxreg_lc_config_init(mlxreg_lc, regmap, data);
if (err)
goto mlxreg_lc_probe_fail;
goto mlxreg_lc_config_init_fail;
return err;
mlxreg_lc_probe_fail:
mlxreg_lc_config_init_fail:
regcache_sync_fail:
regmap_write_fail:
devm_regmap_init_i2c_fail:
if (data->hpdev.client) {
i2c_unregister_device(data->hpdev.client);
data->hpdev.client = NULL;
}
i2c_new_device_fail:
i2c_put_adapter(data->hpdev.adapter);
data->hpdev.adapter = NULL;
i2c_get_adapter_fail:
/* Clear event notification callback and handle. */
if (data->notifier) {
data->notifier->user_handler = NULL;
data->notifier->handle = NULL;
}
return err;
}
......@@ -868,11 +905,18 @@ static int mlxreg_lc_remove(struct platform_device *pdev)
struct mlxreg_core_data *data = dev_get_platdata(&pdev->dev);
struct mlxreg_lc *mlxreg_lc = platform_get_drvdata(pdev);
/* Clear event notification callback. */
if (data->notifier) {
data->notifier->user_handler = NULL;
data->notifier->handle = NULL;
}
/*
* Probing and removing are invoked by hotplug events raised upon line card insertion and
* removing. If probing procedure fails all data is cleared. However, hotplug event still
* will be raised on line card removing and activate removing procedure. In this case there
* is nothing to remove.
*/
if (!data->notifier || !data->notifier->handle)
return 0;
/* Clear event notification callback and handle. */
data->notifier->user_handler = NULL;
data->notifier->handle = NULL;
/* Destroy static I2C device feeding by main power. */
mlxreg_lc_destroy_static_devices(mlxreg_lc, mlxreg_lc->main_devs,
......
......@@ -264,7 +264,7 @@ static ssize_t ec_dbgfs_cmd_write(struct file *file, const char __user *buf,
int i, m;
unsigned char ec_cmd[EC_MAX_CMD_ARGS];
unsigned int ec_cmd_int[EC_MAX_CMD_ARGS];
char cmdbuf[64];
char cmdbuf[64] = "";
int ec_cmd_bytes;
mutex_lock(&ec_dbgfs_lock);
......
......@@ -72,18 +72,45 @@ config SURFACE_AGGREGATOR_CDEV
The provided interface is intended for debugging and development only,
and should not be used otherwise.
config SURFACE_AGGREGATOR_HUB
tristate "Surface System Aggregator Module Subsystem Device Hubs"
depends on SURFACE_AGGREGATOR
depends on SURFACE_AGGREGATOR_BUS
help
Device-hub drivers for Surface System Aggregator Module (SSAM) subsystem
devices.
Provides subsystem hub drivers which manage client devices on various
SSAM subsystems. In some subsystems, notably the BAS subsystem managing
devices contained in the base of the Surface Book 3 and the KIP subsystem
managing type-cover devices in the Surface Pro 8 and Surface Pro X,
devices can be (hot-)removed. Hub devices and drivers are required to
manage these subdevices.
Devices managed via these hubs are:
- Battery/AC devices (Surface Book 3).
- HID input devices (7th-generation and later models with detachable
input devices).
Select M (recommended) or Y here if you want support for the above
mentioned devices on the corresponding Surface models. Without this
module, the respective devices mentioned above will not be instantiated
and thus any functionality provided by them will be missing, even when
drivers for these devices are present. This module only provides the
respective subsystem hubs. Both drivers and device specification (e.g.
via the Surface Aggregator Registry) for these devices still need to be
selected via other options.
config SURFACE_AGGREGATOR_REGISTRY
tristate "Surface System Aggregator Module Device Registry"
depends on SURFACE_AGGREGATOR
depends on SURFACE_AGGREGATOR_BUS
help
Device-registry and device-hubs for Surface System Aggregator Module
(SSAM) devices.
Device-registry for Surface System Aggregator Module (SSAM) devices.
Provides a module and driver which act as a device-registry for SSAM
client devices that cannot be detected automatically, e.g. via ACPI.
Such devices are instead provided via this registry and attached via
device hubs, also provided in this module.
Such devices are instead provided and managed via this registry.
Devices provided via this registry are:
- Platform profile (performance-/cooling-mode) device (5th- and later
......@@ -99,6 +126,29 @@ config SURFACE_AGGREGATOR_REGISTRY
the respective client devices. Drivers for these devices still need to
be selected via the other options.
config SURFACE_AGGREGATOR_TABLET_SWITCH
tristate "Surface Aggregator Generic Tablet-Mode Switch Driver"
depends on SURFACE_AGGREGATOR
depends on SURFACE_AGGREGATOR_BUS
depends on INPUT
help
Provides a tablet-mode switch input device on Microsoft Surface models
using the KIP subsystem for detachable keyboards (e.g. keyboard covers)
or the POS subsystem for device/screen posture changes.
The KIP subsystem is used on newer Surface generations to handle
detachable input peripherals, specifically the keyboard cover (containing
keyboard and touchpad) on the Surface Pro 8 and Surface Pro X. The POS
subsystem is used for device posture change notifications on the Surface
Laptop Studio. This module provides a driver to let user-space know when
the device should be considered in tablet-mode due to the keyboard cover
being detached or folded back (essentially signaling when the keyboard is
not available for input). It does so by creating a tablet-mode switch
input device, sending the standard SW_TABLET_MODE event on mode change.
Select M or Y here, if you want to provide tablet-mode switch input
events on the Surface Pro 8, Surface Pro X, and Surface Laptop Studio.
config SURFACE_DTX
tristate "Surface DTX (Detachment System) Driver"
depends on SURFACE_AGGREGATOR
......
......@@ -9,7 +9,9 @@ obj-$(CONFIG_SURFACE_3_POWER_OPREGION) += surface3_power.o
obj-$(CONFIG_SURFACE_ACPI_NOTIFY) += surface_acpi_notify.o
obj-$(CONFIG_SURFACE_AGGREGATOR) += aggregator/
obj-$(CONFIG_SURFACE_AGGREGATOR_CDEV) += surface_aggregator_cdev.o
obj-$(CONFIG_SURFACE_AGGREGATOR_HUB) += surface_aggregator_hub.o
obj-$(CONFIG_SURFACE_AGGREGATOR_REGISTRY) += surface_aggregator_registry.o
obj-$(CONFIG_SURFACE_AGGREGATOR_TABLET_SWITCH) += surface_aggregator_tabletsw.o
obj-$(CONFIG_SURFACE_DTX) += surface_dtx.o
obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o
obj-$(CONFIG_SURFACE_HOTPLUG) += surface_hotplug.o
......
# SPDX-License-Identifier: GPL-2.0+
# Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
# Copyright (C) 2019-2022 Maximilian Luz <luzmaximilian@gmail.com>
menuconfig SURFACE_AGGREGATOR
tristate "Microsoft Surface System Aggregator Module Subsystem and Drivers"
......
# SPDX-License-Identifier: GPL-2.0+
# Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
# Copyright (C) 2019-2022 Maximilian Luz <luzmaximilian@gmail.com>
# For include/trace/define_trace.h to include trace.h
CFLAGS_core.o = -I$(src)
......
......@@ -2,10 +2,11 @@
/*
* Surface System Aggregator Module bus and device integration.
*
* Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
* Copyright (C) 2019-2022 Maximilian Luz <luzmaximilian@gmail.com>
*/
#include <linux/device.h>
#include <linux/property.h>
#include <linux/slab.h>
#include <linux/surface_aggregator/controller.h>
......@@ -14,6 +15,9 @@
#include "bus.h"
#include "controller.h"
/* -- Device and bus functions. --------------------------------------------- */
static ssize_t modalias_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
......@@ -46,6 +50,7 @@ static void ssam_device_release(struct device *dev)
struct ssam_device *sdev = to_ssam_device(dev);
ssam_controller_put(sdev->ctrl);
fwnode_handle_put(sdev->dev.fwnode);
kfree(sdev);
}
......@@ -363,6 +368,134 @@ void ssam_device_driver_unregister(struct ssam_device_driver *sdrv)
}
EXPORT_SYMBOL_GPL(ssam_device_driver_unregister);
/* -- Bus registration. ----------------------------------------------------- */
/**
* ssam_bus_register() - Register and set-up the SSAM client device bus.
*/
int ssam_bus_register(void)
{
return bus_register(&ssam_bus_type);
}
/**
* ssam_bus_unregister() - Unregister the SSAM client device bus.
*/
void ssam_bus_unregister(void)
{
return bus_unregister(&ssam_bus_type);
}
/* -- Helpers for controller and hub devices. ------------------------------- */
static int ssam_device_uid_from_string(const char *str, struct ssam_device_uid *uid)
{
u8 d, tc, tid, iid, fn;
int n;
n = sscanf(str, "%hhx:%hhx:%hhx:%hhx:%hhx", &d, &tc, &tid, &iid, &fn);
if (n != 5)
return -EINVAL;
uid->domain = d;
uid->category = tc;
uid->target = tid;
uid->instance = iid;
uid->function = fn;
return 0;
}
static int ssam_get_uid_for_node(struct fwnode_handle *node, struct ssam_device_uid *uid)
{
const char *str = fwnode_get_name(node);
/*
* To simplify definitions of firmware nodes, we set the device name
* based on the UID of the device, prefixed with "ssam:".
*/
if (strncmp(str, "ssam:", strlen("ssam:")) != 0)
return -ENODEV;
str += strlen("ssam:");
return ssam_device_uid_from_string(str, uid);
}
static int ssam_add_client_device(struct device *parent, struct ssam_controller *ctrl,
struct fwnode_handle *node)
{
struct ssam_device_uid uid;
struct ssam_device *sdev;
int status;
status = ssam_get_uid_for_node(node, &uid);
if (status)
return status;
sdev = ssam_device_alloc(ctrl, uid);
if (!sdev)
return -ENOMEM;
sdev->dev.parent = parent;
sdev->dev.fwnode = fwnode_handle_get(node);
status = ssam_device_add(sdev);
if (status)
ssam_device_put(sdev);
return status;
}
/**
* __ssam_register_clients() - Register client devices defined under the
* given firmware node as children of the given device.
* @parent: The parent device under which clients should be registered.
* @ctrl: The controller with which client should be registered.
* @node: The firmware node holding definitions of the devices to be added.
*
* Register all clients that have been defined as children of the given root
* firmware node as children of the given parent device. The respective child
* firmware nodes will be associated with the correspondingly created child
* devices.
*
* The given controller will be used to instantiate the new devices. See
* ssam_device_add() for details.
*
* Note that, generally, the use of either ssam_device_register_clients() or
* ssam_register_clients() should be preferred as they directly use the
* firmware node and/or controller associated with the given device. This
* function is only intended for use when different device specifications (e.g.
* ACPI and firmware nodes) need to be combined (as is done in the platform hub
* of the device registry).
*
* Return: Returns zero on success, nonzero on failure.
*/
int __ssam_register_clients(struct device *parent, struct ssam_controller *ctrl,
struct fwnode_handle *node)
{
struct fwnode_handle *child;
int status;
fwnode_for_each_child_node(node, child) {
/*
* Try to add the device specified in the firmware node. If
* this fails with -ENODEV, the node does not specify any SSAM
* device, so ignore it and continue with the next one.
*/
status = ssam_add_client_device(parent, ctrl, child);
if (status && status != -ENODEV)
goto err;
}
return 0;
err:
ssam_remove_clients(parent);
return status;
}
EXPORT_SYMBOL_GPL(__ssam_register_clients);
static int ssam_remove_device(struct device *dev, void *_data)
{
struct ssam_device *sdev = to_ssam_device(dev);
......@@ -387,19 +520,3 @@ void ssam_remove_clients(struct device *dev)
device_for_each_child_reverse(dev, NULL, ssam_remove_device);
}
EXPORT_SYMBOL_GPL(ssam_remove_clients);
/**
* ssam_bus_register() - Register and set-up the SSAM client device bus.
*/
int ssam_bus_register(void)
{
return bus_register(&ssam_bus_type);
}
/**
* ssam_bus_unregister() - Unregister the SSAM client device bus.
*/
void ssam_bus_unregister(void)
{
return bus_unregister(&ssam_bus_type);
}
......@@ -2,7 +2,7 @@
/*
* Surface System Aggregator Module bus and device integration.
*
* Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
* Copyright (C) 2019-2022 Maximilian Luz <luzmaximilian@gmail.com>
*/
#ifndef _SURFACE_AGGREGATOR_BUS_H
......
......@@ -2,7 +2,7 @@
/*
* Main SSAM/SSH controller structure and functionality.
*
* Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
* Copyright (C) 2019-2022 Maximilian Luz <luzmaximilian@gmail.com>
*/
#include <linux/acpi.h>
......@@ -2199,16 +2199,26 @@ static int ssam_nf_refcount_enable(struct ssam_controller *ctrl,
}
/**
* ssam_nf_refcount_disable_free() - Disable event for reference count entry if it is
* no longer in use and free the corresponding entry.
* ssam_nf_refcount_disable_free() - Disable event for reference count entry if
* it is no longer in use and free the corresponding entry.
* @ctrl: The controller to disable the event on.
* @entry: The reference count entry for the event to be disabled.
* @flags: The flags used for enabling the event on the EC.
* @ec: Flag specifying if the event should actually be disabled on the EC.
*
* If the reference count equals zero, i.e. the event is no longer requested by
* any client, the event will be disabled and the corresponding reference count
* entry freed. The reference count entry must not be used any more after a
* call to this function.
* If ``ec`` equals ``true`` and the reference count equals zero (i.e. the
* event is no longer requested by any client), the specified event will be
* disabled on the EC via the corresponding request.
*
* If ``ec`` equals ``false``, no request will be sent to the EC and the event
* can be considered in a detached state (i.e. no longer used but still
* enabled). Disabling an event via this method may be required for
* hot-removable devices, where event disable requests may time out after the
* device has been physically removed.
*
* In both cases, if the reference count equals zero, the corresponding
* reference count entry will be freed. The reference count entry must not be
* used any more after a call to this function.
*
* Also checks if the flags used for disabling the event match the flags used
* for enabling the event and warns if they do not (regardless of reference
......@@ -2223,7 +2233,7 @@ static int ssam_nf_refcount_enable(struct ssam_controller *ctrl,
* returns the status of the event-enable EC command.
*/
static int ssam_nf_refcount_disable_free(struct ssam_controller *ctrl,
struct ssam_nf_refcount_entry *entry, u8 flags)
struct ssam_nf_refcount_entry *entry, u8 flags, bool ec)
{
const struct ssam_event_registry reg = entry->key.reg;
const struct ssam_event_id id = entry->key.id;
......@@ -2232,8 +2242,9 @@ static int ssam_nf_refcount_disable_free(struct ssam_controller *ctrl,
lockdep_assert_held(&nf->lock);
ssam_dbg(ctrl, "disabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n",
reg.target_category, id.target_category, id.instance, entry->refcount);
ssam_dbg(ctrl, "%s event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n",
ec ? "disabling" : "detaching", reg.target_category, id.target_category,
id.instance, entry->refcount);
if (entry->flags != flags) {
ssam_warn(ctrl,
......@@ -2242,7 +2253,7 @@ static int ssam_nf_refcount_disable_free(struct ssam_controller *ctrl,
id.instance);
}
if (entry->refcount == 0) {
if (ec && entry->refcount == 0) {
status = ssam_ssh_event_disable(ctrl, reg, id, flags);
kfree(entry);
}
......@@ -2322,20 +2333,26 @@ int ssam_notifier_register(struct ssam_controller *ctrl, struct ssam_event_notif
EXPORT_SYMBOL_GPL(ssam_notifier_register);
/**
* ssam_notifier_unregister() - Unregister an event notifier.
* @ctrl: The controller the notifier has been registered on.
* @n: The event notifier to unregister.
* __ssam_notifier_unregister() - Unregister an event notifier.
* @ctrl: The controller the notifier has been registered on.
* @n: The event notifier to unregister.
* @disable: Whether to disable the corresponding event on the EC.
*
* Unregister an event notifier. Decrement the usage counter of the associated
* SAM event if the notifier is not marked as an observer. If the usage counter
* reaches zero, the event will be disabled.
* reaches zero and ``disable`` equals ``true``, the event will be disabled.
*
* Useful for hot-removable devices, where communication may fail once the
* device has been physically removed. In that case, specifying ``disable`` as
* ``false`` avoids communication with the EC.
*
* Return: Returns zero on success, %-ENOENT if the given notifier block has
* not been registered on the controller. If the given notifier block was the
* last one associated with its specific event, returns the status of the
* event-disable EC-command.
*/
int ssam_notifier_unregister(struct ssam_controller *ctrl, struct ssam_event_notifier *n)
int __ssam_notifier_unregister(struct ssam_controller *ctrl, struct ssam_event_notifier *n,
bool disable)
{
u16 rqid = ssh_tc_to_rqid(n->event.id.target_category);
struct ssam_nf_refcount_entry *entry;
......@@ -2373,7 +2390,7 @@ int ssam_notifier_unregister(struct ssam_controller *ctrl, struct ssam_event_not
goto remove;
}
status = ssam_nf_refcount_disable_free(ctrl, entry, n->event.flags);
status = ssam_nf_refcount_disable_free(ctrl, entry, n->event.flags, disable);
}
remove:
......@@ -2383,7 +2400,7 @@ int ssam_notifier_unregister(struct ssam_controller *ctrl, struct ssam_event_not
return status;
}
EXPORT_SYMBOL_GPL(ssam_notifier_unregister);
EXPORT_SYMBOL_GPL(__ssam_notifier_unregister);
/**
* ssam_controller_event_enable() - Enable the specified event.
......@@ -2477,7 +2494,7 @@ int ssam_controller_event_disable(struct ssam_controller *ctrl,
return -ENOENT;
}
status = ssam_nf_refcount_disable_free(ctrl, entry, flags);
status = ssam_nf_refcount_disable_free(ctrl, entry, flags, true);
mutex_unlock(&nf->lock);
return status;
......
......@@ -2,7 +2,7 @@
/*
* Main SSAM/SSH controller structure and functionality.
*
* Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
* Copyright (C) 2019-2022 Maximilian Luz <luzmaximilian@gmail.com>
*/
#ifndef _SURFACE_AGGREGATOR_CONTROLLER_H
......
......@@ -7,7 +7,7 @@
* Handles communication via requests as well as enabling, disabling, and
* relaying of events.
*
* Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
* Copyright (C) 2019-2022 Maximilian Luz <luzmaximilian@gmail.com>
*/
#include <linux/acpi.h>
......
......@@ -2,7 +2,7 @@
/*
* SSH message builder functions.
*
* Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
* Copyright (C) 2019-2022 Maximilian Luz <luzmaximilian@gmail.com>
*/
#ifndef _SURFACE_AGGREGATOR_SSH_MSGB_H
......
......@@ -2,7 +2,7 @@
/*
* SSH packet transport layer.
*
* Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
* Copyright (C) 2019-2022 Maximilian Luz <luzmaximilian@gmail.com>
*/
#include <asm/unaligned.h>
......
......@@ -2,7 +2,7 @@
/*
* SSH packet transport layer.
*
* Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
* Copyright (C) 2019-2022 Maximilian Luz <luzmaximilian@gmail.com>
*/
#ifndef _SURFACE_AGGREGATOR_SSH_PACKET_LAYER_H
......
......@@ -2,7 +2,7 @@
/*
* SSH message parser.
*
* Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
* Copyright (C) 2019-2022 Maximilian Luz <luzmaximilian@gmail.com>
*/
#include <asm/unaligned.h>
......
......@@ -2,7 +2,7 @@
/*
* SSH message parser.
*
* Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
* Copyright (C) 2019-2022 Maximilian Luz <luzmaximilian@gmail.com>
*/
#ifndef _SURFACE_AGGREGATOR_SSH_PARSER_H
......
......@@ -2,7 +2,7 @@
/*
* SSH request transport layer.
*
* Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
* Copyright (C) 2019-2022 Maximilian Luz <luzmaximilian@gmail.com>
*/
#include <asm/unaligned.h>
......
......@@ -2,7 +2,7 @@
/*
* SSH request transport layer.
*
* Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
* Copyright (C) 2019-2022 Maximilian Luz <luzmaximilian@gmail.com>
*/
#ifndef _SURFACE_AGGREGATOR_SSH_REQUEST_LAYER_H
......
......@@ -2,7 +2,7 @@
/*
* Trace points for SSAM/SSH.
*
* Copyright (C) 2020-2021 Maximilian Luz <luzmaximilian@gmail.com>
* Copyright (C) 2020-2022 Maximilian Luz <luzmaximilian@gmail.com>
*/
#undef TRACE_SYSTEM
......@@ -76,7 +76,7 @@ TRACE_DEFINE_ENUM(SSAM_SSH_TC_HID);
TRACE_DEFINE_ENUM(SSAM_SSH_TC_TCH);
TRACE_DEFINE_ENUM(SSAM_SSH_TC_BKL);
TRACE_DEFINE_ENUM(SSAM_SSH_TC_TAM);
TRACE_DEFINE_ENUM(SSAM_SSH_TC_ACC);
TRACE_DEFINE_ENUM(SSAM_SSH_TC_ACC0);
TRACE_DEFINE_ENUM(SSAM_SSH_TC_UFI);
TRACE_DEFINE_ENUM(SSAM_SSH_TC_USC);
TRACE_DEFINE_ENUM(SSAM_SSH_TC_PEN);
......@@ -85,6 +85,11 @@ TRACE_DEFINE_ENUM(SSAM_SSH_TC_AUD);
TRACE_DEFINE_ENUM(SSAM_SSH_TC_SMC);
TRACE_DEFINE_ENUM(SSAM_SSH_TC_KPD);
TRACE_DEFINE_ENUM(SSAM_SSH_TC_REG);
TRACE_DEFINE_ENUM(SSAM_SSH_TC_SPT);
TRACE_DEFINE_ENUM(SSAM_SSH_TC_SYS);
TRACE_DEFINE_ENUM(SSAM_SSH_TC_ACC1);
TRACE_DEFINE_ENUM(SSAM_SSH_TC_SHB);
TRACE_DEFINE_ENUM(SSAM_SSH_TC_POS);
#define SSAM_PTR_UID_LEN 9
#define SSAM_U8_FIELD_NOT_APPLICABLE ((u16)-1)
......@@ -229,40 +234,45 @@ static inline u32 ssam_trace_get_request_tc(const struct ssh_packet *p)
#define ssam_show_ssh_tc(rqid) \
__print_symbolic(rqid, \
{ SSAM_SSH_TC_NOT_APPLICABLE, "N/A" }, \
{ SSAM_SSH_TC_SAM, "SAM" }, \
{ SSAM_SSH_TC_BAT, "BAT" }, \
{ SSAM_SSH_TC_TMP, "TMP" }, \
{ SSAM_SSH_TC_PMC, "PMC" }, \
{ SSAM_SSH_TC_FAN, "FAN" }, \
{ SSAM_SSH_TC_PoM, "PoM" }, \
{ SSAM_SSH_TC_DBG, "DBG" }, \
{ SSAM_SSH_TC_KBD, "KBD" }, \
{ SSAM_SSH_TC_FWU, "FWU" }, \
{ SSAM_SSH_TC_UNI, "UNI" }, \
{ SSAM_SSH_TC_LPC, "LPC" }, \
{ SSAM_SSH_TC_TCL, "TCL" }, \
{ SSAM_SSH_TC_SFL, "SFL" }, \
{ SSAM_SSH_TC_KIP, "KIP" }, \
{ SSAM_SSH_TC_EXT, "EXT" }, \
{ SSAM_SSH_TC_BLD, "BLD" }, \
{ SSAM_SSH_TC_BAS, "BAS" }, \
{ SSAM_SSH_TC_SEN, "SEN" }, \
{ SSAM_SSH_TC_SRQ, "SRQ" }, \
{ SSAM_SSH_TC_MCU, "MCU" }, \
{ SSAM_SSH_TC_HID, "HID" }, \
{ SSAM_SSH_TC_TCH, "TCH" }, \
{ SSAM_SSH_TC_BKL, "BKL" }, \
{ SSAM_SSH_TC_TAM, "TAM" }, \
{ SSAM_SSH_TC_ACC, "ACC" }, \
{ SSAM_SSH_TC_UFI, "UFI" }, \
{ SSAM_SSH_TC_USC, "USC" }, \
{ SSAM_SSH_TC_PEN, "PEN" }, \
{ SSAM_SSH_TC_VID, "VID" }, \
{ SSAM_SSH_TC_AUD, "AUD" }, \
{ SSAM_SSH_TC_SMC, "SMC" }, \
{ SSAM_SSH_TC_KPD, "KPD" }, \
{ SSAM_SSH_TC_REG, "REG" } \
{ SSAM_SSH_TC_NOT_APPLICABLE, "N/A" }, \
{ SSAM_SSH_TC_SAM, "SAM" }, \
{ SSAM_SSH_TC_BAT, "BAT" }, \
{ SSAM_SSH_TC_TMP, "TMP" }, \
{ SSAM_SSH_TC_PMC, "PMC" }, \
{ SSAM_SSH_TC_FAN, "FAN" }, \
{ SSAM_SSH_TC_PoM, "PoM" }, \
{ SSAM_SSH_TC_DBG, "DBG" }, \
{ SSAM_SSH_TC_KBD, "KBD" }, \
{ SSAM_SSH_TC_FWU, "FWU" }, \
{ SSAM_SSH_TC_UNI, "UNI" }, \
{ SSAM_SSH_TC_LPC, "LPC" }, \
{ SSAM_SSH_TC_TCL, "TCL" }, \
{ SSAM_SSH_TC_SFL, "SFL" }, \
{ SSAM_SSH_TC_KIP, "KIP" }, \
{ SSAM_SSH_TC_EXT, "EXT" }, \
{ SSAM_SSH_TC_BLD, "BLD" }, \
{ SSAM_SSH_TC_BAS, "BAS" }, \
{ SSAM_SSH_TC_SEN, "SEN" }, \
{ SSAM_SSH_TC_SRQ, "SRQ" }, \
{ SSAM_SSH_TC_MCU, "MCU" }, \
{ SSAM_SSH_TC_HID, "HID" }, \
{ SSAM_SSH_TC_TCH, "TCH" }, \
{ SSAM_SSH_TC_BKL, "BKL" }, \
{ SSAM_SSH_TC_TAM, "TAM" }, \
{ SSAM_SSH_TC_ACC0, "ACC0" }, \
{ SSAM_SSH_TC_UFI, "UFI" }, \
{ SSAM_SSH_TC_USC, "USC" }, \
{ SSAM_SSH_TC_PEN, "PEN" }, \
{ SSAM_SSH_TC_VID, "VID" }, \
{ SSAM_SSH_TC_AUD, "AUD" }, \
{ SSAM_SSH_TC_SMC, "SMC" }, \
{ SSAM_SSH_TC_KPD, "KPD" }, \
{ SSAM_SSH_TC_REG, "REG" }, \
{ SSAM_SSH_TC_SPT, "SPT" }, \
{ SSAM_SSH_TC_SYS, "SYS" }, \
{ SSAM_SSH_TC_ACC1, "ACC1" }, \
{ SSAM_SSH_TC_SHB, "SMB" }, \
{ SSAM_SSH_TC_POS, "POS" } \
)
DECLARE_EVENT_CLASS(ssam_frame_class,
......
......@@ -8,7 +8,7 @@
* notifications sent from ACPI via the SAN interface by providing them to any
* registered external driver.
*
* Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com>
* Copyright (C) 2019-2022 Maximilian Luz <luzmaximilian@gmail.com>
*/
#include <asm/unaligned.h>
......@@ -37,6 +37,7 @@ struct san_data {
#define to_san_data(ptr, member) \
container_of(ptr, struct san_data, member)
static struct workqueue_struct *san_wq;
/* -- dGPU notifier interface. ---------------------------------------------- */
......@@ -356,7 +357,7 @@ static u32 san_evt_bat_nf(struct ssam_event_notifier *nf,
memcpy(&work->event, event, sizeof(struct ssam_event) + event->length);
schedule_delayed_work(&work->work, delay);
queue_delayed_work(san_wq, &work->work, delay);
return SSAM_NOTIF_HANDLED;
}
......@@ -861,7 +862,7 @@ static int san_remove(struct platform_device *pdev)
* We have unregistered our event sources. Now we need to ensure that
* all delayed works they may have spawned are run to completion.
*/
flush_scheduled_work();
flush_workqueue(san_wq);
return 0;
}
......@@ -881,7 +882,27 @@ static struct platform_driver surface_acpi_notify = {
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
},
};
module_platform_driver(surface_acpi_notify);
static int __init san_init(void)
{
int ret;
san_wq = alloc_workqueue("san_wq", 0, 0);
if (!san_wq)
return -ENOMEM;
ret = platform_driver_register(&surface_acpi_notify);
if (ret)
destroy_workqueue(san_wq);
return ret;
}
module_init(san_init);
static void __exit san_exit(void)
{
platform_driver_unregister(&surface_acpi_notify);
destroy_workqueue(san_wq);
}
module_exit(san_exit);
MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
MODULE_DESCRIPTION("Surface ACPI Notify driver for Surface System Aggregator Module");
......
......@@ -3,7 +3,7 @@
* Provides user-space access to the SSAM EC via the /dev/surface/aggregator
* misc device. Intended for debugging and development.
*
* Copyright (C) 2020-2021 Maximilian Luz <luzmaximilian@gmail.com>
* Copyright (C) 2020-2022 Maximilian Luz <luzmaximilian@gmail.com>
*/
#include <linux/fs.h>
......
This diff is collapsed.
This diff is collapsed.
......@@ -8,7 +8,7 @@
* acknowledge (to speed things up), abort (e.g. in case the dGPU is still in
* use), or request detachment via user-space.
*
* Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
* Copyright (C) 2019-2022 Maximilian Luz <luzmaximilian@gmail.com>
*/
#include <linux/fs.h>
......
......@@ -4,7 +4,7 @@
* properly configuring the respective GPEs. Required for wakeup via lid on
* newer Intel-based Microsoft Surface devices.
*
* Copyright (C) 2020 Maximilian Luz <luzmaximilian@gmail.com>
* Copyright (C) 2020-2022 Maximilian Luz <luzmaximilian@gmail.com>
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
......@@ -171,6 +171,18 @@ static const struct dmi_system_id dmi_lid_device_table[] = {
},
.driver_data = (void *)lid_device_props_l4D,
},
{
.ident = "Surface Laptop 4 (Intel 13\")",
.matches = {
/*
* We match for SKU here due to different variants: The
* AMD (15") version does not rely on GPEs.
*/
DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1950:1951"),
},
.driver_data = (void *)lid_device_props_l4B,
},
{
.ident = "Surface Laptop Studio",
.matches = {
......
......@@ -10,7 +10,7 @@
* Event signaling is handled via ACPI, which will generate the appropriate
* device-check notifications to be picked up by the PCIe hot-plug driver.
*
* Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
* Copyright (C) 2019-2022 Maximilian Luz <luzmaximilian@gmail.com>
*/
#include <linux/acpi.h>
......
......@@ -3,7 +3,7 @@
* Surface Platform Profile / Performance Mode driver for Surface System
* Aggregator Module (thermal subsystem).
*
* Copyright (C) 2021 Maximilian Luz <luzmaximilian@gmail.com>
* Copyright (C) 2021-2022 Maximilian Luz <luzmaximilian@gmail.com>
*/
#include <asm/unaligned.h>
......
......@@ -177,17 +177,15 @@ config ACER_WIRELESS
config ACER_WMI
tristate "Acer WMI Laptop Extras"
depends on ACPI
select LEDS_CLASS
select NEW_LEDS
depends on BACKLIGHT_CLASS_DEVICE
depends on SERIO_I8042
depends on INPUT
depends on RFKILL || RFKILL = n
depends on ACPI_WMI
select ACPI_VIDEO
select INPUT_SPARSEKMAP
# Acer WMI depends on ACPI_VIDEO when ACPI is enabled
select ACPI_VIDEO if ACPI
select LEDS_CLASS
select NEW_LEDS
help
This is a driver for newer Acer (and Wistron) laptops. It adds
wireless radio and bluetooth control, and on some laptops,
......@@ -196,32 +194,7 @@ config ACER_WMI
If you have an ACPI-WMI compatible Acer/ Wistron laptop, say Y or M
here.
config AMD_PMC
tristate "AMD SoC PMC driver"
depends on ACPI && PCI && RTC_CLASS
help
The driver provides support for AMD Power Management Controller
primarily responsible for S2Idle transactions that are driven from
a platform firmware running on SMU. This driver also provides a debug
mechanism to investigate the S2Idle transactions and failures.
Say Y or M here if you have a notebook powered by AMD RYZEN CPU/APU.
If you choose to compile this driver as a module the module will be
called amd-pmc.
config AMD_HSMP
tristate "AMD HSMP Driver"
depends on AMD_NB && X86_64
help
The driver provides a way for user space tools to monitor and manage
system management functionality on EPYC server CPUs from AMD.
Host System Management Port (HSMP) interface is a mailbox interface
between the x86 core and the System Management Unit (SMU) firmware.
If you choose to compile this driver as a module the module will be
called amd_hsmp.
source "drivers/platform/x86/amd/Kconfig"
config ADV_SWBUTTON
tristate "Advantech ACPI Software Button Driver"
......@@ -300,6 +273,8 @@ config ASUS_WMI
select INPUT_SPARSEKMAP
select LEDS_CLASS
select NEW_LEDS
select LEDS_TRIGGERS
select LEDS_TRIGGER_AUDIO
select ACPI_PLATFORM_PROFILE
help
Say Y here if you have a WMI aware Asus laptop (like Eee PCs or new
......@@ -1164,7 +1139,14 @@ config WINMATE_FM07_KEYS
endif # X86_PLATFORM_DEVICES
config PMC_ATOM
def_bool y
depends on PCI
select COMMON_CLK
config P2SB
bool "Primary to Sideband (P2SB) bridge access support"
depends on PCI && X86
help
The Primary to Sideband (P2SB) bridge is an interface to some
PCI devices connected through it. In particular, SPI NOR controller
in Intel Apollo Lake SoC is one of such devices.
The main purpose of this library is to unhide P2SB device in case
firmware kept it hidden on some platforms in order to access devices
behind it.
......@@ -23,8 +23,7 @@ obj-$(CONFIG_ACER_WIRELESS) += acer-wireless.o
obj-$(CONFIG_ACER_WMI) += acer-wmi.o
# AMD
obj-$(CONFIG_AMD_PMC) += amd-pmc.o
obj-$(CONFIG_AMD_HSMP) += amd_hsmp.o
obj-y += amd/
# Advantech
obj-$(CONFIG_ADV_SWBUTTON) += adv_swbutton.o
......@@ -120,13 +119,17 @@ obj-$(CONFIG_X86_ANDROID_TABLETS) += x86-android-tablets.o
# Intel uncore drivers
obj-$(CONFIG_INTEL_IPS) += intel_ips.o
# Intel miscellaneous drivers
intel_p2sb-y := p2sb.o
obj-$(CONFIG_P2SB) += intel_p2sb.o
# Intel PMIC / PMC / P-Unit devices
obj-$(CONFIG_INTEL_SCU_IPC) += intel_scu_ipc.o
obj-$(CONFIG_INTEL_SCU_PCI) += intel_scu_pcidrv.o
obj-$(CONFIG_INTEL_SCU_PLATFORM) += intel_scu_pltdrv.o
obj-$(CONFIG_INTEL_SCU_WDT) += intel_scu_wdt.o
obj-$(CONFIG_INTEL_SCU_IPC_UTIL) += intel_scu_ipcutil.o
obj-$(CONFIG_PMC_ATOM) += pmc_atom.o
obj-$(CONFIG_X86_INTEL_LPSS) += pmc_atom.o
# Siemens Simatic Industrial PCs
obj-$(CONFIG_SIEMENS_SIMATIC_IPC) += simatic-ipc.o
......
......@@ -1615,12 +1615,7 @@ static int read_brightness(struct backlight_device *bd)
static int update_bl_status(struct backlight_device *bd)
{
int intensity = bd->props.brightness;
if (bd->props.power != FB_BLANK_UNBLANK)
intensity = 0;
if (bd->props.fb_blank != FB_BLANK_UNBLANK)
intensity = 0;
int intensity = backlight_get_brightness(bd);
set_u32(intensity, ACER_CAP_BRIGHTNESS);
......
# SPDX-License-Identifier: GPL-2.0-only
#
# AMD x86 Platform Specific Drivers
#
config AMD_PMC
tristate "AMD SoC PMC driver"
depends on ACPI && PCI && RTC_CLASS
help
The driver provides support for AMD Power Management Controller
primarily responsible for S2Idle transactions that are driven from
a platform firmware running on SMU. This driver also provides a debug
mechanism to investigate the S2Idle transactions and failures.
Say Y or M here if you have a notebook powered by AMD RYZEN CPU/APU.
If you choose to compile this driver as a module the module will be
called amd-pmc.
config AMD_HSMP
tristate "AMD HSMP Driver"
depends on AMD_NB && X86_64
help
The driver provides a way for user space tools to monitor and manage
system management functionality on EPYC server CPUs from AMD.
Host System Management Port (HSMP) interface is a mailbox interface
between the x86 core and the System Management Unit (SMU) firmware.
If you choose to compile this driver as a module the module will be
called amd_hsmp.
# SPDX-License-Identifier: GPL-2.0
#
# Makefile for drivers/platform/x86/amd
# AMD x86 Platform-Specific Drivers
#
amd-pmc-y := pmc.o
obj-$(CONFIG_AMD_PMC) += amd-pmc.o
amd_hsmp-y := hsmp.o
obj-$(CONFIG_AMD_HSMP) += amd_hsmp.o
......@@ -291,10 +291,7 @@ static int gmux_get_brightness(struct backlight_device *bd)
static int gmux_update_status(struct backlight_device *bd)
{
struct apple_gmux_data *gmux_data = bl_get_data(bd);
u32 brightness = bd->props.brightness;
if (bd->props.state & BL_CORE_SUSPENDED)
return 0;
u32 brightness = backlight_get_brightness(bd);
gmux_write32(gmux_data, GMUX_PORT_BRIGHTNESS, brightness);
......
......@@ -208,6 +208,7 @@ struct asus_wmi {
int kbd_led_wk;
struct led_classdev lightbar_led;
int lightbar_led_wk;
struct led_classdev micmute_led;
struct workqueue_struct *led_workqueue;
struct work_struct tpd_led_work;
struct work_struct wlan_led_work;
......@@ -1028,12 +1029,23 @@ static enum led_brightness lightbar_led_get(struct led_classdev *led_cdev)
return result & ASUS_WMI_DSTS_LIGHTBAR_MASK;
}
static int micmute_led_set(struct led_classdev *led_cdev,
enum led_brightness brightness)
{
int state = brightness != LED_OFF;
int err;
err = asus_wmi_set_devstate(ASUS_WMI_DEVID_MICMUTE_LED, state, NULL);
return err < 0 ? err : 0;
}
static void asus_wmi_led_exit(struct asus_wmi *asus)
{
led_classdev_unregister(&asus->kbd_led);
led_classdev_unregister(&asus->tpd_led);
led_classdev_unregister(&asus->wlan_led);
led_classdev_unregister(&asus->lightbar_led);
led_classdev_unregister(&asus->micmute_led);
if (asus->led_workqueue)
destroy_workqueue(asus->led_workqueue);
......@@ -1105,6 +1117,19 @@ static int asus_wmi_led_init(struct asus_wmi *asus)
&asus->lightbar_led);
}
if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_MICMUTE_LED)) {
asus->micmute_led.name = "asus::micmute";
asus->micmute_led.max_brightness = 1;
asus->micmute_led.brightness = ledtrig_audio_get(LED_AUDIO_MICMUTE);
asus->micmute_led.brightness_set_blocking = micmute_led_set;
asus->micmute_led.default_trigger = "audio-micmute";
rv = led_classdev_register(&asus->platform_device->dev,
&asus->micmute_led);
if (rv)
goto error;
}
error:
if (rv)
asus_wmi_led_exit(asus);
......
......@@ -324,9 +324,7 @@ static int bl_update_status(struct backlight_device *b)
if (ret)
return ret;
set_backlight_state((b->props.power == FB_BLANK_UNBLANK)
&& !(b->props.state & BL_CORE_SUSPENDED)
&& !(b->props.state & BL_CORE_FBBLANK));
set_backlight_state(!backlight_is_blank(b));
return 0;
}
......
......@@ -5,7 +5,6 @@
menuconfig X86_PLATFORM_DRIVERS_DELL
bool "Dell X86 Platform Specific Device Drivers"
depends on X86_PLATFORM_DEVICES
help
Say Y here to get to see options for device drivers for various
Dell x86 platforms, including vendor-specific laptop extension drivers.
......
......@@ -20,25 +20,16 @@
#define PMT_XA_MAX INT_MAX
#define PMT_XA_LIMIT XA_LIMIT(PMT_XA_START, PMT_XA_MAX)
/*
* Early implementations of PMT on client platforms have some
* differences from the server platforms (which use the Out Of Band
* Management Services Module OOBMSM). This list tracks those
* platforms as needed to handle those differences. Newer client
* platforms are expected to be fully compatible with server.
*/
static const struct pci_device_id pmt_telem_early_client_pci_ids[] = {
{ PCI_VDEVICE(INTEL, 0x467d) }, /* ADL */
{ PCI_VDEVICE(INTEL, 0x490e) }, /* DG1 */
{ PCI_VDEVICE(INTEL, 0x9a0d) }, /* TGL */
{ }
};
bool intel_pmt_is_early_client_hw(struct device *dev)
{
struct pci_dev *parent = to_pci_dev(dev->parent);
struct intel_vsec_device *ivdev = dev_to_ivdev(dev);
return !!pci_match_id(pmt_telem_early_client_pci_ids, parent);
/*
* Early implementations of PMT on client platforms have some
* differences from the server platforms (which use the Out Of Band
* Management Services Module OOBMSM).
*/
return !!(ivdev->info->quirks & VSEC_QUIRK_EARLY_HW);
}
EXPORT_SYMBOL_GPL(intel_pmt_is_early_client_hw);
......
......@@ -23,12 +23,19 @@
#define TELEM_GUID_OFFSET 0x4
#define TELEM_BASE_OFFSET 0x8
#define TELEM_ACCESS(v) ((v) & GENMASK(3, 0))
#define TELEM_TYPE(v) (((v) & GENMASK(7, 4)) >> 4)
/* size is in bytes */
#define TELEM_SIZE(v) (((v) & GENMASK(27, 12)) >> 10)
/* Used by client hardware to identify a fixed telemetry entry*/
#define TELEM_CLIENT_FIXED_BLOCK_GUID 0x10000000
enum telem_type {
TELEM_TYPE_PUNIT = 0,
TELEM_TYPE_CRASHLOG,
TELEM_TYPE_PUNIT_FIXED,
};
struct pmt_telem_priv {
int num_entries;
struct intel_pmt_entry entry[];
......@@ -39,10 +46,15 @@ static bool pmt_telem_region_overlaps(struct intel_pmt_entry *entry,
{
u32 guid = readl(entry->disc_table + TELEM_GUID_OFFSET);
if (guid != TELEM_CLIENT_FIXED_BLOCK_GUID)
return false;
if (intel_pmt_is_early_client_hw(dev)) {
u32 type = TELEM_TYPE(readl(entry->disc_table));
if ((type == TELEM_TYPE_PUNIT_FIXED) ||
(guid == TELEM_CLIENT_FIXED_BLOCK_GUID))
return true;
}
return intel_pmt_is_early_client_hw(dev);
return false;
}
static int pmt_telem_header_decode(struct intel_pmt_entry *entry,
......
......@@ -277,29 +277,38 @@ static int isst_if_get_platform_info(void __user *argp)
return 0;
}
#define ISST_MAX_BUS_NUMBER 2
struct isst_if_cpu_info {
/* For BUS 0 and BUS 1 only, which we need for PUNIT interface */
int bus_info[2];
struct pci_dev *pci_dev[2];
int bus_info[ISST_MAX_BUS_NUMBER];
struct pci_dev *pci_dev[ISST_MAX_BUS_NUMBER];
int punit_cpu_id;
int numa_node;
};
struct isst_if_pkg_info {
struct pci_dev *pci_dev[ISST_MAX_BUS_NUMBER];
};
static struct isst_if_cpu_info *isst_cpu_info;
static struct isst_if_pkg_info *isst_pkg_info;
#define ISST_MAX_PCI_DOMAINS 8
static struct pci_dev *_isst_if_get_pci_dev(int cpu, int bus_no, int dev, int fn)
{
struct pci_dev *matched_pci_dev = NULL;
struct pci_dev *pci_dev = NULL;
int no_matches = 0;
int no_matches = 0, pkg_id;
int i, bus_number;
if (bus_no < 0 || bus_no > 1 || cpu < 0 || cpu >= nr_cpu_ids ||
cpu >= num_possible_cpus())
if (bus_no < 0 || bus_no >= ISST_MAX_BUS_NUMBER || cpu < 0 ||
cpu >= nr_cpu_ids || cpu >= num_possible_cpus())
return NULL;
pkg_id = topology_physical_package_id(cpu);
bus_number = isst_cpu_info[cpu].bus_info[bus_no];
if (bus_number < 0)
return NULL;
......@@ -324,6 +333,8 @@ static struct pci_dev *_isst_if_get_pci_dev(int cpu, int bus_no, int dev, int fn
}
if (node == isst_cpu_info[cpu].numa_node) {
isst_pkg_info[pkg_id].pci_dev[bus_no] = _pci_dev;
pci_dev = _pci_dev;
break;
}
......@@ -342,6 +353,10 @@ static struct pci_dev *_isst_if_get_pci_dev(int cpu, int bus_no, int dev, int fn
if (!pci_dev && no_matches == 1)
pci_dev = matched_pci_dev;
/* Return pci_dev pointer for any matched CPU in the package */
if (!pci_dev)
pci_dev = isst_pkg_info[pkg_id].pci_dev[bus_no];
return pci_dev;
}
......@@ -361,8 +376,8 @@ struct pci_dev *isst_if_get_pci_dev(int cpu, int bus_no, int dev, int fn)
{
struct pci_dev *pci_dev;
if (bus_no < 0 || bus_no > 1 || cpu < 0 || cpu >= nr_cpu_ids ||
cpu >= num_possible_cpus())
if (bus_no < 0 || bus_no >= ISST_MAX_BUS_NUMBER || cpu < 0 ||
cpu >= nr_cpu_ids || cpu >= num_possible_cpus())
return NULL;
pci_dev = isst_cpu_info[cpu].pci_dev[bus_no];
......@@ -417,10 +432,19 @@ static int isst_if_cpu_info_init(void)
if (!isst_cpu_info)
return -ENOMEM;
isst_pkg_info = kcalloc(topology_max_packages(),
sizeof(*isst_pkg_info),
GFP_KERNEL);
if (!isst_pkg_info) {
kfree(isst_cpu_info);
return -ENOMEM;
}
ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN,
"platform/x86/isst-if:online",
isst_if_cpu_online, NULL);
if (ret < 0) {
kfree(isst_pkg_info);
kfree(isst_cpu_info);
return ret;
}
......@@ -433,6 +457,7 @@ static int isst_if_cpu_info_init(void)
static void isst_if_cpu_info_exit(void)
{
cpuhp_remove_state(isst_if_online_id);
kfree(isst_pkg_info);
kfree(isst_cpu_info);
};
......
......@@ -15,6 +15,7 @@
#include <linux/auxiliary_bus.h>
#include <linux/bits.h>
#include <linux/delay.h>
#include <linux/kernel.h>
#include <linux/idr.h>
#include <linux/module.h>
......@@ -30,9 +31,13 @@
#define INTEL_DVSEC_TABLE_BAR(x) ((x) & GENMASK(2, 0))
#define INTEL_DVSEC_TABLE_OFFSET(x) ((x) & GENMASK(31, 3))
#define TABLE_OFFSET_SHIFT 3
#define PMT_XA_START 0
#define PMT_XA_MAX INT_MAX
#define PMT_XA_LIMIT XA_LIMIT(PMT_XA_START, PMT_XA_MAX)
static DEFINE_IDA(intel_vsec_ida);
static DEFINE_IDA(intel_vsec_sdsi_ida);
static DEFINE_XARRAY_ALLOC(auxdev_array);
/**
* struct intel_vsec_header - Common fields of Intel VSEC and DVSEC registers.
......@@ -54,12 +59,6 @@ struct intel_vsec_header {
u32 offset;
};
/* Platform specific data */
struct intel_vsec_platform_info {
struct intel_vsec_header **capabilities;
unsigned long quirks;
};
enum intel_vsec_id {
VSEC_ID_TELEMETRY = 2,
VSEC_ID_WATCHER = 3,
......@@ -138,7 +137,7 @@ static int intel_vsec_add_aux(struct pci_dev *pdev, struct intel_vsec_device *in
const char *name)
{
struct auxiliary_device *auxdev = &intel_vsec_dev->auxdev;
int ret;
int ret, id;
ret = ida_alloc(intel_vsec_dev->ida, GFP_KERNEL);
if (ret < 0) {
......@@ -165,14 +164,26 @@ static int intel_vsec_add_aux(struct pci_dev *pdev, struct intel_vsec_device *in
return ret;
}
return devm_add_action_or_reset(&pdev->dev, intel_vsec_remove_aux, auxdev);
ret = devm_add_action_or_reset(&pdev->dev, intel_vsec_remove_aux,
auxdev);
if (ret < 0)
return ret;
/* Add auxdev to list */
ret = xa_alloc(&auxdev_array, &id, intel_vsec_dev, PMT_XA_LIMIT,
GFP_KERNEL);
if (ret)
return ret;
return 0;
}
static int intel_vsec_add_dev(struct pci_dev *pdev, struct intel_vsec_header *header,
unsigned long quirks)
struct intel_vsec_platform_info *info)
{
struct intel_vsec_device *intel_vsec_dev;
struct resource *res, *tmp;
unsigned long quirks = info->quirks;
int i;
if (!intel_vsec_allowed(header->id) || intel_vsec_disabled(header->id, quirks))
......@@ -216,7 +227,7 @@ static int intel_vsec_add_dev(struct pci_dev *pdev, struct intel_vsec_header *he
intel_vsec_dev->pcidev = pdev;
intel_vsec_dev->resource = res;
intel_vsec_dev->num_resources = header->num_entries;
intel_vsec_dev->quirks = quirks;
intel_vsec_dev->info = info;
if (header->id == VSEC_ID_SDSI)
intel_vsec_dev->ida = &intel_vsec_sdsi_ida;
......@@ -226,14 +237,15 @@ static int intel_vsec_add_dev(struct pci_dev *pdev, struct intel_vsec_header *he
return intel_vsec_add_aux(pdev, intel_vsec_dev, intel_vsec_name(header->id));
}
static bool intel_vsec_walk_header(struct pci_dev *pdev, unsigned long quirks,
struct intel_vsec_header **header)
static bool intel_vsec_walk_header(struct pci_dev *pdev,
struct intel_vsec_platform_info *info)
{
struct intel_vsec_header **header = info->capabilities;
bool have_devices = false;
int ret;
for ( ; *header; header++) {
ret = intel_vsec_add_dev(pdev, *header, quirks);
ret = intel_vsec_add_dev(pdev, *header, info);
if (ret)
dev_info(&pdev->dev, "Could not add device for DVSEC id %d\n",
(*header)->id);
......@@ -244,7 +256,8 @@ static bool intel_vsec_walk_header(struct pci_dev *pdev, unsigned long quirks,
return have_devices;
}
static bool intel_vsec_walk_dvsec(struct pci_dev *pdev, unsigned long quirks)
static bool intel_vsec_walk_dvsec(struct pci_dev *pdev,
struct intel_vsec_platform_info *info)
{
bool have_devices = false;
int pos = 0;
......@@ -283,7 +296,7 @@ static bool intel_vsec_walk_dvsec(struct pci_dev *pdev, unsigned long quirks)
pci_read_config_dword(pdev, pos + PCI_DVSEC_HEADER2, &hdr);
header.id = PCI_DVSEC_HEADER2_ID(hdr);
ret = intel_vsec_add_dev(pdev, &header, quirks);
ret = intel_vsec_add_dev(pdev, &header, info);
if (ret)
continue;
......@@ -293,7 +306,8 @@ static bool intel_vsec_walk_dvsec(struct pci_dev *pdev, unsigned long quirks)
return have_devices;
}
static bool intel_vsec_walk_vsec(struct pci_dev *pdev, unsigned long quirks)
static bool intel_vsec_walk_vsec(struct pci_dev *pdev,
struct intel_vsec_platform_info *info)
{
bool have_devices = false;
int pos = 0;
......@@ -327,7 +341,7 @@ static bool intel_vsec_walk_vsec(struct pci_dev *pdev, unsigned long quirks)
header.tbir = INTEL_DVSEC_TABLE_BAR(table);
header.offset = INTEL_DVSEC_TABLE_OFFSET(table);
ret = intel_vsec_add_dev(pdev, &header, quirks);
ret = intel_vsec_add_dev(pdev, &header, info);
if (ret)
continue;
......@@ -341,25 +355,25 @@ static int intel_vsec_pci_probe(struct pci_dev *pdev, const struct pci_device_id
{
struct intel_vsec_platform_info *info;
bool have_devices = false;
unsigned long quirks = 0;
int ret;
ret = pcim_enable_device(pdev);
if (ret)
return ret;
pci_save_state(pdev);
info = (struct intel_vsec_platform_info *)id->driver_data;
if (info)
quirks = info->quirks;
if (!info)
return -EINVAL;
if (intel_vsec_walk_dvsec(pdev, quirks))
if (intel_vsec_walk_dvsec(pdev, info))
have_devices = true;
if (intel_vsec_walk_vsec(pdev, quirks))
if (intel_vsec_walk_vsec(pdev, info))
have_devices = true;
if (info && (info->quirks & VSEC_QUIRK_NO_DVSEC) &&
intel_vsec_walk_header(pdev, quirks, info->capabilities))
intel_vsec_walk_header(pdev, info))
have_devices = true;
if (!have_devices)
......@@ -370,7 +384,8 @@ static int intel_vsec_pci_probe(struct pci_dev *pdev, const struct pci_device_id
/* TGL info */
static const struct intel_vsec_platform_info tgl_info = {
.quirks = VSEC_QUIRK_NO_WATCHER | VSEC_QUIRK_NO_CRASHLOG | VSEC_QUIRK_TABLE_SHIFT,
.quirks = VSEC_QUIRK_NO_WATCHER | VSEC_QUIRK_NO_CRASHLOG |
VSEC_QUIRK_TABLE_SHIFT | VSEC_QUIRK_EARLY_HW,
};
/* DG1 info */
......@@ -390,26 +405,89 @@ static struct intel_vsec_header *dg1_capabilities[] = {
static const struct intel_vsec_platform_info dg1_info = {
.capabilities = dg1_capabilities,
.quirks = VSEC_QUIRK_NO_DVSEC,
.quirks = VSEC_QUIRK_NO_DVSEC | VSEC_QUIRK_EARLY_HW,
};
#define PCI_DEVICE_ID_INTEL_VSEC_ADL 0x467d
#define PCI_DEVICE_ID_INTEL_VSEC_DG1 0x490e
#define PCI_DEVICE_ID_INTEL_VSEC_OOBMSM 0x09a7
#define PCI_DEVICE_ID_INTEL_VSEC_RPL 0xa77d
#define PCI_DEVICE_ID_INTEL_VSEC_TGL 0x9a0d
static const struct pci_device_id intel_vsec_pci_ids[] = {
{ PCI_DEVICE_DATA(INTEL, VSEC_ADL, &tgl_info) },
{ PCI_DEVICE_DATA(INTEL, VSEC_DG1, &dg1_info) },
{ PCI_DEVICE_DATA(INTEL, VSEC_OOBMSM, NULL) },
{ PCI_DEVICE_DATA(INTEL, VSEC_OOBMSM, &(struct intel_vsec_platform_info) {}) },
{ PCI_DEVICE_DATA(INTEL, VSEC_RPL, &tgl_info) },
{ PCI_DEVICE_DATA(INTEL, VSEC_TGL, &tgl_info) },
{ }
};
MODULE_DEVICE_TABLE(pci, intel_vsec_pci_ids);
static pci_ers_result_t intel_vsec_pci_error_detected(struct pci_dev *pdev,
pci_channel_state_t state)
{
pci_ers_result_t status = PCI_ERS_RESULT_NEED_RESET;
dev_info(&pdev->dev, "PCI error detected, state %d", state);
if (state == pci_channel_io_perm_failure)
status = PCI_ERS_RESULT_DISCONNECT;
else
pci_disable_device(pdev);
return status;
}
static pci_ers_result_t intel_vsec_pci_slot_reset(struct pci_dev *pdev)
{
struct intel_vsec_device *intel_vsec_dev;
pci_ers_result_t status = PCI_ERS_RESULT_DISCONNECT;
const struct pci_device_id *pci_dev_id;
unsigned long index;
dev_info(&pdev->dev, "Resetting PCI slot\n");
msleep(2000);
if (pci_enable_device(pdev)) {
dev_info(&pdev->dev,
"Failed to re-enable PCI device after reset.\n");
goto out;
}
status = PCI_ERS_RESULT_RECOVERED;
xa_for_each(&auxdev_array, index, intel_vsec_dev) {
/* check if pdev doesn't match */
if (pdev != intel_vsec_dev->pcidev)
continue;
devm_release_action(&pdev->dev, intel_vsec_remove_aux,
&intel_vsec_dev->auxdev);
}
pci_disable_device(pdev);
pci_restore_state(pdev);
pci_dev_id = pci_match_id(intel_vsec_pci_ids, pdev);
intel_vsec_pci_probe(pdev, pci_dev_id);
out:
return status;
}
static void intel_vsec_pci_resume(struct pci_dev *pdev)
{
dev_info(&pdev->dev, "Done resuming PCI device\n");
}
static const struct pci_error_handlers intel_vsec_pci_err_handlers = {
.error_detected = intel_vsec_pci_error_detected,
.slot_reset = intel_vsec_pci_slot_reset,
.resume = intel_vsec_pci_resume,
};
static struct pci_driver intel_vsec_pci_driver = {
.name = "intel_vsec",
.id_table = intel_vsec_pci_ids,
.probe = intel_vsec_pci_probe,
.err_handler = &intel_vsec_pci_err_handlers,
};
module_pci_driver(intel_vsec_pci_driver);
......
......@@ -20,6 +20,15 @@ enum intel_vsec_quirks {
/* DVSEC not present (provided in driver data) */
VSEC_QUIRK_NO_DVSEC = BIT(3),
/* Platforms requiring quirk in the auxiliary driver */
VSEC_QUIRK_EARLY_HW = BIT(4),
};
/* Platform specific data */
struct intel_vsec_platform_info {
struct intel_vsec_header **capabilities;
unsigned long quirks;
};
struct intel_vsec_device {
......@@ -27,7 +36,7 @@ struct intel_vsec_device {
struct pci_dev *pcidev;
struct resource *resource;
struct ida *ida;
unsigned long quirks;
struct intel_vsec_platform_info *info;
int num_resources;
};
......
This diff is collapsed.
// SPDX-License-Identifier: GPL-2.0
/*
* Primary to Sideband (P2SB) bridge access support
*
* Copyright (c) 2017, 2021-2022 Intel Corporation.
*
* Authors: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
* Jonathan Yong <jonathan.yong@intel.com>
*/
#include <linux/bits.h>
#include <linux/export.h>
#include <linux/pci.h>
#include <linux/platform_data/x86/p2sb.h>
#include <asm/cpu_device_id.h>
#include <asm/intel-family.h>
#define P2SBC 0xe0
#define P2SBC_HIDE BIT(8)
static const struct x86_cpu_id p2sb_cpu_ids[] = {
X86_MATCH_INTEL_FAM6_MODEL(ATOM_GOLDMONT, PCI_DEVFN(13, 0)),
X86_MATCH_INTEL_FAM6_MODEL(ATOM_GOLDMONT_D, PCI_DEVFN(31, 1)),
X86_MATCH_INTEL_FAM6_MODEL(ATOM_SILVERMONT_D, PCI_DEVFN(31, 1)),
X86_MATCH_INTEL_FAM6_MODEL(KABYLAKE, PCI_DEVFN(31, 1)),
X86_MATCH_INTEL_FAM6_MODEL(KABYLAKE_L, PCI_DEVFN(31, 1)),
X86_MATCH_INTEL_FAM6_MODEL(SKYLAKE, PCI_DEVFN(31, 1)),
X86_MATCH_INTEL_FAM6_MODEL(SKYLAKE_L, PCI_DEVFN(31, 1)),
{}
};
static int p2sb_get_devfn(unsigned int *devfn)
{
const struct x86_cpu_id *id;
id = x86_match_cpu(p2sb_cpu_ids);
if (!id)
return -ENODEV;
*devfn = (unsigned int)id->driver_data;
return 0;
}
static int p2sb_read_bar0(struct pci_dev *pdev, struct resource *mem)
{
/* Copy resource from the first BAR of the device in question */
*mem = pdev->resource[0];
return 0;
}
static int p2sb_scan_and_read(struct pci_bus *bus, unsigned int devfn, struct resource *mem)
{
struct pci_dev *pdev;
int ret;
pdev = pci_scan_single_device(bus, devfn);
if (!pdev)
return -ENODEV;
ret = p2sb_read_bar0(pdev, mem);
pci_stop_and_remove_bus_device(pdev);
return ret;
}
/**
* p2sb_bar - Get Primary to Sideband (P2SB) bridge device BAR
* @bus: PCI bus to communicate with
* @devfn: PCI slot and function to communicate with
* @mem: memory resource to be filled in
*
* The BIOS prevents the P2SB device from being enumerated by the PCI
* subsystem, so we need to unhide and hide it back to lookup the BAR.
*
* if @bus is NULL, the bus 0 in domain 0 will be used.
* If @devfn is 0, it will be replaced by devfn of the P2SB device.
*
* Caller must provide a valid pointer to @mem.
*
* Locking is handled by pci_rescan_remove_lock mutex.
*
* Return:
* 0 on success or appropriate errno value on error.
*/
int p2sb_bar(struct pci_bus *bus, unsigned int devfn, struct resource *mem)
{
struct pci_dev *pdev_p2sb;
unsigned int devfn_p2sb;
u32 value = P2SBC_HIDE;
int ret;
/* Get devfn for P2SB device itself */
ret = p2sb_get_devfn(&devfn_p2sb);
if (ret)
return ret;
/* if @bus is NULL, use bus 0 in domain 0 */
bus = bus ?: pci_find_bus(0, 0);
/*
* Prevent concurrent PCI bus scan from seeing the P2SB device and
* removing via sysfs while it is temporarily exposed.
*/
pci_lock_rescan_remove();
/* Unhide the P2SB device, if needed */
pci_bus_read_config_dword(bus, devfn_p2sb, P2SBC, &value);
if (value & P2SBC_HIDE)
pci_bus_write_config_dword(bus, devfn_p2sb, P2SBC, 0);
pdev_p2sb = pci_scan_single_device(bus, devfn_p2sb);
if (devfn)
ret = p2sb_scan_and_read(bus, devfn, mem);
else
ret = p2sb_read_bar0(pdev_p2sb, mem);
pci_stop_and_remove_bus_device(pdev_p2sb);
/* Hide the P2SB device, if it was hidden */
if (value & P2SBC_HIDE)
pci_bus_write_config_dword(bus, devfn_p2sb, P2SBC, P2SBC_HIDE);
pci_unlock_rescan_remove();
if (ret)
return ret;
if (mem->flags == 0)
return -ENODEV;
return 0;
}
EXPORT_SYMBOL_GPL(p2sb_bar);
......@@ -998,19 +998,23 @@ static int acpi_pcc_hotkey_add(struct acpi_device *device)
pr_err("Couldn't retrieve BIOS data\n");
goto out_input;
}
/* initialize backlight */
memset(&props, 0, sizeof(struct backlight_properties));
props.type = BACKLIGHT_PLATFORM;
props.max_brightness = pcc->sinf[SINF_AC_MAX_BRIGHT];
pcc->backlight = backlight_device_register("panasonic", NULL, pcc,
&pcc_backlight_ops, &props);
if (IS_ERR(pcc->backlight)) {
result = PTR_ERR(pcc->backlight);
goto out_input;
}
/* read the initial brightness setting from the hardware */
pcc->backlight->props.brightness = pcc->sinf[SINF_AC_CUR_BRIGHT];
if (acpi_video_get_backlight_type() == acpi_backlight_vendor) {
/* initialize backlight */
memset(&props, 0, sizeof(struct backlight_properties));
props.type = BACKLIGHT_PLATFORM;
props.max_brightness = pcc->sinf[SINF_AC_MAX_BRIGHT];
pcc->backlight = backlight_device_register("panasonic", NULL, pcc,
&pcc_backlight_ops, &props);
if (IS_ERR(pcc->backlight)) {
result = PTR_ERR(pcc->backlight);
goto out_input;
}
/* read the initial brightness setting from the hardware */
pcc->backlight->props.brightness = pcc->sinf[SINF_AC_CUR_BRIGHT];
}
/* Reset initial sticky key mode since the hardware register state is not consistent */
acpi_pcc_write_sset(pcc, SINF_STICKY_KEY, 0);
......
......@@ -389,21 +389,16 @@ static const struct dmi_system_id critclk_systems[] = {
},
},
{
/* pmc_plt_clk0 - 3 are used for the 4 ethernet controllers */
.ident = "Lex 3I380D",
/*
* Lex System / Lex Computech Co. makes a lot of Bay Trail
* based embedded boards which often come with multiple
* ethernet controllers using multiple pmc_plt_clks. See:
* https://www.lex.com.tw/products/embedded-ipc-board/
*/
.ident = "Lex BayTrail",
.callback = dmi_callback,
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Lex BayTrail"),
DMI_MATCH(DMI_PRODUCT_NAME, "3I380D"),
},
},
{
/* pmc_plt_clk* - are used for ethernet controllers */
.ident = "Lex 2I385SW",
.callback = dmi_callback,
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Lex BayTrail"),
DMI_MATCH(DMI_PRODUCT_NAME, "2I385SW"),
},
},
{
......
This diff is collapsed.
......@@ -4341,7 +4341,7 @@ sony_pic_read_possible_resource(struct acpi_resource *resource, void *context)
{
struct acpi_resource_irq *p = &resource->data.irq;
struct sony_pic_irq *interrupt = NULL;
if (!p || !p->interrupt_count) {
if (!p->interrupt_count) {
/*
* IRQ descriptors may have no IRQ# bits set,
* particularly those those w/ _STA disabled
......@@ -4374,11 +4374,6 @@ sony_pic_read_possible_resource(struct acpi_resource *resource, void *context)
struct acpi_resource_io *io = &resource->data.io;
struct sony_pic_ioport *ioport =
list_first_entry(&dev->ioports, struct sony_pic_ioport, list);
if (!io) {
dprintk("Blank IO resource\n");
return AE_OK;
}
if (!ioport->io1.minimum) {
memcpy(&ioport->io1, io, sizeof(*io));
dprintk("IO1 at 0x%.4x (0x%.2x)\n", ioport->io1.minimum,
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
......@@ -1647,6 +1647,7 @@ config SIEMENS_SIMATIC_IPC_WDT
tristate "Siemens Simatic IPC Watchdog"
depends on SIEMENS_SIMATIC_IPC
select WATCHDOG_CORE
select P2SB
help
This driver adds support for several watchdogs found in Industrial
PCs from Siemens.
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment