From f22558d701755f2f9cfdbd4a0862a58ef5fb1dbb Mon Sep 17 00:00:00 2001 From: Lubomir Rintel Date: Mon, 13 May 2019 09:56:32 +0200 Subject: [PATCH 01/74] dt-bindings: olpc,xo1.75-ec: Add OLPC XO-1.75 EC bindings The OLPC XO-1.75 Embedded Controller is a SPI master that uses extra signals for handshaking. It needs to know when is the slave (Linux) side's TX FIFO ready for transfer (the ready-gpio signal on the SPI controller node) and when does it wish to respond with a command (the cmd-gpio property). Signed-off-by: Lubomir Rintel Acked-by: Pavel Machek Reviewed-by: Rob Herring Signed-off-by: Andy Shevchenko --- .../bindings/misc/olpc,xo1.75-ec.txt | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 Documentation/devicetree/bindings/misc/olpc,xo1.75-ec.txt diff --git a/Documentation/devicetree/bindings/misc/olpc,xo1.75-ec.txt b/Documentation/devicetree/bindings/misc/olpc,xo1.75-ec.txt new file mode 100644 index 000000000000..8c4d649cdd8f --- /dev/null +++ b/Documentation/devicetree/bindings/misc/olpc,xo1.75-ec.txt @@ -0,0 +1,23 @@ +OLPC XO-1.75 Embedded Controller + +Required properties: +- compatible: Should be "olpc,xo1.75-ec". +- cmd-gpios: gpio specifier of the CMD pin + +The embedded controller requires the SPI controller driver to signal readiness +to receive a transfer (that is, when TX FIFO contains the response data) by +strobing the ACK pin with the ready signal. See the "ready-gpios" property of the +SSP binding as documented in: +. + +Example: + &ssp3 { + spi-slave; + ready-gpios = <&gpio 125 GPIO_ACTIVE_HIGH>; + + slave { + compatible = "olpc,xo1.75-ec"; + spi-cpha; + cmd-gpios = <&gpio 155 GPIO_ACTIVE_HIGH>; + }; + }; From 2dc78630497780b36975e5bcb69bbbf4d2ee0e46 Mon Sep 17 00:00:00 2001 From: Lubomir Rintel Date: Mon, 13 May 2019 09:56:33 +0200 Subject: [PATCH 02/74] Platform: OLPC: Remove an unused include Also, the header is x86 specific, while there are non-x86 OLPC machines. Signed-off-by: Lubomir Rintel Acked-by: Pavel Machek Signed-off-by: Andy Shevchenko --- drivers/platform/olpc/olpc-ec.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/platform/olpc/olpc-ec.c b/drivers/platform/olpc/olpc-ec.c index 374a8028fec7..981955dce926 100644 --- a/drivers/platform/olpc/olpc-ec.c +++ b/drivers/platform/olpc/olpc-ec.c @@ -17,7 +17,6 @@ #include #include #include -#include struct ec_cmd_desc { u8 cmd; From ec9964b4803300fb86f8e8fd9b421e59f7a71dc5 Mon Sep 17 00:00:00 2001 From: Lubomir Rintel Date: Mon, 13 May 2019 09:56:34 +0200 Subject: [PATCH 03/74] Platform: OLPC: Move EC-specific functionality out from x86 Move the olpc-ec driver away from the X86 OLPC platform so that it could be used by the ARM based laptops too. Notably, the driver for the OLPC battery, which is also used on the ARM models, builds on this driver's interface. It is actually plaform independent: the OLPC EC commands with their argument and responses are mostly the same despite the delivery mechanism is different. Signed-off-by: Lubomir Rintel Acked-by: Pavel Machek Signed-off-by: Andy Shevchenko --- arch/x86/include/asm/olpc.h | 31 -------- arch/x86/platform/olpc/olpc.c | 119 +++++----------------------- drivers/platform/olpc/olpc-ec.c | 99 ++++++++++++++++++++++- drivers/power/supply/olpc_battery.c | 1 - include/linux/olpc-ec.h | 32 +++++++- 5 files changed, 145 insertions(+), 137 deletions(-) diff --git a/arch/x86/include/asm/olpc.h b/arch/x86/include/asm/olpc.h index c2bf1de5d901..6fe76282aceb 100644 --- a/arch/x86/include/asm/olpc.h +++ b/arch/x86/include/asm/olpc.h @@ -9,12 +9,10 @@ struct olpc_platform_t { int flags; uint32_t boardrev; - int ecver; }; #define OLPC_F_PRESENT 0x01 #define OLPC_F_DCON 0x02 -#define OLPC_F_EC_WIDE_SCI 0x04 #ifdef CONFIG_OLPC @@ -64,13 +62,6 @@ static inline int olpc_board_at_least(uint32_t rev) return olpc_platform_info.boardrev >= rev; } -extern void olpc_ec_wakeup_set(u16 value); -extern void olpc_ec_wakeup_clear(u16 value); -extern bool olpc_ec_wakeup_available(void); - -extern int olpc_ec_mask_write(u16 bits); -extern int olpc_ec_sci_query(u16 *sci_value); - #else static inline int machine_is_olpc(void) @@ -83,14 +74,6 @@ static inline int olpc_has_dcon(void) return 0; } -static inline void olpc_ec_wakeup_set(u16 value) { } -static inline void olpc_ec_wakeup_clear(u16 value) { } - -static inline bool olpc_ec_wakeup_available(void) -{ - return false; -} - #endif #ifdef CONFIG_OLPC_XO1_PM @@ -101,20 +84,6 @@ extern void olpc_xo1_pm_wakeup_clear(u16 value); extern int pci_olpc_init(void); -/* SCI source values */ - -#define EC_SCI_SRC_EMPTY 0x00 -#define EC_SCI_SRC_GAME 0x01 -#define EC_SCI_SRC_BATTERY 0x02 -#define EC_SCI_SRC_BATSOC 0x04 -#define EC_SCI_SRC_BATERR 0x08 -#define EC_SCI_SRC_EBOOK 0x10 /* XO-1 only */ -#define EC_SCI_SRC_WLAN 0x20 /* XO-1 only */ -#define EC_SCI_SRC_ACPWR 0x40 -#define EC_SCI_SRC_BATCRIT 0x80 -#define EC_SCI_SRC_GPWAKE 0x100 /* XO-1.5 only */ -#define EC_SCI_SRC_ALL 0x1FF - /* GPIO assignments */ #define OLPC_GPIO_MIC_AC 1 diff --git a/arch/x86/platform/olpc/olpc.c b/arch/x86/platform/olpc/olpc.c index f0e920fb98ad..c6c62b4f251f 100644 --- a/arch/x86/platform/olpc/olpc.c +++ b/arch/x86/platform/olpc/olpc.c @@ -30,9 +30,6 @@ struct olpc_platform_t olpc_platform_info; EXPORT_SYMBOL_GPL(olpc_platform_info); -/* EC event mask to be applied during suspend (defining wakeup sources). */ -static u16 ec_wakeup_mask; - /* what the timeout *should* be (in ms) */ #define EC_BASE_TIMEOUT 20 @@ -186,83 +183,6 @@ err: return ret; } -void olpc_ec_wakeup_set(u16 value) -{ - ec_wakeup_mask |= value; -} -EXPORT_SYMBOL_GPL(olpc_ec_wakeup_set); - -void olpc_ec_wakeup_clear(u16 value) -{ - ec_wakeup_mask &= ~value; -} -EXPORT_SYMBOL_GPL(olpc_ec_wakeup_clear); - -/* - * Returns true if the compile and runtime configurations allow for EC events - * to wake the system. - */ -bool olpc_ec_wakeup_available(void) -{ - if (!machine_is_olpc()) - return false; - - /* - * XO-1 EC wakeups are available when olpc-xo1-sci driver is - * compiled in - */ -#ifdef CONFIG_OLPC_XO1_SCI - if (olpc_platform_info.boardrev < olpc_board_pre(0xd0)) /* XO-1 */ - return true; -#endif - - /* - * XO-1.5 EC wakeups are available when olpc-xo15-sci driver is - * compiled in - */ -#ifdef CONFIG_OLPC_XO15_SCI - if (olpc_platform_info.boardrev >= olpc_board_pre(0xd0)) /* XO-1.5 */ - return true; -#endif - - return false; -} -EXPORT_SYMBOL_GPL(olpc_ec_wakeup_available); - -int olpc_ec_mask_write(u16 bits) -{ - if (olpc_platform_info.flags & OLPC_F_EC_WIDE_SCI) { - __be16 ec_word = cpu_to_be16(bits); - return olpc_ec_cmd(EC_WRITE_EXT_SCI_MASK, (void *) &ec_word, 2, - NULL, 0); - } else { - unsigned char ec_byte = bits & 0xff; - return olpc_ec_cmd(EC_WRITE_SCI_MASK, &ec_byte, 1, NULL, 0); - } -} -EXPORT_SYMBOL_GPL(olpc_ec_mask_write); - -int olpc_ec_sci_query(u16 *sci_value) -{ - int ret; - - if (olpc_platform_info.flags & OLPC_F_EC_WIDE_SCI) { - __be16 ec_word; - ret = olpc_ec_cmd(EC_EXT_SCI_QUERY, - NULL, 0, (void *) &ec_word, 2); - if (ret == 0) - *sci_value = be16_to_cpu(ec_word); - } else { - unsigned char ec_byte; - ret = olpc_ec_cmd(EC_SCI_QUERY, NULL, 0, &ec_byte, 1); - if (ret == 0) - *sci_value = ec_byte; - } - - return ret; -} -EXPORT_SYMBOL_GPL(olpc_ec_sci_query); - static bool __init check_ofw_architecture(struct device_node *root) { const char *olpc_arch; @@ -296,6 +216,10 @@ static bool __init platform_detect(void) if (success) { olpc_platform_info.boardrev = get_board_revision(root); olpc_platform_info.flags |= OLPC_F_PRESENT; + + pr_info("OLPC board revision %s%X\n", + ((olpc_platform_info.boardrev & 0xf) < 8) ? "pre" : "", + olpc_platform_info.boardrev >> 4); } of_node_put(root); @@ -315,27 +239,8 @@ static int __init add_xo1_platform_devices(void) return PTR_ERR_OR_ZERO(pdev); } -static int olpc_xo1_ec_probe(struct platform_device *pdev) -{ - /* get the EC revision */ - olpc_ec_cmd(EC_FIRMWARE_REV, NULL, 0, - (unsigned char *) &olpc_platform_info.ecver, 1); - - /* EC version 0x5f adds support for wide SCI mask */ - if (olpc_platform_info.ecver >= 0x5f) - olpc_platform_info.flags |= OLPC_F_EC_WIDE_SCI; - - pr_info("OLPC board revision %s%X (EC=%x)\n", - ((olpc_platform_info.boardrev & 0xf) < 8) ? "pre" : "", - olpc_platform_info.boardrev >> 4, - olpc_platform_info.ecver); - - return 0; -} static int olpc_xo1_ec_suspend(struct platform_device *pdev) { - olpc_ec_mask_write(ec_wakeup_mask); - /* * Squelch SCIs while suspended. This is a fix for * . @@ -359,15 +264,27 @@ static int olpc_xo1_ec_resume(struct platform_device *pdev) } static struct olpc_ec_driver ec_xo1_driver = { - .probe = olpc_xo1_ec_probe, .suspend = olpc_xo1_ec_suspend, .resume = olpc_xo1_ec_resume, .ec_cmd = olpc_xo1_ec_cmd, +#ifdef CONFIG_OLPC_XO1_SCI + /* + * XO-1 EC wakeups are available when olpc-xo1-sci driver is + * compiled in + */ + .wakeup_available = true, +#endif }; static struct olpc_ec_driver ec_xo1_5_driver = { - .probe = olpc_xo1_ec_probe, .ec_cmd = olpc_xo1_ec_cmd, +#ifdef CONFIG_OLPC_XO1_5_SCI + /* + * XO-1.5 EC wakeups are available when olpc-xo15-sci driver is + * compiled in + */ + .wakeup_available = true, +#endif }; static int __init olpc_init(void) diff --git a/drivers/platform/olpc/olpc-ec.c b/drivers/platform/olpc/olpc-ec.c index 981955dce926..2a647455a368 100644 --- a/drivers/platform/olpc/olpc-ec.c +++ b/drivers/platform/olpc/olpc-ec.c @@ -32,6 +32,7 @@ struct ec_cmd_desc { struct olpc_ec_priv { struct olpc_ec_driver *drv; + u8 version; struct work_struct worker; struct mutex cmd_lock; @@ -41,6 +42,12 @@ struct olpc_ec_priv { struct dentry *dbgfs_dir; + /* + * EC event mask to be applied during suspend (defining wakeup + * sources). + */ + u16 ec_wakeup_mask; + /* * Running an EC command while suspending means we don't always finish * the command before the machine suspends. This means that the EC @@ -149,6 +156,88 @@ int olpc_ec_cmd(u8 cmd, u8 *inbuf, size_t inlen, u8 *outbuf, size_t outlen) } EXPORT_SYMBOL_GPL(olpc_ec_cmd); +void olpc_ec_wakeup_set(u16 value) +{ + struct olpc_ec_priv *ec = ec_priv; + + if (WARN_ON(!ec)) + return; + + ec->ec_wakeup_mask |= value; +} +EXPORT_SYMBOL_GPL(olpc_ec_wakeup_set); + +void olpc_ec_wakeup_clear(u16 value) +{ + struct olpc_ec_priv *ec = ec_priv; + + if (WARN_ON(!ec)) + return; + + ec->ec_wakeup_mask &= ~value; +} +EXPORT_SYMBOL_GPL(olpc_ec_wakeup_clear); + +int olpc_ec_mask_write(u16 bits) +{ + struct olpc_ec_priv *ec = ec_priv; + + if (WARN_ON(!ec)) + return -ENODEV; + + /* EC version 0x5f adds support for wide SCI mask */ + if (ec->version >= 0x5f) { + __be16 ec_word = cpu_to_be16(bits); + + return olpc_ec_cmd(EC_WRITE_EXT_SCI_MASK, (void *)&ec_word, 2, NULL, 0); + } else { + u8 ec_byte = bits & 0xff; + + return olpc_ec_cmd(EC_WRITE_SCI_MASK, &ec_byte, 1, NULL, 0); + } +} +EXPORT_SYMBOL_GPL(olpc_ec_mask_write); + +/* + * Returns true if the compile and runtime configurations allow for EC events + * to wake the system. + */ +bool olpc_ec_wakeup_available(void) +{ + if (WARN_ON(!ec_driver)) + return false; + + return ec_driver->wakeup_available; +} +EXPORT_SYMBOL_GPL(olpc_ec_wakeup_available); + +int olpc_ec_sci_query(u16 *sci_value) +{ + struct olpc_ec_priv *ec = ec_priv; + int ret; + + if (WARN_ON(!ec)) + return -ENODEV; + + /* EC version 0x5f adds support for wide SCI mask */ + if (ec->version >= 0x5f) { + __be16 ec_word; + + ret = olpc_ec_cmd(EC_EXT_SCI_QUERY, NULL, 0, (void *)&ec_word, 2); + if (ret == 0) + *sci_value = be16_to_cpu(ec_word); + } else { + u8 ec_byte; + + ret = olpc_ec_cmd(EC_SCI_QUERY, NULL, 0, &ec_byte, 1); + if (ret == 0) + *sci_value = ec_byte; + } + + return ret; +} +EXPORT_SYMBOL_GPL(olpc_ec_sci_query); + #ifdef CONFIG_DEBUG_FS /* @@ -276,14 +365,16 @@ static int olpc_ec_probe(struct platform_device *pdev) ec_priv = ec; platform_set_drvdata(pdev, ec); - err = ec_driver->probe ? ec_driver->probe(pdev) : 0; + /* get the EC revision */ + err = olpc_ec_cmd(EC_FIRMWARE_REV, NULL, 0, &ec->version, 1); if (err) { ec_priv = NULL; kfree(ec); - } else { - ec->dbgfs_dir = olpc_ec_setup_debugfs(); + return err; } + ec->dbgfs_dir = olpc_ec_setup_debugfs(); + return err; } @@ -293,6 +384,8 @@ static int olpc_ec_suspend(struct device *dev) struct olpc_ec_priv *ec = platform_get_drvdata(pdev); int err = 0; + olpc_ec_mask_write(ec->ec_wakeup_mask); + if (ec_driver->suspend) err = ec_driver->suspend(pdev); if (!err) diff --git a/drivers/power/supply/olpc_battery.c b/drivers/power/supply/olpc_battery.c index 7720e4c2ac0b..066ec9a11153 100644 --- a/drivers/power/supply/olpc_battery.c +++ b/drivers/power/supply/olpc_battery.c @@ -20,7 +20,6 @@ #include #include #include -#include #define EC_BAT_VOLTAGE 0x10 /* uint16_t, *9.76/32, mV */ diff --git a/include/linux/olpc-ec.h b/include/linux/olpc-ec.h index 79bdc6328c52..7fa3d27f7fee 100644 --- a/include/linux/olpc-ec.h +++ b/include/linux/olpc-ec.h @@ -16,14 +16,28 @@ #define EC_SCI_QUERY 0x84 #define EC_EXT_SCI_QUERY 0x85 +/* SCI source values */ +#define EC_SCI_SRC_EMPTY 0x00 +#define EC_SCI_SRC_GAME 0x01 +#define EC_SCI_SRC_BATTERY 0x02 +#define EC_SCI_SRC_BATSOC 0x04 +#define EC_SCI_SRC_BATERR 0x08 +#define EC_SCI_SRC_EBOOK 0x10 /* XO-1 only */ +#define EC_SCI_SRC_WLAN 0x20 /* XO-1 only */ +#define EC_SCI_SRC_ACPWR 0x40 +#define EC_SCI_SRC_BATCRIT 0x80 +#define EC_SCI_SRC_GPWAKE 0x100 /* XO-1.5 only */ +#define EC_SCI_SRC_ALL 0x1FF + struct platform_device; struct olpc_ec_driver { - int (*probe)(struct platform_device *); int (*suspend)(struct platform_device *); int (*resume)(struct platform_device *); int (*ec_cmd)(u8, u8 *, size_t, u8 *, size_t, void *); + + bool wakeup_available; }; #ifdef CONFIG_OLPC @@ -33,11 +47,27 @@ extern void olpc_ec_driver_register(struct olpc_ec_driver *drv, void *arg); extern int olpc_ec_cmd(u8 cmd, u8 *inbuf, size_t inlen, u8 *outbuf, size_t outlen); +extern void olpc_ec_wakeup_set(u16 value); +extern void olpc_ec_wakeup_clear(u16 value); + +extern int olpc_ec_mask_write(u16 bits); +extern int olpc_ec_sci_query(u16 *sci_value); + +extern bool olpc_ec_wakeup_available(void); + #else static inline int olpc_ec_cmd(u8 cmd, u8 *inbuf, size_t inlen, u8 *outbuf, size_t outlen) { return -ENODEV; } +static inline void olpc_ec_wakeup_set(u16 value) { } +static inline void olpc_ec_wakeup_clear(u16 value) { } + +static inline bool olpc_ec_wakeup_available(void) +{ + return false; +} + #endif /* CONFIG_OLPC */ #endif /* _LINUX_OLPC_EC_H */ From 560331eaee6c552adc1b8201063ccbfe5009bc12 Mon Sep 17 00:00:00 2001 From: Lubomir Rintel Date: Mon, 13 May 2019 09:56:35 +0200 Subject: [PATCH 04/74] Platform: OLPC: Avoid a warning if the EC didn't register yet Just return EPROBE_DEFER, so that whoever attempted to use the EC call can defer their work. Signed-off-by: Lubomir Rintel Acked-by: Pavel Machek Signed-off-by: Andy Shevchenko --- drivers/platform/olpc/olpc-ec.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/platform/olpc/olpc-ec.c b/drivers/platform/olpc/olpc-ec.c index 2a647455a368..a91f78245f5e 100644 --- a/drivers/platform/olpc/olpc-ec.c +++ b/drivers/platform/olpc/olpc-ec.c @@ -125,8 +125,11 @@ int olpc_ec_cmd(u8 cmd, u8 *inbuf, size_t inlen, u8 *outbuf, size_t outlen) struct olpc_ec_priv *ec = ec_priv; struct ec_cmd_desc desc; - /* Ensure a driver and ec hook have been registered */ - if (WARN_ON(!ec_driver || !ec_driver->ec_cmd)) + /* Driver not yet registered. */ + if (!ec_driver) + return -EPROBE_DEFER; + + if (WARN_ON(!ec_driver->ec_cmd)) return -ENODEV; if (!ec) From 8097548f3af9ec990169574ad9d874052b78bff8 Mon Sep 17 00:00:00 2001 From: Lubomir Rintel Date: Mon, 13 May 2019 09:56:36 +0200 Subject: [PATCH 05/74] Platform: OLPC: Use BIT() and GENMASK() for event masks Just a cosmetic tidy-up. Signed-off-by: Lubomir Rintel Reviewed-by: Andy Shevchenko Signed-off-by: Andy Shevchenko --- include/linux/olpc-ec.h | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/include/linux/olpc-ec.h b/include/linux/olpc-ec.h index 7fa3d27f7fee..f7b6a7eda232 100644 --- a/include/linux/olpc-ec.h +++ b/include/linux/olpc-ec.h @@ -2,6 +2,8 @@ #ifndef _LINUX_OLPC_EC_H #define _LINUX_OLPC_EC_H +#include + /* XO-1 EC commands */ #define EC_FIRMWARE_REV 0x08 #define EC_WRITE_SCI_MASK 0x1b @@ -17,17 +19,16 @@ #define EC_EXT_SCI_QUERY 0x85 /* SCI source values */ -#define EC_SCI_SRC_EMPTY 0x00 -#define EC_SCI_SRC_GAME 0x01 -#define EC_SCI_SRC_BATTERY 0x02 -#define EC_SCI_SRC_BATSOC 0x04 -#define EC_SCI_SRC_BATERR 0x08 -#define EC_SCI_SRC_EBOOK 0x10 /* XO-1 only */ -#define EC_SCI_SRC_WLAN 0x20 /* XO-1 only */ -#define EC_SCI_SRC_ACPWR 0x40 -#define EC_SCI_SRC_BATCRIT 0x80 -#define EC_SCI_SRC_GPWAKE 0x100 /* XO-1.5 only */ -#define EC_SCI_SRC_ALL 0x1FF +#define EC_SCI_SRC_GAME BIT(0) +#define EC_SCI_SRC_BATTERY BIT(1) +#define EC_SCI_SRC_BATSOC BIT(2) +#define EC_SCI_SRC_BATERR BIT(3) +#define EC_SCI_SRC_EBOOK BIT(4) /* XO-1 only */ +#define EC_SCI_SRC_WLAN BIT(5) /* XO-1 only */ +#define EC_SCI_SRC_ACPWR BIT(6) +#define EC_SCI_SRC_BATCRIT BIT(7) +#define EC_SCI_SRC_GPWAKE BIT(8) /* XO-1.5 only */ +#define EC_SCI_SRC_ALL GENMASK(8, 0) struct platform_device; From 0c3d931b3ab9efeea4948b5373c62095449d0101 Mon Sep 17 00:00:00 2001 From: Lubomir Rintel Date: Mon, 13 May 2019 09:56:37 +0200 Subject: [PATCH 06/74] Platform: OLPC: Add XO-1.75 EC driver It's based off the driver from the OLPC kernel sources. Somewhat modernized and cleaned up, for better or worse. Modified to plug into the olpc-ec driver infrastructure (so that battery interface and debugfs could be reused) and the SPI slave framework. Signed-off-by: Lubomir Rintel Reviewed-by: Andy Shevchenko Signed-off-by: Andy Shevchenko --- arch/x86/Kconfig | 1 + drivers/platform/Kconfig | 2 + drivers/platform/Makefile | 2 +- drivers/platform/olpc/Kconfig | 14 + drivers/platform/olpc/Makefile | 3 +- drivers/platform/olpc/olpc-xo175-ec.c | 752 ++++++++++++++++++++++++++ include/linux/olpc-ec.h | 4 +- 7 files changed, 774 insertions(+), 4 deletions(-) create mode 100644 drivers/platform/olpc/Kconfig create mode 100644 drivers/platform/olpc/olpc-xo175-ec.c diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig index 2bbbd4d1ba31..cb1c073b3c7e 100644 --- a/arch/x86/Kconfig +++ b/arch/x86/Kconfig @@ -2698,6 +2698,7 @@ config OLPC select OF select OF_PROMTREE select IRQ_DOMAIN + select OLPC_EC ---help--- Add support for detecting the unique features of the OLPC XO hardware. diff --git a/drivers/platform/Kconfig b/drivers/platform/Kconfig index d4c2e424a700..4313d73d3618 100644 --- a/drivers/platform/Kconfig +++ b/drivers/platform/Kconfig @@ -10,3 +10,5 @@ source "drivers/platform/goldfish/Kconfig" source "drivers/platform/chrome/Kconfig" source "drivers/platform/mellanox/Kconfig" + +source "drivers/platform/olpc/Kconfig" diff --git a/drivers/platform/Makefile b/drivers/platform/Makefile index 4b2ce58bcd9c..6fda58c021ca 100644 --- a/drivers/platform/Makefile +++ b/drivers/platform/Makefile @@ -6,6 +6,6 @@ obj-$(CONFIG_X86) += x86/ obj-$(CONFIG_MELLANOX_PLATFORM) += mellanox/ obj-$(CONFIG_MIPS) += mips/ -obj-$(CONFIG_OLPC) += olpc/ +obj-$(CONFIG_OLPC_EC) += olpc/ obj-$(CONFIG_GOLDFISH) += goldfish/ obj-$(CONFIG_CHROME_PLATFORMS) += chrome/ diff --git a/drivers/platform/olpc/Kconfig b/drivers/platform/olpc/Kconfig new file mode 100644 index 000000000000..559f843199d7 --- /dev/null +++ b/drivers/platform/olpc/Kconfig @@ -0,0 +1,14 @@ +config OLPC_EC + bool + +config OLPC_XO175_EC + tristate "OLPC XO 1.75 Embedded Controller" + depends on ARCH_MMP || COMPILE_TEST + select SPI_SLAVE + select OLPC_EC + help + Include support for the OLPC XO Embedded Controller (EC). The EC + provides various platform services, including support for the power, + button, restart, shutdown and battery charging status. + + Unless you have an OLPC XO laptop, you will want to say N. diff --git a/drivers/platform/olpc/Makefile b/drivers/platform/olpc/Makefile index dc8b26bc7209..01fe6ba01665 100644 --- a/drivers/platform/olpc/Makefile +++ b/drivers/platform/olpc/Makefile @@ -1,4 +1,5 @@ # # OLPC XO platform-specific drivers # -obj-$(CONFIG_OLPC) += olpc-ec.o +obj-$(CONFIG_OLPC_EC) += olpc-ec.o +obj-$(CONFIG_OLPC_XO175_EC) += olpc-xo175-ec.o diff --git a/drivers/platform/olpc/olpc-xo175-ec.c b/drivers/platform/olpc/olpc-xo175-ec.c new file mode 100644 index 000000000000..344d14f3da54 --- /dev/null +++ b/drivers/platform/olpc/olpc-xo175-ec.c @@ -0,0 +1,752 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for the OLPC XO-1.75 Embedded Controller. + * + * The EC protocol is documented at: + * http://wiki.laptop.org/go/XO_1.75_HOST_to_EC_Protocol + * + * Copyright (C) 2010 One Laptop per Child Foundation. + * Copyright (C) 2018 Lubomir Rintel + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct ec_cmd_t { + u8 cmd; + u8 bytes_returned; +}; + +enum ec_chan_t { + CHAN_NONE = 0, + CHAN_SWITCH, + CHAN_CMD_RESP, + CHAN_KEYBOARD, + CHAN_TOUCHPAD, + CHAN_EVENT, + CHAN_DEBUG, + CHAN_CMD_ERROR, +}; + +/* + * EC events + */ +#define EVENT_AC_CHANGE 1 /* AC plugged/unplugged */ +#define EVENT_BATTERY_STATUS 2 /* Battery low/full/error/gone */ +#define EVENT_BATTERY_CRITICAL 3 /* Battery critical voltage */ +#define EVENT_BATTERY_SOC_CHANGE 4 /* 1% SOC Change */ +#define EVENT_BATTERY_ERROR 5 /* Abnormal error, query for cause */ +#define EVENT_POWER_PRESSED 6 /* Power button was pressed */ +#define EVENT_POWER_PRESS_WAKE 7 /* Woken up with a power button */ +#define EVENT_TIMED_HOST_WAKE 8 /* Host wake timer */ +#define EVENT_OLS_HIGH_LIMIT 9 /* OLS crossed dark threshold */ +#define EVENT_OLS_LOW_LIMIT 10 /* OLS crossed light threshold */ + +/* + * EC commands + * (from http://dev.laptop.org/git/users/rsmith/ec-1.75/tree/ec_cmd.h) + */ +#define CMD_GET_API_VERSION 0x08 /* out: u8 */ +#define CMD_READ_VOLTAGE 0x10 /* out: u16, *9.76/32, mV */ +#define CMD_READ_CURRENT 0x11 /* out: s16, *15.625/120, mA */ +#define CMD_READ_ACR 0x12 /* out: s16, *6250/15, uAh */ +#define CMD_READ_BATT_TEMPERATURE 0x13 /* out: u16, *100/256, deg C */ +#define CMD_READ_AMBIENT_TEMPERATURE 0x14 /* unimplemented, no hardware */ +#define CMD_READ_BATTERY_STATUS 0x15 /* out: u8, bitmask */ +#define CMD_READ_SOC 0x16 /* out: u8, percentage */ +#define CMD_READ_GAUGE_ID 0x17 /* out: u8 * 8 */ +#define CMD_READ_GAUGE_DATA 0x18 /* in: u8 addr, out: u8 data */ +#define CMD_READ_BOARD_ID 0x19 /* out: u16 (platform id) */ +#define CMD_READ_BATT_ERR_CODE 0x1f /* out: u8, error bitmask */ +#define CMD_SET_DCON_POWER 0x26 /* in: u8 */ +#define CMD_RESET_EC 0x28 /* none */ +#define CMD_READ_BATTERY_TYPE 0x2c /* out: u8 */ +#define CMD_SET_AUTOWAK 0x33 /* out: u8 */ +#define CMD_SET_EC_WAKEUP_TIMER 0x36 /* in: u32, out: ? */ +#define CMD_READ_EXT_SCI_MASK 0x37 /* ? */ +#define CMD_WRITE_EXT_SCI_MASK 0x38 /* ? */ +#define CMD_CLEAR_EC_WAKEUP_TIMER 0x39 /* none */ +#define CMD_ENABLE_RUNIN_DISCHARGE 0x3B /* none */ +#define CMD_DISABLE_RUNIN_DISCHARGE 0x3C /* none */ +#define CMD_READ_MPPT_ACTIVE 0x3d /* out: u8 */ +#define CMD_READ_MPPT_LIMIT 0x3e /* out: u8 */ +#define CMD_SET_MPPT_LIMIT 0x3f /* in: u8 */ +#define CMD_DISABLE_MPPT 0x40 /* none */ +#define CMD_ENABLE_MPPT 0x41 /* none */ +#define CMD_READ_VIN 0x42 /* out: u16 */ +#define CMD_EXT_SCI_QUERY 0x43 /* ? */ +#define RSP_KEYBOARD_DATA 0x48 /* ? */ +#define RSP_TOUCHPAD_DATA 0x49 /* ? */ +#define CMD_GET_FW_VERSION 0x4a /* out: u8 * 16 */ +#define CMD_POWER_CYCLE 0x4b /* none */ +#define CMD_POWER_OFF 0x4c /* none */ +#define CMD_RESET_EC_SOFT 0x4d /* none */ +#define CMD_READ_GAUGE_U16 0x4e /* ? */ +#define CMD_ENABLE_MOUSE 0x4f /* ? */ +#define CMD_ECHO 0x52 /* in: u8 * 5, out: u8 * 5 */ +#define CMD_GET_FW_DATE 0x53 /* out: u8 * 16 */ +#define CMD_GET_FW_USER 0x54 /* out: u8 * 16 */ +#define CMD_TURN_OFF_POWER 0x55 /* none (same as 0x4c) */ +#define CMD_READ_OLS 0x56 /* out: u16 */ +#define CMD_OLS_SMT_LEDON 0x57 /* none */ +#define CMD_OLS_SMT_LEDOFF 0x58 /* none */ +#define CMD_START_OLS_ASSY 0x59 /* none */ +#define CMD_STOP_OLS_ASSY 0x5a /* none */ +#define CMD_OLS_SMTTEST_STOP 0x5b /* none */ +#define CMD_READ_VIN_SCALED 0x5c /* out: u16 */ +#define CMD_READ_BAT_MIN_W 0x5d /* out: u16 */ +#define CMD_READ_BAR_MAX_W 0x5e /* out: u16 */ +#define CMD_RESET_BAT_MINMAX_W 0x5f /* none */ +#define CMD_READ_LOCATION 0x60 /* in: u16 addr, out: u8 data */ +#define CMD_WRITE_LOCATION 0x61 /* in: u16 addr, u8 data */ +#define CMD_KEYBOARD_CMD 0x62 /* in: u8, out: ? */ +#define CMD_TOUCHPAD_CMD 0x63 /* in: u8, out: ? */ +#define CMD_GET_FW_HASH 0x64 /* out: u8 * 16 */ +#define CMD_SUSPEND_HINT 0x65 /* in: u8 */ +#define CMD_ENABLE_WAKE_TIMER 0x66 /* in: u8 */ +#define CMD_SET_WAKE_TIMER 0x67 /* in: 32 */ +#define CMD_ENABLE_WAKE_AUTORESET 0x68 /* in: u8 */ +#define CMD_OLS_SET_LIMITS 0x69 /* in: u16, u16 */ +#define CMD_OLS_GET_LIMITS 0x6a /* out: u16, u16 */ +#define CMD_OLS_SET_CEILING 0x6b /* in: u16 */ +#define CMD_OLS_GET_CEILING 0x6c /* out: u16 */ + +/* + * Accepted EC commands, and how many bytes they return. There are plenty + * of EC commands that are no longer implemented, or are implemented only on + * certain older boards. + */ +static const struct ec_cmd_t olpc_xo175_ec_cmds[] = { + { CMD_GET_API_VERSION, 1 }, + { CMD_READ_VOLTAGE, 2 }, + { CMD_READ_CURRENT, 2 }, + { CMD_READ_ACR, 2 }, + { CMD_READ_BATT_TEMPERATURE, 2 }, + { CMD_READ_BATTERY_STATUS, 1 }, + { CMD_READ_SOC, 1 }, + { CMD_READ_GAUGE_ID, 8 }, + { CMD_READ_GAUGE_DATA, 1 }, + { CMD_READ_BOARD_ID, 2 }, + { CMD_READ_BATT_ERR_CODE, 1 }, + { CMD_SET_DCON_POWER, 0 }, + { CMD_RESET_EC, 0 }, + { CMD_READ_BATTERY_TYPE, 1 }, + { CMD_ENABLE_RUNIN_DISCHARGE, 0 }, + { CMD_DISABLE_RUNIN_DISCHARGE, 0 }, + { CMD_READ_MPPT_ACTIVE, 1 }, + { CMD_READ_MPPT_LIMIT, 1 }, + { CMD_SET_MPPT_LIMIT, 0 }, + { CMD_DISABLE_MPPT, 0 }, + { CMD_ENABLE_MPPT, 0 }, + { CMD_READ_VIN, 2 }, + { CMD_GET_FW_VERSION, 16 }, + { CMD_POWER_CYCLE, 0 }, + { CMD_POWER_OFF, 0 }, + { CMD_RESET_EC_SOFT, 0 }, + { CMD_ECHO, 5 }, + { CMD_GET_FW_DATE, 16 }, + { CMD_GET_FW_USER, 16 }, + { CMD_TURN_OFF_POWER, 0 }, + { CMD_READ_OLS, 2 }, + { CMD_OLS_SMT_LEDON, 0 }, + { CMD_OLS_SMT_LEDOFF, 0 }, + { CMD_START_OLS_ASSY, 0 }, + { CMD_STOP_OLS_ASSY, 0 }, + { CMD_OLS_SMTTEST_STOP, 0 }, + { CMD_READ_VIN_SCALED, 2 }, + { CMD_READ_BAT_MIN_W, 2 }, + { CMD_READ_BAR_MAX_W, 2 }, + { CMD_RESET_BAT_MINMAX_W, 0 }, + { CMD_READ_LOCATION, 1 }, + { CMD_WRITE_LOCATION, 0 }, + { CMD_GET_FW_HASH, 16 }, + { CMD_SUSPEND_HINT, 0 }, + { CMD_ENABLE_WAKE_TIMER, 0 }, + { CMD_SET_WAKE_TIMER, 0 }, + { CMD_ENABLE_WAKE_AUTORESET, 0 }, + { CMD_OLS_SET_LIMITS, 0 }, + { CMD_OLS_GET_LIMITS, 4 }, + { CMD_OLS_SET_CEILING, 0 }, + { CMD_OLS_GET_CEILING, 2 }, + { CMD_READ_EXT_SCI_MASK, 2 }, + { CMD_WRITE_EXT_SCI_MASK, 0 }, + + { } +}; + +#define EC_MAX_CMD_DATA_LEN 5 +#define EC_MAX_RESP_LEN 16 + +#define LOG_BUF_SIZE 128 + +#define PM_WAKEUP_TIME 1000 + +#define EC_ALL_EVENTS GENMASK(15, 0) + +enum ec_state_t { + CMD_STATE_IDLE = 0, + CMD_STATE_WAITING_FOR_SWITCH, + CMD_STATE_CMD_IN_TX_FIFO, + CMD_STATE_CMD_SENT, + CMD_STATE_RESP_RECEIVED, + CMD_STATE_ERROR_RECEIVED, +}; + +struct olpc_xo175_ec_cmd { + u8 command; + u8 nr_args; + u8 data_len; + u8 args[EC_MAX_CMD_DATA_LEN]; +}; + +struct olpc_xo175_ec_resp { + u8 channel; + u8 byte; +}; + +struct olpc_xo175_ec { + bool suspended; + + /* SPI related stuff. */ + struct spi_device *spi; + struct spi_transfer xfer; + struct spi_message msg; + union { + struct olpc_xo175_ec_cmd cmd; + struct olpc_xo175_ec_resp resp; + } tx_buf, rx_buf; + + /* GPIO for the CMD signals. */ + struct gpio_desc *gpio_cmd; + + /* Command handling related state. */ + spinlock_t cmd_state_lock; + int cmd_state; + bool cmd_running; + struct completion cmd_done; + struct olpc_xo175_ec_cmd cmd; + u8 resp_data[EC_MAX_RESP_LEN]; + int expected_resp_len; + int resp_len; + + /* Power button. */ + struct input_dev *pwrbtn; + + /* Debug handling. */ + char logbuf[LOG_BUF_SIZE]; + int logbuf_len; +}; + +static struct platform_device *olpc_ec; + +static int olpc_xo175_ec_resp_len(u8 cmd) +{ + const struct ec_cmd_t *p; + + for (p = olpc_xo175_ec_cmds; p->cmd; p++) { + if (p->cmd == cmd) + return p->bytes_returned; + } + + return -EINVAL; +} + +static void olpc_xo175_ec_flush_logbuf(struct olpc_xo175_ec *priv) +{ + dev_dbg(&priv->spi->dev, "got debug string [%*pE]\n", + priv->logbuf_len, priv->logbuf); + priv->logbuf_len = 0; +} + +static void olpc_xo175_ec_complete(void *arg); + +static void olpc_xo175_ec_send_command(struct olpc_xo175_ec *priv, void *cmd, + size_t cmdlen) +{ + int ret; + + memcpy(&priv->tx_buf, cmd, cmdlen); + priv->xfer.len = cmdlen; + + spi_message_init_with_transfers(&priv->msg, &priv->xfer, 1); + + priv->msg.complete = olpc_xo175_ec_complete; + priv->msg.context = priv; + + ret = spi_async(priv->spi, &priv->msg); + if (ret) + dev_err(&priv->spi->dev, "spi_async() failed %d\n", ret); +} + +static void olpc_xo175_ec_read_packet(struct olpc_xo175_ec *priv) +{ + u8 nonce[] = {0xA5, 0x5A}; + + olpc_xo175_ec_send_command(priv, nonce, sizeof(nonce)); +} + +static void olpc_xo175_ec_complete(void *arg) +{ + struct olpc_xo175_ec *priv = arg; + struct device *dev = &priv->spi->dev; + struct power_supply *psy; + unsigned long flags; + u8 channel; + u8 byte; + int ret; + + ret = priv->msg.status; + if (ret) { + dev_err(dev, "SPI transfer failed: %d\n", ret); + + spin_lock_irqsave(&priv->cmd_state_lock, flags); + if (priv->cmd_running) { + priv->resp_len = 0; + priv->cmd_state = CMD_STATE_ERROR_RECEIVED; + complete(&priv->cmd_done); + } + spin_unlock_irqrestore(&priv->cmd_state_lock, flags); + + if (ret != -EINTR) + olpc_xo175_ec_read_packet(priv); + + return; + } + + channel = priv->rx_buf.resp.channel; + byte = priv->rx_buf.resp.byte; + + switch (channel) { + case CHAN_NONE: + spin_lock_irqsave(&priv->cmd_state_lock, flags); + + if (!priv->cmd_running) { + /* We can safely ignore these */ + dev_err(dev, "spurious FIFO read packet\n"); + spin_unlock_irqrestore(&priv->cmd_state_lock, flags); + return; + } + + priv->cmd_state = CMD_STATE_CMD_SENT; + if (!priv->expected_resp_len) + complete(&priv->cmd_done); + olpc_xo175_ec_read_packet(priv); + + spin_unlock_irqrestore(&priv->cmd_state_lock, flags); + return; + + case CHAN_SWITCH: + spin_lock_irqsave(&priv->cmd_state_lock, flags); + + if (!priv->cmd_running) { + /* Just go with the flow */ + dev_err(dev, "spurious SWITCH packet\n"); + memset(&priv->cmd, 0, sizeof(priv->cmd)); + priv->cmd.command = CMD_ECHO; + } + + priv->cmd_state = CMD_STATE_CMD_IN_TX_FIFO; + + /* Throw command into TxFIFO */ + gpiod_set_value_cansleep(priv->gpio_cmd, 0); + olpc_xo175_ec_send_command(priv, &priv->cmd, sizeof(priv->cmd)); + + spin_unlock_irqrestore(&priv->cmd_state_lock, flags); + return; + + case CHAN_CMD_RESP: + spin_lock_irqsave(&priv->cmd_state_lock, flags); + + if (!priv->cmd_running) { + dev_err(dev, "spurious response packet\n"); + } else if (priv->resp_len >= priv->expected_resp_len) { + dev_err(dev, "too many response packets\n"); + } else { + priv->resp_data[priv->resp_len++] = byte; + if (priv->resp_len == priv->expected_resp_len) { + priv->cmd_state = CMD_STATE_RESP_RECEIVED; + complete(&priv->cmd_done); + } + } + + spin_unlock_irqrestore(&priv->cmd_state_lock, flags); + break; + + case CHAN_CMD_ERROR: + spin_lock_irqsave(&priv->cmd_state_lock, flags); + + if (!priv->cmd_running) { + dev_err(dev, "spurious cmd error packet\n"); + } else { + priv->resp_data[0] = byte; + priv->resp_len = 1; + priv->cmd_state = CMD_STATE_ERROR_RECEIVED; + complete(&priv->cmd_done); + } + spin_unlock_irqrestore(&priv->cmd_state_lock, flags); + break; + + case CHAN_KEYBOARD: + dev_warn(dev, "keyboard is not supported\n"); + break; + + case CHAN_TOUCHPAD: + dev_warn(dev, "touchpad is not supported\n"); + break; + + case CHAN_EVENT: + dev_dbg(dev, "got event %.2x\n", byte); + switch (byte) { + case EVENT_AC_CHANGE: + psy = power_supply_get_by_name("olpc-ac"); + if (psy) { + power_supply_changed(psy); + power_supply_put(psy); + } + break; + case EVENT_BATTERY_STATUS: + case EVENT_BATTERY_CRITICAL: + case EVENT_BATTERY_SOC_CHANGE: + case EVENT_BATTERY_ERROR: + psy = power_supply_get_by_name("olpc-battery"); + if (psy) { + power_supply_changed(psy); + power_supply_put(psy); + } + break; + case EVENT_POWER_PRESSED: + input_report_key(priv->pwrbtn, KEY_POWER, 1); + input_sync(priv->pwrbtn); + input_report_key(priv->pwrbtn, KEY_POWER, 0); + input_sync(priv->pwrbtn); + /* fall through */ + case EVENT_POWER_PRESS_WAKE: + case EVENT_TIMED_HOST_WAKE: + pm_wakeup_event(priv->pwrbtn->dev.parent, + PM_WAKEUP_TIME); + break; + default: + dev_dbg(dev, "ignored unknown event %.2x\n", byte); + break; + } + break; + + case CHAN_DEBUG: + if (byte == '\n') { + olpc_xo175_ec_flush_logbuf(priv); + } else if (isprint(byte)) { + priv->logbuf[priv->logbuf_len++] = byte; + if (priv->logbuf_len == LOG_BUF_SIZE) + olpc_xo175_ec_flush_logbuf(priv); + } + break; + + default: + dev_warn(dev, "unknown channel: %d, %.2x\n", channel, byte); + break; + } + + /* Most non-command packets get the TxFIFO refilled and an ACK. */ + olpc_xo175_ec_read_packet(priv); +} + +/* + * This function is protected with a mutex. We can safely assume that + * there will be only one instance of this function running at a time. + * One of the ways in which we enforce this is by waiting until we get + * all response bytes back from the EC, rather than just the number that + * the caller requests (otherwise, we might start a new command while an + * old command's response bytes are still incoming). + */ +static int olpc_xo175_ec_cmd(u8 cmd, u8 *inbuf, size_t inlen, u8 *resp, + size_t resp_len, void *ec_cb_arg) +{ + struct olpc_xo175_ec *priv = ec_cb_arg; + struct device *dev = &priv->spi->dev; + unsigned long flags; + size_t nr_bytes; + int ret = 0; + + dev_dbg(dev, "CMD %x, %zd bytes expected\n", cmd, resp_len); + + if (inlen > 5) { + dev_err(dev, "command len %zd too big!\n", resp_len); + return -EOVERFLOW; + } + + /* Suspending in the middle of an EC command hoses things badly! */ + if (WARN_ON(priv->suspended)) + return -EBUSY; + + /* Ensure a valid command and return bytes */ + ret = olpc_xo175_ec_resp_len(cmd); + if (ret < 0) { + dev_err_ratelimited(dev, "unknown command 0x%x\n", cmd); + + /* + * Assume the best in our callers, and allow unknown commands + * through. I'm not the charitable type, but it was beaten + * into me. Just maintain a minimum standard of sanity. + */ + if (resp_len > sizeof(priv->resp_data)) { + dev_err(dev, "response too big: %zd!\n", resp_len); + return -EOVERFLOW; + } + nr_bytes = resp_len; + } else { + nr_bytes = (size_t)ret; + } + resp_len = min(resp_len, nr_bytes); + + spin_lock_irqsave(&priv->cmd_state_lock, flags); + + /* Initialize the state machine */ + init_completion(&priv->cmd_done); + priv->cmd_running = true; + priv->cmd_state = CMD_STATE_WAITING_FOR_SWITCH; + memset(&priv->cmd, 0, sizeof(priv->cmd)); + priv->cmd.command = cmd; + priv->cmd.nr_args = inlen; + priv->cmd.data_len = 0; + memcpy(priv->cmd.args, inbuf, inlen); + priv->expected_resp_len = nr_bytes; + priv->resp_len = 0; + + /* Tickle the cmd gpio to get things started */ + gpiod_set_value_cansleep(priv->gpio_cmd, 1); + + spin_unlock_irqrestore(&priv->cmd_state_lock, flags); + + /* The irq handler should do the rest */ + if (!wait_for_completion_timeout(&priv->cmd_done, + msecs_to_jiffies(4000))) { + dev_err(dev, "EC cmd error: timeout in STATE %d\n", + priv->cmd_state); + gpiod_set_value_cansleep(priv->gpio_cmd, 0); + spi_slave_abort(priv->spi); + olpc_xo175_ec_read_packet(priv); + return -ETIMEDOUT; + } + + spin_lock_irqsave(&priv->cmd_state_lock, flags); + + /* Deal with the results. */ + if (priv->cmd_state == CMD_STATE_ERROR_RECEIVED) { + /* EC-provided error is in the single response byte */ + dev_err(dev, "command 0x%x returned error 0x%x\n", + cmd, priv->resp_data[0]); + ret = -EREMOTEIO; + } else if (priv->resp_len != nr_bytes) { + dev_err(dev, "command 0x%x returned %d bytes, expected %zd bytes\n", + cmd, priv->resp_len, nr_bytes); + ret = -EREMOTEIO; + } else { + /* + * We may have 8 bytes in priv->resp, but we only care about + * what we've been asked for. If the caller asked for only 2 + * bytes, give them that. We've guaranteed that + * resp_len <= priv->resp_len and priv->resp_len == nr_bytes. + */ + memcpy(resp, priv->resp_data, resp_len); + } + + /* This should already be low, but just in case. */ + gpiod_set_value_cansleep(priv->gpio_cmd, 0); + priv->cmd_running = false; + + spin_unlock_irqrestore(&priv->cmd_state_lock, flags); + + return ret; +} + +static int olpc_xo175_ec_set_event_mask(unsigned int mask) +{ + u8 args[2]; + + args[0] = mask >> 0; + args[1] = mask >> 8; + return olpc_ec_cmd(CMD_WRITE_EXT_SCI_MASK, args, 2, NULL, 0); +} + +static void olpc_xo175_ec_power_off(void) +{ + while (1) { + olpc_ec_cmd(CMD_POWER_OFF, NULL, 0, NULL, 0); + mdelay(1000); + } +} + +static int __maybe_unused olpc_xo175_ec_suspend(struct device *dev) +{ + struct olpc_xo175_ec *priv = dev_get_drvdata(dev); + static struct { + u8 suspend; + u32 suspend_count; + } __packed hintargs; + static unsigned int suspend_count; + + /* + * SOC_SLEEP is not wired to the EC on B3 and earlier boards. + * This command lets the EC know instead. The suspend count doesn't seem + * to be used anywhere but in the EC debug output. + */ + hintargs.suspend = 1; + hintargs.suspend_count = suspend_count++; + olpc_ec_cmd(CMD_SUSPEND_HINT, (void *)&hintargs, sizeof(hintargs), + NULL, 0); + + /* + * After we've sent the suspend hint, don't allow further EC commands + * to be run until we've resumed. Userspace tasks should be frozen, + * but kernel threads and interrupts could still schedule EC commands. + */ + priv->suspended = true; + + return 0; +} + +static int __maybe_unused olpc_xo175_ec_resume_noirq(struct device *dev) +{ + struct olpc_xo175_ec *priv = dev_get_drvdata(dev); + + priv->suspended = false; + + return 0; +} + +static int __maybe_unused olpc_xo175_ec_resume(struct device *dev) +{ + u8 x = 0; + + /* + * The resume hint is only needed if no other commands are + * being sent during resume. all it does is tell the EC + * the SoC is definitely awake. + */ + olpc_ec_cmd(CMD_SUSPEND_HINT, &x, 1, NULL, 0); + + /* Enable all EC events while we're awake */ + olpc_xo175_ec_set_event_mask(EC_ALL_EVENTS); + + return 0; +} + +static struct olpc_ec_driver olpc_xo175_ec_driver = { + .ec_cmd = olpc_xo175_ec_cmd, +}; + +static int olpc_xo175_ec_remove(struct spi_device *spi) +{ + if (pm_power_off == olpc_xo175_ec_power_off) + pm_power_off = NULL; + + spi_slave_abort(spi); + + platform_device_unregister(olpc_ec); + olpc_ec = NULL; + + return 0; +} + +static int olpc_xo175_ec_probe(struct spi_device *spi) +{ + struct olpc_xo175_ec *priv; + int ret; + + if (olpc_ec) { + dev_err(&spi->dev, "OLPC EC already registered.\n"); + return -EBUSY; + } + + priv = devm_kzalloc(&spi->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->gpio_cmd = devm_gpiod_get(&spi->dev, "cmd", GPIOD_OUT_LOW); + if (IS_ERR(priv->gpio_cmd)) { + dev_err(&spi->dev, "failed to get cmd gpio: %ld\n", + PTR_ERR(priv->gpio_cmd)); + return PTR_ERR(priv->gpio_cmd); + } + + priv->spi = spi; + + spin_lock_init(&priv->cmd_state_lock); + priv->cmd_state = CMD_STATE_IDLE; + init_completion(&priv->cmd_done); + + priv->logbuf_len = 0; + + /* Set up power button input device */ + priv->pwrbtn = devm_input_allocate_device(&spi->dev); + if (!priv->pwrbtn) + return -ENOMEM; + priv->pwrbtn->name = "Power Button"; + priv->pwrbtn->dev.parent = &spi->dev; + input_set_capability(priv->pwrbtn, EV_KEY, KEY_POWER); + ret = input_register_device(priv->pwrbtn); + if (ret) { + dev_err(&spi->dev, "error registering input device: %d\n", ret); + return ret; + } + + spi_set_drvdata(spi, priv); + + priv->xfer.rx_buf = &priv->rx_buf; + priv->xfer.tx_buf = &priv->tx_buf; + + olpc_xo175_ec_read_packet(priv); + + olpc_ec_driver_register(&olpc_xo175_ec_driver, priv); + olpc_ec = platform_device_register_resndata(&spi->dev, "olpc-ec", -1, + NULL, 0, NULL, 0); + + /* Enable all EC events while we're awake */ + olpc_xo175_ec_set_event_mask(EC_ALL_EVENTS); + + if (pm_power_off == NULL) + pm_power_off = olpc_xo175_ec_power_off; + + dev_info(&spi->dev, "OLPC XO-1.75 Embedded Controller driver\n"); + + return 0; +} + +static const struct dev_pm_ops olpc_xo175_ec_pm_ops = { + SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(NULL, olpc_xo175_ec_resume_noirq) + SET_RUNTIME_PM_OPS(olpc_xo175_ec_suspend, olpc_xo175_ec_resume, NULL) +}; + +static const struct of_device_id olpc_xo175_ec_of_match[] = { + { .compatible = "olpc,xo1.75-ec" }, + { } +}; +MODULE_DEVICE_TABLE(of, olpc_xo175_ec_of_match); + +static struct spi_driver olpc_xo175_ec_spi_driver = { + .driver = { + .name = "olpc-xo175-ec", + .of_match_table = olpc_xo175_ec_of_match, + .pm = &olpc_xo175_ec_pm_ops, + }, + .probe = olpc_xo175_ec_probe, + .remove = olpc_xo175_ec_remove, +}; +module_spi_driver(olpc_xo175_ec_spi_driver); + +MODULE_DESCRIPTION("OLPC XO-1.75 Embedded Controller driver"); +MODULE_AUTHOR("Lennert Buytenhek "); /* Functionality */ +MODULE_AUTHOR("Lubomir Rintel "); /* Bugs */ +MODULE_LICENSE("GPL"); diff --git a/include/linux/olpc-ec.h b/include/linux/olpc-ec.h index f7b6a7eda232..c4602364e909 100644 --- a/include/linux/olpc-ec.h +++ b/include/linux/olpc-ec.h @@ -41,7 +41,7 @@ struct olpc_ec_driver { bool wakeup_available; }; -#ifdef CONFIG_OLPC +#ifdef CONFIG_OLPC_EC extern void olpc_ec_driver_register(struct olpc_ec_driver *drv, void *arg); @@ -69,6 +69,6 @@ static inline bool olpc_ec_wakeup_available(void) return false; } -#endif /* CONFIG_OLPC */ +#endif /* CONFIG_OLPC_EC */ #endif /* _LINUX_OLPC_EC_H */ From 231c0c216172a92feb02a99edff6b38b3ebd90a8 Mon Sep 17 00:00:00 2001 From: Lubomir Rintel Date: Mon, 13 May 2019 09:56:38 +0200 Subject: [PATCH 07/74] Platform: OLPC: Add a regulator for the DCON All OLPC ECs are able to turn the power to the DCON on an off. Use the regulator framework to expose the functionality. Signed-off-by: Lubomir Rintel Signed-off-by: Andy Shevchenko --- drivers/platform/olpc/Kconfig | 1 + drivers/platform/olpc/olpc-ec.c | 67 +++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/drivers/platform/olpc/Kconfig b/drivers/platform/olpc/Kconfig index 559f843199d7..858ac1d2290a 100644 --- a/drivers/platform/olpc/Kconfig +++ b/drivers/platform/olpc/Kconfig @@ -1,4 +1,5 @@ config OLPC_EC + select REGULATOR bool config OLPC_XO175_EC diff --git a/drivers/platform/olpc/olpc-ec.c b/drivers/platform/olpc/olpc-ec.c index a91f78245f5e..8566bcf2938d 100644 --- a/drivers/platform/olpc/olpc-ec.c +++ b/drivers/platform/olpc/olpc-ec.c @@ -16,6 +16,7 @@ #include #include #include +#include #include struct ec_cmd_desc { @@ -36,6 +37,10 @@ struct olpc_ec_priv { struct work_struct worker; struct mutex cmd_lock; + /* DCON regulator */ + struct regulator_dev *dcon_rdev; + bool dcon_enabled; + /* Pending EC commands */ struct list_head cmd_q; spinlock_t cmd_q_lock; @@ -346,9 +351,61 @@ static struct dentry *olpc_ec_setup_debugfs(void) #endif /* CONFIG_DEBUG_FS */ +static int olpc_ec_set_dcon_power(struct olpc_ec_priv *ec, bool state) +{ + unsigned char ec_byte = state; + int ret; + + if (ec->dcon_enabled == state) + return 0; + + ret = olpc_ec_cmd(EC_DCON_POWER_MODE, &ec_byte, 1, NULL, 0); + if (ret) + return ret; + + ec->dcon_enabled = state; + return 0; +} + +static int dcon_regulator_enable(struct regulator_dev *rdev) +{ + struct olpc_ec_priv *ec = rdev_get_drvdata(rdev); + + return olpc_ec_set_dcon_power(ec, true); +} + +static int dcon_regulator_disable(struct regulator_dev *rdev) +{ + struct olpc_ec_priv *ec = rdev_get_drvdata(rdev); + + return olpc_ec_set_dcon_power(ec, false); +} + +static int dcon_regulator_is_enabled(struct regulator_dev *rdev) +{ + struct olpc_ec_priv *ec = rdev_get_drvdata(rdev); + + return ec->dcon_enabled ? 1 : 0; +} + +static struct regulator_ops dcon_regulator_ops = { + .enable = dcon_regulator_enable, + .disable = dcon_regulator_disable, + .is_enabled = dcon_regulator_is_enabled, +}; + +static const struct regulator_desc dcon_desc = { + .name = "dcon", + .id = 0, + .ops = &dcon_regulator_ops, + .type = REGULATOR_VOLTAGE, + .owner = THIS_MODULE, +}; + static int olpc_ec_probe(struct platform_device *pdev) { struct olpc_ec_priv *ec; + struct regulator_config config = { }; int err; if (!ec_driver) @@ -376,6 +433,16 @@ static int olpc_ec_probe(struct platform_device *pdev) return err; } + config.dev = pdev->dev.parent; + config.driver_data = ec; + ec->dcon_enabled = true; + ec->dcon_rdev = devm_regulator_register(&pdev->dev, &dcon_desc, + &config); + if (IS_ERR(ec->dcon_rdev)) { + dev_err(&pdev->dev, "failed to register DCON regulator\n"); + return PTR_ERR(ec->dcon_rdev); + } + ec->dbgfs_dir = olpc_ec_setup_debugfs(); return err; From ef071bab655b61e3235868a445b72346b8cc0193 Mon Sep 17 00:00:00 2001 From: Lubomir Rintel Date: Mon, 13 May 2019 09:56:39 +0200 Subject: [PATCH 08/74] power: supply: olpc_battery: Allow building the driver on non-x86 With the recent changes to the driver and addition of OLPC XO-1.75 EC driver it is possible to use this driver on ARM-based XO laptops as well. Signed-off-by: Lubomir Rintel Signed-off-by: Andy Shevchenko --- drivers/power/supply/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig index 26dacdab03cc..5a40dffd1c95 100644 --- a/drivers/power/supply/Kconfig +++ b/drivers/power/supply/Kconfig @@ -151,7 +151,7 @@ config BATTERY_PMU config BATTERY_OLPC tristate "One Laptop Per Child battery" - depends on X86_32 && OLPC + depends on OLPC_EC help Say Y to enable support for the battery on the OLPC laptop. From ad04ca76c6536c7a3176b7413fde20fa3318b7bf Mon Sep 17 00:00:00 2001 From: YueHaibing Date: Tue, 28 May 2019 17:28:05 +0800 Subject: [PATCH 09/74] Platform: OLPC: Fix build error without CONFIG_SPI Fix gcc build error while CONFIG_SPI is not set drivers/platform/olpc/olpc-xo175-ec.o: In function `olpc_xo175_ec_remove': olpc-xo175-ec.c:(.text+0x190): undefined reference to `spi_slave_abort' drivers/platform/olpc/olpc-xo175-ec.o: In function `olpc_xo175_ec_send_command': olpc-xo175-ec.c:(.text+0x374): undefined reference to `spi_async' drivers/platform/olpc/olpc-xo175-ec.o: In function `olpc_xo175_ec_cmd': olpc-xo175-ec.c:(.text+0x8a0): undefined reference to `spi_slave_abort' drivers/platform/olpc/olpc-xo175-ec.o: In function `olpc_xo175_ec_spi_driver_init': olpc-xo175-ec.c:(.init.text+0x14): undefined reference to `__spi_register_driver' We should depends on CONFIG_SPI_SLAVE other than directly select it. Reported-by: Hulk Robot Fixes: 0c3d931b3ab9 ("Platform: OLPC: Add XO-1.75 EC driver") Signed-off-by: YueHaibing Signed-off-by: Andy Shevchenko --- drivers/platform/olpc/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/platform/olpc/Kconfig b/drivers/platform/olpc/Kconfig index 858ac1d2290a..1d3244a25ffc 100644 --- a/drivers/platform/olpc/Kconfig +++ b/drivers/platform/olpc/Kconfig @@ -5,7 +5,7 @@ config OLPC_EC config OLPC_XO175_EC tristate "OLPC XO 1.75 Embedded Controller" depends on ARCH_MMP || COMPILE_TEST - select SPI_SLAVE + depends on SPI_SLAVE select OLPC_EC help Include support for the OLPC XO Embedded Controller (EC). The EC From cdaf018c54bd957d5ee00b1b8c3c74e18138ee6e Mon Sep 17 00:00:00 2001 From: YueHaibing Date: Tue, 28 May 2019 17:28:06 +0800 Subject: [PATCH 10/74] Platform: OLPC: Add INPUT dependencies Building with CONFIG_INPUT set to m: drivers/platform/olpc/olpc-xo175-ec.o: In function `olpc_xo175_ec_complete': olpc-xo175-ec.c:(.text+0x75d): undefined reference to `input_event' olpc-xo175-ec.c:(.text+0x76f): undefined reference to `input_event' olpc-xo175-ec.c:(.text+0x787): undefined reference to `input_event' olpc-xo175-ec.c:(.text+0x799): undefined reference to `input_event' drivers/platform/olpc/olpc-xo175-ec.o: In function `olpc_xo175_ec_probe': olpc-xo175-ec.c:(.text+0x8d5): undefined reference to `devm_input_allocate_device' olpc-xo175-ec.c:(.text+0x910): undefined reference to `input_set_capability' olpc-xo175-ec.c:(.text+0x91c): undefined reference to `input_register_device' This patch add INPUT dependencies to fix this. Reported-by: Hulk Robot Fixes: 0c3d931b3ab9 ("Platform: OLPC: Add XO-1.75 EC driver") Signed-off-by: YueHaibing Signed-off-by: Andy Shevchenko --- drivers/platform/olpc/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/platform/olpc/Kconfig b/drivers/platform/olpc/Kconfig index 1d3244a25ffc..1fa676c5e7fa 100644 --- a/drivers/platform/olpc/Kconfig +++ b/drivers/platform/olpc/Kconfig @@ -6,6 +6,7 @@ config OLPC_XO175_EC tristate "OLPC XO 1.75 Embedded Controller" depends on ARCH_MMP || COMPILE_TEST depends on SPI_SLAVE + depends on INPUT select OLPC_EC help Include support for the OLPC XO Embedded Controller (EC). The EC From b8a84365bbff0f860c5dc5795405430d92d68966 Mon Sep 17 00:00:00 2001 From: Lubomir Rintel Date: Wed, 29 May 2019 10:34:02 +0200 Subject: [PATCH 11/74] Platform: OLPC: Make olpc_dt_compatible_match() static __init Addresses a kbuild warning: >> WARNING: vmlinux.o(.text+0x3b764): Section mismatch in reference from the function olpc_dt_compatible_match() to the function .init.text:olpc_dt_getproperty() Signed-off-by: Lubomir Rintel Reported-by: kbuild test robot Fixes: a7a9bacb9a32 (x86/platform/olpc: Use a correct version when making up a battery node) Signed-off-by: Andy Shevchenko --- arch/x86/platform/olpc/olpc_dt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arch/x86/platform/olpc/olpc_dt.c b/arch/x86/platform/olpc/olpc_dt.c index 0296c5b55e6f..114c52986568 100644 --- a/arch/x86/platform/olpc/olpc_dt.c +++ b/arch/x86/platform/olpc/olpc_dt.c @@ -220,7 +220,7 @@ static u32 __init olpc_dt_get_board_revision(void) return be32_to_cpu(rev); } -int olpc_dt_compatible_match(phandle node, const char *compat) +static int __init olpc_dt_compatible_match(phandle node, const char *compat) { char buf[64], *p; int plen, len; From fd43f16cca951b5eea6703f5f98381cf4883f86c Mon Sep 17 00:00:00 2001 From: Lubomir Rintel Date: Wed, 29 May 2019 10:34:03 +0200 Subject: [PATCH 12/74] Platform: OLPC: Fix olpc_xo175_ec_cmd() return value Reset the ret variable to make sure it olpc_xo175_ec_cmd() ends up returning zero on success. Fixes: 0c3d931b3ab9 ("Platform: OLPC: Add XO-1.75 EC driver") Signed-off-by: Lubomir Rintel Signed-off-by: Andy Shevchenko --- drivers/platform/olpc/olpc-xo175-ec.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/platform/olpc/olpc-xo175-ec.c b/drivers/platform/olpc/olpc-xo175-ec.c index 344d14f3da54..48d6f0d87583 100644 --- a/drivers/platform/olpc/olpc-xo175-ec.c +++ b/drivers/platform/olpc/olpc-xo175-ec.c @@ -507,6 +507,7 @@ static int olpc_xo175_ec_cmd(u8 cmd, u8 *inbuf, size_t inlen, u8 *resp, nr_bytes = resp_len; } else { nr_bytes = (size_t)ret; + ret = 0; } resp_len = min(resp_len, nr_bytes); From af21f32c78e0f3446e50bd01dbd041f4082d5fda Mon Sep 17 00:00:00 2001 From: Lubomir Rintel Date: Wed, 29 May 2019 10:34:04 +0200 Subject: [PATCH 13/74] Platform: OLPC: Require CONFIG_POWER_SUPPLY for XO-1.75 EC ERROR: "power_supply_put" [drivers/platform/olpc/olpc-xo175-ec.ko] undefined! ERROR: "power_supply_changed" [drivers/platform/olpc/olpc-xo175-ec.ko] undefined! ERROR: "power_supply_get_by_name" [drivers/platform/olpc/olpc-xo175-ec.ko] undefined! Adding the dependency seems like a more reasonable thing compared to ifdef-ing the bits, as if one has an XO-1.75 they almost certainly want a baterry and AC adapter support. Reported-by: Randy Dunlap Signed-off-by: Lubomir Rintel Acked-by: Randy Dunlap Signed-off-by: Andy Shevchenko --- drivers/platform/olpc/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/platform/olpc/Kconfig b/drivers/platform/olpc/Kconfig index 1fa676c5e7fa..6f35c01254e5 100644 --- a/drivers/platform/olpc/Kconfig +++ b/drivers/platform/olpc/Kconfig @@ -7,6 +7,7 @@ config OLPC_XO175_EC depends on ARCH_MMP || COMPILE_TEST depends on SPI_SLAVE depends on INPUT + depends on POWER_SUPPLY select OLPC_EC help Include support for the OLPC XO Embedded Controller (EC). The EC From 4e6d2739407aaf693cca2569eb58408228d2d3b0 Mon Sep 17 00:00:00 2001 From: Lubomir Rintel Date: Wed, 29 May 2019 10:34:05 +0200 Subject: [PATCH 14/74] Platform: OLPC: Add a config menu category for XO 1.75 Randy Dunlap says: drivers/platform/olpc/Kconfig needs to use "menuconfig" like all of the other Kconfig files in drivers/platform/ so that its menu is listed in the correct place in *config interfaces. Otherwise he's sad. Reported-by: Randy Dunlap Signed-off-by: Lubomir Rintel Acked-by: Randy Dunlap Signed-off-by: Andy Shevchenko --- drivers/platform/olpc/Kconfig | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/drivers/platform/olpc/Kconfig b/drivers/platform/olpc/Kconfig index 6f35c01254e5..919b489e24e8 100644 --- a/drivers/platform/olpc/Kconfig +++ b/drivers/platform/olpc/Kconfig @@ -2,9 +2,19 @@ config OLPC_EC select REGULATOR bool +menuconfig OLPC_XO175 + bool "Platform support for OLPC XO 1.75 hardware" + depends on ARCH_MMP || COMPILE_TEST + help + Say Y here to get to see options for the ARM-based OLPC platform. + This option alone does not add any kernel code. + + Unless you have an OLPC XO laptop, you will want to say N. + +if OLPC_XO175 + config OLPC_XO175_EC tristate "OLPC XO 1.75 Embedded Controller" - depends on ARCH_MMP || COMPILE_TEST depends on SPI_SLAVE depends on INPUT depends on POWER_SUPPLY @@ -15,3 +25,5 @@ config OLPC_XO175_EC button, restart, shutdown and battery charging status. Unless you have an OLPC XO laptop, you will want to say N. + +endif # OLPC_XO175 From 1dd93f873d8ed8e5b228d1ae324b1f3c1e94bfa8 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 12 Jun 2019 09:02:02 +0200 Subject: [PATCH 15/74] platform/x86: asus-wmi: Only Tell EC the OS will handle display hotkeys from asus_nb_wmi MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit 78f3ac76d9e5 ("platform/x86: asus-wmi: Tell the EC the OS will handle the display off hotkey") causes the backlight to be permanently off on various EeePC laptop models using the eeepc-wmi driver (Asus EeePC 1015BX, Asus EeePC 1025C). The asus_wmi_set_devstate(ASUS_WMI_DEVID_BACKLIGHT, 2, NULL) call added by that commit is made conditional in this commit and only enabled in the quirk_entry structs in the asus-nb-wmi driver fixing the broken display / backlight on various EeePC laptop models. Cc: João Paulo Rechi Vita Fixes: 78f3ac76d9e5 ("platform/x86: asus-wmi: Tell the EC the OS will handle the display off hotkey") Signed-off-by: Hans de Goede Signed-off-by: Andy Shevchenko --- drivers/platform/x86/asus-nb-wmi.c | 8 ++++++++ drivers/platform/x86/asus-wmi.c | 2 +- drivers/platform/x86/asus-wmi.h | 1 + 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/drivers/platform/x86/asus-nb-wmi.c b/drivers/platform/x86/asus-nb-wmi.c index b6f2ff95c3ed..59f3a37a44d7 100644 --- a/drivers/platform/x86/asus-nb-wmi.c +++ b/drivers/platform/x86/asus-nb-wmi.c @@ -78,10 +78,12 @@ static bool asus_q500a_i8042_filter(unsigned char data, unsigned char str, static struct quirk_entry quirk_asus_unknown = { .wapf = 0, + .wmi_backlight_set_devstate = true, }; static struct quirk_entry quirk_asus_q500a = { .i8042_filter = asus_q500a_i8042_filter, + .wmi_backlight_set_devstate = true, }; /* @@ -92,26 +94,32 @@ static struct quirk_entry quirk_asus_q500a = { static struct quirk_entry quirk_asus_x55u = { .wapf = 4, .wmi_backlight_power = true, + .wmi_backlight_set_devstate = true, .no_display_toggle = true, }; static struct quirk_entry quirk_asus_wapf4 = { .wapf = 4, + .wmi_backlight_set_devstate = true, }; static struct quirk_entry quirk_asus_x200ca = { .wapf = 2, + .wmi_backlight_set_devstate = true, }; static struct quirk_entry quirk_asus_ux303ub = { .wmi_backlight_native = true, + .wmi_backlight_set_devstate = true, }; static struct quirk_entry quirk_asus_x550lb = { + .wmi_backlight_set_devstate = true, .xusb2pr = 0x01D9, }; static struct quirk_entry quirk_asus_forceals = { + .wmi_backlight_set_devstate = true, .wmi_force_als_set = true, }; diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index f94691615881..12462726312c 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -2159,7 +2159,7 @@ static int asus_wmi_add(struct platform_device *pdev) err = asus_wmi_backlight_init(asus); if (err && err != -ENODEV) goto fail_backlight; - } else + } else if (asus->driver->quirks->wmi_backlight_set_devstate) err = asus_wmi_set_devstate(ASUS_WMI_DEVID_BACKLIGHT, 2, NULL); if (asus_wmi_has_fnlock_key(asus)) { diff --git a/drivers/platform/x86/asus-wmi.h b/drivers/platform/x86/asus-wmi.h index 6c1311f4b04d..57a79bddb286 100644 --- a/drivers/platform/x86/asus-wmi.h +++ b/drivers/platform/x86/asus-wmi.h @@ -44,6 +44,7 @@ struct quirk_entry { bool store_backlight_power; bool wmi_backlight_power; bool wmi_backlight_native; + bool wmi_backlight_set_devstate; bool wmi_force_als_set; int wapf; /* From 89ae3a07362585dc0856bae5605b0ec77b984ec8 Mon Sep 17 00:00:00 2001 From: Mathew King Date: Mon, 20 May 2019 16:41:24 -0600 Subject: [PATCH 16/74] platform/x86: intel-vbtn: Report switch events when event wakes device When a switch event, such as tablet mode/laptop mode or docked/undocked, wakes a device make sure that the value of the swich is reported. Without when a device is put in tablet mode from laptop mode when it is suspended or vice versa the device will wake up but mode will be incorrect. Tested by suspending a device in laptop mode and putting it in tablet mode, the device resumes and is in tablet mode. When suspending the device in tablet mode and putting it in laptop mode the device resumes and is in laptop mode. Signed-off-by: Mathew King Reviewed-by: Jett Rink Reviewed-by: Mario Limonciello Signed-off-by: Andy Shevchenko --- drivers/platform/x86/intel-vbtn.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/drivers/platform/x86/intel-vbtn.c b/drivers/platform/x86/intel-vbtn.c index 06cd7e818ed5..a0d0cecff55f 100644 --- a/drivers/platform/x86/intel-vbtn.c +++ b/drivers/platform/x86/intel-vbtn.c @@ -76,12 +76,24 @@ static void notify_handler(acpi_handle handle, u32 event, void *context) struct platform_device *device = context; struct intel_vbtn_priv *priv = dev_get_drvdata(&device->dev); unsigned int val = !(event & 1); /* Even=press, Odd=release */ - const struct key_entry *ke_rel; + const struct key_entry *ke, *ke_rel; bool autorelease; if (priv->wakeup_mode) { - if (sparse_keymap_entry_from_scancode(priv->input_dev, event)) { + ke = sparse_keymap_entry_from_scancode(priv->input_dev, event); + if (ke) { pm_wakeup_hard_event(&device->dev); + + /* + * Switch events like tablet mode will wake the device + * and report the new switch position to the input + * subsystem. + */ + if (ke->type == KE_SW) + sparse_keymap_report_event(priv->input_dev, + event, + val, + 0); return; } goto out_unknown; From fa882fc80dc8bf6ed24c0a85e73c17d62d2b1754 Mon Sep 17 00:00:00 2001 From: Vadim Pasternak Date: Wed, 5 Jun 2019 07:51:03 +0000 Subject: [PATCH 17/74] platform/x86: mlx-platform: Fix parent device in i2c-mux-reg device registration Fix the issue found while running kernel with the option CONFIG_DEBUG_TEST_DRIVER_REMOVE. Driver 'mlx-platform' registers 'i2c_mlxcpld' device and then registers few underlying 'i2c-mux-reg' devices: priv->pdev_i2c = platform_device_register_simple("i2c_mlxcpld", nr, NULL, 0); ... 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])); But actual parent of "i2c-mux-reg" device is priv->pdev_i2c->dev and not mlxplat_dev->dev. Patch fixes parent device parameter in a call to platform_device_register_resndata() for "i2c-mux-reg". It solves the race during initialization flow while 'i2c_mlxcpld.1' is removing after probe, while 'i2c-mux-reg.0' is still in probing flow: 'i2c_mlxcpld.1' flow: probe -> remove -> probe. 'i2c-mux-reg.0' flow: probe -> ... [ 12:621096] Registering platform device 'i2c_mlxcpld.1'. Parent at platform [ 12:621117] device: 'i2c_mlxcpld.1': device_add [ 12:621155] bus: 'platform': add device i2c_mlxcpld.1 [ 12:621384] Registering platform device 'i2c-mux-reg.0'. Parent at mlxplat [ 12:621395] device: 'i2c-mux-reg.0': device_add [ 12:621425] bus: 'platform': add device i2c-mux-reg.0 [ 12:621806] Registering platform device 'i2c-mux-reg.1'. Parent at mlxplat [ 12:621828] device: 'i2c-mux-reg.1': device_add [ 12:621892] bus: 'platform': add device i2c-mux-reg.1 [ 12:621906] bus: 'platform': add driver i2c_mlxcpld [ 12:621996] bus: 'platform': driver_probe_device: matched device i2c_mlxcpld.1 with driver i2c_mlxcpld [ 12:622003] bus: 'platform': really_probe: probing driver i2c_mlxcpld with device i2c_mlxcpld.1 [ 12:622100] i2c_mlxcpld i2c_mlxcpld.1: no default pinctrl state [ 12:622293] device: 'i2c-1': device_add [ 12:627280] bus: 'i2c': add device i2c-1 [ 12:627692] device: 'i2c-1': device_add [ 12.629639] bus: 'platform': add driver i2c-mux-reg [ 12.629718] bus: 'platform': driver_probe_device: matched device i2c-mux-reg.0 with driver i2c-mux-reg [ 12.629723] bus: 'platform': really_probe: probing driver i2c-mux-reg with device i2c-mux-reg.0 [ 12.629818] i2c-mux-reg i2c-mux-reg.0: no default pinctrl state [ 12.629981] platform i2c-mux-reg.0: Driver i2c-mux-reg requests probe deferral [ 12.629986] platform i2c-mux-reg.0: Added to deferred list [ 12.629992] bus: 'platform': driver_probe_device: matched device i2c-mux-reg.1 with driver i2c-mux-reg [ 12.629997] bus: 'platform': really_probe: probing driver i2c-mux-reg with device i2c-mux-reg.1 [ 12.630091] i2c-mux-reg i2c-mux-reg.1: no default pinctrl state [ 12.630247] platform i2c-mux-reg.1: Driver i2c-mux-reg requests probe deferral [ 12.630252] platform i2c-mux-reg.1: Added to deferred list [ 12.640892] devices_kset: Moving i2c-mux-reg.0 to end of list [ 12.640900] platform i2c-mux-reg.0: Retrying from deferred list [ 12.640911] bus: 'platform': driver_probe_device: matched device i2c-mux-reg.0 with driver i2c-mux-reg [ 12.640919] bus: 'platform': really_probe: probing driver i2c-mux-reg with device i2c-mux-reg.0 [ 12.640999] i2c-mux-reg i2c-mux-reg.0: no default pinctrl state [ 12.641177] platform i2c-mux-reg.0: Driver i2c-mux-reg requests probe deferral [ 12.641187] platform i2c-mux-reg.0: Added to deferred list [ 12.641198] devices_kset: Moving i2c-mux-reg.1 to end of list [ 12.641219] platform i2c-mux-reg.1: Retrying from deferred list [ 12.641237] bus: 'platform': driver_probe_device: matched device i2c-mux-reg.1 with driver i2c-mux-reg [ 12.641247] bus: 'platform': really_probe: probing driver i2c-mux-reg with device i2c-mux-reg.1 [ 12.641331] i2c-mux-reg i2c-mux-reg.1: no default pinctrl state [ 12.641465] platform i2c-mux-reg.1: Driver i2c-mux-reg requests probe deferral [ 12.641469] platform i2c-mux-reg.1: Added to deferred list [ 12.646427] device: 'i2c-1': device_add [ 12.646647] bus: 'i2c': add device i2c-1 [ 12.647104] device: 'i2c-1': device_add [ 12.669231] devices_kset: Moving i2c-mux-reg.0 to end of list [ 12.669240] platform i2c-mux-reg.0: Retrying from deferred list [ 12.669258] bus: 'platform': driver_probe_device: matched device i2c-mux-reg.0 with driver i2c-mux-reg [ 12.669263] bus: 'platform': really_probe: probing driver i2c-mux-reg with device i2c-mux-reg.0 [ 12.669343] i2c-mux-reg i2c-mux-reg.0: no default pinctrl state [ 12.669585] device: 'i2c-2': device_add [ 12.669795] bus: 'i2c': add device i2c-2 [ 12.670201] device: 'i2c-2': device_add [ 12.671427] i2c i2c-1: Added multiplexed i2c bus 2 [ 12.671514] device: 'i2c-3': device_add [ 12.671724] bus: 'i2c': add device i2c-3 [ 12.672136] device: 'i2c-3': device_add [ 12.673378] i2c i2c-1: Added multiplexed i2c bus 3 [ 12.673472] device: 'i2c-4': device_add [ 12.673676] bus: 'i2c': add device i2c-4 [ 12.674060] device: 'i2c-4': device_add [ 12.675861] i2c i2c-1: Added multiplexed i2c bus 4 [ 12.675941] device: 'i2c-5': device_add [ 12.676150] bus: 'i2c': add device i2c-5 [ 12.676550] device: 'i2c-5': device_add [ 12.678103] i2c i2c-1: Added multiplexed i2c bus 5 [ 12.678193] device: 'i2c-6': device_add [ 12.678395] bus: 'i2c': add device i2c-6 [ 12.678774] device: 'i2c-6': device_add [ 12.679969] i2c i2c-1: Added multiplexed i2c bus 6 [ 12.680065] device: 'i2c-7': device_add [ 12.680275] bus: 'i2c': add device i2c-7 [ 12.680913] device: 'i2c-7': device_add [ 12.682506] i2c i2c-1: Added multiplexed i2c bus 7 [ 12.682600] device: 'i2c-8': device_add [ 12.682808] bus: 'i2c': add device i2c-8 [ 12.683189] device: 'i2c-8': device_add [ 12.683907] device: 'i2c-1': device_unregister [ 12.683945] device: 'i2c-1': device_unregister [ 12.684387] device: 'i2c-1': device_create_release [ 12.684536] bus: 'i2c': remove device i2c-1 [ 12.686019] i2c i2c-8: Failed to create compatibility class link [ 12.686086] ------------[ cut here ]------------ [ 12.686087] can't create symlink to mux device [ 12.686224] Workqueue: events deferred_probe_work_func [ 12.686135] WARNING: CPU: 7 PID: 436 at drivers/i2c/i2c-mux.c:416 i2c_mux_add_adapter+0x729/0x7d0 [i2c_mux] [ 12.686232] RIP: 0010:i2c_mux_add_adapter+0x729/0x7d0 [i2c_mux] [ 0x190/0x190 [i2c_mux] [ 12.686300] ? i2c_mux_alloc+0xac/0x110 [i2c_mux] [ 12.686306] ? i2c_mux_reg_set+0x200/0x200 [i2c_mux_reg] [ 12.686313] i2c_mux_reg_probe+0x22c/0x731 [i2c_mux_reg] [ 12.686322] ? i2c_mux_reg_deselect+0x60/0x60 [i2c_mux_reg] [ 12.686346] platform_drv_probe+0xa8/0x110 [ 12.686351] really_probe+0x185/0x720 [ 12.686358] driver_probe_device+0xdf/0x1f0 ... [ 12.686522] i2c i2c-1: Added multiplexed i2c bus 8 [ 12.686621] device: 'i2c-9': device_add [ 12.686626] kobject_add_internal failed for i2c-9 (error: -2 parent: i2c-1) [ 12.694729] i2c-core: adapter 'i2c-1-mux (chan_id 8)': can't register device (-2) [ 12.705726] i2c i2c-1: failed to add mux-adapter 8 as bus 9 (error=-2) [ 12.714494] device: 'i2c-8': device_unregister [ 12.714537] device: 'i2c-8': device_unregister Fixes: 6613d18e9038 ("platform/x86: mlx-platform: Move module from arch/x86") Signed-off-by: Vadim Pasternak Signed-off-by: Andy Shevchenko --- drivers/platform/x86/mlx-platform.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/platform/x86/mlx-platform.c b/drivers/platform/x86/mlx-platform.c index cee039f57499..983f02b5b106 100644 --- a/drivers/platform/x86/mlx-platform.c +++ b/drivers/platform/x86/mlx-platform.c @@ -2032,7 +2032,7 @@ static int __init mlxplat_init(void) for (i = 0; i < ARRAY_SIZE(mlxplat_mux_data); i++) { priv->pdev_mux[i] = platform_device_register_resndata( - &mlxplat_dev->dev, + &priv->pdev_i2c->dev, "i2c-mux-reg", i, NULL, 0, &mlxplat_mux_data[i], sizeof(mlxplat_mux_data[i])); From 0bfcd24b39c29dbaba81a188ca364563866e6ecc Mon Sep 17 00:00:00 2001 From: Vadim Pasternak Date: Thu, 23 May 2019 16:41:52 +0000 Subject: [PATCH 18/74] platform/mellanox: mlxreg-hotplug: Add devm_free_irq call to remove flow Add devm_free_irq() call to mlxreg-hotplug remove() for clean release of devices irq resource. Fix debugobjects warning triggered by rmmod It prevents of use-after-free memory, related to mlxreg_hotplug_work_handler. Issue has been reported as debugobjects warning triggered by 'rmmod mlxtreg-hotplug' flow, while running kernel with CONFIG_DEBUG_OBJECTS* options. [ 2489.623551] ODEBUG: free active (active state 0) object type: work_struct hint: mlxreg_hotplug_work_handler+0x0/0x7f0 [mlxreg_hotplug] [ 2489.637097] WARNING: CPU: 5 PID: 3924 at lib/debugobjects.c:328 debug_print_object+0xfe/0x180 [ 2489.637165] RIP: 0010:debug_print_object+0xfe/0x180 ? [ 2489.637214] Call Trace: [ 2489.637225] __debug_check_no_obj_freed+0x25e/0x320 [ 2489.637231] kfree+0x82/0x110 [ 2489.637238] release_nodes+0x33c/0x4e0 [ 2489.637242] ? devres_remove_group+0x1b0/0x1b0 [ 2489.637247] device_release_driver_internal+0x146/0x270 [ 2489.637251] driver_detach+0x73/0xe0 [ 2489.637254] bus_remove_driver+0xa1/0x170 [ 2489.637261] __x64_sys_delete_module+0x29e/0x320 [ 2489.637265] ? __ia32_sys_delete_module+0x320/0x320 [ 2489.637268] ? blkcg_exit_queue+0x20/0x20 [ 2489.637273] ? task_work_run+0x7d/0x100 [ 2489.637278] ? exit_to_usermode_loop+0x5b/0xf0 [ 2489.637281] do_syscall_64+0x73/0x160 [ 2489.637287] entry_SYSCALL_64_after_hwframe+0x44/0xa9 [ 2489.637290] RIP: 0033:0x7f95c3596fd7 The difference in release flow with and with no devm_free_irq is listed below: bus: 'platform': remove driver mlxreg-hotplug mlxreg_hotplug_remove(start) -> devm_free_irq (with new code) mlxreg_hotplug_remove (end) release_nodes (start) mlxreg-hotplug: DEVRES REL devm_hwmon_release (8 bytes) device: 'hwmon3': device_unregister PM: Removing info for No Bus:hwmon3 mlxreg-hotplug: DEVRES REL devm_kzalloc_release (88 bytes) mlxreg-hotplug: DEVRES REL devm_kzalloc_release (6 bytes) mlxreg-hotplug: DEVRES REL devm_kzalloc_release (5 bytes) mlxreg-hotplug: DEVRES REL devm_kzalloc_release (5 bytes) mlxreg-hotplug: DEVRES REL devm_kzalloc_release (5 bytes) mlxreg-hotplug: DEVRES REL devm_kzalloc_release (5 bytes) mlxreg-hotplug: DEVRES REL devm_kzalloc_release (5 bytes) mlxreg-hotplug: DEVRES REL devm_kzalloc_release (5 bytes) mlxreg-hotplug: DEVRES REL devm_kzalloc_release (5 bytes) mlxreg-hotplug: DEVRES REL devm_kzalloc_release (5 bytes) mlxreg-hotplug: DEVRES REL devm_kzalloc_release (5 bytes) mlxreg-hotplug: DEVRES REL devm_kzalloc_release (5 bytes) mlxreg-hotplug: DEVRES REL devm_irq_release (16 bytes) (no new code) mlxreg-hotplug: DEVRES REL devm_kzalloc_release (1376 bytes) ------------[ cut here ]------------ (no new code): ODEBUG: free active (active state 0) object type: work_struct hint: mlxreg_hotplug_work_handler release_nodes(end) driver: 'mlxreg-hotplug': driver_release Fixes: 1f976f6978bf ("platform/x86: Move Mellanox platform hotplug driver to platform/mellanox") Signed-off-by: Vadim Pasternak Signed-off-by: Andy Shevchenko --- drivers/platform/mellanox/mlxreg-hotplug.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/platform/mellanox/mlxreg-hotplug.c b/drivers/platform/mellanox/mlxreg-hotplug.c index 687ce6817d0d..f85a1b9d129b 100644 --- a/drivers/platform/mellanox/mlxreg-hotplug.c +++ b/drivers/platform/mellanox/mlxreg-hotplug.c @@ -694,6 +694,7 @@ static int mlxreg_hotplug_remove(struct platform_device *pdev) /* Clean interrupts setup. */ mlxreg_hotplug_unset_irq(priv); + devm_free_irq(&pdev->dev, priv->irq, priv); return 0; } From 6baac53e03c31a9612c8919da7b9ae4b34ba6f2a Mon Sep 17 00:00:00 2001 From: Daniel Smith Date: Fri, 24 May 2019 02:09:13 +0700 Subject: [PATCH 19/74] platform/x86: touchscreen_dmi: Add info for the CHUWI Hi10 Plus tablet. Added touch screen info for CHUWI Hi10 Plus tablet. Signed-off-by: Daniel Smith Signed-off-by: Andy Shevchenko --- drivers/platform/x86/touchscreen_dmi.c | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/drivers/platform/x86/touchscreen_dmi.c b/drivers/platform/x86/touchscreen_dmi.c index bd0856d2e825..1dbb53c3f1e7 100644 --- a/drivers/platform/x86/touchscreen_dmi.c +++ b/drivers/platform/x86/touchscreen_dmi.c @@ -91,6 +91,22 @@ static const struct ts_dmi_data chuwi_hi10_air_data = { .properties = chuwi_hi10_air_props, }; +static const struct property_entry chuwi_hi10_plus_props[] = { + PROPERTY_ENTRY_U32("touchscreen-min-x", 0), + PROPERTY_ENTRY_U32("touchscreen-min-y", 5), + PROPERTY_ENTRY_U32("touchscreen-size-x", 1914), + PROPERTY_ENTRY_U32("touchscreen-size-y", 1283), + PROPERTY_ENTRY_STRING("firmware-name", "gsl1680-chuwi-hi10plus.fw"), + PROPERTY_ENTRY_U32("silead,max-fingers", 10), + PROPERTY_ENTRY_BOOL("silead,home-button"), + { } +}; + +static const struct ts_dmi_data chuwi_hi10_plus_data = { + .acpi_name = "MSSL0017:00", + .properties = chuwi_hi10_plus_props, +}; + static const struct property_entry chuwi_vi8_props[] = { PROPERTY_ENTRY_U32("touchscreen-min-x", 4), PROPERTY_ENTRY_U32("touchscreen-min-y", 6), @@ -605,6 +621,15 @@ static const struct dmi_system_id touchscreen_dmi_table[] = { DMI_MATCH(DMI_PRODUCT_SKU, "P1W6_C109D_B"), }, }, + { + /* Chuwi Hi10 Plus (CWI527) */ + .driver_data = (void *)&chuwi_hi10_plus_data, + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Hampoo"), + DMI_MATCH(DMI_PRODUCT_NAME, "Hi10 plus tablet"), + DMI_MATCH(DMI_BOARD_NAME, "Cherry Trail CR"), + }, + }, { /* Chuwi Vi8 (CWI506) */ .driver_data = (void *)&chuwi_vi8_data, From 8d4b2daff2567ac189a83ed99aa2f0338afbd5f0 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Mon, 3 Jun 2019 21:23:38 +0200 Subject: [PATCH 20/74] platform/x86: Remove left-over BACKLIGHT_LCD_SUPPORT The CONFIG_BACKLIGHT_LCD_SUPPORT was removed in commit 8c5dc8d9f19c ("video: backlight: Remove useless BACKLIGHT_LCD_SUPPORT kernel symbol"). Options protected by CONFIG_BACKLIGHT_LCD_SUPPORT are now available directly. Signed-off-by: Krzysztof Kozlowski Signed-off-by: Andy Shevchenko --- drivers/platform/x86/Kconfig | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 85b92a95e4c8..ae1e74d5e888 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -905,7 +905,6 @@ config TOSHIBA_WMI config ACPI_CMPC tristate "CMPC Laptop Extras" depends on ACPI && INPUT - depends on BACKLIGHT_LCD_SUPPORT depends on RFKILL || RFKILL=n select BACKLIGHT_CLASS_DEVICE help @@ -1129,7 +1128,6 @@ config INTEL_OAKTRAIL config SAMSUNG_Q10 tristate "Samsung Q10 Extras" depends on ACPI - depends on BACKLIGHT_LCD_SUPPORT select BACKLIGHT_CLASS_DEVICE ---help--- This driver provides support for backlight control on Samsung Q10 From 92a74ce3c466011375bdf8282e64b13a7052aee8 Mon Sep 17 00:00:00 2001 From: Young Xiao <92siuyang@gmail.com> Date: Wed, 29 May 2019 09:55:51 +0800 Subject: [PATCH 21/74] platform/x86: intel_menlow: avoid null pointer deference error Fix a null pointer deference by acpi_driver_data() if device is null (dereference before check). We should only set cdev and check this is OK after we are sure device is not null. Signed-off-by: Young Xiao <92siuyang@gmail.com> Signed-off-by: Andy Shevchenko --- drivers/platform/x86/intel_menlow.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/platform/x86/intel_menlow.c b/drivers/platform/x86/intel_menlow.c index 77eb8709c931..b102f6dd5693 100644 --- a/drivers/platform/x86/intel_menlow.c +++ b/drivers/platform/x86/intel_menlow.c @@ -180,9 +180,13 @@ static int intel_menlow_memory_add(struct acpi_device *device) static int intel_menlow_memory_remove(struct acpi_device *device) { - struct thermal_cooling_device *cdev = acpi_driver_data(device); + struct thermal_cooling_device *cdev; - if (!device || !cdev) + if (!device) + return -EINVAL; + + cdev = acpi_driver_data(device); + if (!cdev) return -EINVAL; sysfs_remove_link(&device->dev.kobj, "thermal_cooling"); From 84f669b4b8c909f3588bc80416b47e3c185504de Mon Sep 17 00:00:00 2001 From: YueHaibing Date: Wed, 29 May 2019 22:38:44 +0800 Subject: [PATCH 22/74] platform/x86: pcengines-apuv2: Make two symbols static Fix sparse warnings: drivers/platform/x86/pcengines-apuv2.c:80:27: warning: symbol 'gpios_led_table' was not declared. Should it be static? drivers/platform/x86/pcengines-apuv2.c:113:27: warning: symbol 'gpios_key_table' was not declared. Should it be static? Reported-by: Hulk Robot Signed-off-by: YueHaibing Acked-By: Enrico Weigelt, metux IT consult Signed-off-by: Andy Shevchenko --- drivers/platform/x86/pcengines-apuv2.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/platform/x86/pcengines-apuv2.c b/drivers/platform/x86/pcengines-apuv2.c index c1ca931e1fab..b0d3110ae378 100644 --- a/drivers/platform/x86/pcengines-apuv2.c +++ b/drivers/platform/x86/pcengines-apuv2.c @@ -77,7 +77,7 @@ static const struct gpio_led_platform_data apu2_leds_pdata = { .leds = apu2_leds, }; -struct gpiod_lookup_table gpios_led_table = { +static struct gpiod_lookup_table gpios_led_table = { .dev_id = "leds-gpio", .table = { GPIO_LOOKUP_IDX(AMD_FCH_GPIO_DRIVER_NAME, APU2_GPIO_LINE_LED1, @@ -110,7 +110,7 @@ static const struct gpio_keys_platform_data apu2_keys_pdata = { .name = "apu2-keys", }; -struct gpiod_lookup_table gpios_key_table = { +static struct gpiod_lookup_table gpios_key_table = { .dev_id = "gpio-keys-polled", .table = { GPIO_LOOKUP_IDX(AMD_FCH_GPIO_DRIVER_NAME, APU2_GPIO_LINE_MODESW, From a2558e2478043209ef597a17951297abe23278e9 Mon Sep 17 00:00:00 2001 From: Colin Sindle Date: Mon, 3 Jun 2019 13:14:46 +0200 Subject: [PATCH 23/74] platform/x86: hp_accel: Add support for HP ProBook 450 G0 HP ProBook 450 G0 needs a non-standard mapping (x_inverted). Signed-off-by: Colin Sindle Signed-off-by: Andy Shevchenko --- drivers/platform/x86/hp_accel.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/platform/x86/hp_accel.c b/drivers/platform/x86/hp_accel.c index 7b12abe86b94..dcfc4f70e9f5 100644 --- a/drivers/platform/x86/hp_accel.c +++ b/drivers/platform/x86/hp_accel.c @@ -242,6 +242,7 @@ static const struct dmi_system_id lis3lv02d_dmi_ids[] = { AXIS_DMI_MATCH("HPB440G3", "HP ProBook 440 G3", x_inverted_usd), AXIS_DMI_MATCH("HPB440G4", "HP ProBook 440 G4", x_inverted), AXIS_DMI_MATCH("HPB442x", "HP ProBook 442", xy_rotated_left), + AXIS_DMI_MATCH("HPB450G0", "HP ProBook 450 G0", x_inverted), AXIS_DMI_MATCH("HPB452x", "HP ProBook 452", y_inverted), AXIS_DMI_MATCH("HPB522x", "HP ProBook 522", xy_swap), AXIS_DMI_MATCH("HPB532x", "HP ProBook 532", y_inverted), From 0b9dd93492eeaf7233b17c505c29cab10ef196b0 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Wed, 12 Jun 2019 14:12:51 +0200 Subject: [PATCH 24/74] platform/x86: acer-wmi: no need to check return value of debugfs_create functions When calling debugfs functions, there is no need to ever check the return value. The function can work or not, but the code logic should never do something different based on this. Also, because there is no need to save the file dentry, remove the variable that was saving it and just recursively delete the whole directory. Cc: "Lee, Chun-Yi" Cc: Darren Hart Cc: Andy Shevchenko Cc: platform-driver-x86@vger.kernel.org Cc: linux-kernel@vger.kernel.org Signed-off-by: Greg Kroah-Hartman Signed-off-by: Andy Shevchenko --- drivers/platform/x86/acer-wmi.c | 29 +++++------------------------ 1 file changed, 5 insertions(+), 24 deletions(-) diff --git a/drivers/platform/x86/acer-wmi.c b/drivers/platform/x86/acer-wmi.c index fcfeadd1301f..5d0cfe837f21 100644 --- a/drivers/platform/x86/acer-wmi.c +++ b/drivers/platform/x86/acer-wmi.c @@ -272,7 +272,6 @@ struct acer_data { struct acer_debug { struct dentry *root; - struct dentry *devices; u32 wmid_devices; }; @@ -2161,29 +2160,15 @@ static struct platform_device *acer_platform_device; static void remove_debugfs(void) { - debugfs_remove(interface->debug.devices); - debugfs_remove(interface->debug.root); + debugfs_remove_recursive(interface->debug.root); } -static int __init create_debugfs(void) +static void __init create_debugfs(void) { interface->debug.root = debugfs_create_dir("acer-wmi", NULL); - if (!interface->debug.root) { - pr_err("Failed to create debugfs directory"); - return -ENOMEM; - } - interface->debug.devices = debugfs_create_u32("devices", S_IRUGO, - interface->debug.root, - &interface->debug.wmid_devices); - if (!interface->debug.devices) - goto error_debugfs; - - return 0; - -error_debugfs: - remove_debugfs(); - return -ENOMEM; + debugfs_create_u32("devices", S_IRUGO, interface->debug.root, + &interface->debug.wmid_devices); } static int __init acer_wmi_init(void) @@ -2313,9 +2298,7 @@ static int __init acer_wmi_init(void) if (wmi_has_guid(WMID_GUID2)) { interface->debug.wmid_devices = get_wmid_devices(); - err = create_debugfs(); - if (err) - goto error_create_debugfs; + create_debugfs(); } /* Override any initial settings with values from the commandline */ @@ -2323,8 +2306,6 @@ static int __init acer_wmi_init(void) return 0; -error_create_debugfs: - platform_device_del(acer_platform_device); error_device_add: platform_device_put(acer_platform_device); error_device_alloc: From d2785d37ed6fdceb2d8d17058f09577dadf3e592 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Wed, 12 Jun 2019 14:12:52 +0200 Subject: [PATCH 25/74] platform/x86: asus-wmi: no need to check return value of debugfs_create functions When calling debugfs functions, there is no need to ever check the return value. The function can work or not, but the code logic should never do something different based on this. Cc: Corentin Chary Cc: Darren Hart Cc: Andy Shevchenko Cc: acpi4asus-user@lists.sourceforge.net Cc: platform-driver-x86@vger.kernel.org Cc: linux-kernel@vger.kernel.org Signed-off-by: Greg Kroah-Hartman Signed-off-by: Andy Shevchenko --- drivers/platform/x86/asus-wmi.c | 47 ++++++++------------------------- 1 file changed, 11 insertions(+), 36 deletions(-) diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index 12462726312c..7e946ce7f66f 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -2018,50 +2018,29 @@ static void asus_wmi_debugfs_exit(struct asus_wmi *asus) debugfs_remove_recursive(asus->debug.root); } -static int asus_wmi_debugfs_init(struct asus_wmi *asus) +static void asus_wmi_debugfs_init(struct asus_wmi *asus) { - struct dentry *dent; int i; asus->debug.root = debugfs_create_dir(asus->driver->name, NULL); - if (!asus->debug.root) { - pr_err("failed to create debugfs directory\n"); - goto error_debugfs; - } - dent = debugfs_create_x32("method_id", S_IRUGO | S_IWUSR, - asus->debug.root, &asus->debug.method_id); - if (!dent) - goto error_debugfs; + debugfs_create_x32("method_id", S_IRUGO | S_IWUSR, asus->debug.root, + &asus->debug.method_id); - dent = debugfs_create_x32("dev_id", S_IRUGO | S_IWUSR, - asus->debug.root, &asus->debug.dev_id); - if (!dent) - goto error_debugfs; + debugfs_create_x32("dev_id", S_IRUGO | S_IWUSR, asus->debug.root, + &asus->debug.dev_id); - dent = debugfs_create_x32("ctrl_param", S_IRUGO | S_IWUSR, - asus->debug.root, &asus->debug.ctrl_param); - if (!dent) - goto error_debugfs; + debugfs_create_x32("ctrl_param", S_IRUGO | S_IWUSR, asus->debug.root, + &asus->debug.ctrl_param); for (i = 0; i < ARRAY_SIZE(asus_wmi_debug_files); i++) { struct asus_wmi_debugfs_node *node = &asus_wmi_debug_files[i]; node->asus = asus; - dent = debugfs_create_file(node->name, S_IFREG | S_IRUGO, - asus->debug.root, node, - &asus_wmi_debugfs_io_ops); - if (!dent) { - pr_err("failed to create debug file: %s\n", node->name); - goto error_debugfs; - } + debugfs_create_file(node->name, S_IFREG | S_IRUGO, + asus->debug.root, node, + &asus_wmi_debugfs_io_ops); } - - return 0; - -error_debugfs: - asus_wmi_debugfs_exit(asus); - return -ENOMEM; } static int asus_wmi_fan_init(struct asus_wmi *asus) @@ -2175,14 +2154,10 @@ static int asus_wmi_add(struct platform_device *pdev) goto fail_wmi_handler; } - err = asus_wmi_debugfs_init(asus); - if (err) - goto fail_debugfs; + asus_wmi_debugfs_init(asus); return 0; -fail_debugfs: - wmi_remove_notify_handler(asus->driver->event_guid); fail_wmi_handler: asus_wmi_backlight_exit(asus); fail_backlight: From 9ea18802d04c46eb22927d78ea89556c31e015f9 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Wed, 12 Jun 2019 14:12:53 +0200 Subject: [PATCH 26/74] platform/x86: dell-laptop: no need to check return value of debugfs_create functions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When calling debugfs functions, there is no need to ever check the return value. The function can work or not, but the code logic should never do something different based on this. Cc: Matthew Garrett Cc: "Pali Rohár" Cc: Darren Hart Cc: Andy Shevchenko Cc: platform-driver-x86@vger.kernel.org Cc: linux-kernel@vger.kernel.org Signed-off-by: Greg Kroah-Hartman Reviewed-by: Pali Rohár Signed-off-by: Andy Shevchenko --- drivers/platform/x86/dell-laptop.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/platform/x86/dell-laptop.c b/drivers/platform/x86/dell-laptop.c index a561f653cf13..94a2f259031c 100644 --- a/drivers/platform/x86/dell-laptop.c +++ b/drivers/platform/x86/dell-laptop.c @@ -2176,9 +2176,8 @@ static int __init dell_init(void) kbd_led_init(&platform_device->dev); dell_laptop_dir = debugfs_create_dir("dell_laptop", NULL); - if (dell_laptop_dir != NULL) - debugfs_create_file("rfkill", 0444, dell_laptop_dir, NULL, - &dell_debugfs_fops); + debugfs_create_file("rfkill", 0444, dell_laptop_dir, NULL, + &dell_debugfs_fops); dell_laptop_register_notifier(&dell_laptop_notifier); From 17f1bf38c8821ce851d95708a1f1e226d17ec2c5 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Wed, 12 Jun 2019 14:12:54 +0200 Subject: [PATCH 27/74] platform/x86: ideapad-laptop: no need to check return value of debugfs_create functions When calling debugfs functions, there is no need to ever check the return value. The function can work or not, but the code logic should never do something different based on this. Cc: Ike Panhc Cc: Darren Hart Cc: Andy Shevchenko Cc: platform-driver-x86@vger.kernel.org Cc: linux-kernel@vger.kernel.org Signed-off-by: Greg Kroah-Hartman Signed-off-by: Andy Shevchenko --- drivers/platform/x86/ideapad-laptop.c | 36 ++++++--------------------- 1 file changed, 7 insertions(+), 29 deletions(-) diff --git a/drivers/platform/x86/ideapad-laptop.c b/drivers/platform/x86/ideapad-laptop.c index 2d94536dea88..54ea28df9229 100644 --- a/drivers/platform/x86/ideapad-laptop.c +++ b/drivers/platform/x86/ideapad-laptop.c @@ -330,34 +330,15 @@ static int debugfs_cfg_show(struct seq_file *s, void *data) } DEFINE_SHOW_ATTRIBUTE(debugfs_cfg); -static int ideapad_debugfs_init(struct ideapad_private *priv) +static void ideapad_debugfs_init(struct ideapad_private *priv) { - struct dentry *node; + struct dentry *dir; - priv->debug = debugfs_create_dir("ideapad", NULL); - if (priv->debug == NULL) { - pr_err("failed to create debugfs directory"); - goto errout; - } + dir = debugfs_create_dir("ideapad", NULL); + priv->debug = dir; - node = debugfs_create_file("cfg", S_IRUGO, priv->debug, priv, - &debugfs_cfg_fops); - if (!node) { - pr_err("failed to create cfg in debugfs"); - goto errout; - } - - node = debugfs_create_file("status", S_IRUGO, priv->debug, priv, - &debugfs_status_fops); - if (!node) { - pr_err("failed to create status in debugfs"); - goto errout; - } - - return 0; - -errout: - return -ENOMEM; + debugfs_create_file("cfg", S_IRUGO, dir, priv, &debugfs_cfg_fops); + debugfs_create_file("status", S_IRUGO, dir, priv, &debugfs_status_fops); } static void ideapad_debugfs_exit(struct ideapad_private *priv) @@ -1026,9 +1007,7 @@ static int ideapad_acpi_add(struct platform_device *pdev) if (ret) return ret; - ret = ideapad_debugfs_init(priv); - if (ret) - goto debugfs_failed; + ideapad_debugfs_init(priv); ret = ideapad_input_init(priv); if (ret) @@ -1085,7 +1064,6 @@ backlight_failed: ideapad_input_exit(priv); input_failed: ideapad_debugfs_exit(priv); -debugfs_failed: ideapad_sysfs_exit(priv); return ret; } From d30cdc9a8adb1d18a2eed47dceaa2998adf5f47d Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Wed, 12 Jun 2019 14:12:55 +0200 Subject: [PATCH 28/74] platform/x86: samsung-laptop: no need to check return value of debugfs_create functions When calling debugfs functions, there is no need to ever check the return value. The function can work or not, but the code logic should never do something different based on this. Cc: Corentin Chary Cc: Darren Hart Cc: Andy Shevchenko Cc: platform-driver-x86@vger.kernel.org Cc: linux-kernel@vger.kernel.org Signed-off-by: Greg Kroah-Hartman Signed-off-by: Andy Shevchenko --- drivers/platform/x86/samsung-laptop.c | 89 +++++++-------------------- 1 file changed, 23 insertions(+), 66 deletions(-) diff --git a/drivers/platform/x86/samsung-laptop.c b/drivers/platform/x86/samsung-laptop.c index 7b160ee98115..e84f11398c1b 100644 --- a/drivers/platform/x86/samsung-laptop.c +++ b/drivers/platform/x86/samsung-laptop.c @@ -1280,15 +1280,12 @@ static void samsung_debugfs_exit(struct samsung_laptop *samsung) debugfs_remove_recursive(samsung->debug.root); } -static int samsung_debugfs_init(struct samsung_laptop *samsung) +static void samsung_debugfs_init(struct samsung_laptop *samsung) { - struct dentry *dent; + struct dentry *root; - samsung->debug.root = debugfs_create_dir("samsung-laptop", NULL); - if (!samsung->debug.root) { - pr_err("failed to create debugfs directory"); - goto error_debugfs; - } + root = debugfs_create_dir("samsung-laptop", NULL); + samsung->debug.root = root; samsung->debug.f0000_wrapper.data = samsung->f0000_segment; samsung->debug.f0000_wrapper.size = 0xffff; @@ -1299,60 +1296,24 @@ static int samsung_debugfs_init(struct samsung_laptop *samsung) samsung->debug.sdiag_wrapper.data = samsung->sdiag; samsung->debug.sdiag_wrapper.size = strlen(samsung->sdiag); - dent = debugfs_create_u16("command", S_IRUGO | S_IWUSR, - samsung->debug.root, &samsung->debug.command); - if (!dent) - goto error_debugfs; - - dent = debugfs_create_u32("d0", S_IRUGO | S_IWUSR, samsung->debug.root, - &samsung->debug.data.d0); - if (!dent) - goto error_debugfs; - - dent = debugfs_create_u32("d1", S_IRUGO | S_IWUSR, samsung->debug.root, - &samsung->debug.data.d1); - if (!dent) - goto error_debugfs; - - dent = debugfs_create_u16("d2", S_IRUGO | S_IWUSR, samsung->debug.root, - &samsung->debug.data.d2); - if (!dent) - goto error_debugfs; - - dent = debugfs_create_u8("d3", S_IRUGO | S_IWUSR, samsung->debug.root, - &samsung->debug.data.d3); - if (!dent) - goto error_debugfs; - - dent = debugfs_create_blob("data", S_IRUGO | S_IWUSR, - samsung->debug.root, - &samsung->debug.data_wrapper); - if (!dent) - goto error_debugfs; - - dent = debugfs_create_blob("f0000_segment", S_IRUSR | S_IWUSR, - samsung->debug.root, - &samsung->debug.f0000_wrapper); - if (!dent) - goto error_debugfs; - - dent = debugfs_create_file("call", S_IFREG | S_IRUGO, - samsung->debug.root, samsung, - &samsung_laptop_call_fops); - if (!dent) - goto error_debugfs; - - dent = debugfs_create_blob("sdiag", S_IRUGO | S_IWUSR, - samsung->debug.root, - &samsung->debug.sdiag_wrapper); - if (!dent) - goto error_debugfs; - - return 0; - -error_debugfs: - samsung_debugfs_exit(samsung); - return -ENOMEM; + debugfs_create_u16("command", S_IRUGO | S_IWUSR, root, + &samsung->debug.command); + debugfs_create_u32("d0", S_IRUGO | S_IWUSR, root, + &samsung->debug.data.d0); + debugfs_create_u32("d1", S_IRUGO | S_IWUSR, root, + &samsung->debug.data.d1); + debugfs_create_u16("d2", S_IRUGO | S_IWUSR, root, + &samsung->debug.data.d2); + debugfs_create_u8("d3", S_IRUGO | S_IWUSR, root, + &samsung->debug.data.d3); + debugfs_create_blob("data", S_IRUGO | S_IWUSR, root, + &samsung->debug.data_wrapper); + debugfs_create_blob("f0000_segment", S_IRUSR | S_IWUSR, root, + &samsung->debug.f0000_wrapper); + debugfs_create_file("call", S_IFREG | S_IRUGO, root, samsung, + &samsung_laptop_call_fops); + debugfs_create_blob("sdiag", S_IRUGO | S_IWUSR, root, + &samsung->debug.sdiag_wrapper); } static void samsung_sabi_exit(struct samsung_laptop *samsung) @@ -1745,9 +1706,7 @@ static int __init samsung_init(void) if (ret) goto error_lid_handling; - ret = samsung_debugfs_init(samsung); - if (ret) - goto error_debugfs; + samsung_debugfs_init(samsung); samsung->pm_nb.notifier_call = samsung_pm_notification; register_pm_notifier(&samsung->pm_nb); @@ -1755,8 +1714,6 @@ static int __init samsung_init(void) samsung_platform_device = samsung->platform_device; return ret; -error_debugfs: - samsung_lid_handling_exit(samsung); error_lid_handling: samsung_leds_exit(samsung); error_leds: From d42c06c44baf31d13ff22fb3d583952c4b7b0ecd Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Wed, 12 Jun 2019 14:12:56 +0200 Subject: [PATCH 29/74] platform/x86: pmc_atom: no need to check return value of debugfs_create functions When calling debugfs functions, there is no need to ever check the return value. The function can work or not, but the code logic should never do something different based on this. Cc: Darren Hart Cc: Andy Shevchenko Cc: platform-driver-x86@vger.kernel.org Cc: linux-kernel@vger.kernel.org Signed-off-by: Greg Kroah-Hartman Signed-off-by: Andy Shevchenko --- drivers/platform/x86/pmc_atom.c | 43 ++++++++------------------------- 1 file changed, 10 insertions(+), 33 deletions(-) diff --git a/drivers/platform/x86/pmc_atom.c b/drivers/platform/x86/pmc_atom.c index b1d804376237..2104e1ad38d9 100644 --- a/drivers/platform/x86/pmc_atom.c +++ b/drivers/platform/x86/pmc_atom.c @@ -350,45 +350,24 @@ static int pmc_sleep_tmr_show(struct seq_file *s, void *unused) DEFINE_SHOW_ATTRIBUTE(pmc_sleep_tmr); -static void pmc_dbgfs_unregister(struct pmc_dev *pmc) +static void pmc_dbgfs_register(struct pmc_dev *pmc) { - debugfs_remove_recursive(pmc->dbgfs_dir); -} - -static int pmc_dbgfs_register(struct pmc_dev *pmc) -{ - struct dentry *dir, *f; + struct dentry *dir; dir = debugfs_create_dir("pmc_atom", NULL); - if (!dir) - return -ENOMEM; pmc->dbgfs_dir = dir; - f = debugfs_create_file("dev_state", S_IFREG | S_IRUGO, - dir, pmc, &pmc_dev_state_fops); - if (!f) - goto err; - - f = debugfs_create_file("pss_state", S_IFREG | S_IRUGO, - dir, pmc, &pmc_pss_state_fops); - if (!f) - goto err; - - f = debugfs_create_file("sleep_state", S_IFREG | S_IRUGO, - dir, pmc, &pmc_sleep_tmr_fops); - if (!f) - goto err; - - return 0; -err: - pmc_dbgfs_unregister(pmc); - return -ENODEV; + debugfs_create_file("dev_state", S_IFREG | S_IRUGO, dir, pmc, + &pmc_dev_state_fops); + debugfs_create_file("pss_state", S_IFREG | S_IRUGO, dir, pmc, + &pmc_pss_state_fops); + debugfs_create_file("sleep_state", S_IFREG | S_IRUGO, dir, pmc, + &pmc_sleep_tmr_fops); } #else -static int pmc_dbgfs_register(struct pmc_dev *pmc) +static void pmc_dbgfs_register(struct pmc_dev *pmc) { - return 0; } #endif /* CONFIG_DEBUG_FS */ @@ -500,9 +479,7 @@ static int pmc_setup_dev(struct pci_dev *pdev, const struct pci_device_id *ent) /* PMC hardware registers setup */ pmc_hw_reg_setup(pmc); - ret = pmc_dbgfs_register(pmc); - if (ret) - dev_warn(&pdev->dev, "debugfs register failed\n"); + pmc_dbgfs_register(pmc); /* Register platform clocks - PMC_PLT_CLK [0..5] */ ret = pmc_setup_clks(pdev, pmc->regmap, data); From 151675540a2f936280493f3d114c2a687ae1dc63 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Wed, 12 Jun 2019 14:12:57 +0200 Subject: [PATCH 30/74] platform/x86: intel_pmc: no need to check return value of debugfs_create functions When calling debugfs functions, there is no need to ever check the return value. The function can work or not, but the code logic should never do something different based on this. Cc: Rajneesh Bhardwaj Cc: Vishwanath Somayaji Cc: Darren Hart Cc: Andy Shevchenko Cc: platform-driver-x86@vger.kernel.org Cc: linux-kernel@vger.kernel.org Signed-off-by: Greg Kroah-Hartman Signed-off-by: Andy Shevchenko --- drivers/platform/x86/intel_pmc_core.c | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/drivers/platform/x86/intel_pmc_core.c b/drivers/platform/x86/intel_pmc_core.c index 1d902230ba61..27d6470e43ec 100644 --- a/drivers/platform/x86/intel_pmc_core.c +++ b/drivers/platform/x86/intel_pmc_core.c @@ -753,14 +753,11 @@ static void pmc_core_dbgfs_unregister(struct pmc_dev *pmcdev) debugfs_remove_recursive(pmcdev->dbgfs_dir); } -static int pmc_core_dbgfs_register(struct pmc_dev *pmcdev) +static void pmc_core_dbgfs_register(struct pmc_dev *pmcdev) { struct dentry *dir; dir = debugfs_create_dir("pmc_core", NULL); - if (!dir) - return -ENOMEM; - pmcdev->dbgfs_dir = dir; debugfs_create_file("slp_s0_residency_usec", 0444, dir, pmcdev, @@ -794,13 +791,10 @@ static int pmc_core_dbgfs_register(struct pmc_dev *pmcdev) debugfs_create_bool("slp_s0_dbg_latch", 0644, dir, &slps0_dbg_latch); } - - return 0; } #else -static inline int pmc_core_dbgfs_register(struct pmc_dev *pmcdev) +static inline void pmc_core_dbgfs_register(struct pmc_dev *pmcdev) { - return 0; } static inline void pmc_core_dbgfs_unregister(struct pmc_dev *pmcdev) @@ -862,7 +856,6 @@ static int pmc_core_probe(struct platform_device *pdev) struct pmc_dev *pmcdev = &pmc; const struct x86_cpu_id *cpu_id; u64 slp_s0_addr; - int err; if (device_initialized) return -ENODEV; @@ -896,12 +889,7 @@ static int pmc_core_probe(struct platform_device *pdev) pmcdev->pmc_xram_read_bit = pmc_core_check_read_lock_bit(); dmi_check_system(pmc_core_dmi_table); - err = pmc_core_dbgfs_register(pmcdev); - if (err < 0) { - dev_warn(&pdev->dev, "debugfs register failed.\n"); - iounmap(pmcdev->regbase); - return err; - } + pmc_core_dbgfs_register(pmcdev); device_initialized = true; dev_info(&pdev->dev, " initialized\n"); From 2a5753559ed30461f0aade19290009973a15ce5a Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Wed, 12 Jun 2019 14:12:58 +0200 Subject: [PATCH 31/74] platform/x86: intel_telemetry: no need to check return value of debugfs_create functions When calling debugfs functions, there is no need to ever check the return value. The function can work or not, but the code logic should never do something different based on this. Cc: Rajneesh Bhardwaj Cc: "David E. Box" Cc: Darren Hart Cc: Andy Shevchenko Cc: platform-driver-x86@vger.kernel.org Cc: linux-kernel@vger.kernel.org Signed-off-by: Greg Kroah-Hartman Signed-off-by: Andy Shevchenko --- .../platform/x86/intel_telemetry_debugfs.c | 76 ++++--------------- 1 file changed, 15 insertions(+), 61 deletions(-) diff --git a/drivers/platform/x86/intel_telemetry_debugfs.c b/drivers/platform/x86/intel_telemetry_debugfs.c index 98ba9185a27b..e84d3e983e0c 100644 --- a/drivers/platform/x86/intel_telemetry_debugfs.c +++ b/drivers/platform/x86/intel_telemetry_debugfs.c @@ -900,7 +900,7 @@ static int __init telemetry_debugfs_init(void) { const struct x86_cpu_id *id; int err; - struct dentry *f; + struct dentry *dir; /* Only APL supported for now */ id = x86_match_cpu(telemetry_debugfs_cpu_ids); @@ -923,68 +923,22 @@ static int __init telemetry_debugfs_init(void) register_pm_notifier(&pm_notifier); - err = -ENOMEM; - debugfs_conf->telemetry_dbg_dir = debugfs_create_dir("telemetry", NULL); - if (!debugfs_conf->telemetry_dbg_dir) - goto out_pm; - - f = debugfs_create_file("pss_info", S_IFREG | S_IRUGO, - debugfs_conf->telemetry_dbg_dir, NULL, - &telem_pss_states_fops); - if (!f) { - pr_err("pss_sample_info debugfs register failed\n"); - goto out; - } - - f = debugfs_create_file("ioss_info", S_IFREG | S_IRUGO, - debugfs_conf->telemetry_dbg_dir, NULL, - &telem_ioss_states_fops); - if (!f) { - pr_err("ioss_sample_info debugfs register failed\n"); - goto out; - } - - f = debugfs_create_file("soc_states", S_IFREG | S_IRUGO, - debugfs_conf->telemetry_dbg_dir, - NULL, &telem_soc_states_fops); - if (!f) { - pr_err("ioss_sample_info debugfs register failed\n"); - goto out; - } - - f = debugfs_create_file("s0ix_residency_usec", S_IFREG | S_IRUGO, - debugfs_conf->telemetry_dbg_dir, - NULL, &telem_s0ix_fops); - if (!f) { - pr_err("s0ix_residency_usec debugfs register failed\n"); - goto out; - } - - f = debugfs_create_file("pss_trace_verbosity", S_IFREG | S_IRUGO, - debugfs_conf->telemetry_dbg_dir, NULL, - &telem_pss_trc_verb_ops); - if (!f) { - pr_err("pss_trace_verbosity debugfs register failed\n"); - goto out; - } - - f = debugfs_create_file("ioss_trace_verbosity", S_IFREG | S_IRUGO, - debugfs_conf->telemetry_dbg_dir, NULL, - &telem_ioss_trc_verb_ops); - if (!f) { - pr_err("ioss_trace_verbosity debugfs register failed\n"); - goto out; - } + dir = debugfs_create_dir("telemetry", NULL); + debugfs_conf->telemetry_dbg_dir = dir; + debugfs_create_file("pss_info", S_IFREG | S_IRUGO, dir, NULL, + &telem_pss_states_fops); + debugfs_create_file("ioss_info", S_IFREG | S_IRUGO, dir, NULL, + &telem_ioss_states_fops); + debugfs_create_file("soc_states", S_IFREG | S_IRUGO, dir, NULL, + &telem_soc_states_fops); + debugfs_create_file("s0ix_residency_usec", S_IFREG | S_IRUGO, dir, NULL, + &telem_s0ix_fops); + debugfs_create_file("pss_trace_verbosity", S_IFREG | S_IRUGO, dir, NULL, + &telem_pss_trc_verb_ops); + debugfs_create_file("ioss_trace_verbosity", S_IFREG | S_IRUGO, dir, + NULL, &telem_ioss_trc_verb_ops); return 0; - -out: - debugfs_remove_recursive(debugfs_conf->telemetry_dbg_dir); - debugfs_conf->telemetry_dbg_dir = NULL; -out_pm: - unregister_pm_notifier(&pm_notifier); - - return err; } static void __exit telemetry_debugfs_exit(void) From cd10ee006ab504b2b9c5ed992cc06d3e110c6311 Mon Sep 17 00:00:00 2001 From: Yurii Pavlovskyi Date: Tue, 14 May 2019 20:50:30 +0200 Subject: [PATCH 32/74] platform/x86: asus-wmi: Fix hwmon device cleanup The driver does not clean up the hwmon device on exit or error. To reproduce the bug, repeat rmmod, insmod to verify that device number /sys/devices/platform/asus-nb-wmi/hwmon/hwmon?? grows every time. Replace call for registering device with devm_* version that unregisters it automatically. Signed-off-by: Yurii Pavlovskyi Reviewed-by: Daniel Drake Signed-off-by: Andy Shevchenko --- drivers/platform/x86/asus-wmi.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index 7e946ce7f66f..6a590ec4c4ef 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -1428,11 +1428,12 @@ __ATTRIBUTE_GROUPS(hwmon_attribute); static int asus_wmi_hwmon_init(struct asus_wmi *asus) { + struct device *dev = &asus->platform_device->dev; struct device *hwmon; - hwmon = hwmon_device_register_with_groups(&asus->platform_device->dev, - "asus", asus, - hwmon_attribute_groups); + hwmon = devm_hwmon_device_register_with_groups(dev, "asus", asus, + hwmon_attribute_groups); + if (IS_ERR(hwmon)) { pr_err("Could not register asus hwmon device\n"); return PTR_ERR(hwmon); From 8853a2f6498b244d0cfff5c11158e2f76e323975 Mon Sep 17 00:00:00 2001 From: Yurii Pavlovskyi Date: Tue, 14 May 2019 20:51:25 +0200 Subject: [PATCH 33/74] platform/x86: asus-wmi: Fix preserving keyboard backlight intensity on load The error code and return value are mixed up. The intensity is always set to 0 on load as kbd_led_read returns either 0 or negative value. To reproduce set backlight to maximum, reload driver and try to increase it using keyboard hotkey, the intensity will drop as a result. Correct the implementation. Signed-off-by: Yurii Pavlovskyi Reviewed-by: Daniel Drake Signed-off-by: Andy Shevchenko --- drivers/platform/x86/asus-wmi.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index 6a590ec4c4ef..b536e757df17 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -595,8 +595,7 @@ static int asus_wmi_led_init(struct asus_wmi *asus) goto error; } - led_val = kbd_led_read(asus, NULL, NULL); - if (led_val >= 0) { + if (!kbd_led_read(asus, &led_val, NULL)) { asus->kbd_led_wk = led_val; asus->kbd_led.name = "asus::kbd_backlight"; asus->kbd_led.flags = LED_BRIGHT_HW_CHANGED; From 98e865a522983f2afde075648ec9d15ea4bb9194 Mon Sep 17 00:00:00 2001 From: Yurii Pavlovskyi Date: Tue, 14 May 2019 20:54:50 +0200 Subject: [PATCH 34/74] platform/x86: asus-wmi: Increase input buffer size of WMI methods The asus-nb-wmi driver is matched by WMI alias but fails to load on TUF Gaming series laptops producing multiple ACPI errors in the kernel log. The input buffer for WMI method invocation size is 2 dwords, whereas 3 are expected by this model. FX505GM: .. Method (WMNB, 3, Serialized) { P8XH (Zero, 0x11) CreateDWordField (Arg2, Zero, IIA0) CreateDWordField (Arg2, 0x04, IIA1) CreateDWordField (Arg2, 0x08, IIA2) Local0 = (Arg1 & 0xFFFFFFFF) ... Compare with older K54C: ... Method (WMNB, 3, NotSerialized) { CreateDWordField (Arg2, 0x00, IIA0) CreateDWordField (Arg2, 0x04, IIA1) Local0 = (Arg1 & 0xFFFFFFFF) ... Increase buffer size to 3 dwords. No negative consequences of this change are expected, as the input buffer size is not verified. The original function is replaced by a wrapper for a new method passing value 0 for the last parameter. The new function will be used to control RGB keyboard backlight. Signed-off-by: Yurii Pavlovskyi Reviewed-by: Daniel Drake Signed-off-by: Andy Shevchenko --- drivers/platform/x86/asus-wmi.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index b536e757df17..c67f11e0d6e7 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -98,6 +98,7 @@ static bool ashs_present(void) struct bios_args { u32 arg0; u32 arg1; + u32 arg2; /* At least TUF Gaming series uses 3 dword input buffer. */ } __packed; /* @@ -224,11 +225,13 @@ static void asus_wmi_input_exit(struct asus_wmi *asus) asus->inputdev = NULL; } -int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1, u32 *retval) +static int asus_wmi_evaluate_method3(u32 method_id, + u32 arg0, u32 arg1, u32 arg2, u32 *retval) { struct bios_args args = { .arg0 = arg0, .arg1 = arg1, + .arg2 = arg2, }; struct acpi_buffer input = { (acpi_size) sizeof(args), &args }; struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; @@ -260,6 +263,11 @@ exit: return 0; } + +int asus_wmi_evaluate_method(u32 method_id, u32 arg0, u32 arg1, u32 *retval) +{ + return asus_wmi_evaluate_method3(method_id, arg0, arg1, 0, retval); +} EXPORT_SYMBOL_GPL(asus_wmi_evaluate_method); static int asus_wmi_evaluate_method_agfn(const struct acpi_buffer args) From e7488e58c7cfe4be0c52db68622a0397bb75258e Mon Sep 17 00:00:00 2001 From: Yurii Pavlovskyi Date: Tue, 14 May 2019 20:59:01 +0200 Subject: [PATCH 35/74] platform/x86: wmi: Add function to get _UID of WMI device Add a new function to acpi.h / wmi.c that returns _UID of the ACPI WMI device. For example, it returns "ATK" for the following declaration in DSDT: Device (ATKD) { Name (_HID, "PNP0C14" /* Windows Management Instrumentation Device */) // _HID: Hardware ID Name (_UID, "ATK") // _UID: Unique ID .. Generally, it is possible that multiple PNP0C14 ACPI devices are present in the system as mentioned in the commit message of commit bff431e49ff5 ("ACPI: WMI: Add ACPI-WMI mapping driver"). Therefore the _UID is returned for a specific ACPI device that declares the given GUID, to which it is also mapped by other methods of wmi module. Signed-off-by: Yurii Pavlovskyi Signed-off-by: Andy Shevchenko --- drivers/platform/x86/wmi.c | 19 +++++++++++++++++++ include/linux/acpi.h | 1 + 2 files changed, 20 insertions(+) diff --git a/drivers/platform/x86/wmi.c b/drivers/platform/x86/wmi.c index 7b26b6ccf1a0..b08ffb769cbe 100644 --- a/drivers/platform/x86/wmi.c +++ b/drivers/platform/x86/wmi.c @@ -635,6 +635,25 @@ bool wmi_has_guid(const char *guid_string) } EXPORT_SYMBOL_GPL(wmi_has_guid); +/** + * wmi_get_acpi_device_uid() - Get _UID name of ACPI device that defines GUID + * @guid_string: 36 char string of the form fa50ff2b-f2e8-45de-83fa-65417f2f49ba + * + * Find the _UID of ACPI device associated with this WMI GUID. + * + * Return: The ACPI _UID field value or NULL if the WMI GUID was not found + */ +char *wmi_get_acpi_device_uid(const char *guid_string) +{ + struct wmi_block *wblock = NULL; + + if (!find_guid(guid_string, &wblock)) + return NULL; + + return acpi_device_uid(wblock->acpi_device); +} +EXPORT_SYMBOL_GPL(wmi_get_acpi_device_uid); + static struct wmi_block *dev_to_wblock(struct device *dev) { return container_of(dev, struct wmi_block, dev.dev); diff --git a/include/linux/acpi.h b/include/linux/acpi.h index 98440df7fe42..d867a9a904f9 100644 --- a/include/linux/acpi.h +++ b/include/linux/acpi.h @@ -380,6 +380,7 @@ extern acpi_status wmi_install_notify_handler(const char *guid, extern acpi_status wmi_remove_notify_handler(const char *guid); extern acpi_status wmi_get_event_data(u32 event, struct acpi_buffer *out); extern bool wmi_has_guid(const char *guid); +extern char *wmi_get_acpi_device_uid(const char *guid); #endif /* CONFIG_ACPI_WMI */ From e0668f28888184f6c633110a37386f2d4a6fa00e Mon Sep 17 00:00:00 2001 From: Yurii Pavlovskyi Date: Tue, 14 May 2019 21:00:31 +0200 Subject: [PATCH 36/74] platform/x86: asus-wmi: Improve DSTS WMI method ID detection The DSTS method detection mistakenly selects DCTS instead of DSTS if nothing is returned when the method ID is not defined in WMNB. As a result, the control of keyboard backlight is not functional for TUF Gaming series laptops. Implement detection based on _UID of the WMI device instead. There is evidence that DCTS is handled by ACPI WMI devices that have _UID ASUSWMI, whereas none of the devices without ASUSWMI respond to DCTS and DSTS is used instead [1]. DSDT examples: FX505GM (_UID ATK): Method (WMNB, 3, Serialized) { ... If ((Local0 == 0x53545344)) { ... Return (Zero) } ... // No return } K54C (_UID ATK): Method (WMNB, 3, Serialized) { ... If ((Local0 == 0x53545344)) { ... Return (0x02) } ... Return (0xFFFFFFFE) } [1] Link: https://lkml.org/lkml/2019/4/11/322 Signed-off-by: Yurii Pavlovskyi Suggested-by: Daniel Drake Signed-off-by: Andy Shevchenko --- drivers/hid/hid-asus.c | 2 +- drivers/platform/x86/asus-wmi.c | 23 +++++++++++++++++++--- include/linux/platform_data/x86/asus-wmi.h | 4 ++-- 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c index 336aeaed1159..1d01fe23ca0c 100644 --- a/drivers/hid/hid-asus.c +++ b/drivers/hid/hid-asus.c @@ -396,7 +396,7 @@ static bool asus_kbd_wmi_led_control_present(struct hid_device *hdev) if (!IS_ENABLED(CONFIG_ASUS_WMI)) return false; - ret = asus_wmi_evaluate_method(ASUS_WMI_METHODID_DSTS2, + ret = asus_wmi_evaluate_method(ASUS_WMI_METHODID_DSTS, ASUS_WMI_DEVID_KBD_BACKLIGHT, 0, &value); hid_dbg(hdev, "WMI backlight check: rc %d value %x", ret, value); if (ret) diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index c67f11e0d6e7..ef526dcfeac5 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -83,6 +83,8 @@ MODULE_LICENSE("GPL"); #define USB_INTEL_XUSB2PR 0xD0 #define PCI_DEVICE_ID_INTEL_LYNXPOINT_LP_XHCI 0x9c31 +#define ASUS_ACPI_UID_ASUSWMI "ASUSWMI" + static const char * const ashs_ids[] = { "ATK4001", "ATK4002", NULL }; static bool ashs_present(void) @@ -1874,6 +1876,8 @@ static int asus_wmi_sysfs_init(struct platform_device *device) */ static int asus_wmi_platform_init(struct asus_wmi *asus) { + struct device *dev = &asus->platform_device->dev; + char *wmi_uid; int rv; /* INIT enable hotkeys on some models */ @@ -1903,11 +1907,24 @@ static int asus_wmi_platform_init(struct asus_wmi *asus) * Note, on most Eeepc, there is no way to check if a method exist * or note, while on notebooks, they returns 0xFFFFFFFE on failure, * but once again, SPEC may probably be used for that kind of things. + * + * Additionally at least TUF Gaming series laptops return nothing for + * unknown methods, so the detection in this way is not possible. + * + * There is strong indication that only ACPI WMI devices that have _UID + * equal to "ASUSWMI" use DCTS whereas those with "ATK" use DSTS. */ - if (!asus_wmi_evaluate_method(ASUS_WMI_METHODID_DSTS, 0, 0, NULL)) + wmi_uid = wmi_get_acpi_device_uid(ASUS_WMI_MGMT_GUID); + if (!wmi_uid) + return -ENODEV; + + if (!strcmp(wmi_uid, ASUS_ACPI_UID_ASUSWMI)) { + dev_info(dev, "Detected ASUSWMI, use DCTS\n"); + asus->dsts_id = ASUS_WMI_METHODID_DCTS; + } else { + dev_info(dev, "Detected %s, not ASUSWMI, use DSTS\n", wmi_uid); asus->dsts_id = ASUS_WMI_METHODID_DSTS; - else - asus->dsts_id = ASUS_WMI_METHODID_DSTS2; + } /* CWAP allow to define the behavior of the Fn+F2 key, * this method doesn't seems to be present on Eee PCs */ diff --git a/include/linux/platform_data/x86/asus-wmi.h b/include/linux/platform_data/x86/asus-wmi.h index bfba245636a7..0668f76df921 100644 --- a/include/linux/platform_data/x86/asus-wmi.h +++ b/include/linux/platform_data/x86/asus-wmi.h @@ -18,8 +18,8 @@ #define ASUS_WMI_METHODID_GDSP 0x50534447 /* Get DiSPlay output */ #define ASUS_WMI_METHODID_DEVP 0x50564544 /* DEVice Policy */ #define ASUS_WMI_METHODID_OSVR 0x5256534F /* OS VeRsion */ -#define ASUS_WMI_METHODID_DSTS 0x53544344 /* Device STatuS */ -#define ASUS_WMI_METHODID_DSTS2 0x53545344 /* Device STatuS #2*/ +#define ASUS_WMI_METHODID_DCTS 0x53544344 /* Device status (DCTS) */ +#define ASUS_WMI_METHODID_DSTS 0x53545344 /* Device status (DSTS) */ #define ASUS_WMI_METHODID_BSTS 0x53545342 /* Bios STatuS ? */ #define ASUS_WMI_METHODID_DEVS 0x53564544 /* DEVice Set */ #define ASUS_WMI_METHODID_CFVS 0x53564643 /* CPU Frequency Volt Set */ From 8abd752bd4733ed2e812fc37413ef80e13946c34 Mon Sep 17 00:00:00 2001 From: Yurii Pavlovskyi Date: Tue, 14 May 2019 21:01:24 +0200 Subject: [PATCH 37/74] platform/x86: asus-wmi: Refactor WMI event handling Refactor WMI event handling into separate functions for getting the event code and handling the retrieved event code as a preparation for introduction of WMI event queue support. Signed-off-by: Yurii Pavlovskyi Signed-off-by: Andy Shevchenko --- drivers/platform/x86/asus-wmi.c | 66 +++++++++++++++++++++------------ 1 file changed, 42 insertions(+), 24 deletions(-) diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index ef526dcfeac5..fc4ccdedb626 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -85,6 +85,8 @@ MODULE_LICENSE("GPL"); #define ASUS_ACPI_UID_ASUSWMI "ASUSWMI" +#define WMI_EVENT_MASK 0xFFFF + static const char * const ashs_ids[] = { "ATK4001", "ATK4002", NULL }; static bool ashs_present(void) @@ -1651,83 +1653,99 @@ static void asus_wmi_fnlock_update(struct asus_wmi *asus) asus_wmi_set_devstate(ASUS_WMI_DEVID_FNLOCK, mode, NULL); } -static void asus_wmi_notify(u32 value, void *context) +static int asus_wmi_get_event_code(u32 value) { - struct asus_wmi *asus = context; struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL }; union acpi_object *obj; acpi_status status; int code; - int orig_code; - unsigned int key_value = 1; - bool autorelease = 1; status = wmi_get_event_data(value, &response); - if (status != AE_OK) { - pr_err("bad event status 0x%x\n", status); - return; + if (ACPI_FAILURE(status)) { + pr_warn("Failed to get WMI notify code: %s\n", + acpi_format_exception(status)); + return -EIO; } obj = (union acpi_object *)response.pointer; - if (!obj || obj->type != ACPI_TYPE_INTEGER) - goto exit; + if (obj && obj->type == ACPI_TYPE_INTEGER) + code = (int)(obj->integer.value & WMI_EVENT_MASK); + else + code = -EIO; + + kfree(obj); + return code; +} + +static void asus_wmi_handle_event_code(int code, struct asus_wmi *asus) +{ + int orig_code; + unsigned int key_value = 1; + bool autorelease = 1; - code = obj->integer.value; orig_code = code; if (asus->driver->key_filter) { asus->driver->key_filter(asus->driver, &code, &key_value, &autorelease); if (code == ASUS_WMI_KEY_IGNORE) - goto exit; + return; } if (code >= NOTIFY_BRNUP_MIN && code <= NOTIFY_BRNUP_MAX) code = ASUS_WMI_BRN_UP; - else if (code >= NOTIFY_BRNDOWN_MIN && - code <= NOTIFY_BRNDOWN_MAX) + else if (code >= NOTIFY_BRNDOWN_MIN && code <= NOTIFY_BRNDOWN_MAX) code = ASUS_WMI_BRN_DOWN; if (code == ASUS_WMI_BRN_DOWN || code == ASUS_WMI_BRN_UP) { if (acpi_video_get_backlight_type() == acpi_backlight_vendor) { asus_wmi_backlight_notify(asus, orig_code); - goto exit; + return; } } if (code == NOTIFY_KBD_BRTUP) { kbd_led_set_by_kbd(asus, asus->kbd_led_wk + 1); - goto exit; + return; } if (code == NOTIFY_KBD_BRTDWN) { kbd_led_set_by_kbd(asus, asus->kbd_led_wk - 1); - goto exit; + return; } if (code == NOTIFY_KBD_BRTTOGGLE) { if (asus->kbd_led_wk == asus->kbd_led.max_brightness) kbd_led_set_by_kbd(asus, 0); else kbd_led_set_by_kbd(asus, asus->kbd_led_wk + 1); - goto exit; + return; } if (code == NOTIFY_FNLOCK_TOGGLE) { asus->fnlock_locked = !asus->fnlock_locked; asus_wmi_fnlock_update(asus); - goto exit; + return; } - if (is_display_toggle(code) && - asus->driver->quirks->no_display_toggle) - goto exit; + if (is_display_toggle(code) && asus->driver->quirks->no_display_toggle) + return; if (!sparse_keymap_report_event(asus->inputdev, code, key_value, autorelease)) pr_info("Unknown key %x pressed\n", code); +} -exit: - kfree(obj); +static void asus_wmi_notify(u32 value, void *context) +{ + struct asus_wmi *asus = context; + int code = asus_wmi_get_event_code(value); + + if (code < 0) { + pr_warn("Failed to get notify code: %d\n", code); + return; + } + + asus_wmi_handle_event_code(code, asus); } /* From 1a373d15e283937b51eaf5debf4fc31474c31436 Mon Sep 17 00:00:00 2001 From: Yurii Pavlovskyi Date: Tue, 14 May 2019 21:02:09 +0200 Subject: [PATCH 38/74] platform/x86: asus-wmi: Support WMI event queue Event codes are expected to be retrieved from a queue on at least some models. Specifically, very likely the ACPI WMI devices with _UID ATK are queued whereas those with ASUSWMI are not [1]. The WMI event codes are pushed into a circular buffer queue. After the INIT method is called, ACPI code is allowed to push events into this buffer. The INIT method cannot be reverted. If the module is unloaded and an event (such as hotkey press) gets emitted before inserting it back the events get processed delayed by one or if the queue overflows, additionally delayed by about 3 seconds. It might be considered a minor issue and no normal user would likely observe this (there is little reason unloading the driver), but it does significantly frustrate a developer who is unlucky enough to encounter this. Therefore, the fallback to unqueued behavior occurs whenever something unexpected happens. The fix flushes the old key codes out of the queue on load. After receiving event the queue is read until either ..FFFF or 1 is encountered. Also as noted in [1] it is checked whether notify code is equal to 0xFF before enabling queue processing in WMI notify handler. DSDT examples: FX505GM Device (ATKD) { .. Name (ATKQ, Package (0x10) { 0xFFFFFFFF, .. } Method (IANQ, 1, Serialized) { If ((AQNO >= 0x10)) { Local0 = 0x64 While ((Local0 && (AQNO >= 0x10))) { Local0-- Sleep (0x0A) } ... .. AQTI++ AQTI &= 0x0F ATKQ [AQTI] = Arg0 ... } Method (GANQ, 0, Serialized) { .. If (AQNO) { ... Local0 = DerefOf (ATKQ [AQHI]) AQHI++ AQHI &= 0x0F Return (Local0) } Return (One) } This code is almost identical to K54C, which does return Ones on empty queue. K54C: Method (GANQ, 0, Serialized) { If (AQNO) { ... Return (Local0) } Return (Ones) } [1] Link: https://lkml.org/lkml/2019/4/12/104 Signed-off-by: Yurii Pavlovskyi Suggested-by: Daniel Drake Signed-off-by: Andy Shevchenko --- drivers/platform/x86/asus-wmi.c | 73 ++++++++++++++++++++++++++++++--- 1 file changed, 68 insertions(+), 5 deletions(-) diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index fc4ccdedb626..83bf145e9ca5 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -84,6 +84,13 @@ MODULE_LICENSE("GPL"); #define PCI_DEVICE_ID_INTEL_LYNXPOINT_LP_XHCI 0x9c31 #define ASUS_ACPI_UID_ASUSWMI "ASUSWMI" +#define ASUS_ACPI_UID_ATK "ATK" + +#define WMI_EVENT_QUEUE_SIZE 0x10 +#define WMI_EVENT_QUEUE_END 0x1 +#define WMI_EVENT_MASK 0xFFFF +/* The WMI hotkey event value is always the same. */ +#define WMI_EVENT_VALUE_ATK 0xFF #define WMI_EVENT_MASK 0xFFFF @@ -150,6 +157,7 @@ struct asus_wmi { int dsts_id; int spec; int sfun; + bool wmi_event_queue; struct input_dev *inputdev; struct backlight_device *backlight_device; @@ -1738,14 +1746,52 @@ static void asus_wmi_handle_event_code(int code, struct asus_wmi *asus) static void asus_wmi_notify(u32 value, void *context) { struct asus_wmi *asus = context; - int code = asus_wmi_get_event_code(value); + int code; + int i; - if (code < 0) { - pr_warn("Failed to get notify code: %d\n", code); - return; + for (i = 0; i < WMI_EVENT_QUEUE_SIZE + 1; i++) { + code = asus_wmi_get_event_code(value); + + if (code < 0) { + pr_warn("Failed to get notify code: %d\n", code); + return; + } + + if (code == WMI_EVENT_QUEUE_END || code == WMI_EVENT_MASK) + return; + + asus_wmi_handle_event_code(code, asus); + + /* + * Double check that queue is present: + * ATK (with queue) uses 0xff, ASUSWMI (without) 0xd2. + */ + if (!asus->wmi_event_queue || value != WMI_EVENT_VALUE_ATK) + return; } - asus_wmi_handle_event_code(code, asus); + pr_warn("Failed to process event queue, last code: 0x%x\n", code); +} + +static int asus_wmi_notify_queue_flush(struct asus_wmi *asus) +{ + int code; + int i; + + for (i = 0; i < WMI_EVENT_QUEUE_SIZE + 1; i++) { + code = asus_wmi_get_event_code(WMI_EVENT_VALUE_ATK); + + if (code < 0) { + pr_warn("Failed to get event during flush: %d\n", code); + return code; + } + + if (code == WMI_EVENT_QUEUE_END || code == WMI_EVENT_MASK) + return 0; + } + + pr_warn("Failed to flush event queue\n"); + return -EIO; } /* @@ -1944,6 +1990,23 @@ static int asus_wmi_platform_init(struct asus_wmi *asus) asus->dsts_id = ASUS_WMI_METHODID_DSTS; } + /* + * Some devices can have multiple event codes stored in a queue before + * the module load if it was unloaded intermittently after calling + * the INIT method (enables event handling). The WMI notify handler is + * expected to retrieve all event codes until a retrieved code equals + * queue end marker (One or Ones). Old codes are flushed from the queue + * upon module load. Not enabling this when it should be has minimal + * visible impact so fall back if anything goes wrong. + */ + wmi_uid = wmi_get_acpi_device_uid(asus->driver->event_guid); + if (wmi_uid && !strcmp(wmi_uid, ASUS_ACPI_UID_ATK)) { + dev_info(dev, "Detected ATK, enable event queue\n"); + + if (!asus_wmi_notify_queue_flush(asus)) + asus->wmi_event_queue = true; + } + /* CWAP allow to define the behavior of the Fn+F2 key, * this method doesn't seems to be present on Eee PCs */ if (asus->driver->quirks->wapf >= 0) From 2b5767bf86ea261ed98dda96c91e3eee052887b8 Mon Sep 17 00:00:00 2001 From: Yurii Pavlovskyi Date: Tue, 14 May 2019 21:02:58 +0200 Subject: [PATCH 39/74] platform/x86: asus-nb-wmi: Add microphone mute key code The microphone mute key is missing from sparse keymap. It is present on FX505GM and possibly other laptops. Add the missing code. Also, comment on the fan mode switch key, which has the same code as the already used key. Signed-off-by: Yurii Pavlovskyi Signed-off-by: Andy Shevchenko --- drivers/platform/x86/asus-nb-wmi.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/platform/x86/asus-nb-wmi.c b/drivers/platform/x86/asus-nb-wmi.c index 59f3a37a44d7..02f3e9b621cf 100644 --- a/drivers/platform/x86/asus-nb-wmi.c +++ b/drivers/platform/x86/asus-nb-wmi.c @@ -476,6 +476,7 @@ static const struct key_entry asus_nb_wmi_keymap[] = { { KE_KEY, 0x6B, { KEY_TOUCHPAD_TOGGLE } }, { KE_IGNORE, 0x6E, }, /* Low Battery notification */ { KE_KEY, 0x7a, { KEY_ALS_TOGGLE } }, /* Ambient Light Sensor Toggle */ + { KE_KEY, 0x7c, { KEY_MICMUTE } }, { KE_KEY, 0x7D, { KEY_BLUETOOTH } }, /* Bluetooth Enable */ { KE_KEY, 0x7E, { KEY_BLUETOOTH } }, /* Bluetooth Disable */ { KE_KEY, 0x82, { KEY_CAMERA } }, @@ -490,7 +491,7 @@ static const struct key_entry asus_nb_wmi_keymap[] = { { KE_KEY, 0x92, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT + TV + DVI */ { KE_KEY, 0x93, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + CRT + TV + DVI */ { KE_KEY, 0x95, { KEY_MEDIA } }, - { KE_KEY, 0x99, { KEY_PHONE } }, + { KE_KEY, 0x99, { KEY_PHONE } }, /* Conflicts with fan mode switch */ { KE_KEY, 0xA0, { KEY_SWITCHVIDEOMODE } }, /* SDSP HDMI only */ { KE_KEY, 0xA1, { KEY_SWITCHVIDEOMODE } }, /* SDSP LCD + HDMI */ { KE_KEY, 0xA2, { KEY_SWITCHVIDEOMODE } }, /* SDSP CRT + HDMI */ From 1827f3f06aafe67c1d273025a26390d9d2bdd3f7 Mon Sep 17 00:00:00 2001 From: Yurii Pavlovskyi Date: Tue, 14 May 2019 21:03:50 +0200 Subject: [PATCH 40/74] platform/x86: asus-wmi: Refactor error handling Remove exit label as it is only used once from the point in code where no cleanup is required and return can be called immediately. Signed-off-by: Yurii Pavlovskyi Signed-off-by: Andy Shevchenko --- drivers/platform/x86/asus-wmi.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index 83bf145e9ca5..5cbd7fc365b3 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -255,7 +255,7 @@ static int asus_wmi_evaluate_method3(u32 method_id, &input, &output); if (ACPI_FAILURE(status)) - goto exit; + return -EIO; obj = (union acpi_object *)output.pointer; if (obj && obj->type == ACPI_TYPE_INTEGER) @@ -266,10 +266,6 @@ static int asus_wmi_evaluate_method3(u32 method_id, kfree(obj); -exit: - if (ACPI_FAILURE(status)) - return -EIO; - if (tmp == ASUS_WMI_UNSUPPORTED_METHOD) return -ENODEV; From 54a3121f00dad3227735085fd568cdee5a733696 Mon Sep 17 00:00:00 2001 From: Yurii Pavlovskyi Date: Tue, 14 May 2019 21:04:48 +0200 Subject: [PATCH 41/74] platform/x86: asus-wmi: Organize code into sections The driver has grown pretty big and will grow more, which makes it hard to navigate and understand. Add uniform comments to the code and ensure that it is sorted into logical sections. Signed-off-by: Yurii Pavlovskyi Signed-off-by: Andy Shevchenko --- drivers/platform/x86/asus-wmi.c | 89 +++++++++++++++++---------------- 1 file changed, 47 insertions(+), 42 deletions(-) diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index 5cbd7fc365b3..3a4e61cfa15d 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -200,6 +200,8 @@ struct asus_wmi { struct asus_wmi_driver *driver; }; +/* Input **********************************************************************/ + static int asus_wmi_input_init(struct asus_wmi *asus) { int err; @@ -237,6 +239,8 @@ static void asus_wmi_input_exit(struct asus_wmi *asus) asus->inputdev = NULL; } +/* WMI ************************************************************************/ + static int asus_wmi_evaluate_method3(u32 method_id, u32 arg0, u32 arg1, u32 arg2, u32 *retval) { @@ -349,9 +353,8 @@ static int asus_wmi_get_devstate_simple(struct asus_wmi *asus, u32 dev_id) ASUS_WMI_DSTS_STATUS_BIT); } -/* - * LEDs - */ +/* LEDs ***********************************************************************/ + /* * These functions actually update the LED's, and are called from a * workqueue. By doing this as separate work rather than when the LED @@ -661,6 +664,7 @@ error: return rv; } +/* RF *************************************************************************/ /* * PCI hotplug (for wlan rfkill) @@ -1083,6 +1087,8 @@ exit: return result; } +/* Quirks *********************************************************************/ + static void asus_wmi_set_xusb2pr(struct asus_wmi *asus) { struct pci_dev *xhci_pdev; @@ -1115,9 +1121,8 @@ static void asus_wmi_set_als(void) asus_wmi_set_devstate(ASUS_WMI_DEVID_ALS_ENABLE, 1, NULL); } -/* - * Hwmon device - */ +/* Hwmon device ***************************************************************/ + static int asus_hwmon_agfn_fan_speed_read(struct asus_wmi *asus, int fan, int *speed) { @@ -1456,9 +1461,27 @@ static int asus_wmi_hwmon_init(struct asus_wmi *asus) return 0; } -/* - * Backlight - */ +static int asus_wmi_fan_init(struct asus_wmi *asus) +{ + int status; + + asus->asus_hwmon_pwm = -1; + asus->asus_hwmon_num_fans = -1; + asus->asus_hwmon_fan_manual_mode = false; + + status = asus_hwmon_get_fan_number(asus, &asus->asus_hwmon_num_fans); + if (status) { + asus->asus_hwmon_num_fans = 0; + pr_warn("Could not determine number of fans: %d\n", status); + return -ENXIO; + } + + pr_info("Number of fans: %d\n", asus->asus_hwmon_num_fans); + return 0; +} + +/* Backlight ******************************************************************/ + static int read_backlight_power(struct asus_wmi *asus) { int ret; @@ -1640,6 +1663,8 @@ static int is_display_toggle(int code) return 0; } +/* Fn-lock ********************************************************************/ + static bool asus_wmi_has_fnlock_key(struct asus_wmi *asus) { u32 result; @@ -1657,6 +1682,8 @@ static void asus_wmi_fnlock_update(struct asus_wmi *asus) asus_wmi_set_devstate(ASUS_WMI_DEVID_FNLOCK, mode, NULL); } +/* WMI events *****************************************************************/ + static int asus_wmi_get_event_code(u32 value) { struct acpi_buffer response = { ACPI_ALLOCATE_BUFFER, NULL }; @@ -1790,9 +1817,8 @@ static int asus_wmi_notify_queue_flush(struct asus_wmi *asus) return -EIO; } -/* - * Sys helpers - */ +/* Sysfs **********************************************************************/ + static int parse_arg(const char *buf, unsigned long count, int *val) { if (!count) @@ -1931,9 +1957,8 @@ static int asus_wmi_sysfs_init(struct platform_device *device) return sysfs_create_group(&device->dev.kobj, &platform_attribute_group); } -/* - * Platform device - */ +/* Platform device ************************************************************/ + static int asus_wmi_platform_init(struct asus_wmi *asus) { struct device *dev = &asus->platform_device->dev; @@ -2017,9 +2042,8 @@ static void asus_wmi_platform_exit(struct asus_wmi *asus) asus_wmi_sysfs_exit(asus->platform_device); } -/* - * debugfs - */ +/* debugfs ********************************************************************/ + struct asus_wmi_debugfs_node { struct asus_wmi *asus; char *name; @@ -2145,28 +2169,8 @@ static void asus_wmi_debugfs_init(struct asus_wmi *asus) } } -static int asus_wmi_fan_init(struct asus_wmi *asus) -{ - int status; +/* Init / exit ****************************************************************/ - asus->asus_hwmon_pwm = -1; - asus->asus_hwmon_num_fans = -1; - asus->asus_hwmon_fan_manual_mode = false; - - status = asus_hwmon_get_fan_number(asus, &asus->asus_hwmon_num_fans); - if (status) { - asus->asus_hwmon_num_fans = 0; - pr_warn("Could not determine number of fans: %d\n", status); - return -ENXIO; - } - - pr_info("Number of fans: %d\n", asus->asus_hwmon_num_fans); - return 0; -} - -/* - * WMI Driver - */ static int asus_wmi_add(struct platform_device *pdev) { struct platform_driver *pdrv = to_platform_driver(pdev->dev.driver); @@ -2294,9 +2298,8 @@ static int asus_wmi_remove(struct platform_device *device) return 0; } -/* - * Platform driver - hibernate/resume callbacks - */ +/* Platform driver - hibernate/resume callbacks *******************************/ + static int asus_hotk_thaw(struct device *device) { struct asus_wmi *asus = dev_get_drvdata(device); @@ -2372,6 +2375,8 @@ static const struct dev_pm_ops asus_pm_ops = { .resume = asus_hotk_resume, }; +/* Registration ***************************************************************/ + static int asus_wmi_probe(struct platform_device *pdev) { struct platform_driver *pdrv = to_platform_driver(pdev->dev.driver); From 4fd1982545398bf801fe13455e02bc01b6402de6 Mon Sep 17 00:00:00 2001 From: Yurii Pavlovskyi Date: Tue, 14 May 2019 21:05:46 +0200 Subject: [PATCH 42/74] platform/x86: asus-wmi: Enhance detection of thermal data The obviously wrong value 1 for temperature device ID in this driver is returned by at least some devices, including TUF Gaming series laptops, instead of 0 as expected previously. Observable effect is that a temp1_input in hwmon reads temperature near absolute zero. Consider 0.1 K an erroneous value in addition to 0 K. Signed-off-by: Yurii Pavlovskyi Signed-off-by: Andy Shevchenko --- drivers/platform/x86/asus-wmi.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index 3a4e61cfa15d..a1d85667383c 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -1428,8 +1428,11 @@ static umode_t asus_hwmon_sysfs_is_visible(struct kobject *kobj, else ok = fan_attr <= asus->asus_hwmon_num_fans; } else if (dev_id == ASUS_WMI_DEVID_THERMAL_CTRL) { - /* If value is zero, something is clearly wrong */ - if (!value) + /* + * If the temperature value in deci-Kelvin is near the absolute + * zero temperature, something is clearly wrong + */ + if (value == 0 || value == 1) ok = false; } else if (fan_attr <= asus->asus_hwmon_num_fans && fan_attr != -1) { ok = true; From b096f626a6827ad2ced5ebdbdc04e62422d463f6 Mon Sep 17 00:00:00 2001 From: Yurii Pavlovskyi Date: Tue, 14 May 2019 21:07:05 +0200 Subject: [PATCH 43/74] platform/x86: asus-wmi: Switch fan boost mode The WMI exposes a write-only device ID where up to three fan modes can be switched on some laptops (TUF Gaming FX505GM). There is a hotkey combination Fn-F5 that does have a fan icon, which is designed to toggle between fan modes. The DSTS of the device ID returns information about the presence of this capability and the presence of each of the two additional fan modes as a bitmask (0x01 - overboost present, 0x02 - silent present) [1]. Add a SysFS entry that reads the last written value and updates value in WMI on write and a hotkey handler that toggles the modes taking into account their availability according to DSTS. Modes: * 0x00 - normal or balanced, * 0x01 - overboost, increased fan RPM, * 0x02 - silent, decreased fan RPM [1] Link: https://lkml.org/lkml/2019/4/12/110 Signed-off-by: Yurii Pavlovskyi Suggested-by: Daniel Drake Signed-off-by: Andy Shevchenko --- .../ABI/testing/sysfs-platform-asus-wmi | 10 ++ drivers/platform/x86/asus-wmi.c | 151 +++++++++++++++++- include/linux/platform_data/x86/asus-wmi.h | 1 + 3 files changed, 154 insertions(+), 8 deletions(-) diff --git a/Documentation/ABI/testing/sysfs-platform-asus-wmi b/Documentation/ABI/testing/sysfs-platform-asus-wmi index 019e1e29370e..87ae5cc983bf 100644 --- a/Documentation/ABI/testing/sysfs-platform-asus-wmi +++ b/Documentation/ABI/testing/sysfs-platform-asus-wmi @@ -36,3 +36,13 @@ KernelVersion: 3.5 Contact: "AceLan Kao" Description: Resume on lid open. 1 means on, 0 means off. + +What: /sys/devices/platform//fan_mode +Date: Apr 2019 +KernelVersion: 5.2 +Contact: "Yurii Pavlovskyi" +Description: + Fan boost mode: + * 0 - normal, + * 1 - overboost, + * 2 - silent diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index a1d85667383c..5712bc56fa10 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -70,6 +70,7 @@ MODULE_LICENSE("GPL"); #define NOTIFY_KBD_BRTUP 0xc4 #define NOTIFY_KBD_BRTDWN 0xc5 #define NOTIFY_KBD_BRTTOGGLE 0xc7 +#define NOTIFY_KBD_FBM 0x99 #define ASUS_WMI_FNLOCK_BIOS_DISABLED BIT(0) @@ -80,6 +81,13 @@ MODULE_LICENSE("GPL"); #define ASUS_FAN_CTRL_MANUAL 1 #define ASUS_FAN_CTRL_AUTO 2 +#define ASUS_FAN_MODE_NORMAL 0 +#define ASUS_FAN_MODE_OVERBOOST 1 +#define ASUS_FAN_MODE_OVERBOOST_MASK 0x01 +#define ASUS_FAN_MODE_SILENT 2 +#define ASUS_FAN_MODE_SILENT_MASK 0x02 +#define ASUS_FAN_MODES_MASK 0x03 + #define USB_INTEL_XUSB2PR 0xD0 #define PCI_DEVICE_ID_INTEL_LYNXPOINT_LP_XHCI 0x9c31 @@ -187,6 +195,10 @@ struct asus_wmi { int asus_hwmon_num_fans; int asus_hwmon_pwm; + bool fan_mode_available; + u8 fan_mode_mask; + u8 fan_mode; + struct hotplug_slot hotplug_slot; struct mutex hotplug_lock; struct mutex wmi_lock; @@ -1483,6 +1495,116 @@ static int asus_wmi_fan_init(struct asus_wmi *asus) return 0; } +/* Fan mode *******************************************************************/ + +static int fan_mode_check_present(struct asus_wmi *asus) +{ + u32 result; + int err; + + asus->fan_mode_available = false; + + err = asus_wmi_get_devstate(asus, ASUS_WMI_DEVID_FAN_MODE, &result); + if (err) { + if (err == -ENODEV) + return 0; + else + return err; + } + + if ((result & ASUS_WMI_DSTS_PRESENCE_BIT) && + (result & ASUS_FAN_MODES_MASK)) { + asus->fan_mode_available = true; + asus->fan_mode_mask = result & ASUS_FAN_MODES_MASK; + } + + return 0; +} + +static int fan_mode_write(struct asus_wmi *asus) +{ + int err; + u8 value; + u32 retval; + + value = asus->fan_mode; + + pr_info("Set fan mode: %u\n", value); + err = asus_wmi_set_devstate(ASUS_WMI_DEVID_FAN_MODE, value, &retval); + + if (err) { + pr_warn("Failed to set fan mode: %d\n", err); + return err; + } + + if (retval != 1) { + pr_warn("Failed to set fan mode (retval): 0x%x\n", retval); + return -EIO; + } + + return 0; +} + +static int fan_mode_switch_next(struct asus_wmi *asus) +{ + if (asus->fan_mode == ASUS_FAN_MODE_NORMAL) { + if (asus->fan_mode_mask & ASUS_FAN_MODE_OVERBOOST_MASK) + asus->fan_mode = ASUS_FAN_MODE_OVERBOOST; + else if (asus->fan_mode_mask & ASUS_FAN_MODE_SILENT_MASK) + asus->fan_mode = ASUS_FAN_MODE_SILENT; + } else if (asus->fan_mode == ASUS_FAN_MODE_OVERBOOST) { + if (asus->fan_mode_mask & ASUS_FAN_MODE_SILENT_MASK) + asus->fan_mode = ASUS_FAN_MODE_SILENT; + else + asus->fan_mode = ASUS_FAN_MODE_NORMAL; + } else { + asus->fan_mode = ASUS_FAN_MODE_NORMAL; + } + + return fan_mode_write(asus); +} + +static ssize_t fan_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct asus_wmi *asus = dev_get_drvdata(dev); + + return scnprintf(buf, PAGE_SIZE, "%d\n", asus->fan_mode); +} + +static ssize_t fan_mode_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int result; + u8 new_mode; + + struct asus_wmi *asus = dev_get_drvdata(dev); + + result = kstrtou8(buf, 10, &new_mode); + if (result < 0) { + pr_warn("Trying to store invalid value\n"); + return result; + } + + if (new_mode == ASUS_FAN_MODE_OVERBOOST) { + if (!(asus->fan_mode_mask & ASUS_FAN_MODE_OVERBOOST_MASK)) + return -EINVAL; + } else if (new_mode == ASUS_FAN_MODE_SILENT) { + if (!(asus->fan_mode_mask & ASUS_FAN_MODE_SILENT_MASK)) + return -EINVAL; + } else if (new_mode != ASUS_FAN_MODE_NORMAL) { + return -EINVAL; + } + + asus->fan_mode = new_mode; + fan_mode_write(asus); + + return result; +} + +// Fan mode: 0 - normal, 1 - overboost, 2 - silent +static DEVICE_ATTR_RW(fan_mode); + /* Backlight ******************************************************************/ static int read_backlight_power(struct asus_wmi *asus) @@ -1761,6 +1883,11 @@ static void asus_wmi_handle_event_code(int code, struct asus_wmi *asus) return; } + if (asus->fan_mode_available && code == NOTIFY_KBD_FBM) { + fan_mode_switch_next(asus); + return; + } + if (is_display_toggle(code) && asus->driver->quirks->no_display_toggle) return; @@ -1917,6 +2044,7 @@ static struct attribute *platform_attributes[] = { &dev_attr_touchpad.attr, &dev_attr_lid_resume.attr, &dev_attr_als_enable.attr, + &dev_attr_fan_mode.attr, NULL }; @@ -1938,6 +2066,8 @@ static umode_t asus_sysfs_is_visible(struct kobject *kobj, devid = ASUS_WMI_DEVID_LID_RESUME; else if (attr == &dev_attr_als_enable.attr) devid = ASUS_WMI_DEVID_ALS_ENABLE; + else if (attr == &dev_attr_fan_mode.attr) + ok = asus->fan_mode_available; if (devid != -1) ok = !(asus_wmi_get_devstate_simple(asus, devid) < 0); @@ -2037,12 +2167,7 @@ static int asus_wmi_platform_init(struct asus_wmi *asus) asus_wmi_set_devstate(ASUS_WMI_DEVID_CWAP, asus->driver->quirks->wapf, NULL); - return asus_wmi_sysfs_init(asus->platform_device); -} - -static void asus_wmi_platform_exit(struct asus_wmi *asus) -{ - asus_wmi_sysfs_exit(asus->platform_device); + return 0; } /* debugfs ********************************************************************/ @@ -2200,6 +2325,14 @@ static int asus_wmi_add(struct platform_device *pdev) if (err) goto fail_platform; + err = fan_mode_check_present(asus); + if (err) + goto fail_fan_mode; + + err = asus_wmi_sysfs_init(asus->platform_device); + if (err) + goto fail_sysfs; + err = asus_wmi_input_init(asus); if (err) goto fail_input; @@ -2277,7 +2410,9 @@ fail_leds: fail_hwmon: asus_wmi_input_exit(asus); fail_input: - asus_wmi_platform_exit(asus); + asus_wmi_sysfs_exit(asus->platform_device); +fail_sysfs: +fail_fan_mode: fail_platform: kfree(asus); return err; @@ -2294,7 +2429,7 @@ static int asus_wmi_remove(struct platform_device *device) asus_wmi_led_exit(asus); asus_wmi_rfkill_exit(asus); asus_wmi_debugfs_exit(asus); - asus_wmi_platform_exit(asus); + asus_wmi_sysfs_exit(asus->platform_device); asus_hwmon_fan_set_auto(asus); kfree(asus); diff --git a/include/linux/platform_data/x86/asus-wmi.h b/include/linux/platform_data/x86/asus-wmi.h index 0668f76df921..8551156b8dca 100644 --- a/include/linux/platform_data/x86/asus-wmi.h +++ b/include/linux/platform_data/x86/asus-wmi.h @@ -57,6 +57,7 @@ #define ASUS_WMI_DEVID_KBD_BACKLIGHT 0x00050021 #define ASUS_WMI_DEVID_LIGHT_SENSOR 0x00050022 /* ?? */ #define ASUS_WMI_DEVID_LIGHTBAR 0x00050025 +#define ASUS_WMI_DEVID_FAN_MODE 0x00110018 /* Misc */ #define ASUS_WMI_DEVID_CAMERA 0x00060013 From 3e58167ac3703c9b15a8600ab559d239f3d4dd97 Mon Sep 17 00:00:00 2001 From: Yurii Pavlovskyi Date: Tue, 14 May 2019 21:07:46 +0200 Subject: [PATCH 44/74] platform/x86: asus-wmi: Do not disable keyboard backlight on unloading The keyboard backlight is automatically disabled when the module is unloaded as it is exposed as a ledclass device. Change this behavior to ignore setting brightness when the device is in unloading state. Signed-off-by: Yurii Pavlovskyi Signed-off-by: Andy Shevchenko --- drivers/platform/x86/asus-wmi.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index 5712bc56fa10..508e6ad47793 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -471,6 +471,10 @@ static void do_kbd_led_set(struct led_classdev *led_cdev, int value) static void kbd_led_set(struct led_classdev *led_cdev, enum led_brightness value) { + /* Prevent disabling keyboard backlight on module unregister */ + if (led_cdev->flags & LED_UNREGISTERING) + return; + do_kbd_led_set(led_cdev, value); } From a48e23385fcf397e69e2a75d72a81c545ec8bec2 Mon Sep 17 00:00:00 2001 From: Mattias Jacobsson <2pi@mok.nu> Date: Mon, 27 May 2019 18:21:29 +0200 Subject: [PATCH 45/74] platform/x86: wmi: add context pointer field to struct wmi_device_id When using wmi_install_notify_handler() to initialize a WMI handler a data pointer can be supplied which will be passed on to the notification handler. No similar feature exist when handling WMI events via struct wmi_driver. Add a context field pointer to struct wmi_device_id and add a function find_guid_context() to retrieve that context pointer. Signed-off-by: Mattias Jacobsson <2pi@mok.nu> Signed-off-by: Andy Shevchenko --- drivers/platform/x86/wmi.c | 22 ++++++++++++++++++++++ include/linux/mod_devicetable.h | 1 + 2 files changed, 23 insertions(+) diff --git a/drivers/platform/x86/wmi.c b/drivers/platform/x86/wmi.c index b08ffb769cbe..f3be1c008856 100644 --- a/drivers/platform/x86/wmi.c +++ b/drivers/platform/x86/wmi.c @@ -146,6 +146,28 @@ static bool find_guid(const char *guid_string, struct wmi_block **out) return false; } +static const void *find_guid_context(struct wmi_block *wblock, + struct wmi_driver *wdriver) +{ + const struct wmi_device_id *id; + uuid_le guid_input; + + if (wblock == NULL || wdriver == NULL) + return NULL; + if (wdriver->id_table == NULL) + return NULL; + + id = wdriver->id_table; + while (*id->guid_string) { + if (uuid_le_to_bin(id->guid_string, &guid_input)) + continue; + if (!memcmp(wblock->gblock.guid, &guid_input, 16)) + return id->context; + id++; + } + return NULL; +} + static int get_subobj_info(acpi_handle handle, const char *pathname, struct acpi_device_info **info) { diff --git a/include/linux/mod_devicetable.h b/include/linux/mod_devicetable.h index 448621c32e4d..09366859aac2 100644 --- a/include/linux/mod_devicetable.h +++ b/include/linux/mod_devicetable.h @@ -798,6 +798,7 @@ struct tee_client_device_id { */ struct wmi_device_id { const char guid_string[UUID_STRING_LEN+1]; + const void *context; }; #endif /* LINUX_MOD_DEVICETABLE_H */ From 440c4983de262f78033ec58f6abcd199a664327d Mon Sep 17 00:00:00 2001 From: Mattias Jacobsson <2pi@mok.nu> Date: Mon, 27 May 2019 18:21:30 +0200 Subject: [PATCH 46/74] platform/x86: wmi: add context argument to the probe function The struct wmi_device_id has a context pointer field, forward this pointer as an argument to the probe function in struct wmi_driver. Update existing users of the same probe function to accept this new context argument. Signed-off-by: Mattias Jacobsson <2pi@mok.nu> Signed-off-by: Andy Shevchenko --- drivers/platform/x86/dell-smbios-wmi.c | 2 +- drivers/platform/x86/dell-wmi-descriptor.c | 3 ++- drivers/platform/x86/dell-wmi.c | 2 +- drivers/platform/x86/huawei-wmi.c | 2 +- drivers/platform/x86/intel-wmi-thunderbolt.c | 3 ++- drivers/platform/x86/wmi-bmof.c | 2 +- drivers/platform/x86/wmi.c | 3 ++- include/linux/wmi.h | 2 +- 8 files changed, 11 insertions(+), 8 deletions(-) diff --git a/drivers/platform/x86/dell-smbios-wmi.c b/drivers/platform/x86/dell-smbios-wmi.c index c3ed3c8c17b9..add2687079f7 100644 --- a/drivers/platform/x86/dell-smbios-wmi.c +++ b/drivers/platform/x86/dell-smbios-wmi.c @@ -146,7 +146,7 @@ fail_smbios_cmd: return ret; } -static int dell_smbios_wmi_probe(struct wmi_device *wdev) +static int dell_smbios_wmi_probe(struct wmi_device *wdev, const void *context) { struct wmi_driver *wdriver = container_of(wdev->dev.driver, struct wmi_driver, driver); diff --git a/drivers/platform/x86/dell-wmi-descriptor.c b/drivers/platform/x86/dell-wmi-descriptor.c index 14ab250b7d5a..9994fd1a5acf 100644 --- a/drivers/platform/x86/dell-wmi-descriptor.c +++ b/drivers/platform/x86/dell-wmi-descriptor.c @@ -106,7 +106,8 @@ EXPORT_SYMBOL_GPL(dell_wmi_get_hotfix); * WMI buffer length 12 4 * WMI hotfix number 16 4 */ -static int dell_wmi_descriptor_probe(struct wmi_device *wdev) +static int dell_wmi_descriptor_probe(struct wmi_device *wdev, + const void *context) { union acpi_object *obj = NULL; struct descriptor_priv *priv; diff --git a/drivers/platform/x86/dell-wmi.c b/drivers/platform/x86/dell-wmi.c index d118bb73fcae..72b0a69a6ed0 100644 --- a/drivers/platform/x86/dell-wmi.c +++ b/drivers/platform/x86/dell-wmi.c @@ -672,7 +672,7 @@ static int dell_wmi_events_set_enabled(bool enable) return dell_smbios_error(ret); } -static int dell_wmi_probe(struct wmi_device *wdev) +static int dell_wmi_probe(struct wmi_device *wdev, const void *context) { struct dell_wmi_priv *priv; int ret; diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c index 52fcac5b393a..195a7f3638cb 100644 --- a/drivers/platform/x86/huawei-wmi.c +++ b/drivers/platform/x86/huawei-wmi.c @@ -166,7 +166,7 @@ static int huawei_wmi_input_setup(struct wmi_device *wdev) return input_register_device(priv->idev); } -static int huawei_wmi_probe(struct wmi_device *wdev) +static int huawei_wmi_probe(struct wmi_device *wdev, const void *context) { struct huawei_wmi_priv *priv; int err; diff --git a/drivers/platform/x86/intel-wmi-thunderbolt.c b/drivers/platform/x86/intel-wmi-thunderbolt.c index 4dfa61434a76..974c22a7ff61 100644 --- a/drivers/platform/x86/intel-wmi-thunderbolt.c +++ b/drivers/platform/x86/intel-wmi-thunderbolt.c @@ -56,7 +56,8 @@ static const struct attribute_group tbt_attribute_group = { .attrs = tbt_attrs, }; -static int intel_wmi_thunderbolt_probe(struct wmi_device *wdev) +static int intel_wmi_thunderbolt_probe(struct wmi_device *wdev, + const void *context) { int ret; diff --git a/drivers/platform/x86/wmi-bmof.c b/drivers/platform/x86/wmi-bmof.c index 8751a13134be..105a82b6b076 100644 --- a/drivers/platform/x86/wmi-bmof.c +++ b/drivers/platform/x86/wmi-bmof.c @@ -54,7 +54,7 @@ read_bmof(struct file *filp, struct kobject *kobj, return count; } -static int wmi_bmof_probe(struct wmi_device *wdev) +static int wmi_bmof_probe(struct wmi_device *wdev, const void *context) { struct bmof_priv *priv; int ret; diff --git a/drivers/platform/x86/wmi.c b/drivers/platform/x86/wmi.c index f3be1c008856..2163fd8bf9e1 100644 --- a/drivers/platform/x86/wmi.c +++ b/drivers/platform/x86/wmi.c @@ -945,7 +945,8 @@ static int wmi_dev_probe(struct device *dev) dev_warn(dev, "failed to enable device -- probing anyway\n"); if (wdriver->probe) { - ret = wdriver->probe(dev_to_wdev(dev)); + ret = wdriver->probe(dev_to_wdev(dev), + find_guid_context(wblock, wdriver)); if (ret != 0) goto probe_failure; } diff --git a/include/linux/wmi.h b/include/linux/wmi.h index 592f81afecbb..1e84c474a993 100644 --- a/include/linux/wmi.h +++ b/include/linux/wmi.h @@ -44,7 +44,7 @@ struct wmi_driver { struct device_driver driver; const struct wmi_device_id *id_table; - int (*probe)(struct wmi_device *wdev); + int (*probe)(struct wmi_device *wdev, const void *context); int (*remove)(struct wmi_device *wdev); void (*notify)(struct wmi_device *device, union acpi_object *data); long (*filter_callback)(struct wmi_device *wdev, unsigned int cmd, From edb73f4f02472b92dc2473c74211d22807d482e4 Mon Sep 17 00:00:00 2001 From: Mattias Jacobsson <2pi@mok.nu> Date: Mon, 27 May 2019 18:21:31 +0200 Subject: [PATCH 47/74] platform/x86: wmi: add Xiaomi WMI key driver Some function keys on the built in keyboard on Xiaomi's notebooks does not produce any key events when pressed in combination with the function key. Some of these keys do report that they are being pressed via WMI events. This driver reports key events for Fn+F7 and double tap on Fn. Other WMI events that are reported by the hardware but not utilized by this driver are Caps Lock(which already work) and Fn lock/unlock. Signed-off-by: Mattias Jacobsson <2pi@mok.nu> Signed-off-by: Andy Shevchenko --- drivers/platform/x86/Kconfig | 10 ++++ drivers/platform/x86/Makefile | 1 + drivers/platform/x86/xiaomi-wmi.c | 92 +++++++++++++++++++++++++++++++ 3 files changed, 103 insertions(+) create mode 100644 drivers/platform/x86/xiaomi-wmi.c diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index ae1e74d5e888..58494a12a9b0 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -780,6 +780,16 @@ config INTEL_WMI_THUNDERBOLT To compile this driver as a module, choose M here: the module will be called intel-wmi-thunderbolt. +config XIAOMI_WMI + tristate "Xiaomi WMI key driver" + depends on ACPI_WMI + depends on INPUT + help + Say Y here if you want to support WMI-based keys on Xiaomi notebooks. + + To compile this driver as a module, choose M here: the module will + be called xiaomi-wmi. + config MSI_WMI tristate "MSI WMI extras" depends on ACPI_WMI diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 87b0069bd781..f64445d69f99 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -51,6 +51,7 @@ obj-$(CONFIG_SURFACE3_WMI) += surface3-wmi.o obj-$(CONFIG_TOPSTAR_LAPTOP) += topstar-laptop.o obj-$(CONFIG_WMI_BMOF) += wmi-bmof.o obj-$(CONFIG_INTEL_WMI_THUNDERBOLT) += intel-wmi-thunderbolt.o +obj-$(CONFIG_XIAOMI_WMI) += xiaomi-wmi.o # toshiba_acpi must link after wmi to ensure that wmi devices are found # before toshiba_acpi initializes diff --git a/drivers/platform/x86/xiaomi-wmi.c b/drivers/platform/x86/xiaomi-wmi.c new file mode 100644 index 000000000000..601cbb282f54 --- /dev/null +++ b/drivers/platform/x86/xiaomi-wmi.c @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: GPL-2.0 +/* WMI driver for Xiaomi Laptops */ + +#include +#include +#include +#include + +#include + +#define XIAOMI_KEY_FN_ESC_0 "A2095CCE-0491-44E7-BA27-F8ED8F88AA86" +#define XIAOMI_KEY_FN_ESC_1 "7BBE8E39-B486-473D-BA13-66F75C5805CD" +#define XIAOMI_KEY_FN_FN "409B028D-F06B-4C7C-8BBB-EE133A6BD87E" +#define XIAOMI_KEY_CAPSLOCK "83FE7607-053A-4644-822A-21532C621FC7" +#define XIAOMI_KEY_FN_F7 "76E9027C-95D0-4180-8692-DA6747DD1C2D" + +#define XIAOMI_DEVICE(guid, key) \ + .guid_string = (guid), \ + .context = &(const unsigned int){key} + +struct xiaomi_wmi { + struct input_dev *input_dev; + unsigned int key_code; +}; + +int xiaomi_wmi_probe(struct wmi_device *wdev, const void *context) +{ + struct xiaomi_wmi *data; + + if (wdev == NULL || context == NULL) + return -EINVAL; + + data = devm_kzalloc(&wdev->dev, sizeof(struct xiaomi_wmi), GFP_KERNEL); + if (data == NULL) + return -ENOMEM; + dev_set_drvdata(&wdev->dev, data); + + data->input_dev = devm_input_allocate_device(&wdev->dev); + if (data->input_dev == NULL) + return -ENOMEM; + data->input_dev->name = "Xiaomi WMI keys"; + data->input_dev->phys = "wmi/input0"; + + data->key_code = *((const unsigned int *)context); + set_bit(EV_KEY, data->input_dev->evbit); + set_bit(data->key_code, data->input_dev->keybit); + + return input_register_device(data->input_dev); +} + +void xiaomi_wmi_notify(struct wmi_device *wdev, union acpi_object *dummy) +{ + struct xiaomi_wmi *data; + + if (wdev == NULL) + return; + + data = dev_get_drvdata(&wdev->dev); + if (data == NULL) + return; + + input_report_key(data->input_dev, data->key_code, 1); + input_sync(data->input_dev); + input_report_key(data->input_dev, data->key_code, 0); + input_sync(data->input_dev); +} + +static const struct wmi_device_id xiaomi_wmi_id_table[] = { + // { XIAOMI_DEVICE(XIAOMI_KEY_FN_ESC_0, KEY_FN_ESC) }, + // { XIAOMI_DEVICE(XIAOMI_KEY_FN_ESC_1, KEY_FN_ESC) }, + { XIAOMI_DEVICE(XIAOMI_KEY_FN_FN, KEY_PROG1) }, + // { XIAOMI_DEVICE(XIAOMI_KEY_CAPSLOCK, KEY_CAPSLOCK) }, + { XIAOMI_DEVICE(XIAOMI_KEY_FN_F7, KEY_CUT) }, + + /* Terminating entry */ + { } +}; + +static struct wmi_driver xiaomi_wmi_driver = { + .driver = { + .name = "xiaomi-wmi", + }, + .id_table = xiaomi_wmi_id_table, + .probe = xiaomi_wmi_probe, + .notify = xiaomi_wmi_notify, +}; +module_wmi_driver(xiaomi_wmi_driver); + +MODULE_DEVICE_TABLE(wmi, xiaomi_wmi_id_table); +MODULE_AUTHOR("Mattias Jacobsson"); +MODULE_DESCRIPTION("Xiaomi WMI driver"); +MODULE_LICENSE("GPL v2"); From 6d789e60c38f05d2b9bcba10bb69ce2a0e392343 Mon Sep 17 00:00:00 2001 From: Christian Oder Date: Wed, 12 Jun 2019 14:40:53 +0200 Subject: [PATCH 48/74] platform/x86: touchscreen_dmi: Update Hi10 Air filter Turns out the Hi10 Air is built by multiple companies so using Hampoo as a filter is not enough to cover all variants. This has been verified as working on the Hampoo and Morshow version. Signed-off-by: Christian Oder Signed-off-by: Andy Shevchenko --- drivers/platform/x86/touchscreen_dmi.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/platform/x86/touchscreen_dmi.c b/drivers/platform/x86/touchscreen_dmi.c index 1dbb53c3f1e7..a07b7fa08c6a 100644 --- a/drivers/platform/x86/touchscreen_dmi.c +++ b/drivers/platform/x86/touchscreen_dmi.c @@ -617,7 +617,8 @@ static const struct dmi_system_id touchscreen_dmi_table[] = { /* Chuwi Hi10 Air */ .driver_data = (void *)&chuwi_hi10_air_data, .matches = { - DMI_MATCH(DMI_BOARD_VENDOR, "Hampoo"), + DMI_MATCH(DMI_SYS_VENDOR, "CHUWI INNOVATION AND TECHNOLOGY(SHENZHEN)CO.LTD"), + DMI_MATCH(DMI_BOARD_NAME, "Cherry Trail CR"), DMI_MATCH(DMI_PRODUCT_SKU, "P1W6_C109D_B"), }, }, From 368bae87887877a95b19ecb395a8baa1be16e414 Mon Sep 17 00:00:00 2001 From: "Gustavo A. R. Silva" Date: Wed, 8 May 2019 11:49:34 -0500 Subject: [PATCH 49/74] platform/x86: acer-wmi: Mark expected switch fall-throughs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In preparation to enabling -Wimplicit-fallthrough, mark switch cases where we are expecting to fall through. This patch fixes the following warnings: drivers/platform/x86/acer-wmi.c: In function ‘set_u32’: drivers/platform/x86/acer-wmi.c:1378:33: warning: this statement may fall through [-Wimplicit-fallthrough=] if (cap == ACER_CAP_WIRELESS || ^ drivers/platform/x86/acer-wmi.c:1386:3: note: here case ACER_WMID: ^~~~ drivers/platform/x86/acer-wmi.c:1393:12: warning: this statement may fall through [-Wimplicit-fallthrough=] else if (wmi_has_guid(WMID_GUID2)) ^ drivers/platform/x86/acer-wmi.c:1395:3: note: here default: ^~~~~~~ drivers/platform/x86/acer-wmi.c: In function ‘get_u32’: drivers/platform/x86/acer-wmi.c:1340:6: warning: this statement may fall through [-Wimplicit-fallthrough=] if (cap == ACER_CAP_MAILLED) { ^ drivers/platform/x86/acer-wmi.c:1344:2: note: here case ACER_WMID: ^~~~ drivers/platform/x86/acer-wmi.c: In function ‘WMID_get_u32’: drivers/platform/x86/acer-wmi.c:1013:6: warning: this statement may fall through [-Wimplicit-fallthrough=] if (quirks->mailled == 1) { ^ drivers/platform/x86/acer-wmi.c:1018:2: note: here default: ^~~~~~~ Warning level 3 was used: -Wimplicit-fallthrough=3 This patch is part of the ongoing efforts to enable -Wimplicit-fallthrough. Signed-off-by: Gustavo A. R. Silva Signed-off-by: Andy Shevchenko --- drivers/platform/x86/acer-wmi.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/platform/x86/acer-wmi.c b/drivers/platform/x86/acer-wmi.c index 5d0cfe837f21..464cde582661 100644 --- a/drivers/platform/x86/acer-wmi.c +++ b/drivers/platform/x86/acer-wmi.c @@ -1014,6 +1014,7 @@ static acpi_status WMID_get_u32(u32 *value, u32 cap) *value = tmp & 0x1; return 0; } + /* fall through */ default: return AE_ERROR; } @@ -1340,6 +1341,7 @@ static acpi_status get_u32(u32 *value, u32 cap) status = AMW0_get_u32(value, cap); break; } + /* fall through */ case ACER_WMID: status = WMID_get_u32(value, cap); break; @@ -1382,6 +1384,7 @@ static acpi_status set_u32(u32 value, u32 cap) return AMW0_set_u32(value, cap); } + /* fall through */ case ACER_WMID: return WMID_set_u32(value, cap); case ACER_WMID_v2: @@ -1391,6 +1394,7 @@ static acpi_status set_u32(u32 value, u32 cap) return wmid_v2_set_u32(value, cap); else if (wmi_has_guid(WMID_GUID2)) return WMID_set_u32(value, cap); + /* fall through */ default: return AE_BAD_PARAMETER; } From 9452fbf5c6cf5f470e0748fe7a14a683e7765f7a Mon Sep 17 00:00:00 2001 From: Steffen Dirkwinkel Date: Tue, 18 Jun 2019 15:31:02 +0200 Subject: [PATCH 50/74] platform/x86: pmc_atom: Add CB4063 Beckhoff Automation board to critclk_systems DMI table The CB4063 board uses pmc_plt_clk* clocks for ethernet controllers. This adds it to the critclk_systems DMI table so the clocks are marked as CLK_CRITICAL and not turned off. Fixes: 648e921888ad ("clk: x86: Stop marking clocks as CLK_IS_CRITICAL") Signed-off-by: Steffen Dirkwinkel Signed-off-by: Andy Shevchenko --- drivers/platform/x86/pmc_atom.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/platform/x86/pmc_atom.c b/drivers/platform/x86/pmc_atom.c index 2104e1ad38d9..005634c11ebe 100644 --- a/drivers/platform/x86/pmc_atom.c +++ b/drivers/platform/x86/pmc_atom.c @@ -400,6 +400,14 @@ static const struct dmi_system_id critclk_systems[] = { DMI_MATCH(DMI_BOARD_NAME, "CB3163"), }, }, + { + /* pmc_plt_clk* - are used for ethernet controllers */ + .ident = "Beckhoff CB4063", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Beckhoff Automation"), + DMI_MATCH(DMI_BOARD_NAME, "CB4063"), + }, + }, { /* pmc_plt_clk* - are used for ethernet controllers */ .ident = "Beckhoff CB6263", From 568aeeeb69e6af7784418e294b0c37723d03936d Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Wed, 19 Jun 2019 17:50:50 +0300 Subject: [PATCH 51/74] platform/x86: intel_int0002_vgpio: Get rid of custom ICPU() macro Replace custom grown macro with generic INTEL_CPU_FAM6() one. No functional change intended. Signed-off-by: Andy Shevchenko Reviewed-by: Hans de Goede --- drivers/platform/x86/intel_int0002_vgpio.c | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/drivers/platform/x86/intel_int0002_vgpio.c b/drivers/platform/x86/intel_int0002_vgpio.c index 1694a9aec77c..d9542c661ddc 100644 --- a/drivers/platform/x86/intel_int0002_vgpio.c +++ b/drivers/platform/x86/intel_int0002_vgpio.c @@ -51,17 +51,6 @@ #define GPE0A_STS_PORT 0x420 #define GPE0A_EN_PORT 0x428 -#define BAYTRAIL 0x01 -#define CHERRYTRAIL 0x02 - -#define ICPU(model, data) { X86_VENDOR_INTEL, 6, model, X86_FEATURE_ANY, data } - -static const struct x86_cpu_id int0002_cpu_ids[] = { - ICPU(INTEL_FAM6_ATOM_SILVERMONT, BAYTRAIL), /* Valleyview, Bay Trail */ - ICPU(INTEL_FAM6_ATOM_AIRMONT, CHERRYTRAIL), /* Braswell, Cherry Trail */ - {} -}; - /* * As this is not a real GPIO at all, but just a hack to model an event in * ACPI the get / set functions are dummy functions. @@ -157,6 +146,12 @@ static struct irq_chip int0002_cht_irqchip = { */ }; +static const struct x86_cpu_id int0002_cpu_ids[] = { + INTEL_CPU_FAM6(ATOM_SILVERMONT, int0002_byt_irqchip), /* Valleyview, Bay Trail */ + INTEL_CPU_FAM6(ATOM_AIRMONT, int0002_cht_irqchip), /* Braswell, Cherry Trail */ + {} +}; + static int int0002_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -210,10 +205,7 @@ static int int0002_probe(struct platform_device *pdev) return ret; } - if (cpu_id->driver_data == BAYTRAIL) - irq_chip = &int0002_byt_irqchip; - else - irq_chip = &int0002_cht_irqchip; + irq_chip = (struct irq_chip *)cpu_id->driver_data; ret = gpiochip_irqchip_add(chip, irq_chip, 0, handle_edge_irq, IRQ_TYPE_NONE); From 2ee5bfc1efc81179c73abcd33098dd2c86019146 Mon Sep 17 00:00:00 2001 From: Srinivas Pandruvada Date: Wed, 26 Jun 2019 15:38:42 -0700 Subject: [PATCH 52/74] platform/x86: ISST: Update ioctl-number.txt for Intel Speed Select interface Reserve ioctl numbers for intel Speed Select Technology interface drivers. Signed-off-by: Srinivas Pandruvada Signed-off-by: Andy Shevchenko --- Documentation/ioctl/ioctl-number.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/ioctl/ioctl-number.txt b/Documentation/ioctl/ioctl-number.txt index c9558146ac58..ab0b3f686454 100644 --- a/Documentation/ioctl/ioctl-number.txt +++ b/Documentation/ioctl/ioctl-number.txt @@ -348,3 +348,4 @@ Code Seq#(hex) Include File Comments 0xF6 all LTTng Linux Trace Toolkit Next Generation 0xFD all linux/dm-ioctl.h +0xFE all linux/isst_if.h From 35f2c14d2a076b063a76c5bf275c46c0743ba3a0 Mon Sep 17 00:00:00 2001 From: Srinivas Pandruvada Date: Wed, 26 Jun 2019 15:38:43 -0700 Subject: [PATCH 53/74] platform/x86: ISST: Add common API to register and handle ioctls Encapsulate common functions which all Intel Speed Select Technology interface drivers can use. This creates API to register misc device for user kernel communication and handle all common IOCTLs. As part of the registry it allows a callback which is to handle domain specific ioctl processing. There can be multiple drivers register for services, which can be built as modules. So this driver handle contention during registry and as well as during removal. Once user space opened the misc device, the registered driver will be prevented from removal. Also once misc device is opened by the user space new client driver can't register, till the misc device is closed. There are two types of client drivers, one to handle mail box interface and the other is to allow direct read/write to some specific MMIO space. This common driver implements IOCTL ISST_IF_GET_PLATFORM_INFO. Signed-off-by: Srinivas Pandruvada Signed-off-by: Andy Shevchenko --- drivers/platform/x86/Kconfig | 2 + drivers/platform/x86/Makefile | 1 + .../x86/intel_speed_select_if/Kconfig | 17 ++ .../x86/intel_speed_select_if/Makefile | 7 + .../intel_speed_select_if/isst_if_common.c | 182 ++++++++++++++++++ .../intel_speed_select_if/isst_if_common.h | 60 ++++++ include/uapi/linux/isst_if.h | 41 ++++ 7 files changed, 310 insertions(+) create mode 100644 drivers/platform/x86/intel_speed_select_if/Kconfig create mode 100644 drivers/platform/x86/intel_speed_select_if/Makefile create mode 100644 drivers/platform/x86/intel_speed_select_if/isst_if_common.c create mode 100644 drivers/platform/x86/intel_speed_select_if/isst_if_common.h create mode 100644 include/uapi/linux/isst_if.h diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 58494a12a9b0..ebd44d071f7b 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -1336,6 +1336,8 @@ config PCENGINES_APU2 To compile this driver as a module, choose M here: the module will be called pcengines-apuv2. +source "drivers/platform/x86/intel_speed_select_if/Kconfig" + endif # X86_PLATFORM_DEVICES config PMC_ATOM diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index f64445d69f99..3a62157e9062 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -99,3 +99,4 @@ obj-$(CONFIG_INTEL_MRFLD_PWRBTN) += intel_mrfld_pwrbtn.o obj-$(CONFIG_I2C_MULTI_INSTANTIATE) += i2c-multi-instantiate.o obj-$(CONFIG_INTEL_ATOMISP2_PM) += intel_atomisp2_pm.o obj-$(CONFIG_PCENGINES_APU2) += pcengines-apuv2.o +obj-$(CONFIG_INTEL_SPEED_SELECT_INTERFACE) += intel_speed_select_if/ diff --git a/drivers/platform/x86/intel_speed_select_if/Kconfig b/drivers/platform/x86/intel_speed_select_if/Kconfig new file mode 100644 index 000000000000..ce3e3dc076d2 --- /dev/null +++ b/drivers/platform/x86/intel_speed_select_if/Kconfig @@ -0,0 +1,17 @@ +menu "Intel Speed Select Technology interface support" + depends on PCI + depends on X86_64 || COMPILE_TEST + +config INTEL_SPEED_SELECT_INTERFACE + tristate "Intel(R) Speed Select Technology interface drivers" + help + This config enables the Intel(R) Speed Select Technology interface + drivers. The Intel(R) speed select technology features are non + architectural and only supported on specific Xeon(R) servers. + These drivers provide interface to directly communicate with hardware + via MMIO and Mail boxes to enumerate and control all the speed select + features. + + Enable this config, if there is a need to enable and control the + Intel(R) Speed Select Technology features from the user space. +endmenu diff --git a/drivers/platform/x86/intel_speed_select_if/Makefile b/drivers/platform/x86/intel_speed_select_if/Makefile new file mode 100644 index 000000000000..c12687672fc9 --- /dev/null +++ b/drivers/platform/x86/intel_speed_select_if/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile - Intel Speed Select Interface drivers +# Copyright (c) 2019, Intel Corporation. +# + +obj-$(CONFIG_INTEL_SPEED_SELECT_INTERFACE) += isst_if_common.o diff --git a/drivers/platform/x86/intel_speed_select_if/isst_if_common.c b/drivers/platform/x86/intel_speed_select_if/isst_if_common.c new file mode 100644 index 000000000000..ab2bb4862dc8 --- /dev/null +++ b/drivers/platform/x86/intel_speed_select_if/isst_if_common.c @@ -0,0 +1,182 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Intel Speed Select Interface: Common functions + * Copyright (c) 2019, Intel Corporation. + * All rights reserved. + * + * Author: Srinivas Pandruvada + */ + +#include +#include +#include +#include +#include + +#include "isst_if_common.h" + +static struct isst_if_cmd_cb punit_callbacks[ISST_IF_DEV_MAX]; + +static int isst_if_get_platform_info(void __user *argp) +{ + struct isst_if_platform_info info; + + info.api_version = ISST_IF_API_VERSION, + info.driver_version = ISST_IF_DRIVER_VERSION, + info.max_cmds_per_ioctl = ISST_IF_CMD_LIMIT, + info.mbox_supported = punit_callbacks[ISST_IF_DEV_MBOX].registered; + info.mmio_supported = punit_callbacks[ISST_IF_DEV_MMIO].registered; + + if (copy_to_user(argp, &info, sizeof(info))) + return -EFAULT; + + return 0; +} + +static long isst_if_def_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + void __user *argp = (void __user *)arg; + long ret = -ENOTTY; + + switch (cmd) { + case ISST_IF_GET_PLATFORM_INFO: + ret = isst_if_get_platform_info(argp); + break; + default: + break; + } + + return ret; +} + +static DEFINE_MUTEX(punit_misc_dev_lock); +static int misc_usage_count; +static int misc_device_ret; +static int misc_device_open; + +static int isst_if_open(struct inode *inode, struct file *file) +{ + int i, ret = 0; + + /* Fail open, if a module is going away */ + mutex_lock(&punit_misc_dev_lock); + for (i = 0; i < ISST_IF_DEV_MAX; ++i) { + struct isst_if_cmd_cb *cb = &punit_callbacks[i]; + + if (cb->registered && !try_module_get(cb->owner)) { + ret = -ENODEV; + break; + } + } + if (ret) { + int j; + + for (j = 0; j < i; ++j) { + struct isst_if_cmd_cb *cb; + + cb = &punit_callbacks[j]; + if (cb->registered) + module_put(cb->owner); + } + } else { + misc_device_open++; + } + mutex_unlock(&punit_misc_dev_lock); + + return ret; +} + +static int isst_if_relase(struct inode *inode, struct file *f) +{ + int i; + + mutex_lock(&punit_misc_dev_lock); + misc_device_open--; + for (i = 0; i < ISST_IF_DEV_MAX; ++i) { + struct isst_if_cmd_cb *cb = &punit_callbacks[i]; + + if (cb->registered) + module_put(cb->owner); + } + mutex_unlock(&punit_misc_dev_lock); + + return 0; +} + +static const struct file_operations isst_if_char_driver_ops = { + .open = isst_if_open, + .unlocked_ioctl = isst_if_def_ioctl, + .release = isst_if_relase, +}; + +static struct miscdevice isst_if_char_driver = { + .minor = MISC_DYNAMIC_MINOR, + .name = "isst_interface", + .fops = &isst_if_char_driver_ops, +}; + +/** + * isst_if_cdev_register() - Register callback for IOCTL + * @device_type: The device type this callback handling. + * @cb: Callback structure. + * + * This function registers a callback to device type. On very first call + * it will register a misc device, which is used for user kernel interface. + * Other calls simply increment ref count. Registry will fail, if the user + * already opened misc device for operation. Also if the misc device + * creation failed, then it will not try again and all callers will get + * failure code. + * + * Return: Return the return value from the misc creation device or -EINVAL + * for unsupported device type. + */ +int isst_if_cdev_register(int device_type, struct isst_if_cmd_cb *cb) +{ + if (misc_device_ret) + return misc_device_ret; + + if (device_type >= ISST_IF_DEV_MAX) + return -EINVAL; + + mutex_lock(&punit_misc_dev_lock); + if (misc_device_open) { + mutex_unlock(&punit_misc_dev_lock); + return -EAGAIN; + } + if (!misc_usage_count) { + misc_device_ret = misc_register(&isst_if_char_driver); + if (misc_device_ret) + goto unlock_exit; + } + memcpy(&punit_callbacks[device_type], cb, sizeof(*cb)); + punit_callbacks[device_type].registered = 1; + misc_usage_count++; +unlock_exit: + mutex_unlock(&punit_misc_dev_lock); + + return misc_device_ret; +} +EXPORT_SYMBOL_GPL(isst_if_cdev_register); + +/** + * isst_if_cdev_unregister() - Unregister callback for IOCTL + * @device_type: The device type to unregister. + * + * This function unregisters the previously registered callback. If this + * is the last callback unregistering, then misc device is removed. + * + * Return: None. + */ +void isst_if_cdev_unregister(int device_type) +{ + mutex_lock(&punit_misc_dev_lock); + misc_usage_count--; + punit_callbacks[device_type].registered = 0; + if (!misc_usage_count && !misc_device_ret) + misc_deregister(&isst_if_char_driver); + mutex_unlock(&punit_misc_dev_lock); +} +EXPORT_SYMBOL_GPL(isst_if_cdev_unregister); + +MODULE_LICENSE("GPL v2"); diff --git a/drivers/platform/x86/intel_speed_select_if/isst_if_common.h b/drivers/platform/x86/intel_speed_select_if/isst_if_common.h new file mode 100644 index 000000000000..11f339226fb4 --- /dev/null +++ b/drivers/platform/x86/intel_speed_select_if/isst_if_common.h @@ -0,0 +1,60 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Intel Speed Select Interface: Drivers Internal defines + * Copyright (c) 2019, Intel Corporation. + * All rights reserved. + * + * Author: Srinivas Pandruvada + */ + +#ifndef __ISST_IF_COMMON_H +#define __ISST_IF_COMMON_H + +/* + * Validate maximum commands in a single request. + * This is enough to handle command to every core in one ioctl, or all + * possible message id to one CPU. Limit is also helpful for resonse time + * per IOCTL request, as PUNIT may take different times to process each + * request and may hold for long for too many commands. + */ +#define ISST_IF_CMD_LIMIT 64 + +#define ISST_IF_API_VERSION 0x01 +#define ISST_IF_DRIVER_VERSION 0x01 + +#define ISST_IF_DEV_MBOX 0 +#define ISST_IF_DEV_MMIO 1 +#define ISST_IF_DEV_MAX 2 + +/** + * struct isst_if_cmd_cb - Used to register a IOCTL handler + * @registered: Used by the common code to store registry. Caller don't + * to touch this field + * @cmd_size: The command size of the individual command in IOCTL + * @offset: Offset to the first valid member in command structure. + * This will be the offset of the start of the command + * after command count field + * @cmd_callback: Callback function to handle IOCTL. The callback has the + * command pointer with data for command. There is a pointer + * called write_only, which when set, will not copy the + * response to user ioctl buffer. The "resume" argument + * can be used to avoid storing the command for replay + * during system resume + * + * This structure is used to register an handler for IOCTL. To avoid + * code duplication common code handles all the IOCTL command read/write + * including handling multiple command in single IOCTL. The caller just + * need to execute a command via the registered callback. + */ +struct isst_if_cmd_cb { + int registered; + int cmd_size; + int offset; + struct module *owner; + long (*cmd_callback)(u8 *ptr, int *write_only, int resume); +}; + +/* Internal interface functions */ +int isst_if_cdev_register(int type, struct isst_if_cmd_cb *cb); +void isst_if_cdev_unregister(int type); +#endif diff --git a/include/uapi/linux/isst_if.h b/include/uapi/linux/isst_if.h new file mode 100644 index 000000000000..fa94480b5f74 --- /dev/null +++ b/include/uapi/linux/isst_if.h @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Intel Speed Select Interface: OS to hardware Interface + * Copyright (c) 2019, Intel Corporation. + * All rights reserved. + * + * Author: Srinivas Pandruvada + */ + +#ifndef __ISST_IF_H +#define __ISST_IF_H + +#include + +/** + * struct isst_if_platform_info - Define platform information + * @api_version: Version of the firmware document, which this driver + * can communicate + * @driver_version: Driver version, which will help user to send right + * commands. Even if the firmware is capable, driver may + * not be ready + * @max_cmds_per_ioctl: Returns the maximum number of commands driver will + * accept in a single ioctl + * @mbox_supported: Support of mail box interface + * @mmio_supported: Support of mmio interface for core-power feature + * + * Used to return output of IOCTL ISST_IF_GET_PLATFORM_INFO. This + * information can be used by the user space, to get the driver, firmware + * support and also number of commands to send in a single IOCTL request. + */ +struct isst_if_platform_info { + __u16 api_version; + __u16 driver_version; + __u16 max_cmds_per_ioctl; + __u8 mbox_supported; + __u8 mmio_supported; +}; + +#define ISST_IF_MAGIC 0xFE +#define ISST_IF_GET_PLATFORM_INFO _IOR(ISST_IF_MAGIC, 0, struct isst_if_platform_info *) +#endif From 8fbfb6fc67819c1274584ff902f7d03aafe38dab Mon Sep 17 00:00:00 2001 From: Srinivas Pandruvada Date: Wed, 26 Jun 2019 15:38:44 -0700 Subject: [PATCH 54/74] platform/x86: ISST: Store per CPU information There are two per CPU data needs to be stored and cached to avoid repeated MSR readings for accessing them later: - Physical to logical CPU conversion The PUNIT uses a different CPU numbering scheme which is not APIC id based. So we need to establish relationship between PUNIT CPU number and Linux logical CPU numbering which is based on APIC id. There is an MSR 0x53 (MSR_THREAD_ID), which gets physical CPU number for the local CPU where it is read. Also the CPU mask in some messages will inform which CPUs needs to be online/offline for a TDP level. During TDP switch if user offlined some CPUs, then the physical CPU mask can't be converted as we can't read MSR on an offlined CPU to go to a lower TDP level by onlining more CPUs. So the mapping needs to be established at the boot up time. - Bus number corresponding to a CPU A group of CPUs are in a control of a PUNIT. The PUNIT device is exported as PCI device. To do operation on a PUNIT for a CPU, we need to find out to which PCI device it is related to. This is done by reading MSR 0x128 (MSR_CPU_BUS_NUMBER). So during CPU online stages the above MSRs are read and stored. Later this stored information is used to process IOCTLs request from the user space. Signed-off-by: Srinivas Pandruvada Signed-off-by: Andy Shevchenko --- .../intel_speed_select_if/isst_if_common.c | 114 +++++++++++++++++- .../intel_speed_select_if/isst_if_common.h | 1 + 2 files changed, 114 insertions(+), 1 deletion(-) diff --git a/drivers/platform/x86/intel_speed_select_if/isst_if_common.c b/drivers/platform/x86/intel_speed_select_if/isst_if_common.c index ab2bb4862dc8..0e16cbf685d0 100644 --- a/drivers/platform/x86/intel_speed_select_if/isst_if_common.c +++ b/drivers/platform/x86/intel_speed_select_if/isst_if_common.c @@ -7,14 +7,22 @@ * Author: Srinivas Pandruvada */ +#include +#include #include #include #include +#include +#include +#include #include #include #include "isst_if_common.h" +#define MSR_THREAD_ID_INFO 0x53 +#define MSR_CPU_BUS_NUMBER 0x128 + static struct isst_if_cmd_cb punit_callbacks[ISST_IF_DEV_MAX]; static int isst_if_get_platform_info(void __user *argp) @@ -33,6 +41,99 @@ static int isst_if_get_platform_info(void __user *argp) return 0; } + +struct isst_if_cpu_info { + /* For BUS 0 and BUS 1 only, which we need for PUNIT interface */ + int bus_info[2]; + int punit_cpu_id; +}; + +static struct isst_if_cpu_info *isst_cpu_info; + +/** + * isst_if_get_pci_dev() - Get the PCI device instance for a CPU + * @cpu: Logical CPU number. + * @bus_number: The bus number assigned by the hardware. + * @dev: The device number assigned by the hardware. + * @fn: The function number assigned by the hardware. + * + * Using cached bus information, find out the PCI device for a bus number, + * device and function. + * + * Return: Return pci_dev pointer or NULL. + */ +struct pci_dev *isst_if_get_pci_dev(int cpu, int bus_no, int dev, int fn) +{ + int bus_number; + + if (bus_no < 0 || bus_no > 1 || cpu < 0 || cpu >= nr_cpu_ids || + cpu >= num_possible_cpus()) + return NULL; + + bus_number = isst_cpu_info[cpu].bus_info[bus_no]; + if (bus_number < 0) + return NULL; + + return pci_get_domain_bus_and_slot(0, bus_number, PCI_DEVFN(dev, fn)); +} +EXPORT_SYMBOL_GPL(isst_if_get_pci_dev); + +static int isst_if_cpu_online(unsigned int cpu) +{ + u64 data; + int ret; + + ret = rdmsrl_safe(MSR_CPU_BUS_NUMBER, &data); + if (ret) { + /* This is not a fatal error on MSR mailbox only I/F */ + isst_cpu_info[cpu].bus_info[0] = -1; + isst_cpu_info[cpu].bus_info[1] = -1; + } else { + isst_cpu_info[cpu].bus_info[0] = data & 0xff; + isst_cpu_info[cpu].bus_info[1] = (data >> 8) & 0xff; + } + + ret = rdmsrl_safe(MSR_THREAD_ID_INFO, &data); + if (ret) { + isst_cpu_info[cpu].punit_cpu_id = -1; + return ret; + } + isst_cpu_info[cpu].punit_cpu_id = data; + + return 0; +} + +static int isst_if_online_id; + +static int isst_if_cpu_info_init(void) +{ + int ret; + + isst_cpu_info = kcalloc(num_possible_cpus(), + sizeof(*isst_cpu_info), + GFP_KERNEL); + if (!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_cpu_info); + return ret; + } + + isst_if_online_id = ret; + + return 0; +} + +static void isst_if_cpu_info_exit(void) +{ + cpuhp_remove_state(isst_if_online_id); + kfree(isst_cpu_info); +}; + static long isst_if_def_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { @@ -145,9 +246,18 @@ int isst_if_cdev_register(int device_type, struct isst_if_cmd_cb *cb) return -EAGAIN; } if (!misc_usage_count) { + int ret; + misc_device_ret = misc_register(&isst_if_char_driver); if (misc_device_ret) goto unlock_exit; + + ret = isst_if_cpu_info_init(); + if (ret) { + misc_deregister(&isst_if_char_driver); + misc_device_ret = ret; + goto unlock_exit; + } } memcpy(&punit_callbacks[device_type], cb, sizeof(*cb)); punit_callbacks[device_type].registered = 1; @@ -173,8 +283,10 @@ void isst_if_cdev_unregister(int device_type) mutex_lock(&punit_misc_dev_lock); misc_usage_count--; punit_callbacks[device_type].registered = 0; - if (!misc_usage_count && !misc_device_ret) + if (!misc_usage_count && !misc_device_ret) { misc_deregister(&isst_if_char_driver); + isst_if_cpu_info_exit(); + } mutex_unlock(&punit_misc_dev_lock); } EXPORT_SYMBOL_GPL(isst_if_cdev_unregister); diff --git a/drivers/platform/x86/intel_speed_select_if/isst_if_common.h b/drivers/platform/x86/intel_speed_select_if/isst_if_common.h index 11f339226fb4..dade77c58b22 100644 --- a/drivers/platform/x86/intel_speed_select_if/isst_if_common.h +++ b/drivers/platform/x86/intel_speed_select_if/isst_if_common.h @@ -57,4 +57,5 @@ struct isst_if_cmd_cb { /* Internal interface functions */ int isst_if_cdev_register(int type, struct isst_if_cmd_cb *cb); void isst_if_cdev_unregister(int type); +struct pci_dev *isst_if_get_pci_dev(int cpu, int bus, int dev, int fn); #endif From fb5b36a413b9f30fba573fc2a596ab7142dfaf12 Mon Sep 17 00:00:00 2001 From: Srinivas Pandruvada Date: Wed, 26 Jun 2019 15:38:45 -0700 Subject: [PATCH 55/74] platform/x86: ISST: Add IOCTL to Translate Linux logical CPU to PUNIT CPU number Add processing for IOCTL command ISST_IF_GET_PHY_ID. This converts from the Linux logical CPU to PUNIT CPU numbering scheme. Signed-off-by: Srinivas Pandruvada Signed-off-by: Andy Shevchenko --- .../intel_speed_select_if/isst_if_common.c | 74 +++++++++++++++++++ include/uapi/linux/isst_if.h | 28 +++++++ 2 files changed, 102 insertions(+) diff --git a/drivers/platform/x86/intel_speed_select_if/isst_if_common.c b/drivers/platform/x86/intel_speed_select_if/isst_if_common.c index 0e16cbf685d0..72e74d72724b 100644 --- a/drivers/platform/x86/intel_speed_select_if/isst_if_common.c +++ b/drivers/platform/x86/intel_speed_select_if/isst_if_common.c @@ -134,16 +134,90 @@ static void isst_if_cpu_info_exit(void) kfree(isst_cpu_info); }; +static long isst_if_proc_phyid_req(u8 *cmd_ptr, int *write_only, int resume) +{ + struct isst_if_cpu_map *cpu_map; + + cpu_map = (struct isst_if_cpu_map *)cmd_ptr; + if (cpu_map->logical_cpu >= nr_cpu_ids || + cpu_map->logical_cpu >= num_possible_cpus()) + return -EINVAL; + + *write_only = 0; + cpu_map->physical_cpu = isst_cpu_info[cpu_map->logical_cpu].punit_cpu_id; + + return 0; +} + +static long isst_if_exec_multi_cmd(void __user *argp, struct isst_if_cmd_cb *cb) +{ + unsigned char __user *ptr; + u32 cmd_count; + u8 *cmd_ptr; + long ret; + int i; + + /* Each multi command has u32 command count as the first field */ + if (copy_from_user(&cmd_count, argp, sizeof(cmd_count))) + return -EFAULT; + + if (!cmd_count || cmd_count > ISST_IF_CMD_LIMIT) + return -EINVAL; + + cmd_ptr = kmalloc(cb->cmd_size, GFP_KERNEL); + if (!cmd_ptr) + return -ENOMEM; + + /* cb->offset points to start of the command after the command count */ + ptr = argp + cb->offset; + + for (i = 0; i < cmd_count; ++i) { + int wr_only; + + if (signal_pending(current)) { + ret = -EINTR; + break; + } + + if (copy_from_user(cmd_ptr, ptr, cb->cmd_size)) { + ret = -EFAULT; + break; + } + + ret = cb->cmd_callback(cmd_ptr, &wr_only, 0); + if (ret) + break; + + if (!wr_only && copy_to_user(ptr, cmd_ptr, cb->cmd_size)) { + ret = -EFAULT; + break; + } + + ptr += cb->cmd_size; + } + + kfree(cmd_ptr); + + return i ? i : ret; +} + static long isst_if_def_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { void __user *argp = (void __user *)arg; + struct isst_if_cmd_cb cmd_cb; long ret = -ENOTTY; switch (cmd) { case ISST_IF_GET_PLATFORM_INFO: ret = isst_if_get_platform_info(argp); break; + case ISST_IF_GET_PHY_ID: + cmd_cb.cmd_size = sizeof(struct isst_if_cpu_map); + cmd_cb.offset = offsetof(struct isst_if_cpu_maps, cpu_map); + cmd_cb.cmd_callback = isst_if_proc_phyid_req; + ret = isst_if_exec_multi_cmd(argp, &cmd_cb); + break; default: break; } diff --git a/include/uapi/linux/isst_if.h b/include/uapi/linux/isst_if.h index fa94480b5f74..15d1f286a830 100644 --- a/include/uapi/linux/isst_if.h +++ b/include/uapi/linux/isst_if.h @@ -36,6 +36,34 @@ struct isst_if_platform_info { __u8 mmio_supported; }; +/** + * struct isst_if_cpu_map - CPU mapping between logical and physical CPU + * @logical_cpu: Linux logical CPU number + * @physical_cpu: PUNIT CPU number + * + * Used to convert from Linux logical CPU to PUNIT CPU numbering scheme. + * The PUNIT CPU number is different than APIC ID based CPU numbering. + */ +struct isst_if_cpu_map { + __u32 logical_cpu; + __u32 physical_cpu; +}; + +/** + * struct isst_if_cpu_maps - structure for CPU map IOCTL + * @cmd_count: Number of CPU mapping command in cpu_map[] + * @cpu_map[]: Holds one or more CPU map data structure + * + * This structure used with ioctl ISST_IF_GET_PHY_ID to send + * one or more CPU mapping commands. Here IOCTL return value indicates + * number of commands sent or error number if no commands have been sent. + */ +struct isst_if_cpu_maps { + __u32 cmd_count; + struct isst_if_cpu_map cpu_map[1]; +}; + #define ISST_IF_MAGIC 0xFE #define ISST_IF_GET_PLATFORM_INFO _IOR(ISST_IF_MAGIC, 0, struct isst_if_platform_info *) +#define ISST_IF_GET_PHY_ID _IOWR(ISST_IF_MAGIC, 1, struct isst_if_cpu_map *) #endif From d3a23584294c1f379239a3b52bac13e03fecd147 Mon Sep 17 00:00:00 2001 From: Srinivas Pandruvada Date: Wed, 26 Jun 2019 15:38:46 -0700 Subject: [PATCH 56/74] platform/x86: ISST: Add Intel Speed Select mmio interface Added MMIO interface to read/write specific offsets in PUNIT PCI device which export core priortization. This MMIO interface can be used using ioctl interface on /dev/isst_interface using IOCTL ISST_IF_IO_CMD. This MMIO interface is used by the intel-speed-select tool under tools/x86/power to enumerate and set core priority. The MMIO offsets and semantics of the message can be checked from the source code of the tool. Signed-off-by: Srinivas Pandruvada Signed-off-by: Andy Shevchenko --- .../x86/intel_speed_select_if/Makefile | 1 + .../intel_speed_select_if/isst_if_common.c | 6 + .../intel_speed_select_if/isst_if_common.h | 2 + .../x86/intel_speed_select_if/isst_if_mmio.c | 131 ++++++++++++++++++ include/uapi/linux/isst_if.h | 33 +++++ 5 files changed, 173 insertions(+) create mode 100644 drivers/platform/x86/intel_speed_select_if/isst_if_mmio.c diff --git a/drivers/platform/x86/intel_speed_select_if/Makefile b/drivers/platform/x86/intel_speed_select_if/Makefile index c12687672fc9..7e94919208d3 100644 --- a/drivers/platform/x86/intel_speed_select_if/Makefile +++ b/drivers/platform/x86/intel_speed_select_if/Makefile @@ -5,3 +5,4 @@ # obj-$(CONFIG_INTEL_SPEED_SELECT_INTERFACE) += isst_if_common.o +obj-$(CONFIG_INTEL_SPEED_SELECT_INTERFACE) += isst_if_mmio.o diff --git a/drivers/platform/x86/intel_speed_select_if/isst_if_common.c b/drivers/platform/x86/intel_speed_select_if/isst_if_common.c index 72e74d72724b..3f96a3925bc6 100644 --- a/drivers/platform/x86/intel_speed_select_if/isst_if_common.c +++ b/drivers/platform/x86/intel_speed_select_if/isst_if_common.c @@ -206,6 +206,7 @@ static long isst_if_def_ioctl(struct file *file, unsigned int cmd, { void __user *argp = (void __user *)arg; struct isst_if_cmd_cb cmd_cb; + struct isst_if_cmd_cb *cb; long ret = -ENOTTY; switch (cmd) { @@ -218,6 +219,11 @@ static long isst_if_def_ioctl(struct file *file, unsigned int cmd, cmd_cb.cmd_callback = isst_if_proc_phyid_req; ret = isst_if_exec_multi_cmd(argp, &cmd_cb); break; + case ISST_IF_IO_CMD: + cb = &punit_callbacks[ISST_IF_DEV_MMIO]; + if (cb->registered) + ret = isst_if_exec_multi_cmd(argp, cb); + break; default: break; } diff --git a/drivers/platform/x86/intel_speed_select_if/isst_if_common.h b/drivers/platform/x86/intel_speed_select_if/isst_if_common.h index dade77c58b22..cdc7d019748a 100644 --- a/drivers/platform/x86/intel_speed_select_if/isst_if_common.h +++ b/drivers/platform/x86/intel_speed_select_if/isst_if_common.h @@ -10,6 +10,8 @@ #ifndef __ISST_IF_COMMON_H #define __ISST_IF_COMMON_H +#define INTEL_RAPL_PRIO_DEVID_0 0x3451 + /* * Validate maximum commands in a single request. * This is enough to handle command to every core in one ioctl, or all diff --git a/drivers/platform/x86/intel_speed_select_if/isst_if_mmio.c b/drivers/platform/x86/intel_speed_select_if/isst_if_mmio.c new file mode 100644 index 000000000000..1c25a1235b9e --- /dev/null +++ b/drivers/platform/x86/intel_speed_select_if/isst_if_mmio.c @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Intel Speed Select Interface: MMIO Interface + * Copyright (c) 2019, Intel Corporation. + * All rights reserved. + * + * Author: Srinivas Pandruvada + */ + +#include +#include +#include +#include +#include + +#include "isst_if_common.h" + +struct isst_if_device { + void __iomem *punit_mmio; + struct mutex mutex; +}; + +static long isst_if_mmio_rd_wr(u8 *cmd_ptr, int *write_only, int resume) +{ + struct isst_if_device *punit_dev; + struct isst_if_io_reg *io_reg; + struct pci_dev *pdev; + + io_reg = (struct isst_if_io_reg *)cmd_ptr; + if (io_reg->reg < 0x04 || io_reg->reg > 0xD0) + return -EINVAL; + + if (io_reg->read_write && !capable(CAP_SYS_ADMIN)) + return -EPERM; + + pdev = isst_if_get_pci_dev(io_reg->logical_cpu, 0, 0, 1); + if (!pdev) + return -EINVAL; + + punit_dev = pci_get_drvdata(pdev); + if (!punit_dev) + return -EINVAL; + + /* + * Ensure that operation is complete on a PCI device to avoid read + * write race by using per PCI device mutex. + */ + mutex_lock(&punit_dev->mutex); + if (io_reg->read_write) { + writel(io_reg->value, punit_dev->punit_mmio+io_reg->reg); + *write_only = 1; + } else { + io_reg->value = readl(punit_dev->punit_mmio+io_reg->reg); + *write_only = 0; + } + mutex_unlock(&punit_dev->mutex); + + return 0; +} + +static const struct pci_device_id isst_if_ids[] = { + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, INTEL_RAPL_PRIO_DEVID_0)}, + { 0 }, +}; +MODULE_DEVICE_TABLE(pci, isst_if_ids); + +static int isst_if_probe(struct pci_dev *pdev, const struct pci_device_id *ent) +{ + struct isst_if_device *punit_dev; + struct isst_if_cmd_cb cb; + u32 mmio_base, pcu_base; + u64 base_addr; + int ret; + + punit_dev = devm_kzalloc(&pdev->dev, sizeof(*punit_dev), GFP_KERNEL); + if (!punit_dev) + return -ENOMEM; + + ret = pcim_enable_device(pdev); + if (ret) + return ret; + + ret = pci_read_config_dword(pdev, 0xD0, &mmio_base); + if (ret) + return ret; + + ret = pci_read_config_dword(pdev, 0xFC, &pcu_base); + if (ret) + return ret; + + pcu_base &= GENMASK(10, 0); + base_addr = (u64)mmio_base << 23 | (u64) pcu_base << 12; + punit_dev->punit_mmio = devm_ioremap(&pdev->dev, base_addr, 256); + if (!punit_dev->punit_mmio) + return -ENOMEM; + + mutex_init(&punit_dev->mutex); + pci_set_drvdata(pdev, punit_dev); + + memset(&cb, 0, sizeof(cb)); + cb.cmd_size = sizeof(struct isst_if_io_reg); + cb.offset = offsetof(struct isst_if_io_regs, io_reg); + cb.cmd_callback = isst_if_mmio_rd_wr; + cb.owner = THIS_MODULE; + ret = isst_if_cdev_register(ISST_IF_DEV_MMIO, &cb); + if (ret) + mutex_destroy(&punit_dev->mutex); + + return ret; +} + +static void isst_if_remove(struct pci_dev *pdev) +{ + struct isst_if_device *punit_dev; + + punit_dev = pci_get_drvdata(pdev); + isst_if_cdev_unregister(ISST_IF_DEV_MBOX); + mutex_destroy(&punit_dev->mutex); +} + +static struct pci_driver isst_if_pci_driver = { + .name = "isst_if_pci", + .id_table = isst_if_ids, + .probe = isst_if_probe, + .remove = isst_if_remove, +}; + +module_pci_driver(isst_if_pci_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Intel speed select interface mmio driver"); diff --git a/include/uapi/linux/isst_if.h b/include/uapi/linux/isst_if.h index 15d1f286a830..fe2492ade078 100644 --- a/include/uapi/linux/isst_if.h +++ b/include/uapi/linux/isst_if.h @@ -63,7 +63,40 @@ struct isst_if_cpu_maps { struct isst_if_cpu_map cpu_map[1]; }; +/** + * struct isst_if_io_reg - Read write PUNIT IO register + * @read_write: Value 0: Read, 1: Write + * @logical_cpu: Logical CPU number to get target PCI device. + * @reg: PUNIT register offset + * @value: For write operation value to write and for + * for read placeholder read value + * + * Structure to specify read/write data to PUNIT registers. + */ +struct isst_if_io_reg { + __u32 read_write; /* Read:0, Write:1 */ + __u32 logical_cpu; + __u32 reg; + __u32 value; +}; + +/** + * struct isst_if_io_regs - structure for IO register commands + * @cmd_count: Number of io reg commands in io_reg[] + * @io_reg[]: Holds one or more io_reg command structure + * + * This structure used with ioctl ISST_IF_IO_CMD to send + * one or more read/write commands to PUNIT. Here IOCTL return value + * indicates number of requests sent or error number if no requests have + * been sent. + */ +struct isst_if_io_regs { + __u32 req_count; + struct isst_if_io_reg io_reg[1]; +}; + #define ISST_IF_MAGIC 0xFE #define ISST_IF_GET_PLATFORM_INFO _IOR(ISST_IF_MAGIC, 0, struct isst_if_platform_info *) #define ISST_IF_GET_PHY_ID _IOWR(ISST_IF_MAGIC, 1, struct isst_if_cpu_map *) +#define ISST_IF_IO_CMD _IOW(ISST_IF_MAGIC, 2, struct isst_if_io_regs *) #endif From 31a166fe9c269af17977e650846ee4ea50361c07 Mon Sep 17 00:00:00 2001 From: Srinivas Pandruvada Date: Wed, 26 Jun 2019 15:38:47 -0700 Subject: [PATCH 57/74] platform/x86: ISST: Add Intel Speed Select mailbox interface via PCI Add an IOCTL to send mailbox commands to PUNIT using PUNIT PCI device. A limited set of mailbox commands can be sent to PUNIT. This MMIO interface is used by the intel-speed-select tool under tools/x86/power to enumerate and control Intel Speed Select features. The MBOX commands ids and semantics of the message can be checked from the source code of the tool. Signed-off-by: Srinivas Pandruvada Signed-off-by: Andy Shevchenko --- .../x86/intel_speed_select_if/Makefile | 1 + .../intel_speed_select_if/isst_if_common.c | 85 ++++++++ .../intel_speed_select_if/isst_if_common.h | 3 + .../intel_speed_select_if/isst_if_mbox_pci.c | 199 ++++++++++++++++++ include/uapi/linux/isst_if.h | 38 ++++ 5 files changed, 326 insertions(+) create mode 100644 drivers/platform/x86/intel_speed_select_if/isst_if_mbox_pci.c diff --git a/drivers/platform/x86/intel_speed_select_if/Makefile b/drivers/platform/x86/intel_speed_select_if/Makefile index 7e94919208d3..8dec8c858649 100644 --- a/drivers/platform/x86/intel_speed_select_if/Makefile +++ b/drivers/platform/x86/intel_speed_select_if/Makefile @@ -6,3 +6,4 @@ obj-$(CONFIG_INTEL_SPEED_SELECT_INTERFACE) += isst_if_common.o obj-$(CONFIG_INTEL_SPEED_SELECT_INTERFACE) += isst_if_mmio.o +obj-$(CONFIG_INTEL_SPEED_SELECT_INTERFACE) += isst_if_mbox_pci.o diff --git a/drivers/platform/x86/intel_speed_select_if/isst_if_common.c b/drivers/platform/x86/intel_speed_select_if/isst_if_common.c index 3f96a3925bc6..391fc3f12161 100644 --- a/drivers/platform/x86/intel_speed_select_if/isst_if_common.c +++ b/drivers/platform/x86/intel_speed_select_if/isst_if_common.c @@ -25,6 +25,86 @@ static struct isst_if_cmd_cb punit_callbacks[ISST_IF_DEV_MAX]; +struct isst_valid_cmd_ranges { + u16 cmd; + u16 sub_cmd_beg; + u16 sub_cmd_end; +}; + +struct isst_cmd_set_req_type { + u16 cmd; + u16 sub_cmd; + u16 param; +}; + +static const struct isst_valid_cmd_ranges isst_valid_cmds[] = { + {0xD0, 0x00, 0x03}, + {0x7F, 0x00, 0x0B}, + {0x7F, 0x10, 0x12}, + {0x7F, 0x20, 0x23}, +}; + +static const struct isst_cmd_set_req_type isst_cmd_set_reqs[] = { + {0xD0, 0x00, 0x08}, + {0xD0, 0x01, 0x08}, + {0xD0, 0x02, 0x08}, + {0xD0, 0x03, 0x08}, + {0x7F, 0x02, 0x00}, + {0x7F, 0x08, 0x00}, +}; + +/** + * isst_if_mbox_cmd_invalid() - Check invalid mailbox commands + * @cmd: Pointer to the command structure to verify. + * + * Invalid command to PUNIT to may result in instability of the platform. + * This function has a whitelist of commands, which are allowed. + * + * Return: Return true if the command is invalid, else false. + */ +bool isst_if_mbox_cmd_invalid(struct isst_if_mbox_cmd *cmd) +{ + int i; + + if (cmd->logical_cpu >= nr_cpu_ids) + return true; + + for (i = 0; i < ARRAY_SIZE(isst_valid_cmds); ++i) { + if (cmd->command == isst_valid_cmds[i].cmd && + (cmd->sub_command >= isst_valid_cmds[i].sub_cmd_beg && + cmd->sub_command <= isst_valid_cmds[i].sub_cmd_end)) { + return false; + } + } + + return true; +} +EXPORT_SYMBOL_GPL(isst_if_mbox_cmd_invalid); + +/** + * isst_if_mbox_cmd_set_req() - Check mailbox command is a set request + * @cmd: Pointer to the command structure to verify. + * + * Check if the given mail box level is set request and not a get request. + * + * Return: Return true if the command is set_req, else false. + */ +bool isst_if_mbox_cmd_set_req(struct isst_if_mbox_cmd *cmd) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(isst_cmd_set_reqs); ++i) { + if (cmd->command == isst_cmd_set_reqs[i].cmd && + cmd->sub_command == isst_cmd_set_reqs[i].sub_cmd && + cmd->parameter == isst_cmd_set_reqs[i].param) { + return true; + } + } + + return false; +} +EXPORT_SYMBOL_GPL(isst_if_mbox_cmd_set_req); + static int isst_if_get_platform_info(void __user *argp) { struct isst_if_platform_info info; @@ -224,6 +304,11 @@ static long isst_if_def_ioctl(struct file *file, unsigned int cmd, if (cb->registered) ret = isst_if_exec_multi_cmd(argp, cb); break; + case ISST_IF_MBOX_COMMAND: + cb = &punit_callbacks[ISST_IF_DEV_MBOX]; + if (cb->registered) + ret = isst_if_exec_multi_cmd(argp, cb); + break; default: break; } diff --git a/drivers/platform/x86/intel_speed_select_if/isst_if_common.h b/drivers/platform/x86/intel_speed_select_if/isst_if_common.h index cdc7d019748a..7c0f71221da7 100644 --- a/drivers/platform/x86/intel_speed_select_if/isst_if_common.h +++ b/drivers/platform/x86/intel_speed_select_if/isst_if_common.h @@ -11,6 +11,7 @@ #define __ISST_IF_COMMON_H #define INTEL_RAPL_PRIO_DEVID_0 0x3451 +#define INTEL_CFG_MBOX_DEVID_0 0x3459 /* * Validate maximum commands in a single request. @@ -60,4 +61,6 @@ struct isst_if_cmd_cb { int isst_if_cdev_register(int type, struct isst_if_cmd_cb *cb); void isst_if_cdev_unregister(int type); struct pci_dev *isst_if_get_pci_dev(int cpu, int bus, int dev, int fn); +bool isst_if_mbox_cmd_set_req(struct isst_if_mbox_cmd *mbox_cmd); +bool isst_if_mbox_cmd_invalid(struct isst_if_mbox_cmd *cmd); #endif diff --git a/drivers/platform/x86/intel_speed_select_if/isst_if_mbox_pci.c b/drivers/platform/x86/intel_speed_select_if/isst_if_mbox_pci.c new file mode 100644 index 000000000000..1c4f2893cd80 --- /dev/null +++ b/drivers/platform/x86/intel_speed_select_if/isst_if_mbox_pci.c @@ -0,0 +1,199 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Intel Speed Select Interface: Mbox via PCI Interface + * Copyright (c) 2019, Intel Corporation. + * All rights reserved. + * + * Author: Srinivas Pandruvada + */ + +#include +#include +#include +#include +#include +#include + +#include "isst_if_common.h" + +#define PUNIT_MAILBOX_DATA 0xA0 +#define PUNIT_MAILBOX_INTERFACE 0xA4 +#define PUNIT_MAILBOX_BUSY_BIT 31 + +/* + * Commands has variable amount of processing time. Most of the commands will + * be done in 0-3 tries, but some takes up to 50. + * The real processing time was observed as 25us for the most of the commands + * at 2GHz. It is possible to optimize this count taking samples on customer + * systems. + */ +#define OS_MAILBOX_RETRY_COUNT 50 + +struct isst_if_device { + struct mutex mutex; +}; + +static int isst_if_mbox_cmd(struct pci_dev *pdev, + struct isst_if_mbox_cmd *mbox_cmd) +{ + u32 retries, data; + int ret; + + /* Poll for rb bit == 0 */ + retries = OS_MAILBOX_RETRY_COUNT; + do { + ret = pci_read_config_dword(pdev, PUNIT_MAILBOX_INTERFACE, + &data); + if (ret) + return ret; + + if (data & BIT_ULL(PUNIT_MAILBOX_BUSY_BIT)) { + ret = -EBUSY; + continue; + } + ret = 0; + break; + } while (--retries); + + if (ret) + return ret; + + /* Write DATA register */ + ret = pci_write_config_dword(pdev, PUNIT_MAILBOX_DATA, + mbox_cmd->req_data); + if (ret) + return ret; + + /* Write command register */ + data = BIT_ULL(PUNIT_MAILBOX_BUSY_BIT) | + (mbox_cmd->parameter & GENMASK_ULL(13, 0)) << 16 | + (mbox_cmd->sub_command << 8) | + mbox_cmd->command; + + ret = pci_write_config_dword(pdev, PUNIT_MAILBOX_INTERFACE, data); + if (ret) + return ret; + + /* Poll for rb bit == 0 */ + retries = OS_MAILBOX_RETRY_COUNT; + do { + ret = pci_read_config_dword(pdev, PUNIT_MAILBOX_INTERFACE, + &data); + if (ret) + return ret; + + if (data & BIT_ULL(PUNIT_MAILBOX_BUSY_BIT)) { + ret = -EBUSY; + continue; + } + + if (data & 0xff) + return -ENXIO; + + ret = pci_read_config_dword(pdev, PUNIT_MAILBOX_DATA, &data); + if (ret) + return ret; + + mbox_cmd->resp_data = data; + ret = 0; + break; + } while (--retries); + + return ret; +} + +static long isst_if_mbox_proc_cmd(u8 *cmd_ptr, int *write_only, int resume) +{ + struct isst_if_mbox_cmd *mbox_cmd; + struct isst_if_device *punit_dev; + struct pci_dev *pdev; + int ret; + + mbox_cmd = (struct isst_if_mbox_cmd *)cmd_ptr; + + if (isst_if_mbox_cmd_invalid(mbox_cmd)) + return -EINVAL; + + if (isst_if_mbox_cmd_set_req(mbox_cmd) && !capable(CAP_SYS_ADMIN)) + return -EPERM; + + pdev = isst_if_get_pci_dev(mbox_cmd->logical_cpu, 1, 30, 1); + if (!pdev) + return -EINVAL; + + punit_dev = pci_get_drvdata(pdev); + if (!punit_dev) + return -EINVAL; + + /* + * Basically we are allowing one complete mailbox transaction on + * a mapped PCI device at a time. + */ + mutex_lock(&punit_dev->mutex); + ret = isst_if_mbox_cmd(pdev, mbox_cmd); + mutex_unlock(&punit_dev->mutex); + if (ret) + return ret; + + *write_only = 0; + + return 0; +} + +static const struct pci_device_id isst_if_mbox_ids[] = { + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, INTEL_CFG_MBOX_DEVID_0)}, + { 0 }, +}; +MODULE_DEVICE_TABLE(pci, isst_if_mbox_ids); + +static int isst_if_mbox_probe(struct pci_dev *pdev, + const struct pci_device_id *ent) +{ + struct isst_if_device *punit_dev; + struct isst_if_cmd_cb cb; + int ret; + + punit_dev = devm_kzalloc(&pdev->dev, sizeof(*punit_dev), GFP_KERNEL); + if (!punit_dev) + return -ENOMEM; + + ret = pcim_enable_device(pdev); + if (ret) + return ret; + + mutex_init(&punit_dev->mutex); + pci_set_drvdata(pdev, punit_dev); + + memset(&cb, 0, sizeof(cb)); + cb.cmd_size = sizeof(struct isst_if_mbox_cmd); + cb.offset = offsetof(struct isst_if_mbox_cmds, mbox_cmd); + cb.cmd_callback = isst_if_mbox_proc_cmd; + cb.owner = THIS_MODULE; + ret = isst_if_cdev_register(ISST_IF_DEV_MBOX, &cb); + + if (ret) + mutex_destroy(&punit_dev->mutex); + + return ret; +} + +static void isst_if_mbox_remove(struct pci_dev *pdev) +{ + struct isst_if_device *punit_dev; + + punit_dev = pci_get_drvdata(pdev); + isst_if_cdev_unregister(ISST_IF_DEV_MBOX); + mutex_destroy(&punit_dev->mutex); +} + +static struct pci_driver isst_if_pci_driver = { + .name = "isst_if_mbox_pci", + .id_table = isst_if_mbox_ids, + .probe = isst_if_mbox_probe, + .remove = isst_if_mbox_remove, +}; + +module_pci_driver(isst_if_pci_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Intel speed select interface pci mailbox driver"); diff --git a/include/uapi/linux/isst_if.h b/include/uapi/linux/isst_if.h index fe2492ade078..e4b1c2ec3279 100644 --- a/include/uapi/linux/isst_if.h +++ b/include/uapi/linux/isst_if.h @@ -95,8 +95,46 @@ struct isst_if_io_regs { struct isst_if_io_reg io_reg[1]; }; +/** + * struct isst_if_mbox_cmd - Structure to define mail box command + * @logical_cpu: Logical CPU number to get target PCI device + * @parameter: Mailbox parameter value + * @req_data: Request data for the mailbox + * @resp_data: Response data for mailbox command response + * @command: Mailbox command value + * @sub_command: Mailbox sub command value + * @reserved: Unused, set to 0 + * + * Structure to specify mailbox command to be sent to PUNIT. + */ +struct isst_if_mbox_cmd { + __u32 logical_cpu; + __u32 parameter; + __u32 req_data; + __u32 resp_data; + __u16 command; + __u16 sub_command; + __u32 reserved; +}; + +/** + * struct isst_if_mbox_cmds - structure for mailbox commands + * @cmd_count: Number of mailbox commands in mbox_cmd[] + * @mbox_cmd[]: Holds one or more mbox commands + * + * This structure used with ioctl ISST_IF_MBOX_COMMAND to send + * one or more mailbox commands to PUNIT. Here IOCTL return value + * indicates number of commands sent or error number if no commands have + * been sent. + */ +struct isst_if_mbox_cmds { + __u32 cmd_count; + struct isst_if_mbox_cmd mbox_cmd[1]; +}; + #define ISST_IF_MAGIC 0xFE #define ISST_IF_GET_PLATFORM_INFO _IOR(ISST_IF_MAGIC, 0, struct isst_if_platform_info *) #define ISST_IF_GET_PHY_ID _IOWR(ISST_IF_MAGIC, 1, struct isst_if_cpu_map *) #define ISST_IF_IO_CMD _IOW(ISST_IF_MAGIC, 2, struct isst_if_io_regs *) +#define ISST_IF_MBOX_COMMAND _IOWR(ISST_IF_MAGIC, 3, struct isst_if_mbox_cmds *) #endif From 71b21bd7f68a6ee59003f63d2e4f84fd9b0a8d07 Mon Sep 17 00:00:00 2001 From: Srinivas Pandruvada Date: Wed, 26 Jun 2019 15:38:48 -0700 Subject: [PATCH 58/74] platform/x86: ISST: Add Intel Speed Select mailbox interface via MSRs Add an IOCTL to send mailbox commands to PUNIT using PUNIT MSRs for mailbox. Some CPU models don't have PCI device, so need to use MSRs. A limited set of mailbox commands can be sent to PUNIT. This MMIO interface is used by the intel-speed-select tool under tools/x86/power to enumerate and control Intel Speed Select features. The MBOX commands ids and semantics of the message can be checked from the source code of the tool. Signed-off-by: Srinivas Pandruvada Signed-off-by: Andy Shevchenko --- .../x86/intel_speed_select_if/Makefile | 1 + .../intel_speed_select_if/isst_if_mbox_msr.c | 180 ++++++++++++++++++ 2 files changed, 181 insertions(+) create mode 100644 drivers/platform/x86/intel_speed_select_if/isst_if_mbox_msr.c diff --git a/drivers/platform/x86/intel_speed_select_if/Makefile b/drivers/platform/x86/intel_speed_select_if/Makefile index 8dec8c858649..856076206f35 100644 --- a/drivers/platform/x86/intel_speed_select_if/Makefile +++ b/drivers/platform/x86/intel_speed_select_if/Makefile @@ -7,3 +7,4 @@ obj-$(CONFIG_INTEL_SPEED_SELECT_INTERFACE) += isst_if_common.o obj-$(CONFIG_INTEL_SPEED_SELECT_INTERFACE) += isst_if_mmio.o obj-$(CONFIG_INTEL_SPEED_SELECT_INTERFACE) += isst_if_mbox_pci.o +obj-$(CONFIG_INTEL_SPEED_SELECT_INTERFACE) += isst_if_mbox_msr.o diff --git a/drivers/platform/x86/intel_speed_select_if/isst_if_mbox_msr.c b/drivers/platform/x86/intel_speed_select_if/isst_if_mbox_msr.c new file mode 100644 index 000000000000..d4bfc32546b5 --- /dev/null +++ b/drivers/platform/x86/intel_speed_select_if/isst_if_mbox_msr.c @@ -0,0 +1,180 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Intel Speed Select Interface: Mbox via MSR Interface + * Copyright (c) 2019, Intel Corporation. + * All rights reserved. + * + * Author: Srinivas Pandruvada + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "isst_if_common.h" + +#define MSR_OS_MAILBOX_INTERFACE 0xB0 +#define MSR_OS_MAILBOX_DATA 0xB1 +#define MSR_OS_MAILBOX_BUSY_BIT 31 + +/* + * Based on experiments count is never more than 1, as the MSR overhead + * is enough to finish the command. So here this is the worst case number. + */ +#define OS_MAILBOX_RETRY_COUNT 3 + +static int isst_if_send_mbox_cmd(u8 command, u8 sub_command, u32 parameter, + u32 command_data, u32 *response_data) +{ + u32 retries; + u64 data; + int ret; + + /* Poll for rb bit == 0 */ + retries = OS_MAILBOX_RETRY_COUNT; + do { + rdmsrl(MSR_OS_MAILBOX_INTERFACE, data); + if (data & BIT_ULL(MSR_OS_MAILBOX_BUSY_BIT)) { + ret = -EBUSY; + continue; + } + ret = 0; + break; + } while (--retries); + + if (ret) + return ret; + + /* Write DATA register */ + wrmsrl(MSR_OS_MAILBOX_DATA, command_data); + + /* Write command register */ + data = BIT_ULL(MSR_OS_MAILBOX_BUSY_BIT) | + (parameter & GENMASK_ULL(13, 0)) << 16 | + (sub_command << 8) | + command; + wrmsrl(MSR_OS_MAILBOX_INTERFACE, data); + + /* Poll for rb bit == 0 */ + retries = OS_MAILBOX_RETRY_COUNT; + do { + rdmsrl(MSR_OS_MAILBOX_INTERFACE, data); + if (data & BIT_ULL(MSR_OS_MAILBOX_BUSY_BIT)) { + ret = -EBUSY; + continue; + } + + if (data & 0xff) + return -ENXIO; + + if (response_data) { + rdmsrl(MSR_OS_MAILBOX_DATA, data); + *response_data = data; + } + ret = 0; + break; + } while (--retries); + + return ret; +} + +struct msrl_action { + int err; + struct isst_if_mbox_cmd *mbox_cmd; +}; + +/* revisit, smp_call_function_single should be enough for atomic mailbox! */ +static void msrl_update_func(void *info) +{ + struct msrl_action *act = info; + + act->err = isst_if_send_mbox_cmd(act->mbox_cmd->command, + act->mbox_cmd->sub_command, + act->mbox_cmd->parameter, + act->mbox_cmd->req_data, + &act->mbox_cmd->resp_data); +} + +static long isst_if_mbox_proc_cmd(u8 *cmd_ptr, int *write_only, int resume) +{ + struct msrl_action action; + int ret; + + action.mbox_cmd = (struct isst_if_mbox_cmd *)cmd_ptr; + + if (isst_if_mbox_cmd_invalid(action.mbox_cmd)) + return -EINVAL; + + if (isst_if_mbox_cmd_set_req(action.mbox_cmd) && + !capable(CAP_SYS_ADMIN)) + return -EPERM; + + /* + * To complete mailbox command, we need to access two MSRs. + * So we don't want race to complete a mailbox transcation. + * Here smp_call ensures that msrl_update_func() has no race + * and also with wait flag, wait for completion. + * smp_call_function_single is using get_cpu() and put_cpu(). + */ + ret = smp_call_function_single(action.mbox_cmd->logical_cpu, + msrl_update_func, &action, 1); + if (ret) + return ret; + + *write_only = 0; + + return action.err; +} + +#define ICPU(model) { X86_VENDOR_INTEL, 6, model, X86_FEATURE_ANY, } + +static const struct x86_cpu_id isst_if_cpu_ids[] = { + ICPU(INTEL_FAM6_SKYLAKE_X), + {} +}; +MODULE_DEVICE_TABLE(x86cpu, isst_if_cpu_ids); + +static int __init isst_if_mbox_init(void) +{ + struct isst_if_cmd_cb cb; + const struct x86_cpu_id *id; + u64 data; + int ret; + + id = x86_match_cpu(isst_if_cpu_ids); + if (!id) + return -ENODEV; + + /* Check presence of mailbox MSRs */ + ret = rdmsrl_safe(MSR_OS_MAILBOX_INTERFACE, &data); + if (ret) + return ret; + + ret = rdmsrl_safe(MSR_OS_MAILBOX_DATA, &data); + if (ret) + return ret; + + memset(&cb, 0, sizeof(cb)); + cb.cmd_size = sizeof(struct isst_if_mbox_cmd); + cb.offset = offsetof(struct isst_if_mbox_cmds, mbox_cmd); + cb.cmd_callback = isst_if_mbox_proc_cmd; + cb.owner = THIS_MODULE; + return isst_if_cdev_register(ISST_IF_DEV_MBOX, &cb); +} +module_init(isst_if_mbox_init) + +static void __exit isst_if_mbox_exit(void) +{ + isst_if_cdev_unregister(ISST_IF_DEV_MBOX); +} +module_exit(isst_if_mbox_exit) + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Intel speed select interface mailbox driver"); From e765f37b9b8b4fa65682e9a78a2ca2b11d3d9096 Mon Sep 17 00:00:00 2001 From: Srinivas Pandruvada Date: Wed, 26 Jun 2019 15:38:49 -0700 Subject: [PATCH 59/74] platform/x86: ISST: Add Intel Speed Select PUNIT MSR interface While using new non arhitectural features using PUNIT Mailbox and MMIO read/write interface, still there is need to operate using MSRs to control PUNIT. User space could have used user user-space MSR interface for this, but when user space MSR access is disabled, then it can't. Here only limited number of MSRs are allowed using this new interface. Signed-off-by: Srinivas Pandruvada Signed-off-by: Andy Shevchenko --- .../intel_speed_select_if/isst_if_common.c | 59 +++++++++++++++++++ include/uapi/linux/isst_if.h | 32 ++++++++++ 2 files changed, 91 insertions(+) diff --git a/drivers/platform/x86/intel_speed_select_if/isst_if_common.c b/drivers/platform/x86/intel_speed_select_if/isst_if_common.c index 391fc3f12161..de2fb5292f1c 100644 --- a/drivers/platform/x86/intel_speed_select_if/isst_if_common.c +++ b/drivers/platform/x86/intel_speed_select_if/isst_if_common.c @@ -25,6 +25,11 @@ static struct isst_if_cmd_cb punit_callbacks[ISST_IF_DEV_MAX]; +static int punit_msr_white_list[] = { + MSR_TURBO_RATIO_LIMIT, + MSR_CONFIG_TDP_CONTROL, +}; + struct isst_valid_cmd_ranges { u16 cmd; u16 sub_cmd_beg; @@ -229,6 +234,54 @@ static long isst_if_proc_phyid_req(u8 *cmd_ptr, int *write_only, int resume) return 0; } +static bool match_punit_msr_white_list(int msr) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(punit_msr_white_list); ++i) { + if (punit_msr_white_list[i] == msr) + return true; + } + + return false; +} + +static long isst_if_msr_cmd_req(u8 *cmd_ptr, int *write_only, int resume) +{ + struct isst_if_msr_cmd *msr_cmd; + int ret; + + msr_cmd = (struct isst_if_msr_cmd *)cmd_ptr; + + if (!match_punit_msr_white_list(msr_cmd->msr)) + return -EINVAL; + + if (msr_cmd->logical_cpu >= nr_cpu_ids) + return -EINVAL; + + if (msr_cmd->read_write) { + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + ret = wrmsrl_safe_on_cpu(msr_cmd->logical_cpu, + msr_cmd->msr, + msr_cmd->data); + *write_only = 1; + } else { + u64 data; + + ret = rdmsrl_safe_on_cpu(msr_cmd->logical_cpu, + msr_cmd->msr, &data); + if (!ret) { + msr_cmd->data = data; + *write_only = 0; + } + } + + + return ret; +} + static long isst_if_exec_multi_cmd(void __user *argp, struct isst_if_cmd_cb *cb) { unsigned char __user *ptr; @@ -309,6 +362,12 @@ static long isst_if_def_ioctl(struct file *file, unsigned int cmd, if (cb->registered) ret = isst_if_exec_multi_cmd(argp, cb); break; + case ISST_IF_MSR_COMMAND: + cmd_cb.cmd_size = sizeof(struct isst_if_msr_cmd); + cmd_cb.offset = offsetof(struct isst_if_msr_cmds, msr_cmd); + cmd_cb.cmd_callback = isst_if_msr_cmd_req; + ret = isst_if_exec_multi_cmd(argp, &cmd_cb); + break; default: break; } diff --git a/include/uapi/linux/isst_if.h b/include/uapi/linux/isst_if.h index e4b1c2ec3279..d10b832c58c5 100644 --- a/include/uapi/linux/isst_if.h +++ b/include/uapi/linux/isst_if.h @@ -132,9 +132,41 @@ struct isst_if_mbox_cmds { struct isst_if_mbox_cmd mbox_cmd[1]; }; +/** + * struct isst_if_msr_cmd - Structure to define msr command + * @read_write: Value 0: Read, 1: Write + * @logical_cpu: Logical CPU number + * @msr: MSR number + * @data: For write operation, data to write, for read + * place holder + * + * Structure to specify MSR command related to PUNIT. + */ +struct isst_if_msr_cmd { + __u32 read_write; /* Read:0, Write:1 */ + __u32 logical_cpu; + __u64 msr; + __u64 data; +}; + +/** + * struct isst_if_msr_cmds - structure for msr commands + * @cmd_count: Number of mailbox commands in msr_cmd[] + * @msr_cmd[]: Holds one or more msr commands + * + * This structure used with ioctl ISST_IF_MSR_COMMAND to send + * one or more MSR commands. IOCTL return value indicates number of + * commands sent or error number if no commands have been sent. + */ +struct isst_if_msr_cmds { + __u32 cmd_count; + struct isst_if_msr_cmd msr_cmd[1]; +}; + #define ISST_IF_MAGIC 0xFE #define ISST_IF_GET_PLATFORM_INFO _IOR(ISST_IF_MAGIC, 0, struct isst_if_platform_info *) #define ISST_IF_GET_PHY_ID _IOWR(ISST_IF_MAGIC, 1, struct isst_if_cpu_map *) #define ISST_IF_IO_CMD _IOW(ISST_IF_MAGIC, 2, struct isst_if_io_regs *) #define ISST_IF_MBOX_COMMAND _IOWR(ISST_IF_MAGIC, 3, struct isst_if_mbox_cmds *) +#define ISST_IF_MSR_COMMAND _IOWR(ISST_IF_MAGIC, 4, struct isst_if_msr_cmds *) #endif From f607874f35cbd276a837d7147d4e1ec752dfef44 Mon Sep 17 00:00:00 2001 From: Srinivas Pandruvada Date: Wed, 26 Jun 2019 15:38:50 -0700 Subject: [PATCH 60/74] platform/x86: ISST: Restore state on resume Commands which causes PUNIT writes, store them and restore them on system resume. The driver stores all such requests in a hash table and stores the the latest mailbox request parameters. On resume these commands mail box commands are executed again. There are only 5 such mail box commands which will trigger such processing so a very low overhead in store and execute on resume. Also there is no order requirement for mail box commands for these write/set commands. There is one MSR request for changing turbo ratio limits, this also stored and get restored on resume and cpu online. Signed-off-by: Srinivas Pandruvada Signed-off-by: Andy Shevchenko --- .../intel_speed_select_if/isst_if_common.c | 154 ++++++++++++++++++ .../intel_speed_select_if/isst_if_common.h | 3 + .../intel_speed_select_if/isst_if_mbox_msr.c | 38 ++++- .../intel_speed_select_if/isst_if_mbox_pci.c | 15 ++ .../x86/intel_speed_select_if/isst_if_mmio.c | 49 ++++++ 5 files changed, 258 insertions(+), 1 deletion(-) diff --git a/drivers/platform/x86/intel_speed_select_if/isst_if_common.c b/drivers/platform/x86/intel_speed_select_if/isst_if_common.c index de2fb5292f1c..68d75391db57 100644 --- a/drivers/platform/x86/intel_speed_select_if/isst_if_common.c +++ b/drivers/platform/x86/intel_speed_select_if/isst_if_common.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -58,6 +59,151 @@ static const struct isst_cmd_set_req_type isst_cmd_set_reqs[] = { {0x7F, 0x08, 0x00}, }; +struct isst_cmd { + struct hlist_node hnode; + u64 data; + u32 cmd; + int cpu; + int mbox_cmd_type; + u32 param; +}; + +static DECLARE_HASHTABLE(isst_hash, 8); +static DEFINE_MUTEX(isst_hash_lock); + +static int isst_store_new_cmd(int cmd, u32 cpu, int mbox_cmd_type, u32 param, + u32 data) +{ + struct isst_cmd *sst_cmd; + + sst_cmd = kmalloc(sizeof(*sst_cmd), GFP_KERNEL); + if (!sst_cmd) + return -ENOMEM; + + sst_cmd->cpu = cpu; + sst_cmd->cmd = cmd; + sst_cmd->mbox_cmd_type = mbox_cmd_type; + sst_cmd->param = param; + sst_cmd->data = data; + + hash_add(isst_hash, &sst_cmd->hnode, sst_cmd->cmd); + + return 0; +} + +static void isst_delete_hash(void) +{ + struct isst_cmd *sst_cmd; + struct hlist_node *tmp; + int i; + + hash_for_each_safe(isst_hash, i, tmp, sst_cmd, hnode) { + hash_del(&sst_cmd->hnode); + kfree(sst_cmd); + } +} + +/** + * isst_store_cmd() - Store command to a hash table + * @cmd: Mailbox command. + * @sub_cmd: Mailbox sub-command or MSR id. + * @mbox_cmd_type: Mailbox or MSR command. + * @param: Mailbox parameter. + * @data: Mailbox request data or MSR data. + * + * Stores the command to a hash table if there is no such command already + * stored. If already stored update the latest parameter and data for the + * command. + * + * Return: Return result of store to hash table, 0 for success, others for + * failure. + */ +int isst_store_cmd(int cmd, int sub_cmd, u32 cpu, int mbox_cmd_type, + u32 param, u64 data) +{ + struct isst_cmd *sst_cmd; + int full_cmd, ret; + + full_cmd = (cmd & GENMASK_ULL(15, 0)) << 16; + full_cmd |= (sub_cmd & GENMASK_ULL(15, 0)); + mutex_lock(&isst_hash_lock); + hash_for_each_possible(isst_hash, sst_cmd, hnode, full_cmd) { + if (sst_cmd->cmd == full_cmd && sst_cmd->cpu == cpu && + sst_cmd->mbox_cmd_type == mbox_cmd_type) { + sst_cmd->param = param; + sst_cmd->data = data; + mutex_unlock(&isst_hash_lock); + return 0; + } + } + + ret = isst_store_new_cmd(full_cmd, cpu, mbox_cmd_type, param, data); + mutex_unlock(&isst_hash_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(isst_store_cmd); + +static void isst_mbox_resume_command(struct isst_if_cmd_cb *cb, + struct isst_cmd *sst_cmd) +{ + struct isst_if_mbox_cmd mbox_cmd; + int wr_only; + + mbox_cmd.command = (sst_cmd->cmd & GENMASK_ULL(31, 16)) >> 16; + mbox_cmd.sub_command = sst_cmd->cmd & GENMASK_ULL(15, 0); + mbox_cmd.parameter = sst_cmd->param; + mbox_cmd.req_data = sst_cmd->data; + mbox_cmd.logical_cpu = sst_cmd->cpu; + (cb->cmd_callback)((u8 *)&mbox_cmd, &wr_only, 1); +} + +/** + * isst_resume_common() - Process Resume request + * + * On resume replay all mailbox commands and MSRs. + * + * Return: None. + */ +void isst_resume_common(void) +{ + struct isst_cmd *sst_cmd; + int i; + + hash_for_each(isst_hash, i, sst_cmd, hnode) { + struct isst_if_cmd_cb *cb; + + if (sst_cmd->mbox_cmd_type) { + cb = &punit_callbacks[ISST_IF_DEV_MBOX]; + if (cb->registered) + isst_mbox_resume_command(cb, sst_cmd); + } else { + wrmsrl_safe_on_cpu(sst_cmd->cpu, sst_cmd->cmd, + sst_cmd->data); + } + } +} +EXPORT_SYMBOL_GPL(isst_resume_common); + +static void isst_restore_msr_local(int cpu) +{ + struct isst_cmd *sst_cmd; + int i; + + mutex_lock(&isst_hash_lock); + for (i = 0; i < ARRAY_SIZE(punit_msr_white_list); ++i) { + if (!punit_msr_white_list[i]) + break; + + hash_for_each_possible(isst_hash, sst_cmd, hnode, + punit_msr_white_list[i]) { + if (!sst_cmd->mbox_cmd_type && sst_cmd->cpu == cpu) + wrmsrl_safe(sst_cmd->cmd, sst_cmd->data); + } + } + mutex_unlock(&isst_hash_lock); +} + /** * isst_if_mbox_cmd_invalid() - Check invalid mailbox commands * @cmd: Pointer to the command structure to verify. @@ -185,6 +331,8 @@ static int isst_if_cpu_online(unsigned int cpu) } isst_cpu_info[cpu].punit_cpu_id = data; + isst_restore_msr_local(cpu); + return 0; } @@ -267,6 +415,10 @@ static long isst_if_msr_cmd_req(u8 *cmd_ptr, int *write_only, int resume) msr_cmd->msr, msr_cmd->data); *write_only = 1; + if (!ret && !resume) + ret = isst_store_cmd(0, msr_cmd->msr, + msr_cmd->logical_cpu, + 0, 0, msr_cmd->data); } else { u64 data; @@ -507,6 +659,8 @@ void isst_if_cdev_unregister(int device_type) mutex_lock(&punit_misc_dev_lock); misc_usage_count--; punit_callbacks[device_type].registered = 0; + if (device_type == ISST_IF_DEV_MBOX) + isst_delete_hash(); if (!misc_usage_count && !misc_device_ret) { misc_deregister(&isst_if_char_driver); isst_if_cpu_info_exit(); diff --git a/drivers/platform/x86/intel_speed_select_if/isst_if_common.h b/drivers/platform/x86/intel_speed_select_if/isst_if_common.h index 7c0f71221da7..1409a5bb5582 100644 --- a/drivers/platform/x86/intel_speed_select_if/isst_if_common.h +++ b/drivers/platform/x86/intel_speed_select_if/isst_if_common.h @@ -63,4 +63,7 @@ void isst_if_cdev_unregister(int type); struct pci_dev *isst_if_get_pci_dev(int cpu, int bus, int dev, int fn); bool isst_if_mbox_cmd_set_req(struct isst_if_mbox_cmd *mbox_cmd); bool isst_if_mbox_cmd_invalid(struct isst_if_mbox_cmd *cmd); +int isst_store_cmd(int cmd, int sub_command, u32 cpu, int mbox_cmd, + u32 param, u64 data); +void isst_resume_common(void); #endif diff --git a/drivers/platform/x86/intel_speed_select_if/isst_if_mbox_msr.c b/drivers/platform/x86/intel_speed_select_if/isst_if_mbox_msr.c index d4bfc32546b5..89b042aecef3 100644 --- a/drivers/platform/x86/intel_speed_select_if/isst_if_mbox_msr.c +++ b/drivers/platform/x86/intel_speed_select_if/isst_if_mbox_msr.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -128,11 +129,37 @@ static long isst_if_mbox_proc_cmd(u8 *cmd_ptr, int *write_only, int resume) if (ret) return ret; + if (!action.err && !resume && isst_if_mbox_cmd_set_req(action.mbox_cmd)) + action.err = isst_store_cmd(action.mbox_cmd->command, + action.mbox_cmd->sub_command, + action.mbox_cmd->logical_cpu, 1, + action.mbox_cmd->parameter, + action.mbox_cmd->req_data); *write_only = 0; return action.err; } + +static int isst_pm_notify(struct notifier_block *nb, + unsigned long mode, void *_unused) +{ + switch (mode) { + case PM_POST_HIBERNATION: + case PM_POST_RESTORE: + case PM_POST_SUSPEND: + isst_resume_common(); + break; + default: + break; + } + return 0; +} + +static struct notifier_block isst_pm_nb = { + .notifier_call = isst_pm_notify, +}; + #define ICPU(model) { X86_VENDOR_INTEL, 6, model, X86_FEATURE_ANY, } static const struct x86_cpu_id isst_if_cpu_ids[] = { @@ -166,12 +193,21 @@ static int __init isst_if_mbox_init(void) cb.offset = offsetof(struct isst_if_mbox_cmds, mbox_cmd); cb.cmd_callback = isst_if_mbox_proc_cmd; cb.owner = THIS_MODULE; - return isst_if_cdev_register(ISST_IF_DEV_MBOX, &cb); + ret = isst_if_cdev_register(ISST_IF_DEV_MBOX, &cb); + if (ret) + return ret; + + ret = register_pm_notifier(&isst_pm_nb); + if (ret) + isst_if_cdev_unregister(ISST_IF_DEV_MBOX); + + return ret; } module_init(isst_if_mbox_init) static void __exit isst_if_mbox_exit(void) { + unregister_pm_notifier(&isst_pm_nb); isst_if_cdev_unregister(ISST_IF_DEV_MBOX); } module_exit(isst_if_mbox_exit) diff --git a/drivers/platform/x86/intel_speed_select_if/isst_if_mbox_pci.c b/drivers/platform/x86/intel_speed_select_if/isst_if_mbox_pci.c index 1c4f2893cd80..de4169d0796b 100644 --- a/drivers/platform/x86/intel_speed_select_if/isst_if_mbox_pci.c +++ b/drivers/platform/x86/intel_speed_select_if/isst_if_mbox_pci.c @@ -131,6 +131,12 @@ static long isst_if_mbox_proc_cmd(u8 *cmd_ptr, int *write_only, int resume) */ mutex_lock(&punit_dev->mutex); ret = isst_if_mbox_cmd(pdev, mbox_cmd); + if (!ret && !resume && isst_if_mbox_cmd_set_req(mbox_cmd)) + ret = isst_store_cmd(mbox_cmd->command, + mbox_cmd->sub_command, + mbox_cmd->logical_cpu, 1, + mbox_cmd->parameter, + mbox_cmd->req_data); mutex_unlock(&punit_dev->mutex); if (ret) return ret; @@ -186,11 +192,20 @@ static void isst_if_mbox_remove(struct pci_dev *pdev) mutex_destroy(&punit_dev->mutex); } +static int __maybe_unused isst_if_resume(struct device *device) +{ + isst_resume_common(); + return 0; +} + +static SIMPLE_DEV_PM_OPS(isst_if_pm_ops, NULL, isst_if_resume); + static struct pci_driver isst_if_pci_driver = { .name = "isst_if_mbox_pci", .id_table = isst_if_mbox_ids, .probe = isst_if_mbox_probe, .remove = isst_if_mbox_remove, + .driver.pm = &isst_if_pm_ops, }; module_pci_driver(isst_if_pci_driver); diff --git a/drivers/platform/x86/intel_speed_select_if/isst_if_mmio.c b/drivers/platform/x86/intel_speed_select_if/isst_if_mmio.c index 1c25a1235b9e..f7266a115a08 100644 --- a/drivers/platform/x86/intel_speed_select_if/isst_if_mmio.c +++ b/drivers/platform/x86/intel_speed_select_if/isst_if_mmio.c @@ -15,8 +15,20 @@ #include "isst_if_common.h" +struct isst_mmio_range { + int beg; + int end; +}; + +struct isst_mmio_range mmio_range[] = { + {0x04, 0x14}, + {0x20, 0xD0}, +}; + struct isst_if_device { void __iomem *punit_mmio; + u32 range_0[5]; + u32 range_1[45]; struct mutex mutex; }; @@ -118,11 +130,48 @@ static void isst_if_remove(struct pci_dev *pdev) mutex_destroy(&punit_dev->mutex); } +static int __maybe_unused isst_if_suspend(struct device *device) +{ + struct pci_dev *pdev = to_pci_dev(device); + struct isst_if_device *punit_dev; + int i; + + punit_dev = pci_get_drvdata(pdev); + for (i = 0; i < ARRAY_SIZE(punit_dev->range_0); ++i) + punit_dev->range_0[i] = readl(punit_dev->punit_mmio + + mmio_range[0].beg + 4 * i); + for (i = 0; i < ARRAY_SIZE(punit_dev->range_1); ++i) + punit_dev->range_1[i] = readl(punit_dev->punit_mmio + + mmio_range[1].beg + 4 * i); + + return 0; +} + +static int __maybe_unused isst_if_resume(struct device *device) +{ + struct pci_dev *pdev = to_pci_dev(device); + struct isst_if_device *punit_dev; + int i; + + punit_dev = pci_get_drvdata(pdev); + for (i = 0; i < ARRAY_SIZE(punit_dev->range_0); ++i) + writel(punit_dev->range_0[i], punit_dev->punit_mmio + + mmio_range[0].beg + 4 * i); + for (i = 0; i < ARRAY_SIZE(punit_dev->range_1); ++i) + writel(punit_dev->range_1[i], punit_dev->punit_mmio + + mmio_range[1].beg + 4 * i); + + return 0; +} + +static SIMPLE_DEV_PM_OPS(isst_if_pm_ops, isst_if_suspend, isst_if_resume); + static struct pci_driver isst_if_pci_driver = { .name = "isst_if_pci", .id_table = isst_if_ids, .probe = isst_if_probe, .remove = isst_if_remove, + .driver.pm = &isst_if_pm_ops, }; module_pci_driver(isst_if_pci_driver); From 3fb4f7cd472c7f5905c91508e988f6b28372210d Mon Sep 17 00:00:00 2001 From: Srinivas Pandruvada Date: Sun, 30 Jun 2019 10:14:08 -0700 Subject: [PATCH 61/74] tools/power/x86: A tool to validate Intel Speed Select commands The Intel(R) Speed select technologies contains four features. Performance profile:An non architectural mechanism that allows multiple optimized performance profiles per system via static and/or dynamic adjustment of core count, workload, Tjmax, and TDP, etc. aka ISS in the documentation. Base Frequency: Enables users to increase guaranteed base frequency on certain cores (high priority cores) in exchange for lower base frequency on remaining cores (low priority cores). aka PBF in the documenation. Turbo frequency: Enables the ability to set different turbo ratio limits to cores based on priority. aka FACT in the documentation. Core power: An Interface that allows user to define per core/tile priority. There is a multi level help for commands and options. This can be used to check required arguments for each feature and commands for the feature. To start navigating the features start with $sudo intel-speed-select --help For help on a specific feature for example $sudo intel-speed-select perf-profile --help To get help for a command for a feature for example $sudo intel-speed-select perf-profile get-lock-status --help Signed-off-by: Srinivas Pandruvada Acked-by: Len Brown Acked-by: Rafael J. Wysocki Signed-off-by: Andy Shevchenko --- tools/Makefile | 12 +- tools/power/x86/intel-speed-select/Build | 1 + tools/power/x86/intel-speed-select/Makefile | 56 + .../x86/intel-speed-select/isst-config.c | 1607 +++++++++++++++++ .../power/x86/intel-speed-select/isst-core.c | 721 ++++++++ .../x86/intel-speed-select/isst-display.c | 479 +++++ tools/power/x86/intel-speed-select/isst.h | 231 +++ 7 files changed, 3102 insertions(+), 5 deletions(-) create mode 100644 tools/power/x86/intel-speed-select/Build create mode 100644 tools/power/x86/intel-speed-select/Makefile create mode 100644 tools/power/x86/intel-speed-select/isst-config.c create mode 100644 tools/power/x86/intel-speed-select/isst-core.c create mode 100644 tools/power/x86/intel-speed-select/isst-display.c create mode 100644 tools/power/x86/intel-speed-select/isst.h diff --git a/tools/Makefile b/tools/Makefile index 3dfd72ae6c1a..68defd7ecf5d 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -19,6 +19,7 @@ help: @echo ' gpio - GPIO tools' @echo ' hv - tools used when in Hyper-V clients' @echo ' iio - IIO tools' + @echo ' intel-speed-select - Intel Speed Select tool' @echo ' kvm_stat - top-like utility for displaying kvm statistics' @echo ' leds - LEDs tools' @echo ' liblockdep - user-space wrapper for kernel locking-validator' @@ -82,7 +83,7 @@ perf: FORCE selftests: FORCE $(call descend,testing/$@) -turbostat x86_energy_perf_policy: FORCE +turbostat x86_energy_perf_policy intel-speed-select: FORCE $(call descend,power/x86/$@) tmon: FORCE @@ -115,7 +116,7 @@ liblockdep_install: selftests_install: $(call descend,testing/$(@:_install=),install) -turbostat_install x86_energy_perf_policy_install: +turbostat_install x86_energy_perf_policy_install intel-speed-select_install: $(call descend,power/x86/$(@:_install=),install) tmon_install: @@ -132,7 +133,7 @@ install: acpi_install cgroup_install cpupower_install gpio_install \ perf_install selftests_install turbostat_install usb_install \ virtio_install vm_install bpf_install x86_energy_perf_policy_install \ tmon_install freefall_install objtool_install kvm_stat_install \ - wmi_install pci_install debugging_install + wmi_install pci_install debugging_install intel-speed-select_install acpi_clean: $(call descend,power/acpi,clean) @@ -162,7 +163,7 @@ perf_clean: selftests_clean: $(call descend,testing/$(@:_clean=),clean) -turbostat_clean x86_energy_perf_policy_clean: +turbostat_clean x86_energy_perf_policy_clean intel-speed-select_clean: $(call descend,power/x86/$(@:_clean=),clean) tmon_clean: @@ -178,6 +179,7 @@ clean: acpi_clean cgroup_clean cpupower_clean hv_clean firewire_clean \ perf_clean selftests_clean turbostat_clean spi_clean usb_clean virtio_clean \ vm_clean bpf_clean iio_clean x86_energy_perf_policy_clean tmon_clean \ freefall_clean build_clean libbpf_clean libsubcmd_clean liblockdep_clean \ - gpio_clean objtool_clean leds_clean wmi_clean pci_clean firmware_clean debugging_clean + gpio_clean objtool_clean leds_clean wmi_clean pci_clean firmware_clean debugging_clean \ + intel-speed-select_clean .PHONY: FORCE diff --git a/tools/power/x86/intel-speed-select/Build b/tools/power/x86/intel-speed-select/Build new file mode 100644 index 000000000000..b61456d75190 --- /dev/null +++ b/tools/power/x86/intel-speed-select/Build @@ -0,0 +1 @@ +intel-speed-select-y += isst-config.o isst-core.o isst-display.o diff --git a/tools/power/x86/intel-speed-select/Makefile b/tools/power/x86/intel-speed-select/Makefile new file mode 100644 index 000000000000..12c6939dca2a --- /dev/null +++ b/tools/power/x86/intel-speed-select/Makefile @@ -0,0 +1,56 @@ +# SPDX-License-Identifier: GPL-2.0 +include ../../../scripts/Makefile.include + +bindir ?= /usr/bin + +ifeq ($(srctree),) +srctree := $(patsubst %/,%,$(dir $(CURDIR))) +srctree := $(patsubst %/,%,$(dir $(srctree))) +srctree := $(patsubst %/,%,$(dir $(srctree))) +srctree := $(patsubst %/,%,$(dir $(srctree))) +endif + +# Do not use make's built-in rules +# (this improves performance and avoids hard-to-debug behaviour); +MAKEFLAGS += -r + +override CFLAGS += -O2 -Wall -g -D_GNU_SOURCE -I$(OUTPUT)include + +ALL_TARGETS := intel-speed-select +ALL_PROGRAMS := $(patsubst %,$(OUTPUT)%,$(ALL_TARGETS)) + +all: $(ALL_PROGRAMS) + +export srctree OUTPUT CC LD CFLAGS +include $(srctree)/tools/build/Makefile.include + +# +# We need the following to be outside of kernel tree +# +$(OUTPUT)include/linux/isst_if.h: ../../../../include/uapi/linux/isst_if.h + mkdir -p $(OUTPUT)include/linux 2>&1 || true + ln -sf $(CURDIR)/../../../../include/uapi/linux/isst_if.h $@ + +prepare: $(OUTPUT)include/linux/isst_if.h + +ISST_IN := $(OUTPUT)intel-speed-select-in.o + +$(ISST_IN): prepare FORCE + $(Q)$(MAKE) $(build)=intel-speed-select +$(OUTPUT)intel-speed-select: $(ISST_IN) + $(QUIET_LINK)$(CC) $(CFLAGS) $(LDFLAGS) $< -o $@ + +clean: + rm -f $(ALL_PROGRAMS) + rm -rf $(OUTPUT)include/linux/isst_if.h + find $(if $(OUTPUT),$(OUTPUT),.) -name '*.o' -delete -o -name '\.*.d' -delete + +install: $(ALL_PROGRAMS) + install -d -m 755 $(DESTDIR)$(bindir); \ + for program in $(ALL_PROGRAMS); do \ + install $$program $(DESTDIR)$(bindir); \ + done + +FORCE: + +.PHONY: all install clean FORCE prepare diff --git a/tools/power/x86/intel-speed-select/isst-config.c b/tools/power/x86/intel-speed-select/isst-config.c new file mode 100644 index 000000000000..91c5ad1685a1 --- /dev/null +++ b/tools/power/x86/intel-speed-select/isst-config.c @@ -0,0 +1,1607 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Intel Speed Select -- Enumerate and control features + * Copyright (c) 2019 Intel Corporation. + */ + +#include + +#include "isst.h" + +struct process_cmd_struct { + char *feature; + char *command; + void (*process_fn)(void); +}; + +static const char *version_str = "v1.0"; +static const int supported_api_ver = 1; +static struct isst_if_platform_info isst_platform_info; +static char *progname; +static int debug_flag; +static FILE *outf; + +static int cpu_model; + +#define MAX_CPUS_IN_ONE_REQ 64 +static short max_target_cpus; +static unsigned short target_cpus[MAX_CPUS_IN_ONE_REQ]; + +static int topo_max_cpus; +static size_t present_cpumask_size; +static cpu_set_t *present_cpumask; +static size_t target_cpumask_size; +static cpu_set_t *target_cpumask; +static int tdp_level = 0xFF; +static int fact_bucket = 0xFF; +static int fact_avx = 0xFF; +static unsigned long long fact_trl; +static int out_format_json; +static int cmd_help; + +/* clos related */ +static int current_clos = -1; +static int clos_epp = -1; +static int clos_prop_prio = -1; +static int clos_min = -1; +static int clos_max = -1; +static int clos_desired = -1; +static int clos_priority_type; + +struct _cpu_map { + unsigned short core_id; + unsigned short pkg_id; + unsigned short die_id; + unsigned short punit_cpu; + unsigned short punit_cpu_core; +}; +struct _cpu_map *cpu_map; + +void debug_printf(const char *format, ...) +{ + va_list args; + + va_start(args, format); + + if (debug_flag) + vprintf(format, args); + + va_end(args); +} + +static void update_cpu_model(void) +{ + unsigned int ebx, ecx, edx; + unsigned int fms, family; + + __cpuid(1, fms, ebx, ecx, edx); + family = (fms >> 8) & 0xf; + cpu_model = (fms >> 4) & 0xf; + if (family == 6 || family == 0xf) + cpu_model += ((fms >> 16) & 0xf) << 4; +} + +/* Open a file, and exit on failure */ +static FILE *fopen_or_exit(const char *path, const char *mode) +{ + FILE *filep = fopen(path, mode); + + if (!filep) + err(1, "%s: open failed", path); + + return filep; +} + +/* Parse a file containing a single int */ +static int parse_int_file(int fatal, const char *fmt, ...) +{ + va_list args; + char path[PATH_MAX]; + FILE *filep; + int value; + + va_start(args, fmt); + vsnprintf(path, sizeof(path), fmt, args); + va_end(args); + if (fatal) { + filep = fopen_or_exit(path, "r"); + } else { + filep = fopen(path, "r"); + if (!filep) + return -1; + } + if (fscanf(filep, "%d", &value) != 1) + err(1, "%s: failed to parse number from file", path); + fclose(filep); + + return value; +} + +int cpufreq_sysfs_present(void) +{ + DIR *dir; + + dir = opendir("/sys/devices/system/cpu/cpu0/cpufreq"); + if (dir) { + closedir(dir); + return 1; + } + + return 0; +} + +int out_format_is_json(void) +{ + return out_format_json; +} + +int get_physical_package_id(int cpu) +{ + return parse_int_file( + 1, "/sys/devices/system/cpu/cpu%d/topology/physical_package_id", + cpu); +} + +int get_physical_core_id(int cpu) +{ + return parse_int_file( + 1, "/sys/devices/system/cpu/cpu%d/topology/core_id", cpu); +} + +int get_physical_die_id(int cpu) +{ + int ret; + + ret = parse_int_file(0, "/sys/devices/system/cpu/cpu%d/topology/die_id", + cpu); + if (ret < 0) + ret = 0; + + return ret; +} + +int get_topo_max_cpus(void) +{ + return topo_max_cpus; +} + +#define MAX_PACKAGE_COUNT 8 +#define MAX_DIE_PER_PACKAGE 2 +static void for_each_online_package_in_set(void (*callback)(int, void *, void *, + void *, void *), + void *arg1, void *arg2, void *arg3, + void *arg4) +{ + int max_packages[MAX_PACKAGE_COUNT * MAX_PACKAGE_COUNT]; + int pkg_index = 0, i; + + memset(max_packages, 0xff, sizeof(max_packages)); + for (i = 0; i < topo_max_cpus; ++i) { + int j, online, pkg_id, die_id = 0, skip = 0; + + if (!CPU_ISSET_S(i, present_cpumask_size, present_cpumask)) + continue; + if (i) + online = parse_int_file( + 1, "/sys/devices/system/cpu/cpu%d/online", i); + else + online = + 1; /* online entry for CPU 0 needs some special configs */ + + die_id = get_physical_die_id(i); + if (die_id < 0) + die_id = 0; + pkg_id = get_physical_package_id(i); + /* Create an unique id for package, die combination to store */ + pkg_id = (MAX_PACKAGE_COUNT * pkg_id + die_id); + + for (j = 0; j < pkg_index; ++j) { + if (max_packages[j] == pkg_id) { + skip = 1; + break; + } + } + + if (!skip && online && callback) { + callback(i, arg1, arg2, arg3, arg4); + max_packages[pkg_index++] = pkg_id; + } + } +} + +static void for_each_online_target_cpu_in_set( + void (*callback)(int, void *, void *, void *, void *), void *arg1, + void *arg2, void *arg3, void *arg4) +{ + int i; + + for (i = 0; i < topo_max_cpus; ++i) { + int online; + + if (!CPU_ISSET_S(i, target_cpumask_size, target_cpumask)) + continue; + if (i) + online = parse_int_file( + 1, "/sys/devices/system/cpu/cpu%d/online", i); + else + online = + 1; /* online entry for CPU 0 needs some special configs */ + + if (online && callback) + callback(i, arg1, arg2, arg3, arg4); + } +} + +#define BITMASK_SIZE 32 +static void set_max_cpu_num(void) +{ + FILE *filep; + unsigned long dummy; + + topo_max_cpus = 0; + filep = fopen_or_exit( + "/sys/devices/system/cpu/cpu0/topology/thread_siblings", "r"); + while (fscanf(filep, "%lx,", &dummy) == 1) + topo_max_cpus += BITMASK_SIZE; + fclose(filep); + topo_max_cpus--; /* 0 based */ + + debug_printf("max cpus %d\n", topo_max_cpus); +} + +size_t alloc_cpu_set(cpu_set_t **cpu_set) +{ + cpu_set_t *_cpu_set; + size_t size; + + _cpu_set = CPU_ALLOC((topo_max_cpus + 1)); + if (_cpu_set == NULL) + err(3, "CPU_ALLOC"); + size = CPU_ALLOC_SIZE((topo_max_cpus + 1)); + CPU_ZERO_S(size, _cpu_set); + + *cpu_set = _cpu_set; + return size; +} + +void free_cpu_set(cpu_set_t *cpu_set) +{ + CPU_FREE(cpu_set); +} + +static int cpu_cnt[MAX_PACKAGE_COUNT][MAX_DIE_PER_PACKAGE]; +static void set_cpu_present_cpu_mask(void) +{ + size_t size; + DIR *dir; + int i; + + size = alloc_cpu_set(&present_cpumask); + present_cpumask_size = size; + for (i = 0; i < topo_max_cpus; ++i) { + char buffer[256]; + + snprintf(buffer, sizeof(buffer), + "/sys/devices/system/cpu/cpu%d", i); + dir = opendir(buffer); + if (dir) { + int pkg_id, die_id; + + CPU_SET_S(i, size, present_cpumask); + die_id = get_physical_die_id(i); + if (die_id < 0) + die_id = 0; + + pkg_id = get_physical_package_id(i); + if (pkg_id < MAX_PACKAGE_COUNT && + die_id < MAX_DIE_PER_PACKAGE) + cpu_cnt[pkg_id][die_id]++; + } + closedir(dir); + } +} + +int get_cpu_count(int pkg_id, int die_id) +{ + if (pkg_id < MAX_PACKAGE_COUNT && die_id < MAX_DIE_PER_PACKAGE) + return cpu_cnt[pkg_id][die_id] + 1; + + return 0; +} + +static void set_cpu_target_cpu_mask(void) +{ + size_t size; + int i; + + size = alloc_cpu_set(&target_cpumask); + target_cpumask_size = size; + for (i = 0; i < max_target_cpus; ++i) { + if (!CPU_ISSET_S(target_cpus[i], present_cpumask_size, + present_cpumask)) + continue; + + CPU_SET_S(target_cpus[i], size, target_cpumask); + } +} + +static void create_cpu_map(void) +{ + const char *pathname = "/dev/isst_interface"; + int i, fd = 0; + struct isst_if_cpu_maps map; + + cpu_map = malloc(sizeof(*cpu_map) * topo_max_cpus); + if (!cpu_map) + err(3, "cpumap"); + + fd = open(pathname, O_RDWR); + if (fd < 0) + err(-1, "%s open failed", pathname); + + for (i = 0; i < topo_max_cpus; ++i) { + if (!CPU_ISSET_S(i, present_cpumask_size, present_cpumask)) + continue; + + map.cmd_count = 1; + map.cpu_map[0].logical_cpu = i; + + debug_printf(" map logical_cpu:%d\n", + map.cpu_map[0].logical_cpu); + if (ioctl(fd, ISST_IF_GET_PHY_ID, &map) == -1) { + perror("ISST_IF_GET_PHY_ID"); + fprintf(outf, "Error: map logical_cpu:%d\n", + map.cpu_map[0].logical_cpu); + continue; + } + cpu_map[i].core_id = get_physical_core_id(i); + cpu_map[i].pkg_id = get_physical_package_id(i); + cpu_map[i].die_id = get_physical_die_id(i); + cpu_map[i].punit_cpu = map.cpu_map[0].physical_cpu; + cpu_map[i].punit_cpu_core = (map.cpu_map[0].physical_cpu >> + 1); // shift to get core id + + debug_printf( + "map logical_cpu:%d core: %d die:%d pkg:%d punit_cpu:%d punit_core:%d\n", + i, cpu_map[i].core_id, cpu_map[i].die_id, + cpu_map[i].pkg_id, cpu_map[i].punit_cpu, + cpu_map[i].punit_cpu_core); + } + + if (fd) + close(fd); +} + +int find_logical_cpu(int pkg_id, int die_id, int punit_core_id) +{ + int i; + + for (i = 0; i < topo_max_cpus; ++i) { + if (cpu_map[i].pkg_id == pkg_id && + cpu_map[i].die_id == die_id && + cpu_map[i].punit_cpu_core == punit_core_id) + return i; + } + + return -EINVAL; +} + +void set_cpu_mask_from_punit_coremask(int cpu, unsigned long long core_mask, + size_t core_cpumask_size, + cpu_set_t *core_cpumask, int *cpu_cnt) +{ + int i, cnt = 0; + int die_id, pkg_id; + + *cpu_cnt = 0; + die_id = get_physical_die_id(cpu); + pkg_id = get_physical_package_id(cpu); + + for (i = 0; i < 64; ++i) { + if (core_mask & BIT(i)) { + int j; + + for (j = 0; j < topo_max_cpus; ++j) { + if (cpu_map[j].pkg_id == pkg_id && + cpu_map[j].die_id == die_id && + cpu_map[j].punit_cpu_core == i) { + CPU_SET_S(j, core_cpumask_size, + core_cpumask); + ++cnt; + } + } + } + } + + *cpu_cnt = cnt; +} + +int find_phy_core_num(int logical_cpu) +{ + if (logical_cpu < topo_max_cpus) + return cpu_map[logical_cpu].punit_cpu_core; + + return -EINVAL; +} + +static int isst_send_mmio_command(unsigned int cpu, unsigned int reg, int write, + unsigned int *value) +{ + struct isst_if_io_regs io_regs; + const char *pathname = "/dev/isst_interface"; + int cmd; + int fd; + + debug_printf("mmio_cmd cpu:%d reg:%d write:%d\n", cpu, reg, write); + + fd = open(pathname, O_RDWR); + if (fd < 0) + err(-1, "%s open failed", pathname); + + io_regs.req_count = 1; + io_regs.io_reg[0].logical_cpu = cpu; + io_regs.io_reg[0].reg = reg; + cmd = ISST_IF_IO_CMD; + if (write) { + io_regs.io_reg[0].read_write = 1; + io_regs.io_reg[0].value = *value; + } else { + io_regs.io_reg[0].read_write = 0; + } + + if (ioctl(fd, cmd, &io_regs) == -1) { + perror("ISST_IF_IO_CMD"); + fprintf(outf, "Error: mmio_cmd cpu:%d reg:%x read_write:%x\n", + cpu, reg, write); + } else { + if (!write) + *value = io_regs.io_reg[0].value; + + debug_printf( + "mmio_cmd response: cpu:%d reg:%x rd_write:%x resp:%x\n", + cpu, reg, write, *value); + } + + close(fd); + + return 0; +} + +int isst_send_mbox_command(unsigned int cpu, unsigned char command, + unsigned char sub_command, unsigned int parameter, + unsigned int req_data, unsigned int *resp) +{ + const char *pathname = "/dev/isst_interface"; + int fd; + struct isst_if_mbox_cmds mbox_cmds = { 0 }; + + debug_printf( + "mbox_send: cpu:%d command:%x sub_command:%x parameter:%x req_data:%x\n", + cpu, command, sub_command, parameter, req_data); + + if (isst_platform_info.mmio_supported && command == CONFIG_CLOS) { + unsigned int value; + int write = 0; + int clos_id, core_id, ret = 0; + + debug_printf("CLOS %d\n", cpu); + + if (parameter & BIT(MBOX_CMD_WRITE_BIT)) { + value = req_data; + write = 1; + } + + switch (sub_command) { + case CLOS_PQR_ASSOC: + core_id = parameter & 0xff; + ret = isst_send_mmio_command( + cpu, PQR_ASSOC_OFFSET + core_id * 4, write, + &value); + if (!ret && !write) + *resp = value; + break; + case CLOS_PM_CLOS: + clos_id = parameter & 0x03; + ret = isst_send_mmio_command( + cpu, PM_CLOS_OFFSET + clos_id * 4, write, + &value); + if (!ret && !write) + *resp = value; + break; + case CLOS_PM_QOS_CONFIG: + ret = isst_send_mmio_command(cpu, PM_QOS_CONFIG_OFFSET, + write, &value); + if (!ret && !write) + *resp = value; + break; + case CLOS_STATUS: + break; + default: + break; + } + return ret; + } + + mbox_cmds.cmd_count = 1; + mbox_cmds.mbox_cmd[0].logical_cpu = cpu; + mbox_cmds.mbox_cmd[0].command = command; + mbox_cmds.mbox_cmd[0].sub_command = sub_command; + mbox_cmds.mbox_cmd[0].parameter = parameter; + mbox_cmds.mbox_cmd[0].req_data = req_data; + + fd = open(pathname, O_RDWR); + if (fd < 0) + err(-1, "%s open failed", pathname); + + if (ioctl(fd, ISST_IF_MBOX_COMMAND, &mbox_cmds) == -1) { + perror("ISST_IF_MBOX_COMMAND"); + fprintf(outf, + "Error: mbox_cmd cpu:%d command:%x sub_command:%x parameter:%x req_data:%x\n", + cpu, command, sub_command, parameter, req_data); + } else { + *resp = mbox_cmds.mbox_cmd[0].resp_data; + debug_printf( + "mbox_cmd response: cpu:%d command:%x sub_command:%x parameter:%x req_data:%x resp:%x\n", + cpu, command, sub_command, parameter, req_data, *resp); + } + + close(fd); + + return 0; +} + +int isst_send_msr_command(unsigned int cpu, unsigned int msr, int write, + unsigned long long *req_resp) +{ + struct isst_if_msr_cmds msr_cmds; + const char *pathname = "/dev/isst_interface"; + int fd; + + fd = open(pathname, O_RDWR); + if (fd < 0) + err(-1, "%s open failed", pathname); + + msr_cmds.cmd_count = 1; + msr_cmds.msr_cmd[0].logical_cpu = cpu; + msr_cmds.msr_cmd[0].msr = msr; + msr_cmds.msr_cmd[0].read_write = write; + if (write) + msr_cmds.msr_cmd[0].data = *req_resp; + + if (ioctl(fd, ISST_IF_MSR_COMMAND, &msr_cmds) == -1) { + perror("ISST_IF_MSR_COMMAD"); + fprintf(outf, "Error: msr_cmd cpu:%d msr:%x read_write:%d\n", + cpu, msr, write); + } else { + if (!write) + *req_resp = msr_cmds.msr_cmd[0].data; + + debug_printf( + "msr_cmd response: cpu:%d msr:%x rd_write:%x resp:%llx %llx\n", + cpu, msr, write, *req_resp, msr_cmds.msr_cmd[0].data); + } + + close(fd); + + return 0; +} + +static int isst_fill_platform_info(void) +{ + const char *pathname = "/dev/isst_interface"; + int fd; + + fd = open(pathname, O_RDWR); + if (fd < 0) + err(-1, "%s open failed", pathname); + + if (ioctl(fd, ISST_IF_GET_PLATFORM_INFO, &isst_platform_info) == -1) { + perror("ISST_IF_GET_PLATFORM_INFO"); + close(fd); + return -1; + } + + close(fd); + + return 0; +} + +static void isst_print_platform_information(void) +{ + struct isst_if_platform_info platform_info; + const char *pathname = "/dev/isst_interface"; + int fd; + + fd = open(pathname, O_RDWR); + if (fd < 0) + err(-1, "%s open failed", pathname); + + if (ioctl(fd, ISST_IF_GET_PLATFORM_INFO, &platform_info) == -1) { + perror("ISST_IF_GET_PLATFORM_INFO"); + } else { + fprintf(outf, "Platform: API version : %d\n", + platform_info.api_version); + fprintf(outf, "Platform: Driver version : %d\n", + platform_info.driver_version); + fprintf(outf, "Platform: mbox supported : %d\n", + platform_info.mbox_supported); + fprintf(outf, "Platform: mmio supported : %d\n", + platform_info.mmio_supported); + } + + close(fd); + + exit(0); +} + +static void exec_on_get_ctdp_cpu(int cpu, void *arg1, void *arg2, void *arg3, + void *arg4) +{ + int (*fn_ptr)(int cpu, void *arg); + int ret; + + fn_ptr = arg1; + ret = fn_ptr(cpu, arg2); + if (ret) + perror("get_tdp_*"); + else + isst_display_result(cpu, outf, "perf-profile", (char *)arg3, + *(unsigned int *)arg4); +} + +#define _get_tdp_level(desc, suffix, object, help) \ + static void get_tdp_##object(void) \ + { \ + struct isst_pkg_ctdp ctdp; \ +\ + if (cmd_help) { \ + fprintf(stderr, \ + "Print %s [No command arguments are required]\n", \ + help); \ + exit(0); \ + } \ + isst_ctdp_display_information_start(outf); \ + if (max_target_cpus) \ + for_each_online_target_cpu_in_set( \ + exec_on_get_ctdp_cpu, isst_get_ctdp_##suffix, \ + &ctdp, desc, &ctdp.object); \ + else \ + for_each_online_package_in_set(exec_on_get_ctdp_cpu, \ + isst_get_ctdp_##suffix, \ + &ctdp, desc, \ + &ctdp.object); \ + isst_ctdp_display_information_end(outf); \ + } + +_get_tdp_level("get-config-levels", levels, levels, "TDP levels"); +_get_tdp_level("get-config-version", levels, version, "TDP version"); +_get_tdp_level("get-config-enabled", levels, enabled, "TDP enable status"); +_get_tdp_level("get-config-current_level", levels, current_level, + "Current TDP Level"); +_get_tdp_level("get-lock-status", levels, locked, "TDP lock status"); + +static void dump_isst_config_for_cpu(int cpu, void *arg1, void *arg2, + void *arg3, void *arg4) +{ + struct isst_pkg_ctdp pkg_dev; + int ret; + + memset(&pkg_dev, 0, sizeof(pkg_dev)); + ret = isst_get_process_ctdp(cpu, tdp_level, &pkg_dev); + if (ret) { + perror("isst_get_process_ctdp"); + } else { + isst_ctdp_display_information(cpu, outf, tdp_level, &pkg_dev); + isst_get_process_ctdp_complete(cpu, &pkg_dev); + } +} + +static void dump_isst_config(void) +{ + if (cmd_help) { + fprintf(stderr, + "Print Intel(R) Speed Select Technology Performance profile configuration\n"); + fprintf(stderr, + "including base frequency and turbo frequency configurations\n"); + fprintf(stderr, "Optional: -l|--level : Specify tdp level\n"); + fprintf(stderr, + "\tIf no arguments, dump information for all TDP levels\n"); + exit(0); + } + + isst_ctdp_display_information_start(outf); + + if (max_target_cpus) + for_each_online_target_cpu_in_set(dump_isst_config_for_cpu, + NULL, NULL, NULL, NULL); + else + for_each_online_package_in_set(dump_isst_config_for_cpu, NULL, + NULL, NULL, NULL); + + isst_ctdp_display_information_end(outf); +} + +static void set_tdp_level_for_cpu(int cpu, void *arg1, void *arg2, void *arg3, + void *arg4) +{ + int ret; + + ret = isst_set_tdp_level(cpu, tdp_level); + if (ret) + perror("set_tdp_level_for_cpu"); + else + isst_display_result(cpu, outf, "perf-profile", "set_tdp_level", + ret); +} + +static void set_tdp_level(void) +{ + if (cmd_help) { + fprintf(stderr, "Set Config TDP level\n"); + fprintf(stderr, + "\t Arguments: -l|--level : Specify tdp level\n"); + exit(0); + } + + if (tdp_level == 0xff) { + fprintf(outf, "Invalid command: specify tdp_level\n"); + exit(1); + } + isst_ctdp_display_information_start(outf); + if (max_target_cpus) + for_each_online_target_cpu_in_set(set_tdp_level_for_cpu, NULL, + NULL, NULL, NULL); + else + for_each_online_package_in_set(set_tdp_level_for_cpu, NULL, + NULL, NULL, NULL); + isst_ctdp_display_information_end(outf); +} + +static void dump_pbf_config_for_cpu(int cpu, void *arg1, void *arg2, void *arg3, + void *arg4) +{ + struct isst_pbf_info pbf_info; + int ret; + + ret = isst_get_pbf_info(cpu, tdp_level, &pbf_info); + if (ret) { + perror("isst_get_pbf_info"); + } else { + isst_pbf_display_information(cpu, outf, tdp_level, &pbf_info); + isst_get_pbf_info_complete(&pbf_info); + } +} + +static void dump_pbf_config(void) +{ + if (cmd_help) { + fprintf(stderr, + "Print Intel(R) Speed Select Technology base frequency configuration for a TDP level\n"); + fprintf(stderr, + "\tArguments: -l|--level : Specify tdp level\n"); + exit(0); + } + + if (tdp_level == 0xff) { + fprintf(outf, "Invalid command: specify tdp_level\n"); + exit(1); + } + + isst_ctdp_display_information_start(outf); + if (max_target_cpus) + for_each_online_target_cpu_in_set(dump_pbf_config_for_cpu, NULL, + NULL, NULL, NULL); + else + for_each_online_package_in_set(dump_pbf_config_for_cpu, NULL, + NULL, NULL, NULL); + isst_ctdp_display_information_end(outf); +} + +static void set_pbf_for_cpu(int cpu, void *arg1, void *arg2, void *arg3, + void *arg4) +{ + int ret; + int status = *(int *)arg4; + + ret = isst_set_pbf_fact_status(cpu, 1, status); + if (ret) { + perror("isst_set_pbf"); + } else { + if (status) + isst_display_result(cpu, outf, "base-freq", "enable", + ret); + else + isst_display_result(cpu, outf, "base-freq", "disable", + ret); + } +} + +static void set_pbf_enable(void) +{ + int status = 1; + + if (cmd_help) { + fprintf(stderr, + "Enable Intel Speed Select Technology base frequency feature [No command arguments are required]\n"); + exit(0); + } + + isst_ctdp_display_information_start(outf); + if (max_target_cpus) + for_each_online_target_cpu_in_set(set_pbf_for_cpu, NULL, NULL, + NULL, &status); + else + for_each_online_package_in_set(set_pbf_for_cpu, NULL, NULL, + NULL, &status); + isst_ctdp_display_information_end(outf); +} + +static void set_pbf_disable(void) +{ + int status = 0; + + if (cmd_help) { + fprintf(stderr, + "Disable Intel Speed Select Technology base frequency feature [No command arguments are required]\n"); + exit(0); + } + + isst_ctdp_display_information_start(outf); + if (max_target_cpus) + for_each_online_target_cpu_in_set(set_pbf_for_cpu, NULL, NULL, + NULL, &status); + else + for_each_online_package_in_set(set_pbf_for_cpu, NULL, NULL, + NULL, &status); + isst_ctdp_display_information_end(outf); +} + +static void dump_fact_config_for_cpu(int cpu, void *arg1, void *arg2, + void *arg3, void *arg4) +{ + struct isst_fact_info fact_info; + int ret; + + ret = isst_get_fact_info(cpu, tdp_level, &fact_info); + if (ret) + perror("isst_get_fact_bucket_info"); + else + isst_fact_display_information(cpu, outf, tdp_level, fact_bucket, + fact_avx, &fact_info); +} + +static void dump_fact_config(void) +{ + if (cmd_help) { + fprintf(stderr, + "Print complete Intel Speed Select Technology turbo frequency configuration for a TDP level. Other arguments are optional.\n"); + fprintf(stderr, + "\tArguments: -l|--level : Specify tdp level\n"); + fprintf(stderr, + "\tArguments: -b|--bucket : Bucket index to dump\n"); + fprintf(stderr, + "\tArguments: -r|--trl-type : Specify trl type: sse|avx2|avx512\n"); + exit(0); + } + + if (tdp_level == 0xff) { + fprintf(outf, "Invalid command: specify tdp_level\n"); + exit(1); + } + + isst_ctdp_display_information_start(outf); + if (max_target_cpus) + for_each_online_target_cpu_in_set(dump_fact_config_for_cpu, + NULL, NULL, NULL, NULL); + else + for_each_online_package_in_set(dump_fact_config_for_cpu, NULL, + NULL, NULL, NULL); + isst_ctdp_display_information_end(outf); +} + +static void set_fact_for_cpu(int cpu, void *arg1, void *arg2, void *arg3, + void *arg4) +{ + int ret; + int status = *(int *)arg4; + + ret = isst_set_pbf_fact_status(cpu, 0, status); + if (ret) + perror("isst_set_fact"); + else { + if (status) { + struct isst_pkg_ctdp pkg_dev; + + ret = isst_get_ctdp_levels(cpu, &pkg_dev); + if (ret) { + isst_display_result(cpu, outf, "turbo-freq", + "enable", ret); + return; + } + ret = isst_set_trl(cpu, fact_trl); + isst_display_result(cpu, outf, "turbo-freq", "enable", + ret); + } else { + /* Since we modified TRL during Fact enable, restore it */ + isst_set_trl_from_current_tdp(cpu, fact_trl); + isst_display_result(cpu, outf, "turbo-freq", "disable", + ret); + } + } +} + +static void set_fact_enable(void) +{ + int status = 1; + + if (cmd_help) { + fprintf(stderr, + "Enable Intel Speed Select Technology Turbo frequency feature\n"); + fprintf(stderr, + "Optional: -t|--trl : Specify turbo ratio limit\n"); + exit(0); + } + + isst_ctdp_display_information_start(outf); + if (max_target_cpus) + for_each_online_target_cpu_in_set(set_fact_for_cpu, NULL, NULL, + NULL, &status); + else + for_each_online_package_in_set(set_fact_for_cpu, NULL, NULL, + NULL, &status); + isst_ctdp_display_information_end(outf); +} + +static void set_fact_disable(void) +{ + int status = 0; + + if (cmd_help) { + fprintf(stderr, + "Disable Intel Speed Select Technology turbo frequency feature\n"); + fprintf(stderr, + "Optional: -t|--trl : Specify turbo ratio limit\n"); + exit(0); + } + + isst_ctdp_display_information_start(outf); + if (max_target_cpus) + for_each_online_target_cpu_in_set(set_fact_for_cpu, NULL, NULL, + NULL, &status); + else + for_each_online_package_in_set(set_fact_for_cpu, NULL, NULL, + NULL, &status); + isst_ctdp_display_information_end(outf); +} + +static void enable_clos_qos_config(int cpu, void *arg1, void *arg2, void *arg3, + void *arg4) +{ + int ret; + int status = *(int *)arg4; + + ret = isst_pm_qos_config(cpu, status, clos_priority_type); + if (ret) { + perror("isst_pm_qos_config"); + } else { + if (status) + isst_display_result(cpu, outf, "core-power", "enable", + ret); + else + isst_display_result(cpu, outf, "core-power", "disable", + ret); + } +} + +static void set_clos_enable(void) +{ + int status = 1; + + if (cmd_help) { + fprintf(stderr, "Enable core-power for a package/die\n"); + fprintf(stderr, + "\tClos Enable: Specify priority type with [--priority|-p]\n"); + fprintf(stderr, "\t\t 0: Proportional, 1: Ordered\n"); + exit(0); + } + + if (cpufreq_sysfs_present()) { + fprintf(stderr, + "cpufreq subsystem and core-power enable will interfere with each other!\n"); + } + + isst_ctdp_display_information_start(outf); + if (max_target_cpus) + for_each_online_target_cpu_in_set(enable_clos_qos_config, NULL, + NULL, NULL, &status); + else + for_each_online_package_in_set(enable_clos_qos_config, NULL, + NULL, NULL, &status); + isst_ctdp_display_information_end(outf); +} + +static void set_clos_disable(void) +{ + int status = 0; + + if (cmd_help) { + fprintf(stderr, + "Disable core-power: [No command arguments are required]\n"); + exit(0); + } + + isst_ctdp_display_information_start(outf); + if (max_target_cpus) + for_each_online_target_cpu_in_set(enable_clos_qos_config, NULL, + NULL, NULL, &status); + else + for_each_online_package_in_set(enable_clos_qos_config, NULL, + NULL, NULL, &status); + isst_ctdp_display_information_end(outf); +} + +static void dump_clos_config_for_cpu(int cpu, void *arg1, void *arg2, + void *arg3, void *arg4) +{ + struct isst_clos_config clos_config; + int ret; + + ret = isst_pm_get_clos(cpu, current_clos, &clos_config); + if (ret) + perror("isst_pm_get_clos"); + else + isst_clos_display_information(cpu, outf, current_clos, + &clos_config); +} + +static void dump_clos_config(void) +{ + if (cmd_help) { + fprintf(stderr, + "Print Intel Speed Select Technology core power configuration\n"); + fprintf(stderr, + "\tArguments: [-c | --clos]: Specify clos id\n"); + exit(0); + } + if (current_clos < 0 || current_clos > 3) { + fprintf(stderr, "Invalid clos id\n"); + exit(0); + } + + isst_ctdp_display_information_start(outf); + if (max_target_cpus) + for_each_online_target_cpu_in_set(dump_clos_config_for_cpu, + NULL, NULL, NULL, NULL); + else + for_each_online_package_in_set(dump_clos_config_for_cpu, NULL, + NULL, NULL, NULL); + isst_ctdp_display_information_end(outf); +} + +static void set_clos_config_for_cpu(int cpu, void *arg1, void *arg2, void *arg3, + void *arg4) +{ + struct isst_clos_config clos_config; + int ret; + + clos_config.pkg_id = get_physical_package_id(cpu); + clos_config.die_id = get_physical_die_id(cpu); + + clos_config.epp = clos_epp; + clos_config.clos_prop_prio = clos_prop_prio; + clos_config.clos_min = clos_min; + clos_config.clos_max = clos_max; + clos_config.clos_desired = clos_desired; + ret = isst_set_clos(cpu, current_clos, &clos_config); + if (ret) + perror("isst_set_clos"); + else + isst_display_result(cpu, outf, "core-power", "config", ret); +} + +static void set_clos_config(void) +{ + if (cmd_help) { + fprintf(stderr, + "Set core-power configuration for one of the four clos ids\n"); + fprintf(stderr, + "\tSpecify targeted clos id with [--clos|-c]\n"); + fprintf(stderr, "\tSpecify clos EPP with [--epp|-e]\n"); + fprintf(stderr, + "\tSpecify clos Proportional Priority [--weight|-w]\n"); + fprintf(stderr, "\tSpecify clos min with [--min|-n]\n"); + fprintf(stderr, "\tSpecify clos max with [--max|-m]\n"); + fprintf(stderr, "\tSpecify clos desired with [--desired|-d]\n"); + exit(0); + } + + if (current_clos < 0 || current_clos > 3) { + fprintf(stderr, "Invalid clos id\n"); + exit(0); + } + if (clos_epp < 0 || clos_epp > 0x0F) { + fprintf(stderr, "clos epp is not specified, default: 0\n"); + clos_epp = 0; + } + if (clos_prop_prio < 0 || clos_prop_prio > 0x0F) { + fprintf(stderr, + "clos frequency weight is not specified, default: 0\n"); + clos_prop_prio = 0; + } + if (clos_min < 0) { + fprintf(stderr, "clos min is not specified, default: 0\n"); + clos_min = 0; + } + if (clos_max < 0) { + fprintf(stderr, "clos max is not specified, default: 0xff\n"); + clos_max = 0xff; + } + if (clos_desired < 0) { + fprintf(stderr, "clos desired is not specified, default: 0\n"); + clos_desired = 0x00; + } + + isst_ctdp_display_information_start(outf); + if (max_target_cpus) + for_each_online_target_cpu_in_set(set_clos_config_for_cpu, NULL, + NULL, NULL, NULL); + else + for_each_online_package_in_set(set_clos_config_for_cpu, NULL, + NULL, NULL, NULL); + isst_ctdp_display_information_end(outf); +} + +static void set_clos_assoc_for_cpu(int cpu, void *arg1, void *arg2, void *arg3, + void *arg4) +{ + int ret; + + ret = isst_clos_associate(cpu, current_clos); + if (ret) + perror("isst_clos_associate"); + else + isst_display_result(cpu, outf, "core-power", "assoc", ret); +} + +static void set_clos_assoc(void) +{ + if (cmd_help) { + fprintf(stderr, "Associate a clos id to a CPU\n"); + fprintf(stderr, + "\tSpecify targeted clos id with [--clos|-c]\n"); + exit(0); + } + + if (current_clos < 0 || current_clos > 3) { + fprintf(stderr, "Invalid clos id\n"); + exit(0); + } + if (max_target_cpus) + for_each_online_target_cpu_in_set(set_clos_assoc_for_cpu, NULL, + NULL, NULL, NULL); + else { + fprintf(stderr, + "Invalid target cpu. Specify with [-c|--cpu]\n"); + } +} + +static void get_clos_assoc_for_cpu(int cpu, void *arg1, void *arg2, void *arg3, + void *arg4) +{ + int clos, ret; + + ret = isst_clos_get_assoc_status(cpu, &clos); + if (ret) + perror("isst_clos_get_assoc_status"); + else + isst_display_result(cpu, outf, "core-power", "get-assoc", clos); +} + +static void get_clos_assoc(void) +{ + if (cmd_help) { + fprintf(stderr, "Get associate clos id to a CPU\n"); + fprintf(stderr, "\tSpecify targeted cpu id with [--cpu|-c]\n"); + exit(0); + } + if (max_target_cpus) + for_each_online_target_cpu_in_set(get_clos_assoc_for_cpu, NULL, + NULL, NULL, NULL); + else { + fprintf(stderr, + "Invalid target cpu. Specify with [-c|--cpu]\n"); + } +} + +static struct process_cmd_struct isst_cmds[] = { + { "perf-profile", "get-lock-status", get_tdp_locked }, + { "perf-profile", "get-config-levels", get_tdp_levels }, + { "perf-profile", "get-config-version", get_tdp_version }, + { "perf-profile", "get-config-enabled", get_tdp_enabled }, + { "perf-profile", "get-config-current-level", get_tdp_current_level }, + { "perf-profile", "set-config-level", set_tdp_level }, + { "perf-profile", "info", dump_isst_config }, + { "base-freq", "info", dump_pbf_config }, + { "base-freq", "enable", set_pbf_enable }, + { "base-freq", "disable", set_pbf_disable }, + { "turbo-freq", "info", dump_fact_config }, + { "turbo-freq", "enable", set_fact_enable }, + { "turbo-freq", "disable", set_fact_disable }, + { "core-power", "info", dump_clos_config }, + { "core-power", "enable", set_clos_enable }, + { "core-power", "disable", set_clos_disable }, + { "core-power", "config", set_clos_config }, + { "core-power", "assoc", set_clos_assoc }, + { "core-power", "get-assoc", get_clos_assoc }, + { NULL, NULL, NULL } +}; + +/* + * parse cpuset with following syntax + * 1,2,4..6,8-10 and set bits in cpu_subset + */ +void parse_cpu_command(char *optarg) +{ + unsigned int start, end; + char *next; + + next = optarg; + + while (next && *next) { + if (*next == '-') /* no negative cpu numbers */ + goto error; + + start = strtoul(next, &next, 10); + + if (max_target_cpus < MAX_CPUS_IN_ONE_REQ) + target_cpus[max_target_cpus++] = start; + + if (*next == '\0') + break; + + if (*next == ',') { + next += 1; + continue; + } + + if (*next == '-') { + next += 1; /* start range */ + } else if (*next == '.') { + next += 1; + if (*next == '.') + next += 1; /* start range */ + else + goto error; + } + + end = strtoul(next, &next, 10); + if (end <= start) + goto error; + + while (++start <= end) { + if (max_target_cpus < MAX_CPUS_IN_ONE_REQ) + target_cpus[max_target_cpus++] = start; + } + + if (*next == ',') + next += 1; + else if (*next != '\0') + goto error; + } + +#ifdef DEBUG + { + int i; + + for (i = 0; i < max_target_cpus; ++i) + printf("cpu [%d] in arg\n", target_cpus[i]); + } +#endif + return; + +error: + fprintf(stderr, "\"--cpu %s\" malformed\n", optarg); + exit(-1); +} + +static void parse_cmd_args(int argc, int start, char **argv) +{ + int opt; + int option_index; + + static struct option long_options[] = { + { "bucket", required_argument, 0, 'b' }, + { "level", required_argument, 0, 'l' }, + { "trl-type", required_argument, 0, 'r' }, + { "trl", required_argument, 0, 't' }, + { "help", no_argument, 0, 'h' }, + { "clos", required_argument, 0, 'c' }, + { "desired", required_argument, 0, 'd' }, + { "epp", required_argument, 0, 'e' }, + { "min", required_argument, 0, 'n' }, + { "max", required_argument, 0, 'm' }, + { "priority", required_argument, 0, 'p' }, + { "weight", required_argument, 0, 'w' }, + { 0, 0, 0, 0 } + }; + + option_index = start; + + optind = start + 1; + while ((opt = getopt_long(argc, argv, "b:l:t:c:d:e:n:m:p:w:h", + long_options, &option_index)) != -1) { + switch (opt) { + case 'b': + fact_bucket = atoi(optarg); + break; + case 'h': + cmd_help = 1; + break; + case 'l': + tdp_level = atoi(optarg); + break; + case 't': + sscanf(optarg, "0x%llx", &fact_trl); + break; + case 'r': + if (!strncmp(optarg, "sse", 3)) { + fact_avx = 0x01; + } else if (!strncmp(optarg, "avx2", 4)) { + fact_avx = 0x02; + } else if (!strncmp(optarg, "avx512", 4)) { + fact_avx = 0x04; + } else { + fprintf(outf, "Invalid sse,avx options\n"); + exit(1); + } + break; + /* CLOS related */ + case 'c': + current_clos = atoi(optarg); + printf("clos %d\n", current_clos); + break; + case 'd': + clos_desired = atoi(optarg); + break; + case 'e': + clos_epp = atoi(optarg); + break; + case 'n': + clos_min = atoi(optarg); + break; + case 'm': + clos_max = atoi(optarg); + break; + case 'p': + clos_priority_type = atoi(optarg); + break; + case 'w': + clos_prop_prio = atoi(optarg); + break; + default: + printf("no match\n"); + } + } +} + +static void isst_help(void) +{ + printf("perf-profile:\tAn architectural mechanism that allows multiple optimized \n\ + performance profiles per system via static and/or dynamic\n\ + adjustment of core count, workload, Tjmax, and\n\ + TDP, etc.\n"); + printf("\nCommands : For feature=perf-profile\n"); + printf("\tinfo\n"); + printf("\tget-lock-status\n"); + printf("\tget-config-levels\n"); + printf("\tget-config-version\n"); + printf("\tget-config-enabled\n"); + printf("\tget-config-current-level\n"); + printf("\tset-config-level\n"); +} + +static void pbf_help(void) +{ + printf("base-freq:\tEnables users to increase guaranteed base frequency\n\ + on certain cores (high priority cores) in exchange for lower\n\ + base frequency on remaining cores (low priority cores).\n"); + printf("\tcommand : info\n"); + printf("\tcommand : enable\n"); + printf("\tcommand : disable\n"); +} + +static void fact_help(void) +{ + printf("turbo-freq:\tEnables the ability to set different turbo ratio\n\ + limits to cores based on priority.\n"); + printf("\nCommand: For feature=turbo-freq\n"); + printf("\tcommand : info\n"); + printf("\tcommand : enable\n"); + printf("\tcommand : disable\n"); +} + +static void core_power_help(void) +{ + printf("core-power:\tInterface that allows user to define per core/tile\n\ + priority.\n"); + printf("\nCommands : For feature=core-power\n"); + printf("\tinfo\n"); + printf("\tenable\n"); + printf("\tdisable\n"); + printf("\tconfig\n"); + printf("\tassoc\n"); + printf("\tget-assoc\n"); +} + +struct process_cmd_help_struct { + char *feature; + void (*process_fn)(void); +}; + +static struct process_cmd_help_struct isst_help_cmds[] = { + { "perf-profile", isst_help }, + { "base-freq", pbf_help }, + { "turbo-freq", fact_help }, + { "core-power", core_power_help }, + { NULL, NULL } +}; + +void process_command(int argc, char **argv) +{ + int i = 0, matched = 0; + char *feature = argv[optind]; + char *cmd = argv[optind + 1]; + + if (!feature || !cmd) + return; + + debug_printf("feature name [%s] command [%s]\n", feature, cmd); + if (!strcmp(cmd, "-h") || !strcmp(cmd, "--help")) { + while (isst_help_cmds[i].feature) { + if (!strcmp(isst_help_cmds[i].feature, feature)) { + isst_help_cmds[i].process_fn(); + exit(0); + } + ++i; + } + } + + create_cpu_map(); + + i = 0; + while (isst_cmds[i].feature) { + if (!strcmp(isst_cmds[i].feature, feature) && + !strcmp(isst_cmds[i].command, cmd)) { + parse_cmd_args(argc, optind + 1, argv); + isst_cmds[i].process_fn(); + matched = 1; + break; + } + ++i; + } + + if (!matched) + fprintf(stderr, "Invalid command\n"); +} + +static void usage(void) +{ + printf("Intel(R) Speed Select Technology\n"); + printf("\nUsage:\n"); + printf("intel-speed-select [OPTIONS] FEATURE COMMAND COMMAND_ARGUMENTS\n"); + printf("\nUse this tool to enumerate and control the Intel Speed Select Technology features,\n"); + printf("\nFEATURE : [perf-profile|base-freq|turbo-freq|core-power]\n"); + printf("\nFor help on each feature, use --h|--help\n"); + printf("\tFor example: intel-speed-select perf-profile -h\n"); + + printf("\nFor additional help on each command for a feature, use --h|--help\n"); + printf("\tFor example: intel-speed-select perf-profile get-lock-status -h\n"); + printf("\t\t This will print help for the command \"get-lock-status\" for the feature \"perf-profile\"\n"); + + printf("\nOPTIONS\n"); + printf("\t[-c|--cpu] : logical cpu number\n"); + printf("\t\tDefault: Die scoped for all dies in the system with multiple dies/package\n"); + printf("\t\t\t Or Package scoped for all Packages when each package contains one die\n"); + printf("\t[-d|--debug] : Debug mode\n"); + printf("\t[-h|--help] : Print help\n"); + printf("\t[-i|--info] : Print platform information\n"); + printf("\t[-o|--out] : Output file\n"); + printf("\t\t\tDefault : stderr\n"); + printf("\t[-f|--format] : output format [json|text]. Default: text\n"); + printf("\t[-v|--version] : Print version\n"); + + printf("\nResult format\n"); + printf("\tResult display uses a common format for each command:\n"); + printf("\tResults are formatted in text/JSON with\n"); + printf("\t\tPackage, Die, CPU, and command specific results.\n"); + printf("\t\t\tFor Set commands, status is 0 for success and rest for failures\n"); + exit(1); +} + +static void print_version(void) +{ + fprintf(outf, "Version %s\n", version_str); + fprintf(outf, "Build date %s time %s\n", __DATE__, __TIME__); + exit(0); +} + +static void cmdline(int argc, char **argv) +{ + int opt; + int option_index = 0; + + static struct option long_options[] = { + { "cpu", required_argument, 0, 'c' }, + { "debug", no_argument, 0, 'd' }, + { "format", required_argument, 0, 'f' }, + { "help", no_argument, 0, 'h' }, + { "info", no_argument, 0, 'i' }, + { "out", required_argument, 0, 'o' }, + { "version", no_argument, 0, 'v' }, + { 0, 0, 0, 0 } + }; + + progname = argv[0]; + while ((opt = getopt_long_only(argc, argv, "+c:df:hio:v", long_options, + &option_index)) != -1) { + switch (opt) { + case 'c': + parse_cpu_command(optarg); + break; + case 'd': + debug_flag = 1; + printf("Debug Mode ON\n"); + break; + case 'f': + if (!strncmp(optarg, "json", 4)) + out_format_json = 1; + break; + case 'h': + usage(); + break; + case 'i': + isst_print_platform_information(); + break; + case 'o': + if (outf) + fclose(outf); + outf = fopen_or_exit(optarg, "w"); + break; + case 'v': + print_version(); + break; + default: + usage(); + } + } + + if (geteuid() != 0) { + fprintf(stderr, "Must run as root\n"); + exit(0); + } + + if (optind > (argc - 2)) { + fprintf(stderr, "Feature name and|or command not specified\n"); + exit(0); + } + update_cpu_model(); + printf("Intel(R) Speed Select Technology\n"); + printf("Executing on CPU model:%d[0x%x]\n", cpu_model, cpu_model); + set_max_cpu_num(); + set_cpu_present_cpu_mask(); + set_cpu_target_cpu_mask(); + isst_fill_platform_info(); + if (isst_platform_info.api_version > supported_api_ver) { + printf("Incompatible API versions; Upgrade of tool is required\n"); + exit(0); + } + + process_command(argc, argv); +} + +int main(int argc, char **argv) +{ + outf = stderr; + cmdline(argc, argv); + return 0; +} diff --git a/tools/power/x86/intel-speed-select/isst-core.c b/tools/power/x86/intel-speed-select/isst-core.c new file mode 100644 index 000000000000..8de4ac39a008 --- /dev/null +++ b/tools/power/x86/intel-speed-select/isst-core.c @@ -0,0 +1,721 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Intel Speed Select -- Enumerate and control features + * Copyright (c) 2019 Intel Corporation. + */ + +#include "isst.h" + +int isst_get_ctdp_levels(int cpu, struct isst_pkg_ctdp *pkg_dev) +{ + unsigned int resp; + int ret; + + ret = isst_send_mbox_command(cpu, CONFIG_TDP, + CONFIG_TDP_GET_LEVELS_INFO, 0, 0, &resp); + if (ret) + return ret; + + debug_printf("cpu:%d CONFIG_TDP_GET_LEVELS_INFO resp:%x\n", cpu, resp); + + pkg_dev->version = resp & 0xff; + pkg_dev->levels = (resp >> 8) & 0xff; + pkg_dev->current_level = (resp >> 16) & 0xff; + pkg_dev->locked = !!(resp & BIT(24)); + pkg_dev->enabled = !!(resp & BIT(31)); + + return 0; +} + +int isst_get_ctdp_control(int cpu, int config_index, + struct isst_pkg_ctdp_level_info *ctdp_level) +{ + unsigned int resp; + int ret; + + ret = isst_send_mbox_command(cpu, CONFIG_TDP, + CONFIG_TDP_GET_TDP_CONTROL, 0, + config_index, &resp); + if (ret) + return ret; + + ctdp_level->fact_support = resp & BIT(0); + ctdp_level->pbf_support = !!(resp & BIT(1)); + ctdp_level->fact_enabled = !!(resp & BIT(16)); + ctdp_level->pbf_enabled = !!(resp & BIT(17)); + + debug_printf( + "cpu:%d CONFIG_TDP_GET_TDP_CONTROL resp:%x fact_support:%d pbf_support: %d fact_enabled:%d pbf_enabled:%d\n", + cpu, resp, ctdp_level->fact_support, ctdp_level->pbf_support, + ctdp_level->fact_enabled, ctdp_level->pbf_enabled); + + return 0; +} + +int isst_get_tdp_info(int cpu, int config_index, + struct isst_pkg_ctdp_level_info *ctdp_level) +{ + unsigned int resp; + int ret; + + ret = isst_send_mbox_command(cpu, CONFIG_TDP, CONFIG_TDP_GET_TDP_INFO, + 0, config_index, &resp); + if (ret) + return ret; + + ctdp_level->pkg_tdp = resp & GENMASK(14, 0); + ctdp_level->tdp_ratio = (resp & GENMASK(23, 16)) >> 16; + + debug_printf( + "cpu:%d ctdp:%d CONFIG_TDP_GET_TDP_INFO resp:%x tdp_ratio:%d pkg_tdp:%d\n", + cpu, config_index, resp, ctdp_level->tdp_ratio, + ctdp_level->pkg_tdp); + return 0; +} + +int isst_get_pwr_info(int cpu, int config_index, + struct isst_pkg_ctdp_level_info *ctdp_level) +{ + unsigned int resp; + int ret; + + ret = isst_send_mbox_command(cpu, CONFIG_TDP, CONFIG_TDP_GET_PWR_INFO, + 0, config_index, &resp); + if (ret) + return ret; + + ctdp_level->pkg_max_power = resp & GENMASK(14, 0); + ctdp_level->pkg_min_power = (resp & GENMASK(30, 16)) >> 16; + + debug_printf( + "cpu:%d ctdp:%d CONFIG_TDP_GET_PWR_INFO resp:%x pkg_max_power:%d pkg_min_power:%d\n", + cpu, config_index, resp, ctdp_level->pkg_max_power, + ctdp_level->pkg_min_power); + + return 0; +} + +int isst_get_tjmax_info(int cpu, int config_index, + struct isst_pkg_ctdp_level_info *ctdp_level) +{ + unsigned int resp; + int ret; + + ret = isst_send_mbox_command(cpu, CONFIG_TDP, CONFIG_TDP_GET_TJMAX_INFO, + 0, config_index, &resp); + if (ret) + return ret; + + ctdp_level->t_proc_hot = resp & GENMASK(7, 0); + + debug_printf( + "cpu:%d ctdp:%d CONFIG_TDP_GET_TJMAX_INFO resp:%x t_proc_hot:%d\n", + cpu, config_index, resp, ctdp_level->t_proc_hot); + + return 0; +} + +int isst_get_coremask_info(int cpu, int config_index, + struct isst_pkg_ctdp_level_info *ctdp_level) +{ + unsigned int resp; + int i, ret; + + ctdp_level->cpu_count = 0; + for (i = 0; i < 2; ++i) { + unsigned long long mask; + int cpu_count = 0; + + ret = isst_send_mbox_command(cpu, CONFIG_TDP, + CONFIG_TDP_GET_CORE_MASK, 0, + (i << 8) | config_index, &resp); + if (ret) + return ret; + + debug_printf( + "cpu:%d ctdp:%d mask:%d CONFIG_TDP_GET_CORE_MASK resp:%x\n", + cpu, config_index, i, resp); + + mask = (unsigned long long)resp << (32 * i); + set_cpu_mask_from_punit_coremask(cpu, mask, + ctdp_level->core_cpumask_size, + ctdp_level->core_cpumask, + &cpu_count); + ctdp_level->cpu_count += cpu_count; + debug_printf("cpu:%d ctdp:%d mask:%d cpu count:%d\n", cpu, + config_index, i, ctdp_level->cpu_count); + } + + return 0; +} + +int isst_get_get_trl(int cpu, int level, int avx_level, int *trl) +{ + unsigned int req, resp; + int ret; + + req = level | (avx_level << 16); + ret = isst_send_mbox_command(cpu, CONFIG_TDP, + CONFIG_TDP_GET_TURBO_LIMIT_RATIOS, 0, req, + &resp); + if (ret) + return ret; + + debug_printf( + "cpu:%d CONFIG_TDP_GET_TURBO_LIMIT_RATIOS req:%x resp:%x\n", + cpu, req, resp); + + trl[0] = resp & GENMASK(7, 0); + trl[1] = (resp & GENMASK(15, 8)) >> 8; + trl[2] = (resp & GENMASK(23, 16)) >> 16; + trl[3] = (resp & GENMASK(31, 24)) >> 24; + + req = level | BIT(8) | (avx_level << 16); + ret = isst_send_mbox_command(cpu, CONFIG_TDP, + CONFIG_TDP_GET_TURBO_LIMIT_RATIOS, 0, req, + &resp); + if (ret) + return ret; + + debug_printf("cpu:%d CONFIG_TDP_GET_TURBO_LIMIT req:%x resp:%x\n", cpu, + req, resp); + + trl[4] = resp & GENMASK(7, 0); + trl[5] = (resp & GENMASK(15, 8)) >> 8; + trl[6] = (resp & GENMASK(23, 16)) >> 16; + trl[7] = (resp & GENMASK(31, 24)) >> 24; + + return 0; +} + +int isst_set_tdp_level_msr(int cpu, int tdp_level) +{ + int ret; + + debug_printf("cpu: tdp_level via MSR %d\n", cpu, tdp_level); + + if (isst_get_config_tdp_lock_status(cpu)) { + debug_printf("cpu: tdp_locked %d\n", cpu); + return -1; + } + + if (tdp_level > 2) + return -1; /* invalid value */ + + ret = isst_send_msr_command(cpu, 0x64b, 1, + (unsigned long long *)&tdp_level); + if (ret) + return ret; + + debug_printf("cpu: tdp_level via MSR successful %d\n", cpu, tdp_level); + + return 0; +} + +int isst_set_tdp_level(int cpu, int tdp_level) +{ + unsigned int resp; + int ret; + + ret = isst_send_mbox_command(cpu, CONFIG_TDP, CONFIG_TDP_SET_LEVEL, 0, + tdp_level, &resp); + if (ret) + return isst_set_tdp_level_msr(cpu, tdp_level); + + return 0; +} + +int isst_get_pbf_info(int cpu, int level, struct isst_pbf_info *pbf_info) +{ + unsigned int req, resp; + int i, ret; + + pbf_info->core_cpumask_size = alloc_cpu_set(&pbf_info->core_cpumask); + + for (i = 0; i < 2; ++i) { + unsigned long long mask; + int count; + + ret = isst_send_mbox_command(cpu, CONFIG_TDP, + CONFIG_TDP_PBF_GET_CORE_MASK_INFO, + 0, (i << 8) | level, &resp); + if (ret) + return ret; + + debug_printf( + "cpu:%d CONFIG_TDP_PBF_GET_CORE_MASK_INFO resp:%x\n", + cpu, resp); + + mask = (unsigned long long)resp << (32 * i); + set_cpu_mask_from_punit_coremask(cpu, mask, + pbf_info->core_cpumask_size, + pbf_info->core_cpumask, + &count); + } + + req = level; + ret = isst_send_mbox_command(cpu, CONFIG_TDP, + CONFIG_TDP_PBF_GET_P1HI_P1LO_INFO, 0, req, + &resp); + if (ret) + return ret; + + debug_printf("cpu:%d CONFIG_TDP_PBF_GET_P1HI_P1LO_INFO resp:%x\n", cpu, + resp); + + pbf_info->p1_low = resp & 0xff; + pbf_info->p1_high = (resp & GENMASK(15, 8)) >> 8; + + req = level; + ret = isst_send_mbox_command( + cpu, CONFIG_TDP, CONFIG_TDP_PBF_GET_TDP_INFO, 0, req, &resp); + if (ret) + return ret; + + debug_printf("cpu:%d CONFIG_TDP_PBF_GET_TDP_INFO resp:%x\n", cpu, resp); + + pbf_info->tdp = resp & 0xffff; + + req = level; + ret = isst_send_mbox_command( + cpu, CONFIG_TDP, CONFIG_TDP_PBF_GET_TJ_MAX_INFO, 0, req, &resp); + if (ret) + return ret; + + debug_printf("cpu:%d CONFIG_TDP_PBF_GET_TJ_MAX_INFO resp:%x\n", cpu, + resp); + pbf_info->t_control = (resp >> 8) & 0xff; + pbf_info->t_prochot = resp & 0xff; + + return 0; +} + +void isst_get_pbf_info_complete(struct isst_pbf_info *pbf_info) +{ + free_cpu_set(pbf_info->core_cpumask); +} + +int isst_set_pbf_fact_status(int cpu, int pbf, int enable) +{ + struct isst_pkg_ctdp pkg_dev; + struct isst_pkg_ctdp_level_info ctdp_level; + int current_level; + unsigned int req = 0, resp; + int ret; + + ret = isst_get_ctdp_levels(cpu, &pkg_dev); + if (ret) + return ret; + + current_level = pkg_dev.current_level; + + ret = isst_get_ctdp_control(cpu, current_level, &ctdp_level); + if (ret) + return ret; + + if (pbf) { + if (ctdp_level.fact_enabled) + req = BIT(16); + + if (enable) + req |= BIT(17); + else + req &= ~BIT(17); + } else { + if (ctdp_level.pbf_enabled) + req = BIT(17); + + if (enable) + req |= BIT(16); + else + req &= ~BIT(16); + } + + ret = isst_send_mbox_command(cpu, CONFIG_TDP, + CONFIG_TDP_SET_TDP_CONTROL, 0, req, &resp); + if (ret) + return ret; + + debug_printf("cpu:%d CONFIG_TDP_SET_TDP_CONTROL pbf/fact:%d req:%x\n", + cpu, pbf, req); + + return 0; +} + +int isst_get_fact_bucket_info(int cpu, int level, + struct isst_fact_bucket_info *bucket_info) +{ + unsigned int resp; + int i, k, ret; + + for (i = 0; i < 2; ++i) { + int j; + + ret = isst_send_mbox_command( + cpu, CONFIG_TDP, + CONFIG_TDP_GET_FACT_HP_TURBO_LIMIT_NUMCORES, 0, + (i << 8) | level, &resp); + if (ret) + return ret; + + debug_printf( + "cpu:%d CONFIG_TDP_GET_FACT_HP_TURBO_LIMIT_NUMCORES index:%d level:%d resp:%x\n", + cpu, i, level, resp); + + for (j = 0; j < 4; ++j) { + bucket_info[j + (i * 4)].high_priority_cores_count = + (resp >> (j * 8)) & 0xff; + } + } + + for (k = 0; k < 3; ++k) { + for (i = 0; i < 2; ++i) { + int j; + + ret = isst_send_mbox_command( + cpu, CONFIG_TDP, + CONFIG_TDP_GET_FACT_HP_TURBO_LIMIT_RATIOS, 0, + (k << 16) | (i << 8) | level, &resp); + if (ret) + return ret; + + debug_printf( + "cpu:%d CONFIG_TDP_GET_FACT_HP_TURBO_LIMIT_RATIOS index:%d level:%d avx:%d resp:%x\n", + cpu, i, level, k, resp); + + for (j = 0; j < 4; ++j) { + switch (k) { + case 0: + bucket_info[j + (i * 4)].sse_trl = + (resp >> (j * 8)) & 0xff; + break; + case 1: + bucket_info[j + (i * 4)].avx_trl = + (resp >> (j * 8)) & 0xff; + break; + case 2: + bucket_info[j + (i * 4)].avx512_trl = + (resp >> (j * 8)) & 0xff; + break; + default: + break; + } + } + } + } + + return 0; +} + +int isst_get_fact_info(int cpu, int level, struct isst_fact_info *fact_info) +{ + unsigned int resp; + int ret; + + ret = isst_send_mbox_command(cpu, CONFIG_TDP, + CONFIG_TDP_GET_FACT_LP_CLIPPING_RATIO, 0, + level, &resp); + if (ret) + return ret; + + debug_printf("cpu:%d CONFIG_TDP_GET_FACT_LP_CLIPPING_RATIO resp:%x\n", + cpu, resp); + + fact_info->lp_clipping_ratio_license_sse = resp & 0xff; + fact_info->lp_clipping_ratio_license_avx2 = (resp >> 8) & 0xff; + fact_info->lp_clipping_ratio_license_avx512 = (resp >> 16) & 0xff; + + ret = isst_get_fact_bucket_info(cpu, level, fact_info->bucket_info); + + return ret; +} + +int isst_set_trl(int cpu, unsigned long long trl) +{ + int ret; + + if (!trl) + trl = 0xFFFFFFFFFFFFFFFFULL; + + ret = isst_send_msr_command(cpu, 0x1AD, 1, &trl); + if (ret) + return ret; + + return 0; +} + +int isst_set_trl_from_current_tdp(int cpu, unsigned long long trl) +{ + unsigned long long msr_trl; + int ret; + + if (trl) { + msr_trl = trl; + } else { + struct isst_pkg_ctdp pkg_dev; + int trl[8]; + int i; + + ret = isst_get_ctdp_levels(cpu, &pkg_dev); + if (ret) + return ret; + + ret = isst_get_get_trl(cpu, pkg_dev.current_level, 0, trl); + if (ret) + return ret; + + msr_trl = 0; + for (i = 0; i < 8; ++i) { + unsigned long long _trl = trl[i]; + + msr_trl |= (_trl << (i * 8)); + } + } + ret = isst_send_msr_command(cpu, 0x1AD, 1, &msr_trl); + if (ret) + return ret; + + return 0; +} + +/* Return 1 if locked */ +int isst_get_config_tdp_lock_status(int cpu) +{ + unsigned long long tdp_control = 0; + int ret; + + ret = isst_send_msr_command(cpu, 0x64b, 0, &tdp_control); + if (ret) + return ret; + + ret = !!(tdp_control & BIT(31)); + + return ret; +} + +void isst_get_process_ctdp_complete(int cpu, struct isst_pkg_ctdp *pkg_dev) +{ + int i; + + if (!pkg_dev->processed) + return; + + for (i = 0; i < pkg_dev->levels; ++i) { + struct isst_pkg_ctdp_level_info *ctdp_level; + + ctdp_level = &pkg_dev->ctdp_level[i]; + if (ctdp_level->pbf_support) + free_cpu_set(ctdp_level->pbf_info.core_cpumask); + free_cpu_set(ctdp_level->core_cpumask); + } +} + +int isst_get_process_ctdp(int cpu, int tdp_level, struct isst_pkg_ctdp *pkg_dev) +{ + int i, ret; + + if (pkg_dev->processed) + return 0; + + ret = isst_get_ctdp_levels(cpu, pkg_dev); + if (ret) + return ret; + + debug_printf("cpu: %d ctdp enable:%d current level: %d levels:%d\n", + cpu, pkg_dev->enabled, pkg_dev->current_level, + pkg_dev->levels); + + for (i = 0; i <= pkg_dev->levels; ++i) { + struct isst_pkg_ctdp_level_info *ctdp_level; + + if (tdp_level != 0xff && i != tdp_level) + continue; + + debug_printf("cpu:%d Get Information for TDP level:%d\n", cpu, + i); + ctdp_level = &pkg_dev->ctdp_level[i]; + + ctdp_level->processed = 1; + ctdp_level->level = i; + ctdp_level->control_cpu = cpu; + ctdp_level->pkg_id = get_physical_package_id(cpu); + ctdp_level->die_id = get_physical_die_id(cpu); + + ret = isst_get_ctdp_control(cpu, i, ctdp_level); + if (ret) + return ret; + + ret = isst_get_tdp_info(cpu, i, ctdp_level); + if (ret) + return ret; + + ret = isst_get_pwr_info(cpu, i, ctdp_level); + if (ret) + return ret; + + ret = isst_get_tjmax_info(cpu, i, ctdp_level); + if (ret) + return ret; + + ctdp_level->core_cpumask_size = + alloc_cpu_set(&ctdp_level->core_cpumask); + ret = isst_get_coremask_info(cpu, i, ctdp_level); + if (ret) + return ret; + + ret = isst_get_get_trl(cpu, i, 0, + ctdp_level->trl_sse_active_cores); + if (ret) + return ret; + + ret = isst_get_get_trl(cpu, i, 1, + ctdp_level->trl_avx_active_cores); + if (ret) + return ret; + + ret = isst_get_get_trl(cpu, i, 2, + ctdp_level->trl_avx_512_active_cores); + if (ret) + return ret; + + if (ctdp_level->pbf_support) { + ret = isst_get_pbf_info(cpu, i, &ctdp_level->pbf_info); + if (!ret) + ctdp_level->pbf_found = 1; + } + + if (ctdp_level->fact_support) { + ret = isst_get_fact_info(cpu, i, + &ctdp_level->fact_info); + if (ret) + return ret; + } + } + + pkg_dev->processed = 1; + + return 0; +} + +int isst_pm_qos_config(int cpu, int enable_clos, int priority_type) +{ + unsigned int req, resp; + int ret; + + ret = isst_send_mbox_command(cpu, CONFIG_CLOS, CLOS_PM_QOS_CONFIG, 0, 0, + &resp); + if (ret) + return ret; + + debug_printf("cpu:%d CLOS_PM_QOS_CONFIG resp:%x\n", cpu, resp); + + req = resp; + + if (enable_clos) + req = req | BIT(1); + else + req = req & ~BIT(1); + + if (priority_type) + req = req | BIT(2); + else + req = req & ~BIT(2); + + ret = isst_send_mbox_command(cpu, CONFIG_CLOS, CLOS_PM_QOS_CONFIG, + BIT(MBOX_CMD_WRITE_BIT), req, &resp); + if (ret) + return ret; + + debug_printf("cpu:%d CLOS_PM_QOS_CONFIG priority type:%d req:%x\n", cpu, + priority_type, req); + + return 0; +} + +int isst_pm_get_clos(int cpu, int clos, struct isst_clos_config *clos_config) +{ + unsigned int resp; + int ret; + + ret = isst_send_mbox_command(cpu, CONFIG_CLOS, CLOS_PM_CLOS, clos, 0, + &resp); + if (ret) + return ret; + + clos_config->pkg_id = get_physical_package_id(cpu); + clos_config->die_id = get_physical_die_id(cpu); + + clos_config->epp = resp & 0x0f; + clos_config->clos_prop_prio = (resp >> 4) & 0x0f; + clos_config->clos_min = (resp >> 8) & 0xff; + clos_config->clos_max = (resp >> 16) & 0xff; + clos_config->clos_desired = (resp >> 24) & 0xff; + + return 0; +} + +int isst_set_clos(int cpu, int clos, struct isst_clos_config *clos_config) +{ + unsigned int req, resp; + unsigned int param; + int ret; + + req = clos_config->epp & 0x0f; + req |= (clos_config->clos_prop_prio & 0x0f) << 4; + req |= (clos_config->clos_min & 0xff) << 8; + req |= (clos_config->clos_max & 0xff) << 16; + req |= (clos_config->clos_desired & 0xff) << 24; + + param = BIT(MBOX_CMD_WRITE_BIT) | clos; + + ret = isst_send_mbox_command(cpu, CONFIG_CLOS, CLOS_PM_CLOS, param, req, + &resp); + if (ret) + return ret; + + debug_printf("cpu:%d CLOS_PM_CLOS param:%x req:%x\n", cpu, param, req); + + return 0; +} + +int isst_clos_get_assoc_status(int cpu, int *clos_id) +{ + unsigned int resp; + unsigned int param; + int core_id, ret; + + core_id = find_phy_core_num(cpu); + param = core_id; + + ret = isst_send_mbox_command(cpu, CONFIG_CLOS, CLOS_PQR_ASSOC, param, 0, + &resp); + if (ret) + return ret; + + debug_printf("cpu:%d CLOS_PQR_ASSOC param:%x resp:%x\n", cpu, param, + resp); + *clos_id = (resp >> 16) & 0x03; + + return 0; +} + +int isst_clos_associate(int cpu, int clos_id) +{ + unsigned int req, resp; + unsigned int param; + int core_id, ret; + + req = (clos_id & 0x03) << 16; + core_id = find_phy_core_num(cpu); + param = BIT(MBOX_CMD_WRITE_BIT) | core_id; + + ret = isst_send_mbox_command(cpu, CONFIG_CLOS, CLOS_PQR_ASSOC, param, + req, &resp); + if (ret) + return ret; + + debug_printf("cpu:%d CLOS_PQR_ASSOC param:%x req:%x\n", cpu, param, + req); + + return 0; +} diff --git a/tools/power/x86/intel-speed-select/isst-display.c b/tools/power/x86/intel-speed-select/isst-display.c new file mode 100644 index 000000000000..f368b8323742 --- /dev/null +++ b/tools/power/x86/intel-speed-select/isst-display.c @@ -0,0 +1,479 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Intel dynamic_speed_select -- Enumerate and control features + * Copyright (c) 2019 Intel Corporation. + */ + +#include "isst.h" + +#define DISP_FREQ_MULTIPLIER 100000 + +static void printcpumask(int str_len, char *str, int mask_size, + cpu_set_t *cpu_mask) +{ + int i, max_cpus = get_topo_max_cpus(); + unsigned int *mask; + int size, index, curr_index; + + size = max_cpus / (sizeof(unsigned int) * 8); + if (max_cpus % (sizeof(unsigned int) * 8)) + size++; + + mask = calloc(size, sizeof(unsigned int)); + if (!mask) + return; + + for (i = 0; i < max_cpus; ++i) { + int mask_index, bit_index; + + if (!CPU_ISSET_S(i, mask_size, cpu_mask)) + continue; + + mask_index = i / (sizeof(unsigned int) * 8); + bit_index = i % (sizeof(unsigned int) * 8); + mask[mask_index] |= BIT(bit_index); + } + + curr_index = 0; + for (i = size - 1; i >= 0; --i) { + index = snprintf(&str[curr_index], str_len - curr_index, "%08x", + mask[i]); + curr_index += index; + if (i) { + strncat(&str[curr_index], ",", str_len - curr_index); + curr_index++; + } + } + + free(mask); +} + +static void format_and_print_txt(FILE *outf, int level, char *header, + char *value) +{ + char *spaces = " "; + static char delimiters[256]; + int i, j = 0; + + if (!level) + return; + + if (level == 1) { + strcpy(delimiters, " "); + } else { + for (i = 0; i < level - 1; ++i) + j += snprintf(&delimiters[j], sizeof(delimiters) - j, + "%s", spaces); + } + + if (header && value) { + fprintf(outf, "%s", delimiters); + fprintf(outf, "%s:%s\n", header, value); + } else if (header) { + fprintf(outf, "%s", delimiters); + fprintf(outf, "%s\n", header); + } +} + +static int last_level; +static void format_and_print(FILE *outf, int level, char *header, char *value) +{ + char *spaces = " "; + static char delimiters[256]; + int i; + + if (!out_format_is_json()) { + format_and_print_txt(outf, level, header, value); + return; + } + + if (level == 0) { + if (header) + fprintf(outf, "{"); + else + fprintf(outf, "\n}\n"); + + } else { + int j = 0; + + for (i = 0; i < level; ++i) + j += snprintf(&delimiters[j], sizeof(delimiters) - j, + "%s", spaces); + + if (last_level == level) + fprintf(outf, ",\n"); + + if (value) { + if (last_level != level) + fprintf(outf, "\n"); + + fprintf(outf, "%s\"%s\": ", delimiters, header); + fprintf(outf, "\"%s\"", value); + } else { + for (i = last_level - 1; i >= level; --i) { + int k = 0; + + for (j = i; j > 0; --j) + k += snprintf(&delimiters[k], + sizeof(delimiters) - k, + "%s", spaces); + if (i == level && header) + fprintf(outf, "\n%s},", delimiters); + else + fprintf(outf, "\n%s}", delimiters); + } + if (abs(last_level - level) < 3) + fprintf(outf, "\n"); + if (header) + fprintf(outf, "%s\"%s\": {", delimiters, + header); + } + } + + last_level = level; +} + +static void print_packag_info(int cpu, FILE *outf) +{ + char header[256]; + + snprintf(header, sizeof(header), "package-%d", + get_physical_package_id(cpu)); + format_and_print(outf, 1, header, NULL); + snprintf(header, sizeof(header), "die-%d", get_physical_die_id(cpu)); + format_and_print(outf, 2, header, NULL); + snprintf(header, sizeof(header), "cpu-%d", cpu); + format_and_print(outf, 3, header, NULL); +} + +static void _isst_pbf_display_information(int cpu, FILE *outf, int level, + struct isst_pbf_info *pbf_info, + int disp_level) +{ + char header[256]; + char value[256]; + + snprintf(header, sizeof(header), "speed-select-base-freq"); + format_and_print(outf, disp_level, header, NULL); + + snprintf(header, sizeof(header), "high-priority-base-frequency(KHz)"); + snprintf(value, sizeof(value), "%d", + pbf_info->p1_high * DISP_FREQ_MULTIPLIER); + format_and_print(outf, disp_level + 1, header, value); + + snprintf(header, sizeof(header), "high-priority-cpu-mask"); + printcpumask(sizeof(value), value, pbf_info->core_cpumask_size, + pbf_info->core_cpumask); + format_and_print(outf, disp_level + 1, header, value); + + snprintf(header, sizeof(header), "low-priority-base-frequency(KHz)"); + snprintf(value, sizeof(value), "%d", + pbf_info->p1_low * DISP_FREQ_MULTIPLIER); + format_and_print(outf, disp_level + 1, header, value); + + snprintf(header, sizeof(header), "tjunction-temperature(C)"); + snprintf(value, sizeof(value), "%d", pbf_info->t_prochot); + format_and_print(outf, disp_level + 1, header, value); + + snprintf(header, sizeof(header), "thermal-design-power(W)"); + snprintf(value, sizeof(value), "%d", pbf_info->tdp); + format_and_print(outf, disp_level + 1, header, value); +} + +static void _isst_fact_display_information(int cpu, FILE *outf, int level, + int fact_bucket, int fact_avx, + struct isst_fact_info *fact_info, + int base_level) +{ + struct isst_fact_bucket_info *bucket_info = fact_info->bucket_info; + char header[256]; + char value[256]; + int j; + + snprintf(header, sizeof(header), "speed-select-turbo-freq"); + format_and_print(outf, base_level, header, NULL); + for (j = 0; j < ISST_FACT_MAX_BUCKETS; ++j) { + if (fact_bucket != 0xff && fact_bucket != j) + continue; + + if (!bucket_info[j].high_priority_cores_count) + break; + + snprintf(header, sizeof(header), "bucket-%d", j); + format_and_print(outf, base_level + 1, header, NULL); + + snprintf(header, sizeof(header), "high-priority-cores-count"); + snprintf(value, sizeof(value), "%d", + bucket_info[j].high_priority_cores_count); + format_and_print(outf, base_level + 2, header, value); + + if (fact_avx & 0x01) { + snprintf(header, sizeof(header), + "high-priority-max-frequency(KHz)"); + snprintf(value, sizeof(value), "%d", + bucket_info[j].sse_trl * DISP_FREQ_MULTIPLIER); + format_and_print(outf, base_level + 2, header, value); + } + + if (fact_avx & 0x02) { + snprintf(header, sizeof(header), + "high-priority-max-avx2-frequency(KHz)"); + snprintf(value, sizeof(value), "%d", + bucket_info[j].avx_trl * DISP_FREQ_MULTIPLIER); + format_and_print(outf, base_level + 2, header, value); + } + + if (fact_avx & 0x04) { + snprintf(header, sizeof(header), + "high-priority-max-avx512-frequency(KHz)"); + snprintf(value, sizeof(value), "%d", + bucket_info[j].avx512_trl * + DISP_FREQ_MULTIPLIER); + format_and_print(outf, base_level + 2, header, value); + } + } + snprintf(header, sizeof(header), + "speed-select-turbo-freq-clip-frequencies"); + format_and_print(outf, base_level + 1, header, NULL); + snprintf(header, sizeof(header), "low-priority-max-frequency(KHz)"); + snprintf(value, sizeof(value), "%d", + fact_info->lp_clipping_ratio_license_sse * + DISP_FREQ_MULTIPLIER); + format_and_print(outf, base_level + 2, header, value); + snprintf(header, sizeof(header), + "low-priority-max-avx2-frequency(KHz)"); + snprintf(value, sizeof(value), "%d", + fact_info->lp_clipping_ratio_license_avx2 * + DISP_FREQ_MULTIPLIER); + format_and_print(outf, base_level + 2, header, value); + snprintf(header, sizeof(header), + "low-priority-max-avx512-frequency(KHz)"); + snprintf(value, sizeof(value), "%d", + fact_info->lp_clipping_ratio_license_avx512 * + DISP_FREQ_MULTIPLIER); + format_and_print(outf, base_level + 2, header, value); +} + +void isst_ctdp_display_information(int cpu, FILE *outf, int tdp_level, + struct isst_pkg_ctdp *pkg_dev) +{ + char header[256]; + char value[256]; + int i, base_level = 1; + + print_packag_info(cpu, outf); + + for (i = 0; i <= pkg_dev->levels; ++i) { + struct isst_pkg_ctdp_level_info *ctdp_level; + int j; + + ctdp_level = &pkg_dev->ctdp_level[i]; + if (!ctdp_level->processed) + continue; + + snprintf(header, sizeof(header), "perf-profile-level-%d", + ctdp_level->level); + format_and_print(outf, base_level + 3, header, NULL); + + snprintf(header, sizeof(header), "cpu-count"); + j = get_cpu_count(get_physical_die_id(cpu), + get_physical_die_id(cpu)); + snprintf(value, sizeof(value), "%d", j); + format_and_print(outf, base_level + 4, header, value); + + snprintf(header, sizeof(header), "enable-cpu-mask"); + printcpumask(sizeof(value), value, + ctdp_level->core_cpumask_size, + ctdp_level->core_cpumask); + format_and_print(outf, base_level + 4, header, value); + + snprintf(header, sizeof(header), "thermal-design-power-ratio"); + snprintf(value, sizeof(value), "%d", ctdp_level->tdp_ratio); + format_and_print(outf, base_level + 4, header, value); + + snprintf(header, sizeof(header), "base-frequency(KHz)"); + snprintf(value, sizeof(value), "%d", + ctdp_level->tdp_ratio * DISP_FREQ_MULTIPLIER); + format_and_print(outf, base_level + 4, header, value); + + snprintf(header, sizeof(header), + "speed-select-turbo-freq-support"); + snprintf(value, sizeof(value), "%d", ctdp_level->fact_support); + format_and_print(outf, base_level + 4, header, value); + + snprintf(header, sizeof(header), + "speed-select-base-freq-support"); + snprintf(value, sizeof(value), "%d", ctdp_level->pbf_support); + format_and_print(outf, base_level + 4, header, value); + + snprintf(header, sizeof(header), + "speed-select-base-freq-enabled"); + snprintf(value, sizeof(value), "%d", ctdp_level->pbf_enabled); + format_and_print(outf, base_level + 4, header, value); + + snprintf(header, sizeof(header), + "speed-select-turbo-freq-enabled"); + snprintf(value, sizeof(value), "%d", ctdp_level->fact_enabled); + format_and_print(outf, base_level + 4, header, value); + + snprintf(header, sizeof(header), "thermal-design-power(W)"); + snprintf(value, sizeof(value), "%d", ctdp_level->pkg_tdp); + format_and_print(outf, base_level + 4, header, value); + + snprintf(header, sizeof(header), "tjunction-max(C)"); + snprintf(value, sizeof(value), "%d", ctdp_level->t_proc_hot); + format_and_print(outf, base_level + 4, header, value); + + snprintf(header, sizeof(header), "turbo-ratio-limits-sse"); + format_and_print(outf, base_level + 4, header, NULL); + for (j = 0; j < 8; ++j) { + snprintf(header, sizeof(header), "bucket-%d", j); + format_and_print(outf, base_level + 5, header, NULL); + + snprintf(header, sizeof(header), "core-count"); + snprintf(value, sizeof(value), "%d", j); + format_and_print(outf, base_level + 6, header, value); + + snprintf(header, sizeof(header), "turbo-ratio"); + snprintf(value, sizeof(value), "%d", + ctdp_level->trl_sse_active_cores[j]); + format_and_print(outf, base_level + 6, header, value); + } + snprintf(header, sizeof(header), "turbo-ratio-limits-avx"); + format_and_print(outf, base_level + 4, header, NULL); + for (j = 0; j < 8; ++j) { + snprintf(header, sizeof(header), "bucket-%d", j); + format_and_print(outf, base_level + 5, header, NULL); + + snprintf(header, sizeof(header), "core-count"); + snprintf(value, sizeof(value), "%d", j); + format_and_print(outf, base_level + 6, header, value); + + snprintf(header, sizeof(header), "turbo-ratio"); + snprintf(value, sizeof(value), "%d", + ctdp_level->trl_avx_active_cores[j]); + format_and_print(outf, base_level + 6, header, value); + } + + snprintf(header, sizeof(header), "turbo-ratio-limits-avx512"); + format_and_print(outf, base_level + 4, header, NULL); + for (j = 0; j < 8; ++j) { + snprintf(header, sizeof(header), "bucket-%d", j); + format_and_print(outf, base_level + 5, header, NULL); + + snprintf(header, sizeof(header), "core-count"); + snprintf(value, sizeof(value), "%d", j); + format_and_print(outf, base_level + 6, header, value); + + snprintf(header, sizeof(header), "turbo-ratio"); + snprintf(value, sizeof(value), "%d", + ctdp_level->trl_avx_512_active_cores[j]); + format_and_print(outf, base_level + 6, header, value); + } + if (ctdp_level->pbf_support) + _isst_pbf_display_information(cpu, outf, i, + &ctdp_level->pbf_info, + base_level + 4); + if (ctdp_level->fact_support) + _isst_fact_display_information(cpu, outf, i, 0xff, 0xff, + &ctdp_level->fact_info, + base_level + 4); + } + + format_and_print(outf, 1, NULL, NULL); +} + +void isst_ctdp_display_information_start(FILE *outf) +{ + last_level = 0; + format_and_print(outf, 0, "start", NULL); +} + +void isst_ctdp_display_information_end(FILE *outf) +{ + format_and_print(outf, 0, NULL, NULL); +} + +void isst_pbf_display_information(int cpu, FILE *outf, int level, + struct isst_pbf_info *pbf_info) +{ + print_packag_info(cpu, outf); + _isst_pbf_display_information(cpu, outf, level, pbf_info, 4); + format_and_print(outf, 1, NULL, NULL); +} + +void isst_fact_display_information(int cpu, FILE *outf, int level, + int fact_bucket, int fact_avx, + struct isst_fact_info *fact_info) +{ + print_packag_info(cpu, outf); + _isst_fact_display_information(cpu, outf, level, fact_bucket, fact_avx, + fact_info, 4); + format_and_print(outf, 1, NULL, NULL); +} + +void isst_clos_display_information(int cpu, FILE *outf, int clos, + struct isst_clos_config *clos_config) +{ + char header[256]; + char value[256]; + + snprintf(header, sizeof(header), "package-%d", + get_physical_package_id(cpu)); + format_and_print(outf, 1, header, NULL); + snprintf(header, sizeof(header), "die-%d", get_physical_die_id(cpu)); + format_and_print(outf, 2, header, NULL); + snprintf(header, sizeof(header), "cpu-%d", cpu); + format_and_print(outf, 3, header, NULL); + + snprintf(header, sizeof(header), "core-power"); + format_and_print(outf, 4, header, NULL); + + snprintf(header, sizeof(header), "clos"); + snprintf(value, sizeof(value), "%d", clos); + format_and_print(outf, 5, header, value); + + snprintf(header, sizeof(header), "epp"); + snprintf(value, sizeof(value), "%d", clos_config->epp); + format_and_print(outf, 5, header, value); + + snprintf(header, sizeof(header), "clos-proportional-priority"); + snprintf(value, sizeof(value), "%d", clos_config->clos_prop_prio); + format_and_print(outf, 5, header, value); + + snprintf(header, sizeof(header), "clos-min"); + snprintf(value, sizeof(value), "%d", clos_config->clos_min); + format_and_print(outf, 5, header, value); + + snprintf(header, sizeof(header), "clos-max"); + snprintf(value, sizeof(value), "%d", clos_config->clos_max); + format_and_print(outf, 5, header, value); + + snprintf(header, sizeof(header), "clos-desired"); + snprintf(value, sizeof(value), "%d", clos_config->clos_desired); + format_and_print(outf, 5, header, value); + + format_and_print(outf, 1, NULL, NULL); +} + +void isst_display_result(int cpu, FILE *outf, char *feature, char *cmd, + int result) +{ + char header[256]; + char value[256]; + + snprintf(header, sizeof(header), "package-%d", + get_physical_package_id(cpu)); + format_and_print(outf, 1, header, NULL); + snprintf(header, sizeof(header), "die-%d", get_physical_die_id(cpu)); + format_and_print(outf, 2, header, NULL); + snprintf(header, sizeof(header), "cpu-%d", cpu); + format_and_print(outf, 3, header, NULL); + snprintf(header, sizeof(header), "%s", feature); + format_and_print(outf, 4, header, NULL); + snprintf(header, sizeof(header), "%s", cmd); + snprintf(value, sizeof(value), "%d", result); + format_and_print(outf, 5, header, value); + + format_and_print(outf, 1, NULL, NULL); +} diff --git a/tools/power/x86/intel-speed-select/isst.h b/tools/power/x86/intel-speed-select/isst.h new file mode 100644 index 000000000000..221881761609 --- /dev/null +++ b/tools/power/x86/intel-speed-select/isst.h @@ -0,0 +1,231 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Intel Speed Select -- Enumerate and control features + * Copyright (c) 2019 Intel Corporation. + */ + +#ifndef _ISST_H_ +#define _ISST_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define BIT(x) (1 << (x)) +#define GENMASK(h, l) (((~0UL) << (l)) & (~0UL >> (sizeof(long) * 8 - 1 - (h)))) +#define GENMASK_ULL(h, l) \ + (((~0ULL) << (l)) & (~0ULL >> (sizeof(long long) * 8 - 1 - (h)))) + +#define CONFIG_TDP 0x7f +#define CONFIG_TDP_GET_LEVELS_INFO 0x00 +#define CONFIG_TDP_GET_TDP_CONTROL 0x01 +#define CONFIG_TDP_SET_TDP_CONTROL 0x02 +#define CONFIG_TDP_GET_TDP_INFO 0x03 +#define CONFIG_TDP_GET_PWR_INFO 0x04 +#define CONFIG_TDP_GET_TJMAX_INFO 0x05 +#define CONFIG_TDP_GET_CORE_MASK 0x06 +#define CONFIG_TDP_GET_TURBO_LIMIT_RATIOS 0x07 +#define CONFIG_TDP_SET_LEVEL 0x08 +#define CONFIG_TDP_GET_UNCORE_P0_P1_INFO 0X09 +#define CONFIG_TDP_GET_P1_INFO 0x0a +#define CONFIG_TDP_GET_MEM_FREQ 0x0b + +#define CONFIG_TDP_GET_FACT_HP_TURBO_LIMIT_NUMCORES 0x10 +#define CONFIG_TDP_GET_FACT_HP_TURBO_LIMIT_RATIOS 0x11 +#define CONFIG_TDP_GET_FACT_LP_CLIPPING_RATIO 0x12 + +#define CONFIG_TDP_PBF_GET_CORE_MASK_INFO 0x20 +#define CONFIG_TDP_PBF_GET_P1HI_P1LO_INFO 0x21 +#define CONFIG_TDP_PBF_GET_TJ_MAX_INFO 0x22 +#define CONFIG_TDP_PBF_GET_TDP_INFO 0X23 + +#define CONFIG_CLOS 0xd0 +#define CLOS_PQR_ASSOC 0x00 +#define CLOS_PM_CLOS 0x01 +#define CLOS_PM_QOS_CONFIG 0x02 +#define CLOS_STATUS 0x03 + +#define MBOX_CMD_WRITE_BIT 0x08 + +#define PM_QOS_INFO_OFFSET 0x00 +#define PM_QOS_CONFIG_OFFSET 0x04 +#define PM_CLOS_OFFSET 0x08 +#define PQR_ASSOC_OFFSET 0x20 + +struct isst_clos_config { + int pkg_id; + int die_id; + unsigned char epp; + unsigned char clos_prop_prio; + unsigned char clos_min; + unsigned char clos_max; + unsigned char clos_desired; +}; + +struct isst_fact_bucket_info { + int high_priority_cores_count; + int sse_trl; + int avx_trl; + int avx512_trl; +}; + +struct isst_pbf_info { + int pbf_acticated; + int pbf_available; + size_t core_cpumask_size; + cpu_set_t *core_cpumask; + int p1_high; + int p1_low; + int t_control; + int t_prochot; + int tdp; +}; + +#define ISST_TRL_MAX_ACTIVE_CORES 8 +#define ISST_FACT_MAX_BUCKETS 8 +struct isst_fact_info { + int lp_clipping_ratio_license_sse; + int lp_clipping_ratio_license_avx2; + int lp_clipping_ratio_license_avx512; + struct isst_fact_bucket_info bucket_info[ISST_FACT_MAX_BUCKETS]; +}; + +struct isst_pkg_ctdp_level_info { + int processed; + int control_cpu; + int pkg_id; + int die_id; + int level; + int fact_support; + int pbf_support; + int fact_enabled; + int pbf_enabled; + int tdp_ratio; + int active; + int tdp_control; + int pkg_tdp; + int pkg_min_power; + int pkg_max_power; + int fact; + int t_proc_hot; + int uncore_p0; + int uncore_p1; + int sse_p1; + int avx2_p1; + int avx512_p1; + int mem_freq; + size_t core_cpumask_size; + cpu_set_t *core_cpumask; + int cpu_count; + int trl_sse_active_cores[ISST_TRL_MAX_ACTIVE_CORES]; + int trl_avx_active_cores[ISST_TRL_MAX_ACTIVE_CORES]; + int trl_avx_512_active_cores[ISST_TRL_MAX_ACTIVE_CORES]; + int kobj_bucket_index; + int active_bucket; + int fact_max_index; + int fact_max_config; + int pbf_found; + int pbf_active; + struct isst_pbf_info pbf_info; + struct isst_fact_info fact_info; +}; + +#define ISST_MAX_TDP_LEVELS (4 + 1) /* +1 for base config */ +struct isst_pkg_ctdp { + int locked; + int version; + int processed; + int levels; + int current_level; + int enabled; + struct isst_pkg_ctdp_level_info ctdp_level[ISST_MAX_TDP_LEVELS]; +}; + +extern int get_topo_max_cpus(void); +extern int get_cpu_count(int pkg_id, int die_id); + +/* Common interfaces */ +extern void debug_printf(const char *format, ...); +extern int out_format_is_json(void); +extern int get_physical_package_id(int cpu); +extern int get_physical_die_id(int cpu); +extern size_t alloc_cpu_set(cpu_set_t **cpu_set); +extern void free_cpu_set(cpu_set_t *cpu_set); +extern int find_logical_cpu(int pkg_id, int die_id, int phy_cpu); +extern int find_phy_cpu_num(int logical_cpu); +extern int find_phy_core_num(int logical_cpu); +extern void set_cpu_mask_from_punit_coremask(int cpu, + unsigned long long core_mask, + size_t core_cpumask_size, + cpu_set_t *core_cpumask, + int *cpu_cnt); + +extern int isst_send_mbox_command(unsigned int cpu, unsigned char command, + unsigned char sub_command, + unsigned int write, + unsigned int req_data, unsigned int *resp); + +extern int isst_send_msr_command(unsigned int cpu, unsigned int command, + int write, unsigned long long *req_resp); + +extern int isst_get_ctdp_levels(int cpu, struct isst_pkg_ctdp *pkg_dev); +extern int isst_get_process_ctdp(int cpu, int tdp_level, + struct isst_pkg_ctdp *pkg_dev); +extern void isst_get_process_ctdp_complete(int cpu, + struct isst_pkg_ctdp *pkg_dev); +extern void isst_ctdp_display_information(int cpu, FILE *outf, int tdp_level, + struct isst_pkg_ctdp *pkg_dev); +extern void isst_ctdp_display_information_start(FILE *outf); +extern void isst_ctdp_display_information_end(FILE *outf); +extern void isst_pbf_display_information(int cpu, FILE *outf, int level, + struct isst_pbf_info *info); +extern int isst_set_tdp_level(int cpu, int tdp_level); +extern int isst_set_tdp_level_msr(int cpu, int tdp_level); +extern int isst_set_pbf_fact_status(int cpu, int pbf, int enable); +extern int isst_get_pbf_info(int cpu, int level, + struct isst_pbf_info *pbf_info); +extern void isst_get_pbf_info_complete(struct isst_pbf_info *pbf_info); +extern int isst_get_fact_info(int cpu, int level, + struct isst_fact_info *fact_info); +extern int isst_get_fact_bucket_info(int cpu, int level, + struct isst_fact_bucket_info *bucket_info); +extern void isst_fact_display_information(int cpu, FILE *outf, int level, + int fact_bucket, int fact_avx, + struct isst_fact_info *fact_info); +extern int isst_set_trl(int cpu, unsigned long long trl); +extern int isst_set_trl_from_current_tdp(int cpu, unsigned long long trl); +extern int isst_get_config_tdp_lock_status(int cpu); + +extern int isst_pm_qos_config(int cpu, int enable_clos, int priority_type); +extern int isst_pm_get_clos(int cpu, int clos, + struct isst_clos_config *clos_config); +extern int isst_set_clos(int cpu, int clos, + struct isst_clos_config *clos_config); +extern int isst_clos_associate(int cpu, int clos); +extern int isst_clos_get_assoc_status(int cpu, int *clos_id); +extern void isst_clos_display_information(int cpu, FILE *outf, int clos, + struct isst_clos_config *clos_config); + +extern int isst_read_reg(unsigned short reg, unsigned int *val); +extern int isst_write_reg(int reg, unsigned int val); + +extern void isst_display_result(int cpu, FILE *outf, char *feature, char *cmd, + int result); +#endif From 3dda617a14327fb5f7c796b0cda2d211e9a2169e Mon Sep 17 00:00:00 2001 From: Srinivas Pandruvada Date: Tue, 2 Jul 2019 18:53:31 -0700 Subject: [PATCH 62/74] MAINTAINERS: Update for Intel Speed Select Technology Added myself as the maintainer. Signed-off-by: Srinivas Pandruvada Acked-by: Rafael J. Wysocki Signed-off-by: Andy Shevchenko --- MAINTAINERS | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 5cfbea4ce575..b6ed7958372d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -8101,6 +8101,14 @@ S: Supported F: drivers/infiniband/hw/i40iw/ F: include/uapi/rdma/i40iw-abi.h +INTEL SPEED SELECT TECHNOLOGY +M: Srinivas Pandruvada +L: platform-driver-x86@vger.kernel.org +S: Maintained +F: drivers/platform/x86/intel_speed_select_if/ +F: tools/power/x86/intel-speed-select/ +F: include/uapi/linux/isst_if.h + INTEL TELEMETRY DRIVER M: Rajneesh Bhardwaj M: "David E. Box" From 6b266e91a0715dae9923a2791e0b98165163e91f Mon Sep 17 00:00:00 2001 From: Vadim Pasternak Date: Sun, 23 Jun 2019 12:16:24 +0000 Subject: [PATCH 63/74] platform/x86: mlx-platform: Move regmap initialization before all drivers activation Initialize regmap prior drivers starting to allow passing regmap handle to 'i2c_mlxcpld' driver. Signed-off-by: Vadim Pasternak Signed-off-by: Andy Shevchenko --- drivers/platform/x86/mlx-platform.c | 58 ++++++++++++++++------------- 1 file changed, 33 insertions(+), 25 deletions(-) diff --git a/drivers/platform/x86/mlx-platform.c b/drivers/platform/x86/mlx-platform.c index 983f02b5b106..91bdbc2b854f 100644 --- a/drivers/platform/x86/mlx-platform.c +++ b/drivers/platform/x86/mlx-platform.c @@ -159,6 +159,7 @@ * @pdev_io_regs - register access platform devices * @pdev_fan - FAN platform devices * @pdev_wd - array of watchdog platform devices + * @regmap: device register map */ struct mlxplat_priv { struct platform_device *pdev_i2c; @@ -168,6 +169,7 @@ struct mlxplat_priv { struct platform_device *pdev_io_regs; struct platform_device *pdev_fan; struct platform_device *pdev_wd[MLXPLAT_CPLD_WD_MAX_DEVS]; + void *regmap; }; /* Regions for LPC I2C controller and LPC base register space */ @@ -1740,6 +1742,7 @@ static struct mlxreg_core_platform_data *mlxplat_regs_io; static struct mlxreg_core_platform_data *mlxplat_fan; static struct mlxreg_core_platform_data *mlxplat_wd_data[MLXPLAT_CPLD_WD_MAX_DEVS]; +static const struct regmap_config *mlxplat_regmap_config; static int __init mlxplat_dmi_default_matched(const struct dmi_system_id *dmi) { @@ -2018,6 +2021,24 @@ static int __init mlxplat_init(void) } platform_set_drvdata(mlxplat_dev, priv); + mlxplat_mlxcpld_regmap_ctx.base = devm_ioport_map(&mlxplat_dev->dev, + mlxplat_lpc_resources[1].start, 1); + if (!mlxplat_mlxcpld_regmap_ctx.base) { + err = -ENOMEM; + goto fail_alloc; + } + + if (!mlxplat_regmap_config) + mlxplat_regmap_config = &mlxplat_mlxcpld_regmap_config; + + priv->regmap = devm_regmap_init(&mlxplat_dev->dev, NULL, + &mlxplat_mlxcpld_regmap_ctx, + mlxplat_regmap_config); + if (IS_ERR(priv->regmap)) { + err = PTR_ERR(priv->regmap); + return err; + } + err = mlxplat_mlxcpld_verify_bus_topology(&nr); if (nr < 0) goto fail_alloc; @@ -2042,21 +2063,8 @@ static int __init mlxplat_init(void) } } - mlxplat_mlxcpld_regmap_ctx.base = devm_ioport_map(&mlxplat_dev->dev, - mlxplat_lpc_resources[1].start, 1); - if (!mlxplat_mlxcpld_regmap_ctx.base) { - err = -ENOMEM; - goto fail_platform_mux_register; - } - - mlxplat_hotplug->regmap = devm_regmap_init(&mlxplat_dev->dev, NULL, - &mlxplat_mlxcpld_regmap_ctx, - &mlxplat_mlxcpld_regmap_config); - if (IS_ERR(mlxplat_hotplug->regmap)) { - err = PTR_ERR(mlxplat_hotplug->regmap); - goto fail_platform_mux_register; - } - + /* Add hotplug driver */ + mlxplat_hotplug->regmap = priv->regmap; priv->pdev_hotplug = platform_device_register_resndata( &mlxplat_dev->dev, "mlxreg-hotplug", PLATFORM_DEVID_NONE, @@ -2069,16 +2077,16 @@ static int __init mlxplat_init(void) } /* Set default registers. */ - for (j = 0; j < mlxplat_mlxcpld_regmap_config.num_reg_defaults; j++) { - err = regmap_write(mlxplat_hotplug->regmap, - mlxplat_mlxcpld_regmap_default[j].reg, - mlxplat_mlxcpld_regmap_default[j].def); + for (j = 0; j < mlxplat_regmap_config->num_reg_defaults; j++) { + err = regmap_write(priv->regmap, + mlxplat_regmap_config->reg_defaults[j].reg, + mlxplat_regmap_config->reg_defaults[j].def); if (err) goto fail_platform_mux_register; } /* Add LED driver. */ - mlxplat_led->regmap = mlxplat_hotplug->regmap; + mlxplat_led->regmap = priv->regmap; priv->pdev_led = platform_device_register_resndata( &mlxplat_dev->dev, "leds-mlxreg", PLATFORM_DEVID_NONE, NULL, 0, @@ -2090,7 +2098,7 @@ static int __init mlxplat_init(void) /* Add registers io access driver. */ if (mlxplat_regs_io) { - mlxplat_regs_io->regmap = mlxplat_hotplug->regmap; + mlxplat_regs_io->regmap = priv->regmap; priv->pdev_io_regs = platform_device_register_resndata( &mlxplat_dev->dev, "mlxreg-io", PLATFORM_DEVID_NONE, NULL, 0, @@ -2104,7 +2112,7 @@ static int __init mlxplat_init(void) /* Add FAN driver. */ if (mlxplat_fan) { - mlxplat_fan->regmap = mlxplat_hotplug->regmap; + mlxplat_fan->regmap = priv->regmap; priv->pdev_fan = platform_device_register_resndata( &mlxplat_dev->dev, "mlxreg-fan", PLATFORM_DEVID_NONE, NULL, 0, @@ -2119,7 +2127,7 @@ static int __init mlxplat_init(void) /* Add WD drivers. */ for (j = 0; j < MLXPLAT_CPLD_WD_MAX_DEVS; j++) { if (mlxplat_wd_data[j]) { - mlxplat_wd_data[j]->regmap = mlxplat_hotplug->regmap; + mlxplat_wd_data[j]->regmap = priv->regmap; priv->pdev_wd[j] = platform_device_register_resndata( &mlxplat_dev->dev, "mlx-wdt", j, NULL, 0, @@ -2133,8 +2141,8 @@ static int __init mlxplat_init(void) } /* Sync registers with hardware. */ - regcache_mark_dirty(mlxplat_hotplug->regmap); - err = regcache_sync(mlxplat_hotplug->regmap); + regcache_mark_dirty(priv->regmap); + err = regcache_sync(priv->regmap); if (err) goto fail_platform_wd_register; From d66656262a23c20cb6d88b772b65ad26fa3fc38b Mon Sep 17 00:00:00 2001 From: Vadim Pasternak Date: Sun, 23 Jun 2019 12:16:25 +0000 Subject: [PATCH 64/74] platform/x86: mlx-platform: Change API for i2c-mlxcpld driver activation Activate 'i2c-mlxcpld' driver with 'platform_device_register_resndata' instead off 'platform_device_register_simple' in order to pass platform specific info. Add platform i2c data for the next generation systems. Signed-off-by: Vadim Pasternak Signed-off-by: Andy Shevchenko --- drivers/platform/x86/mlx-platform.c | 30 ++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/drivers/platform/x86/mlx-platform.c b/drivers/platform/x86/mlx-platform.c index 91bdbc2b854f..57b1268a3b9c 100644 --- a/drivers/platform/x86/mlx-platform.c +++ b/drivers/platform/x86/mlx-platform.c @@ -44,6 +44,8 @@ #define MLXPLAT_CPLD_LPC_REG_AGGR_MASK_OFFSET 0x3b #define MLXPLAT_CPLD_LPC_REG_AGGRLO_OFFSET 0x40 #define MLXPLAT_CPLD_LPC_REG_AGGRLO_MASK_OFFSET 0x41 +#define MLXPLAT_CPLD_LPC_REG_AGGRCO_OFFSET 0x42 +#define MLXPLAT_CPLD_LPC_REG_AGGRCO_MASK_OFFSET 0x43 #define MLXPLAT_CPLD_LPC_REG_ASIC_HEALTH_OFFSET 0x50 #define MLXPLAT_CPLD_LPC_REG_ASIC_EVENT_OFFSET 0x51 #define MLXPLAT_CPLD_LPC_REG_ASIC_MASK_OFFSET 0x52 @@ -105,7 +107,9 @@ MLXPLAT_CPLD_AGGR_FAN_MASK_DEF) #define MLXPLAT_CPLD_AGGR_ASIC_MASK_NG 0x01 #define MLXPLAT_CPLD_AGGR_MASK_NG_DEF 0x04 +#define MLXPLAT_CPLD_AGGR_MASK_COMEX BIT(0) #define MLXPLAT_CPLD_LOW_AGGR_MASK_LOW 0xc1 +#define MLXPLAT_CPLD_LOW_AGGR_MASK_I2C BIT(6) #define MLXPLAT_CPLD_PSU_MASK GENMASK(1, 0) #define MLXPLAT_CPLD_PWR_MASK GENMASK(1, 0) #define MLXPLAT_CPLD_FAN_MASK GENMASK(3, 0) @@ -183,6 +187,14 @@ static const struct resource mlxplat_lpc_resources[] = { IORESOURCE_IO), }; +/* Platform next generation systems i2c data */ +static struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_i2c_ng_data = { + .cell = MLXPLAT_CPLD_LPC_REG_AGGR_OFFSET, + .mask = MLXPLAT_CPLD_AGGR_MASK_COMEX, + .cell_low = MLXPLAT_CPLD_LPC_REG_AGGRCO_OFFSET, + .mask_low = MLXPLAT_CPLD_LOW_AGGR_MASK_I2C, +}; + /* Platform default channels */ static const int mlxplat_default_channels[][MLXPLAT_CPLD_GRP_CHNL_NUM] = { { @@ -706,7 +718,7 @@ struct mlxreg_core_hotplug_platform_data mlxplat_mlxcpld_default_ng_data = { .items = mlxplat_mlxcpld_default_ng_items, .counter = ARRAY_SIZE(mlxplat_mlxcpld_default_ng_items), .cell = MLXPLAT_CPLD_LPC_REG_AGGR_OFFSET, - .mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF, + .mask = MLXPLAT_CPLD_AGGR_MASK_NG_DEF | MLXPLAT_CPLD_AGGR_MASK_COMEX, .cell_low = MLXPLAT_CPLD_LPC_REG_AGGRLO_OFFSET, .mask_low = MLXPLAT_CPLD_LOW_AGGR_MASK_LOW, }; @@ -1533,6 +1545,7 @@ static bool mlxplat_mlxcpld_writeable_reg(struct device *dev, unsigned int reg) case MLXPLAT_CPLD_LPC_REG_WP2_OFFSET: case MLXPLAT_CPLD_LPC_REG_AGGR_MASK_OFFSET: case MLXPLAT_CPLD_LPC_REG_AGGRLO_MASK_OFFSET: + case MLXPLAT_CPLD_LPC_REG_AGGRCO_MASK_OFFSET: case MLXPLAT_CPLD_LPC_REG_ASIC_EVENT_OFFSET: case MLXPLAT_CPLD_LPC_REG_ASIC_MASK_OFFSET: case MLXPLAT_CPLD_LPC_REG_PSU_EVENT_OFFSET: @@ -1580,6 +1593,8 @@ static bool mlxplat_mlxcpld_readable_reg(struct device *dev, unsigned int reg) case MLXPLAT_CPLD_LPC_REG_AGGR_MASK_OFFSET: case MLXPLAT_CPLD_LPC_REG_AGGRLO_OFFSET: case MLXPLAT_CPLD_LPC_REG_AGGRLO_MASK_OFFSET: + case MLXPLAT_CPLD_LPC_REG_AGGRCO_OFFSET: + case MLXPLAT_CPLD_LPC_REG_AGGRCO_MASK_OFFSET: case MLXPLAT_CPLD_LPC_REG_ASIC_HEALTH_OFFSET: case MLXPLAT_CPLD_LPC_REG_ASIC_EVENT_OFFSET: case MLXPLAT_CPLD_LPC_REG_ASIC_MASK_OFFSET: @@ -1647,6 +1662,8 @@ static bool mlxplat_mlxcpld_volatile_reg(struct device *dev, unsigned int reg) case MLXPLAT_CPLD_LPC_REG_AGGR_MASK_OFFSET: case MLXPLAT_CPLD_LPC_REG_AGGRLO_OFFSET: case MLXPLAT_CPLD_LPC_REG_AGGRLO_MASK_OFFSET: + case MLXPLAT_CPLD_LPC_REG_AGGRCO_OFFSET: + case MLXPLAT_CPLD_LPC_REG_AGGRCO_MASK_OFFSET: case MLXPLAT_CPLD_LPC_REG_ASIC_HEALTH_OFFSET: case MLXPLAT_CPLD_LPC_REG_ASIC_EVENT_OFFSET: case MLXPLAT_CPLD_LPC_REG_ASIC_MASK_OFFSET: @@ -1736,6 +1753,7 @@ static struct resource mlxplat_mlxcpld_resources[] = { }; static struct platform_device *mlxplat_dev; +static struct mlxreg_core_hotplug_platform_data *mlxplat_i2c; static struct mlxreg_core_hotplug_platform_data *mlxplat_hotplug; static struct mlxreg_core_platform_data *mlxplat_led; static struct mlxreg_core_platform_data *mlxplat_regs_io; @@ -1837,6 +1855,7 @@ static int __init mlxplat_dmi_qmb7xx_matched(const struct dmi_system_id *dmi) mlxplat_fan = &mlxplat_default_fan_data; for (i = 0; i < ARRAY_SIZE(mlxplat_mlxcpld_wd_set_type2); i++) mlxplat_wd_data[i] = &mlxplat_mlxcpld_wd_set_type2[i]; + mlxplat_i2c = &mlxplat_mlxcpld_i2c_ng_data; return 1; }; @@ -2044,8 +2063,13 @@ static int __init mlxplat_init(void) goto fail_alloc; nr = (nr == MLXPLAT_CPLD_MAX_PHYS_ADAPTER_NUM) ? -1 : nr; - priv->pdev_i2c = platform_device_register_simple("i2c_mlxcpld", nr, - NULL, 0); + if (mlxplat_i2c) + mlxplat_i2c->regmap = priv->regmap; + priv->pdev_i2c = platform_device_register_resndata( + &mlxplat_dev->dev, "i2c_mlxcpld", + nr, mlxplat_mlxcpld_resources, + ARRAY_SIZE(mlxplat_mlxcpld_resources), + mlxplat_i2c, sizeof(*mlxplat_i2c)); if (IS_ERR(priv->pdev_i2c)) { err = PTR_ERR(priv->pdev_i2c); goto fail_alloc; From a7ff2f99eb547867377f4db7eeae853804d78d96 Mon Sep 17 00:00:00 2001 From: Vadim Pasternak Date: Sun, 23 Jun 2019 12:16:26 +0000 Subject: [PATCH 65/74] platform/x86: mlx-platform: Add regmap structure for the next generation systems MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use separated regamp structures for old and next generation systems. Next generation systems don’t require write protection removing. Signed-off-by: Vadim Pasternak Signed-off-by: Andy Shevchenko --- drivers/platform/x86/mlx-platform.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/drivers/platform/x86/mlx-platform.c b/drivers/platform/x86/mlx-platform.c index 57b1268a3b9c..b166fe100935 100644 --- a/drivers/platform/x86/mlx-platform.c +++ b/drivers/platform/x86/mlx-platform.c @@ -1710,6 +1710,11 @@ static const struct reg_default mlxplat_mlxcpld_regmap_default[] = { { MLXPLAT_CPLD_LPC_REG_WD_CLEAR_WP_OFFSET, 0x00 }, }; +static const struct reg_default mlxplat_mlxcpld_regmap_ng[] = { + { MLXPLAT_CPLD_LPC_REG_PWM_CONTROL_OFFSET, 0x00 }, + { MLXPLAT_CPLD_LPC_REG_WD_CLEAR_WP_OFFSET, 0x00 }, +}; + struct mlxplat_mlxcpld_regmap_context { void __iomem *base; }; @@ -1748,6 +1753,20 @@ static const struct regmap_config mlxplat_mlxcpld_regmap_config = { .reg_write = mlxplat_mlxcpld_reg_write, }; +static const struct regmap_config mlxplat_mlxcpld_regmap_config_ng = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 255, + .cache_type = REGCACHE_FLAT, + .writeable_reg = mlxplat_mlxcpld_writeable_reg, + .readable_reg = mlxplat_mlxcpld_readable_reg, + .volatile_reg = mlxplat_mlxcpld_volatile_reg, + .reg_defaults = mlxplat_mlxcpld_regmap_ng, + .num_reg_defaults = ARRAY_SIZE(mlxplat_mlxcpld_regmap_ng), + .reg_read = mlxplat_mlxcpld_reg_read, + .reg_write = mlxplat_mlxcpld_reg_write, +}; + static struct resource mlxplat_mlxcpld_resources[] = { [0] = DEFINE_RES_IRQ_NAMED(17, "mlxreg-hotplug"), }; @@ -1856,6 +1875,7 @@ static int __init mlxplat_dmi_qmb7xx_matched(const struct dmi_system_id *dmi) for (i = 0; i < ARRAY_SIZE(mlxplat_mlxcpld_wd_set_type2); i++) mlxplat_wd_data[i] = &mlxplat_mlxcpld_wd_set_type2[i]; mlxplat_i2c = &mlxplat_mlxcpld_i2c_ng_data; + mlxplat_regmap_config = &mlxplat_mlxcpld_regmap_config_ng; return 1; }; From cb636bb1dcfb514e85b70ff482b6c583f3eb7960 Mon Sep 17 00:00:00 2001 From: Vadim Pasternak Date: Sun, 23 Jun 2019 12:16:27 +0000 Subject: [PATCH 66/74] platform/x86: mlx-platform: Modify DMI matching order MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Modify DMI matching order: perform matching based on DMI_BOARD_NAME before matching based on DMI_BOARD_VENDOR and DMI_PRODUCT_NAME in order to reduce the number of ‘dmi_table’ entries necessary for new systems support and keep matching order in logical way. For example, the existing check for DMI_PRODUCT_NAME with prefixes “MSN27", “MSN24”, "MSB” matches systems MSN2700-BXXXX, MSN2700-XXXX, MSN2410-BXXXX, MSB7800-XXXX, where ‘XXXX’ specifies some systems hardware flavors. At the same time these systems also matched by DMI_BOARD_NAME “VMOD0001”, because they all have the same platform configuration (LED, interrupt control, mux etcetera). New systems with different platform configuration, but with similar DMI_PRODUCT_NAME MSN2700-2XXXX, MSN2700-2XXXX, MSB7800-2XXXX are about to be added. These system have similar DMI_PRODUCT_NAME, since they have same ports configuration as their predecessors. All new systems will be matched by DMI_BOARD_NAME “VMOD0008”. With the change provided in the patch it is enough just to add “VMOD0008” match following natural after “VMOD0007”, otherwise “VMOD0008” or all “MSN2700-2XXXX”, “MSN2700-2XXXX”, “MSB7800-2XXXX” should be added on top of ‘mlxplat_dmi_table” in order to be matched before “MSN27", “MSN24”, "MSB”. Signed-off-by: Vadim Pasternak Signed-off-by: Andy Shevchenko --- drivers/platform/x86/mlx-platform.c | 72 ++++++++++++++--------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/drivers/platform/x86/mlx-platform.c b/drivers/platform/x86/mlx-platform.c index b166fe100935..858156deff45 100644 --- a/drivers/platform/x86/mlx-platform.c +++ b/drivers/platform/x86/mlx-platform.c @@ -1881,6 +1881,42 @@ static int __init mlxplat_dmi_qmb7xx_matched(const struct dmi_system_id *dmi) }; static const struct dmi_system_id mlxplat_dmi_table[] __initconst = { + { + .callback = mlxplat_dmi_default_matched, + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "VMOD0001"), + }, + }, + { + .callback = mlxplat_dmi_msn21xx_matched, + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "VMOD0002"), + }, + }, + { + .callback = mlxplat_dmi_msn274x_matched, + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "VMOD0003"), + }, + }, + { + .callback = mlxplat_dmi_msn201x_matched, + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "VMOD0004"), + }, + }, + { + .callback = mlxplat_dmi_qmb7xx_matched, + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "VMOD0005"), + }, + }, + { + .callback = mlxplat_dmi_qmb7xx_matched, + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "VMOD0007"), + }, + }, { .callback = mlxplat_dmi_msn274x_matched, .matches = { @@ -1958,42 +1994,6 @@ static const struct dmi_system_id mlxplat_dmi_table[] __initconst = { DMI_MATCH(DMI_PRODUCT_NAME, "MSN38"), }, }, - { - .callback = mlxplat_dmi_default_matched, - .matches = { - DMI_MATCH(DMI_BOARD_NAME, "VMOD0001"), - }, - }, - { - .callback = mlxplat_dmi_msn21xx_matched, - .matches = { - DMI_MATCH(DMI_BOARD_NAME, "VMOD0002"), - }, - }, - { - .callback = mlxplat_dmi_msn274x_matched, - .matches = { - DMI_MATCH(DMI_BOARD_NAME, "VMOD0003"), - }, - }, - { - .callback = mlxplat_dmi_msn201x_matched, - .matches = { - DMI_MATCH(DMI_BOARD_NAME, "VMOD0004"), - }, - }, - { - .callback = mlxplat_dmi_qmb7xx_matched, - .matches = { - DMI_MATCH(DMI_BOARD_NAME, "VMOD0005"), - }, - }, - { - .callback = mlxplat_dmi_qmb7xx_matched, - .matches = { - DMI_MATCH(DMI_BOARD_NAME, "VMOD0007"), - }, - }, { } }; From 262d861bf7d6dfb80f720d09d8fc901c8b24f9f7 Mon Sep 17 00:00:00 2001 From: Vadim Pasternak Date: Sun, 23 Jun 2019 12:16:28 +0000 Subject: [PATCH 67/74] platform/x86: mlx-platform: Add more reset cause attributes Add more attributes for reset cause indication for the cases when system reset has been caused by watchdog, BIOS reload and COMEX thermal shutdown. Signed-off-by: Vadim Pasternak Signed-off-by: Andy Shevchenko --- drivers/platform/x86/mlx-platform.c | 36 +++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/drivers/platform/x86/mlx-platform.c b/drivers/platform/x86/mlx-platform.c index 858156deff45..2b98f299faa4 100644 --- a/drivers/platform/x86/mlx-platform.c +++ b/drivers/platform/x86/mlx-platform.c @@ -1126,6 +1126,12 @@ static struct mlxreg_core_data mlxplat_mlxcpld_msn21xx_regs_io_data[] = { .mask = GENMASK(7, 0) & ~BIT(6), .mode = 0444, }, + { + .label = "reset_sff_wd", + .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(6), + .mode = 0444, + }, { .label = "psu1_on", .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, @@ -1214,6 +1220,18 @@ static struct mlxreg_core_data mlxplat_mlxcpld_default_ng_regs_io_data[] = { .mask = GENMASK(7, 0) & ~BIT(4), .mode = 0444, }, + { + .label = "reset_from_asic", + .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(5), + .mode = 0444, + }, + { + .label = "reset_swb_wd", + .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(6), + .mode = 0444, + }, { .label = "reset_asic_thermal", .reg = MLXPLAT_CPLD_LPC_REG_RESET_CAUSE_OFFSET, @@ -1226,6 +1244,12 @@ static struct mlxreg_core_data mlxplat_mlxcpld_default_ng_regs_io_data[] = { .mask = GENMASK(7, 0) & ~BIT(3), .mode = 0444, }, + { + .label = "reset_comex_wd", + .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE1_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(6), + .mode = 0444, + }, { .label = "reset_voltmon_upgrade_fail", .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET, @@ -1238,6 +1262,18 @@ static struct mlxreg_core_data mlxplat_mlxcpld_default_ng_regs_io_data[] = { .mask = GENMASK(7, 0) & ~BIT(1), .mode = 0444, }, + { + .label = "reset_comex_thermal", + .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(3), + .mode = 0444, + }, + { + .label = "reset_reload_bios", + .reg = MLXPLAT_CPLD_LPC_REG_RST_CAUSE2_OFFSET, + .mask = GENMASK(7, 0) & ~BIT(5), + .mode = 0444, + }, { .label = "psu1_on", .reg = MLXPLAT_CPLD_LPC_REG_GP1_OFFSET, From ba07e3aacc08de2e6f6cd199c3618fbbc2c45f0c Mon Sep 17 00:00:00 2001 From: Vadim Pasternak Date: Sun, 23 Jun 2019 12:16:29 +0000 Subject: [PATCH 68/74] Documentation/ABI: Add new attribute for mlxreg-io sysfs interfaces Add documentation for the new attributes for exposing reset causes for the next type of resets: caused by different watchdog, BIOS reload, ASIC reset request. Signed-off-by: Vadim Pasternak Signed-off-by: Andy Shevchenko --- .../ABI/stable/sysfs-driver-mlxreg-io | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Documentation/ABI/stable/sysfs-driver-mlxreg-io b/Documentation/ABI/stable/sysfs-driver-mlxreg-io index 156319fc5b80..28ca00cc8d6b 100644 --- a/Documentation/ABI/stable/sysfs-driver-mlxreg-io +++ b/Documentation/ABI/stable/sysfs-driver-mlxreg-io @@ -127,3 +127,23 @@ Description: These files show the system reset cause, as following: ComEx the last reset cause. The files are read only. + +Date: June 2019 +KernelVersion: 5.3 +Contact: Vadim Pasternak +Description: These files show the system reset cause, as following: + COMEX thermal shutdown; wathchdog power off or reset was derived + by one of the next components: COMEX, switch board or by Small Form + Factor mezzanine, reset requested from ASIC, reset cuased by BIOS + reload. Value 1 in file means this is reset cause, 0 - otherwise. + Only one of the above causes could be 1 at the same time, representing + only last reset cause. + + The files are read only. + +What: /sys/devices/platform/mlxplat/mlxreg-io/hwmon/hwmon*/reset_comex_thermal +What: /sys/devices/platform/mlxplat/mlxreg-io/hwmon/hwmon*/reset_comex_wd +What: /sys/devices/platform/mlxplat/mlxreg-io/hwmon/hwmon*/reset_from_asic +What: /sys/devices/platform/mlxplat/mlxreg-io/hwmon/hwmon*/reset_reload_bios +What: /sys/devices/platform/mlxplat/mlxreg-io/hwmon/hwmon*/reset_sff_wd +What: /sys/devices/platform/mlxplat/mlxreg-io/hwmon/hwmon*/reset_swb_wd From 8e8fe446a91caf4537f512572e4bf52f8f2154a9 Mon Sep 17 00:00:00 2001 From: Fuqian Huang Date: Mon, 1 Jul 2019 11:24:26 +0800 Subject: [PATCH 69/74] platform/x86: asus-wmi: Use dev_get_drvdata() Using dev_get_drvdata directly. Signed-off-by: Fuqian Huang Signed-off-by: Andy Shevchenko --- drivers/platform/x86/asus-wmi.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index 508e6ad47793..269ed7e94013 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -1402,8 +1402,7 @@ static umode_t asus_hwmon_sysfs_is_visible(struct kobject *kobj, struct attribute *attr, int idx) { struct device *dev = container_of(kobj, struct device, kobj); - struct platform_device *pdev = to_platform_device(dev->parent); - struct asus_wmi *asus = platform_get_drvdata(pdev); + struct asus_wmi *asus = dev_get_drvdata(dev->parent); int dev_id = -1; int fan_attr = -1; u32 value = ASUS_WMI_UNSUPPORTED_METHOD; From c09c6071310df430619df57d67e7ab59902f99ad Mon Sep 17 00:00:00 2001 From: Harry Pan Date: Wed, 19 Jun 2019 16:28:01 +0800 Subject: [PATCH 70/74] platform/x86: intel_pmc_core: transform Pkg C-state residency from TSC ticks into microseconds Refer to the Intel SDM Vol.4, the package C-state residency counters of modern IA micro-architecture are all ticking in TSC frequency, hence we can apply simple math to transform the ticks into microseconds. i.e., residency (ms) = count / tsc_khz residency (us) = count / tsc_khz * 1000 This also aligns to other sysfs debug entries of residency counter in the same metric in microseconds, benefits reading and scripting. v2: restore the accidentally deleted newline, no function change. v3: apply kernel do_div() macro to calculate division Signed-off-by: Harry Pan Signed-off-by: Andy Shevchenko --- drivers/platform/x86/intel_pmc_core.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/platform/x86/intel_pmc_core.c b/drivers/platform/x86/intel_pmc_core.c index 27d6470e43ec..deef5608d059 100644 --- a/drivers/platform/x86/intel_pmc_core.c +++ b/drivers/platform/x86/intel_pmc_core.c @@ -26,6 +26,7 @@ #include #include #include +#include #include "intel_pmc_core.h" @@ -740,7 +741,9 @@ static int pmc_core_pkgc_show(struct seq_file *s, void *unused) if (rdmsrl_safe(map[index].bit_mask, &pcstate_count)) continue; - seq_printf(s, "%-8s : 0x%llx\n", map[index].name, + pcstate_count *= 1000; + do_div(pcstate_count, tsc_khz); + seq_printf(s, "%-8s : %llu\n", map[index].name, pcstate_count); } From b02f6a2ef0a14af5c19780521370673f55c1476d Mon Sep 17 00:00:00 2001 From: Rajat Jain Date: Thu, 27 Jun 2019 20:34:13 -0700 Subject: [PATCH 71/74] platform/x86: intel_pmc_core: Attach using APCI HID "INT33A1" Most modern platforms already have the ACPI device "INT33A1" that could be used to attach to the driver. Switch the driver to using that and thus make the intel_pmc_core.c a pure platform_driver. Some of the legacy platforms though, may still not have this ACPI device in their ACPI tables. Thus for such platforms, move the code to manually instantiate a platform_device into a new file of its own. This would instantiate the intel_pmc_core platform device and thus attach to the driver, if the ACPI device for the same ("INT33A1") is not present in a system where it should be. This was discussed here: https://www.mail-archive.com/linux-kernel@vger.kernel.org/msg1966991.html Signed-off-by: Rajat Jain [andy: renamed to intel_pmc_core_pltdrv.c to be in align with other drivers] Signed-off-by: Andy Shevchenko --- drivers/platform/x86/Makefile | 2 +- drivers/platform/x86/intel_pmc_core.c | 40 +++---------- drivers/platform/x86/intel_pmc_core_pltdrv.c | 62 ++++++++++++++++++++ 3 files changed, 71 insertions(+), 33 deletions(-) create mode 100644 drivers/platform/x86/intel_pmc_core_pltdrv.c diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 3a62157e9062..415104033060 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -90,7 +90,7 @@ 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_INTEL_PMC_CORE) += intel_pmc_core.o intel_pmc_core_pltdrv.o obj-$(CONFIG_PMC_ATOM) += pmc_atom.o obj-$(CONFIG_MLX_PLATFORM) += mlx-platform.o obj-$(CONFIG_INTEL_TURBO_MAX_3) += intel_turbo_max_3.o diff --git a/drivers/platform/x86/intel_pmc_core.c b/drivers/platform/x86/intel_pmc_core.c index deef5608d059..235c0b89f824 100644 --- a/drivers/platform/x86/intel_pmc_core.c +++ b/drivers/platform/x86/intel_pmc_core.c @@ -1014,47 +1014,23 @@ static const struct dev_pm_ops pmc_core_pm_ops = { SET_LATE_SYSTEM_SLEEP_PM_OPS(pmc_core_suspend, pmc_core_resume) }; +static const struct acpi_device_id pmc_core_acpi_ids[] = { + {"INT33A1", 0}, /* _HID for Intel Power Engine, _CID PNP0D80*/ + { } +}; +MODULE_DEVICE_TABLE(acpi, pmc_core_acpi_ids); + static struct platform_driver pmc_core_driver = { .driver = { .name = "intel_pmc_core", + .acpi_match_table = ACPI_PTR(pmc_core_acpi_ids), .pm = &pmc_core_pm_ops, }, .probe = pmc_core_probe, .remove = pmc_core_remove, }; -static struct platform_device pmc_core_device = { - .name = "intel_pmc_core", -}; - -static int __init pmc_core_init(void) -{ - int ret; - - if (!x86_match_cpu(intel_pmc_core_ids)) - return -ENODEV; - - ret = platform_driver_register(&pmc_core_driver); - if (ret) - return ret; - - ret = platform_device_register(&pmc_core_device); - if (ret) { - platform_driver_unregister(&pmc_core_driver); - return ret; - } - - return 0; -} - -static void __exit pmc_core_exit(void) -{ - platform_device_unregister(&pmc_core_device); - platform_driver_unregister(&pmc_core_driver); -} - -module_init(pmc_core_init) -module_exit(pmc_core_exit) +module_platform_driver(pmc_core_driver); MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("Intel PMC Core Driver"); diff --git a/drivers/platform/x86/intel_pmc_core_pltdrv.c b/drivers/platform/x86/intel_pmc_core_pltdrv.c new file mode 100644 index 000000000000..a8754a6db1b8 --- /dev/null +++ b/drivers/platform/x86/intel_pmc_core_pltdrv.c @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* + * Intel PMC Core platform init + * Copyright (c) 2019, Google Inc. + * Author - Rajat Jain + * + * This code instantiates platform devices for intel_pmc_core driver, only + * on supported platforms that may not have the ACPI devices in the ACPI tables. + * No new platforms should be added here, because we expect that new platforms + * should all have the ACPI device, which is the preferred way of enumeration. + */ + +#include +#include +#include + +#include +#include + +static struct platform_device pmc_core_device = { + .name = "intel_pmc_core", +}; + +/* + * intel_pmc_core_platform_ids is the list of platforms where we want to + * instantiate the platform_device if not already instantiated. This is + * different than intel_pmc_core_ids in intel_pmc_core.c which is the + * list of platforms that the driver supports for pmc_core device. The + * other list may grow, but this list should not. + */ +static const struct x86_cpu_id intel_pmc_core_platform_ids[] = { + INTEL_CPU_FAM6(SKYLAKE_MOBILE, pmc_core_device), + INTEL_CPU_FAM6(SKYLAKE_DESKTOP, pmc_core_device), + INTEL_CPU_FAM6(KABYLAKE_MOBILE, pmc_core_device), + INTEL_CPU_FAM6(KABYLAKE_DESKTOP, pmc_core_device), + INTEL_CPU_FAM6(CANNONLAKE_MOBILE, pmc_core_device), + INTEL_CPU_FAM6(ICELAKE_MOBILE, pmc_core_device), + {} +}; +MODULE_DEVICE_TABLE(x86cpu, intel_pmc_core_platform_ids); + +static int __init pmc_core_platform_init(void) +{ + /* Skip creating the platform device if ACPI already has a device */ + if (acpi_dev_present("INT33A1", NULL, -1)) + return -ENODEV; + + if (!x86_match_cpu(intel_pmc_core_platform_ids)) + return -ENODEV; + + return platform_device_register(&pmc_core_device); +} + +static void __exit pmc_core_platform_exit(void) +{ + platform_device_unregister(&pmc_core_device); +} + +module_init(pmc_core_platform_init); +module_exit(pmc_core_platform_exit); +MODULE_LICENSE("GPL v2"); From e6fbb97da5212ceab7149896832d35fa1ab02f34 Mon Sep 17 00:00:00 2001 From: Wei Yongjun Date: Tue, 9 Jul 2019 01:38:42 +0000 Subject: [PATCH 72/74] platform/x86: mlx-platform: Fix error handling in mlxplat_init() Add the missing platform_device_unregister() before return from mlxplat_init() in the error handling case. Fixes: 6b266e91a071 ("platform/x86: mlx-platform: Move regmap initialization before all drivers activation") Signed-off-by: Wei Yongjun Signed-off-by: Andy Shevchenko --- drivers/platform/x86/mlx-platform.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/platform/x86/mlx-platform.c b/drivers/platform/x86/mlx-platform.c index 2b98f299faa4..8fe51e43f1bc 100644 --- a/drivers/platform/x86/mlx-platform.c +++ b/drivers/platform/x86/mlx-platform.c @@ -2111,7 +2111,7 @@ static int __init mlxplat_init(void) mlxplat_regmap_config); if (IS_ERR(priv->regmap)) { err = PTR_ERR(priv->regmap); - return err; + goto fail_alloc; } err = mlxplat_mlxcpld_verify_bus_topology(&nr); From 2b4f3dab09b3a2853c49c866a383ffda8ee54a6f Mon Sep 17 00:00:00 2001 From: Prarit Bhargava Date: Wed, 10 Jul 2019 10:23:00 -0700 Subject: [PATCH 73/74] tools/power/x86/intel-speed-select: Add .gitignore file Add a .gitignore file for build include/ and final binary. Signed-off-by: Prarit Bhargava Acked-by: Rafael J. Wysocki Acked-by: Srinivas Pandruvada Cc: Srinivas Pandruvada Cc: Andy Shevchenko Cc: David Arcari Signed-off-by: Andy Shevchenko --- tools/power/x86/intel-speed-select/.gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 tools/power/x86/intel-speed-select/.gitignore diff --git a/tools/power/x86/intel-speed-select/.gitignore b/tools/power/x86/intel-speed-select/.gitignore new file mode 100644 index 000000000000..f61145925ce9 --- /dev/null +++ b/tools/power/x86/intel-speed-select/.gitignore @@ -0,0 +1,2 @@ +include/ +intel-speed-select From 7d67c8ac25fbc66ee254aa3e33329d1c9bc152ce Mon Sep 17 00:00:00 2001 From: YueHaibing Date: Thu, 4 Jul 2019 14:27:25 +0800 Subject: [PATCH 74/74] platform/x86: Fix PCENGINES_APU2 Kconfig warning Fix Kconfig warning for PCENGINES_APU2 symbol: WARNING: unmet direct dependencies detected for GPIO_AMD_FCH Depends on [n]: GPIOLIB [=n] && HAS_IOMEM [=y] Selected by [y]: - PCENGINES_APU2 [=y] && X86 [=y] && X86_PLATFORM_DEVICES [=y] && INPUT [=y] && INPUT_KEYBOARD [=y] && LEDS_CLASS [=y] WARNING: unmet direct dependencies detected for KEYBOARD_GPIO_POLLED Depends on [n]: !UML && INPUT [=y] && INPUT_KEYBOARD [=y] && GPIOLIB [=n] Selected by [y]: - PCENGINES_APU2 [=y] && X86 [=y] && X86_PLATFORM_DEVICES [=y] && INPUT [=y] && INPUT_KEYBOARD [=y] && LEDS_CLASS [=y] Add GPIOLIB dependency to fix it. Reported-by: Hulk Robot Fixes: f8eb0235f659 ("x86: pcengines apuv2 gpio/leds/keys platform driver") Signed-off-by: YueHaibing Signed-off-by: Andy Shevchenko --- drivers/platform/x86/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index ebd44d071f7b..30f6e74d6a23 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -1324,7 +1324,7 @@ config HUAWEI_WMI config PCENGINES_APU2 tristate "PC Engines APUv2/3 front button and LEDs driver" - depends on INPUT && INPUT_KEYBOARD + depends on INPUT && INPUT_KEYBOARD && GPIOLIB depends on LEDS_CLASS select GPIO_AMD_FCH select KEYBOARD_GPIO_POLLED