From 5b2d3326cdf83fde3590f45c93507c1c852327e8 Mon Sep 17 00:00:00 2001 From: Alexandru Ardelean Date: Fri, 14 May 2021 12:32:26 +0300 Subject: gpio: crystalcove: remove platform_set_drvdata() + cleanup probe The platform_set_drvdata() call is only useful if we need to retrieve back the private information. Since the driver doesn't do that, it's not useful to have it. If this is removed, we can also just do a direct return on devm_gpiochip_add_data(). We don't need to print that this call failed as there are other ways to log/see this during probe. Signed-off-by: Alexandru Ardelean Signed-off-by: Andy Shevchenko --- drivers/gpio/gpio-crystalcove.c | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/drivers/gpio/gpio-crystalcove.c b/drivers/gpio/gpio-crystalcove.c index 2ba225720086..5a909f3c79e8 100644 --- a/drivers/gpio/gpio-crystalcove.c +++ b/drivers/gpio/gpio-crystalcove.c @@ -339,8 +339,6 @@ static int crystalcove_gpio_probe(struct platform_device *pdev) if (!cg) return -ENOMEM; - platform_set_drvdata(pdev, cg); - mutex_init(&cg->buslock); cg->chip.label = KBUILD_MODNAME; cg->chip.direction_input = crystalcove_gpio_dir_in; @@ -372,13 +370,7 @@ static int crystalcove_gpio_probe(struct platform_device *pdev) return retval; } - retval = devm_gpiochip_add_data(&pdev->dev, &cg->chip, cg); - if (retval) { - dev_warn(&pdev->dev, "add gpio chip error: %d\n", retval); - return retval; - } - - return 0; + return devm_gpiochip_add_data(&pdev->dev, &cg->chip, cg); } static struct platform_driver crystalcove_gpio_driver = { -- cgit v1.2.3 From f3019092eca09f3d093dbc306bf118daff15bfd2 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 20 May 2021 12:46:05 +0300 Subject: gpio: wcove: Use IRQ hardware number getter instead of direct access IRQ framework provides special type and getter to transform Linux IRQ to the hardware pin. Use that type and getter function instead of direct access. No functional changes intended. While at it, remove unneeded check in wcove_update_irq_ctrl() since it is guaranteed that function will be called with valid parameter. Signed-off-by: Andy Shevchenko --- drivers/gpio/gpio-wcove.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/drivers/gpio/gpio-wcove.c b/drivers/gpio/gpio-wcove.c index a19eeef6cf1e..923a37a3d6d3 100644 --- a/drivers/gpio/gpio-wcove.c +++ b/drivers/gpio/gpio-wcove.c @@ -129,7 +129,7 @@ static inline int to_ireg(int gpio, enum ctrl_register type, unsigned int *mask) return reg; } -static void wcove_update_irq_mask(struct wcove_gpio *wg, int gpio) +static void wcove_update_irq_mask(struct wcove_gpio *wg, irq_hw_number_t gpio) { unsigned int mask, reg = to_ireg(gpio, IRQ_MASK, &mask); @@ -139,13 +139,10 @@ static void wcove_update_irq_mask(struct wcove_gpio *wg, int gpio) regmap_clear_bits(wg->regmap, reg, mask); } -static void wcove_update_irq_ctrl(struct wcove_gpio *wg, int gpio) +static void wcove_update_irq_ctrl(struct wcove_gpio *wg, irq_hw_number_t gpio) { int reg = to_reg(gpio, CTRL_IN); - if (reg < 0) - return; - regmap_update_bits(wg->regmap, reg, CTLI_INTCNT_BE, wg->intcnt); } @@ -248,8 +245,9 @@ static int wcove_irq_type(struct irq_data *data, unsigned int type) { struct gpio_chip *chip = irq_data_get_irq_chip_data(data); struct wcove_gpio *wg = gpiochip_get_data(chip); + irq_hw_number_t gpio = irqd_to_hwirq(data); - if (data->hwirq >= WCOVE_GPIO_NUM) + if (gpio >= WCOVE_GPIO_NUM) return 0; switch (type) { @@ -286,7 +284,7 @@ static void wcove_bus_sync_unlock(struct irq_data *data) { struct gpio_chip *chip = irq_data_get_irq_chip_data(data); struct wcove_gpio *wg = gpiochip_get_data(chip); - int gpio = data->hwirq; + irq_hw_number_t gpio = irqd_to_hwirq(data); if (wg->update & UPDATE_IRQ_TYPE) wcove_update_irq_ctrl(wg, gpio); @@ -301,8 +299,9 @@ static void wcove_irq_unmask(struct irq_data *data) { struct gpio_chip *chip = irq_data_get_irq_chip_data(data); struct wcove_gpio *wg = gpiochip_get_data(chip); + irq_hw_number_t gpio = irqd_to_hwirq(data); - if (data->hwirq >= WCOVE_GPIO_NUM) + if (gpio >= WCOVE_GPIO_NUM) return; wg->set_irq_mask = false; @@ -313,8 +312,9 @@ static void wcove_irq_mask(struct irq_data *data) { struct gpio_chip *chip = irq_data_get_irq_chip_data(data); struct wcove_gpio *wg = gpiochip_get_data(chip); + irq_hw_number_t gpio = irqd_to_hwirq(data); - if (data->hwirq >= WCOVE_GPIO_NUM) + if (gpio >= WCOVE_GPIO_NUM) return; wg->set_irq_mask = true; -- cgit v1.2.3 From 5d9936648285b8ccb6b61257dd2ddd76f9cd719a Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 20 May 2021 13:00:01 +0300 Subject: gpio: wcove: Unify style of to_reg() with to_ireg() Use ternary and rename parameter to 'type' in to_reg() to be in the same style as to_ireg(). Signed-off-by: Andy Shevchenko --- drivers/gpio/gpio-wcove.c | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/drivers/gpio/gpio-wcove.c b/drivers/gpio/gpio-wcove.c index 923a37a3d6d3..5ce315f64dc5 100644 --- a/drivers/gpio/gpio-wcove.c +++ b/drivers/gpio/gpio-wcove.c @@ -99,19 +99,14 @@ struct wcove_gpio { bool set_irq_mask; }; -static inline int to_reg(int gpio, enum ctrl_register reg_type) +static inline int to_reg(int gpio, enum ctrl_register type) { - unsigned int reg; + unsigned int reg = type == CTRL_IN ? GPIO_IN_CTRL_BASE : GPIO_OUT_CTRL_BASE; if (gpio >= WCOVE_GPIO_NUM) return -EOPNOTSUPP; - if (reg_type == CTRL_IN) - reg = GPIO_IN_CTRL_BASE + gpio; - else - reg = GPIO_OUT_CTRL_BASE + gpio; - - return reg; + return reg + gpio; } static inline int to_ireg(int gpio, enum ctrl_register type, unsigned int *mask) -- cgit v1.2.3 From 2b71b66ac0415db5e4b9e67b11e3af7b485bb421 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 20 May 2021 15:17:15 +0300 Subject: gpio: wcove: Split error handling for CTRL and IRQ registers The wcove_gpio_dbg_show() uses one message to all possible error reads. Split it to two for CTRL and IRQ registers. While at it, switch to use dev_err() instead of pr_err(). Signed-off-by: Andy Shevchenko --- drivers/gpio/gpio-wcove.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/gpio/gpio-wcove.c b/drivers/gpio/gpio-wcove.c index 5ce315f64dc5..16a0fae1e32e 100644 --- a/drivers/gpio/gpio-wcove.c +++ b/drivers/gpio/gpio-wcove.c @@ -364,8 +364,7 @@ static irqreturn_t wcove_gpio_irq_handler(int irq, void *data) return IRQ_HANDLED; } -static void wcove_gpio_dbg_show(struct seq_file *s, - struct gpio_chip *chip) +static void wcove_gpio_dbg_show(struct seq_file *s, struct gpio_chip *chip) { unsigned int ctlo, ctli, irq_mask, irq_status; struct wcove_gpio *wg = gpiochip_get_data(chip); @@ -374,10 +373,15 @@ static void wcove_gpio_dbg_show(struct seq_file *s, for (gpio = 0; gpio < WCOVE_GPIO_NUM; gpio++) { ret += regmap_read(wg->regmap, to_reg(gpio, CTRL_OUT), &ctlo); ret += regmap_read(wg->regmap, to_reg(gpio, CTRL_IN), &ctli); + if (ret) { + dev_err(wg->dev, "Failed to read registers: CTRL out/in\n"); + break; + } + ret += regmap_read(wg->regmap, to_ireg(gpio, IRQ_MASK, &mask), &irq_mask); ret += regmap_read(wg->regmap, to_ireg(gpio, IRQ_STATUS, &mask), &irq_status); if (ret) { - pr_err("Failed to read registers: ctrl out/in or irq status/mask\n"); + dev_err(wg->dev, "Failed to read registers: IRQ status/mask\n"); break; } -- cgit v1.2.3 From 5a6f0dbe621a5c20dc912ac474debf9f11129e03 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 4 May 2021 20:57:42 +0200 Subject: Input: goodix - platform/x86: touchscreen_dmi - Move upside down quirks to touchscreen_dmi.c Move the DMI quirks for upside-down mounted Goodix touchscreens from drivers/input/touchscreen/goodix.c to drivers/platform/x86/touchscreen_dmi.c, where all the other x86 touchscreen quirks live. Note the touchscreen_dmi.c code attaches standard touchscreen device-properties to an i2c-client device based on a combination of a DMI match + a device-name match. I've verified that the: Teclast X98 Pro, WinBook TW100 and WinBook TW700 uses an ACPI devicename of "GDIX1001:00" based on acpidumps and/or dmesg output available on the web. This patch was tested on a Teclast X89 tablet. Signed-off-by: Hans de Goede Link: https://lore.kernel.org/r/20210504185746.175461-2-hdegoede@redhat.com --- drivers/input/touchscreen/goodix.c | 52 ------------------------------- drivers/platform/x86/touchscreen_dmi.c | 56 ++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 52 deletions(-) diff --git a/drivers/input/touchscreen/goodix.c b/drivers/input/touchscreen/goodix.c index c682b028f0a2..4f53d3c57e69 100644 --- a/drivers/input/touchscreen/goodix.c +++ b/drivers/input/touchscreen/goodix.c @@ -178,51 +178,6 @@ static const unsigned long goodix_irq_flags[] = { IRQ_TYPE_LEVEL_HIGH, }; -/* - * Those tablets have their coordinates origin at the bottom right - * of the tablet, as if rotated 180 degrees - */ -static const struct dmi_system_id rotated_screen[] = { -#if defined(CONFIG_DMI) && defined(CONFIG_X86) - { - .ident = "Teclast X89", - .matches = { - /* tPAD is too generic, also match on bios date */ - DMI_MATCH(DMI_BOARD_VENDOR, "TECLAST"), - DMI_MATCH(DMI_BOARD_NAME, "tPAD"), - DMI_MATCH(DMI_BIOS_DATE, "12/19/2014"), - }, - }, - { - .ident = "Teclast X98 Pro", - .matches = { - /* - * Only match BIOS date, because the manufacturers - * BIOS does not report the board name at all - * (sometimes)... - */ - DMI_MATCH(DMI_BOARD_VENDOR, "TECLAST"), - DMI_MATCH(DMI_BIOS_DATE, "10/28/2015"), - }, - }, - { - .ident = "WinBook TW100", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "WinBook"), - DMI_MATCH(DMI_PRODUCT_NAME, "TW100") - } - }, - { - .ident = "WinBook TW700", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "WinBook"), - DMI_MATCH(DMI_PRODUCT_NAME, "TW700") - }, - }, -#endif - {} -}; - static const struct dmi_system_id nine_bytes_report[] = { #if defined(CONFIG_DMI) && defined(CONFIG_X86) { @@ -1123,13 +1078,6 @@ static int goodix_configure_dev(struct goodix_ts_data *ts) ABS_MT_POSITION_Y, ts->prop.max_y); } - if (dmi_check_system(rotated_screen)) { - ts->prop.invert_x = true; - ts->prop.invert_y = true; - dev_dbg(&ts->client->dev, - "Applying '180 degrees rotated screen' quirk\n"); - } - if (dmi_check_system(nine_bytes_report)) { ts->contact_size = 9; diff --git a/drivers/platform/x86/touchscreen_dmi.c b/drivers/platform/x86/touchscreen_dmi.c index 90fe4f8f3c2c..a53e176d94b9 100644 --- a/drivers/platform/x86/touchscreen_dmi.c +++ b/drivers/platform/x86/touchscreen_dmi.c @@ -273,6 +273,23 @@ static const struct ts_dmi_data estar_beauty_hd_data = { .properties = estar_beauty_hd_props, }; +/* Generic props + data for upside-down mounted GDIX1001 touchscreens */ +static const struct property_entry gdix1001_upside_down_props[] = { + PROPERTY_ENTRY_BOOL("touchscreen-inverted-x"), + PROPERTY_ENTRY_BOOL("touchscreen-inverted-y"), + { } +}; + +static const struct ts_dmi_data gdix1001_00_upside_down_data = { + .acpi_name = "GDIX1001:00", + .properties = gdix1001_upside_down_props, +}; + +static const struct ts_dmi_data gdix1001_01_upside_down_data = { + .acpi_name = "GDIX1001:01", + .properties = gdix1001_upside_down_props, +}; + static const struct property_entry gp_electronic_t701_props[] = { PROPERTY_ENTRY_U32("touchscreen-size-x", 960), PROPERTY_ENTRY_U32("touchscreen-size-y", 640), @@ -1287,6 +1304,16 @@ const struct dmi_system_id touchscreen_dmi_table[] = { DMI_MATCH(DMI_BOARD_NAME, "X3 Plus"), }, }, + { + /* Teclast X89 (Windows version / BIOS) */ + .driver_data = (void *)&gdix1001_01_upside_down_data, + .matches = { + /* tPAD is too generic, also match on bios date */ + DMI_MATCH(DMI_BOARD_VENDOR, "TECLAST"), + DMI_MATCH(DMI_BOARD_NAME, "tPAD"), + DMI_MATCH(DMI_BIOS_DATE, "12/19/2014"), + }, + }, { /* Teclast X98 Plus II */ .driver_data = (void *)&teclast_x98plus2_data, @@ -1295,6 +1322,19 @@ const struct dmi_system_id touchscreen_dmi_table[] = { DMI_MATCH(DMI_PRODUCT_NAME, "X98 Plus II"), }, }, + { + /* Teclast X98 Pro */ + .driver_data = (void *)&gdix1001_00_upside_down_data, + .matches = { + /* + * Only match BIOS date, because the manufacturers + * BIOS does not report the board name at all + * (sometimes)... + */ + DMI_MATCH(DMI_BOARD_VENDOR, "TECLAST"), + DMI_MATCH(DMI_BIOS_DATE, "10/28/2015"), + }, + }, { /* Trekstor Primebook C11 */ .driver_data = (void *)&trekstor_primebook_c11_data, @@ -1370,6 +1410,22 @@ const struct dmi_system_id touchscreen_dmi_table[] = { DMI_MATCH(DMI_PRODUCT_NAME, "VINGA Twizzle J116"), }, }, + { + /* "WinBook TW100" */ + .driver_data = (void *)&gdix1001_00_upside_down_data, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "WinBook"), + DMI_MATCH(DMI_PRODUCT_NAME, "TW100") + } + }, + { + /* WinBook TW700 */ + .driver_data = (void *)&gdix1001_00_upside_down_data, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "WinBook"), + DMI_MATCH(DMI_PRODUCT_NAME, "TW700") + }, + }, { /* Yours Y8W81, same case and touchscreen as Chuwi Vi8 */ .driver_data = (void *)&chuwi_vi8_data, -- cgit v1.2.3 From a22e3803f2a4d947ff0083a9448a169269ea0f62 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 4 May 2021 20:57:44 +0200 Subject: platform/x86: touchscreen_dmi: Add an extra entry for the upside down Goodix touchscreen on Teclast X89 tablets Teclast X89 tablets come in 2 versions, with Windows pre-installed and with Android pre-installed. These 2 versions have different DMI strings. Add a match for the DMI strings used by the Android version BIOS. Note the Android version BIOS has a bug in the DSDT where no IRQ is provided, so for the touchscreen to work a DSDT override fixing this is necessary as well. Signed-off-by: Hans de Goede Link: https://lore.kernel.org/r/20210504185746.175461-4-hdegoede@redhat.com --- drivers/platform/x86/touchscreen_dmi.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/platform/x86/touchscreen_dmi.c b/drivers/platform/x86/touchscreen_dmi.c index a53e176d94b9..08ec61660b3c 100644 --- a/drivers/platform/x86/touchscreen_dmi.c +++ b/drivers/platform/x86/touchscreen_dmi.c @@ -1304,6 +1304,14 @@ const struct dmi_system_id touchscreen_dmi_table[] = { DMI_MATCH(DMI_BOARD_NAME, "X3 Plus"), }, }, + { + /* Teclast X89 (Android version / BIOS) */ + .driver_data = (void *)&gdix1001_00_upside_down_data, + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "WISKY"), + DMI_MATCH(DMI_BOARD_NAME, "3G062i"), + }, + }, { /* Teclast X89 (Windows version / BIOS) */ .driver_data = (void *)&gdix1001_01_upside_down_data, -- cgit v1.2.3 From fcd8cf0e3e48f4c66af82c8e799c37cb0cccffe0 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 4 May 2021 20:57:45 +0200 Subject: platform/x86: touchscreen_dmi: Add info for the Goodix GT912 panel of TM800A550L tablets The Bay Trail Glavey TM800A550L tablet, which ships with Android installed from the factory, uses a GT912 touchscreen controller which needs to have its firmware uploaded by the OS to work (this is a first for a x86 based device with a Goodix touchscreen controller). Add a touchscreen_dmi entry for this which specifies the filenames to use for the firmware and config files needed for this. Note this matches on a GDIX1001 ACPI HID, while the original DSDT uses a HID of GODX0911. For the touchscreen to work on these devices a DSDT override is necessary to fix a missing IRQ and broken GPIO settings in the ACPI-resources for the touchscreen. This override also changes the HID to the standard GDIX1001 id typically used for Goodix touchscreens. The DSDT override is available here: https://fedorapeople.org/~jwrdegoede/glavey-tm800a550l-dsdt-override/ Reviewed-by: Bastien Nocera Signed-off-by: Hans de Goede Link: https://lore.kernel.org/r/20210504185746.175461-5-hdegoede@redhat.com --- drivers/platform/x86/touchscreen_dmi.c | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/drivers/platform/x86/touchscreen_dmi.c b/drivers/platform/x86/touchscreen_dmi.c index 08ec61660b3c..d2bbec310726 100644 --- a/drivers/platform/x86/touchscreen_dmi.c +++ b/drivers/platform/x86/touchscreen_dmi.c @@ -290,6 +290,18 @@ static const struct ts_dmi_data gdix1001_01_upside_down_data = { .properties = gdix1001_upside_down_props, }; +static const struct property_entry glavey_tm800a550l_props[] = { + PROPERTY_ENTRY_STRING("firmware-name", "gt912-glavey-tm800a550l.fw"), + PROPERTY_ENTRY_STRING("goodix,config-name", "gt912-glavey-tm800a550l.cfg"), + PROPERTY_ENTRY_U32("goodix,main-clk", 54), + { } +}; + +static const struct ts_dmi_data glavey_tm800a550l_data = { + .acpi_name = "GDIX1001:00", + .properties = glavey_tm800a550l_props, +}; + static const struct property_entry gp_electronic_t701_props[] = { PROPERTY_ENTRY_U32("touchscreen-size-x", 960), PROPERTY_ENTRY_U32("touchscreen-size-y", 640), @@ -1020,6 +1032,15 @@ const struct dmi_system_id touchscreen_dmi_table[] = { DMI_MATCH(DMI_PRODUCT_NAME, "eSTAR BEAUTY HD Intel Quad core"), }, }, + { /* Glavey TM800A550L */ + .driver_data = (void *)&glavey_tm800a550l_data, + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "AMI Corporation"), + DMI_MATCH(DMI_BOARD_NAME, "Aptio CRB"), + /* Above strings are too generic, also match on BIOS version */ + DMI_MATCH(DMI_BIOS_VERSION, "ZY-8-BI-PX4S70VTR400-X423B-005-D"), + }, + }, { /* GP-electronic T701 */ .driver_data = (void *)&gp_electronic_t701_data, -- cgit v1.2.3 From 43582f29b161d820717bc13f562bca27af12e3cf Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Thu, 3 Jun 2021 23:40:04 +0100 Subject: gpiolib: acpi: Introduce acpi_get_and_request_gpiod() helper We need to be able to translate GPIO resources in an ACPI device's _CRS into GPIO descriptor array. Those are represented in _CRS as a pathname to a GPIO device plus the pin's index number: the acpi_get_gpiod() function is perfect for that purpose. As it's currently only used internally within the GPIO layer, provide and export a wrapper function that additionally holds a reference to the GPIO device. Reviewed-by: Andy Shevchenko Signed-off-by: Daniel Scally Signed-off-by: Andy Shevchenko --- drivers/gpio/gpiolib-acpi.c | 28 ++++++++++++++++++++++++++++ include/linux/gpio/consumer.h | 2 ++ 2 files changed, 30 insertions(+) diff --git a/drivers/gpio/gpiolib-acpi.c b/drivers/gpio/gpiolib-acpi.c index 3ef22a3c104d..75cd0c5a5cc4 100644 --- a/drivers/gpio/gpiolib-acpi.c +++ b/drivers/gpio/gpiolib-acpi.c @@ -128,6 +128,34 @@ static struct gpio_desc *acpi_get_gpiod(char *path, int pin) return gpiochip_get_desc(chip, pin); } +/** + * acpi_get_and_request_gpiod - Translate ACPI GPIO pin to GPIO descriptor and + * hold a refcount to the GPIO device. + * @path: ACPI GPIO controller full path name, (e.g. "\\_SB.GPO1") + * @pin: ACPI GPIO pin number (0-based, controller-relative) + * @label: Label to pass to gpiod_request() + * + * This function is a simple pass-through to acpi_get_gpiod(), except that + * as it is intended for use outside of the GPIO layer (in a similar fashion to + * gpiod_get_index() for example) it also holds a reference to the GPIO device. + */ +struct gpio_desc *acpi_get_and_request_gpiod(char *path, int pin, char *label) +{ + struct gpio_desc *gpio; + int ret; + + gpio = acpi_get_gpiod(path, pin); + if (IS_ERR(gpio)) + return gpio; + + ret = gpiod_request(gpio, label); + if (ret) + return ERR_PTR(ret); + + return gpio; +} +EXPORT_SYMBOL_GPL(acpi_get_and_request_gpiod); + static irqreturn_t acpi_gpio_irq_handler(int irq, void *data) { struct acpi_gpio_event *event = data; diff --git a/include/linux/gpio/consumer.h b/include/linux/gpio/consumer.h index c73b25bc9213..566feb56601f 100644 --- a/include/linux/gpio/consumer.h +++ b/include/linux/gpio/consumer.h @@ -692,6 +692,8 @@ int devm_acpi_dev_add_driver_gpios(struct device *dev, const struct acpi_gpio_mapping *gpios); void devm_acpi_dev_remove_driver_gpios(struct device *dev); +struct gpio_desc *acpi_get_and_request_gpiod(char *path, int pin, char *label); + #else /* CONFIG_GPIOLIB && CONFIG_ACPI */ struct acpi_device; -- cgit v1.2.3 From 043d7f09bf614809c10c4acbf0695ef731958300 Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Thu, 3 Jun 2021 23:40:05 +0100 Subject: gpiolib: acpi: Add acpi_gpio_get_io_resource() Add a function to verify that a given ACPI resource represents a GpioIo() type of resource, and return it if so. Reviewed-by: Andy Shevchenko Signed-off-by: Daniel Scally Signed-off-by: Andy Shevchenko --- drivers/gpio/gpiolib-acpi.c | 23 +++++++++++++++++++++++ include/linux/acpi.h | 7 +++++++ 2 files changed, 30 insertions(+) diff --git a/drivers/gpio/gpiolib-acpi.c b/drivers/gpio/gpiolib-acpi.c index 75cd0c5a5cc4..cf99a5752fe0 100644 --- a/drivers/gpio/gpiolib-acpi.c +++ b/drivers/gpio/gpiolib-acpi.c @@ -196,6 +196,29 @@ bool acpi_gpio_get_irq_resource(struct acpi_resource *ares, } EXPORT_SYMBOL_GPL(acpi_gpio_get_irq_resource); +/** + * acpi_gpio_get_io_resource - Fetch details of an ACPI resource if it is a GPIO + * I/O resource or return False if not. + * @ares: Pointer to the ACPI resource to fetch + * @agpio: Pointer to a &struct acpi_resource_gpio to store the output pointer + */ +bool acpi_gpio_get_io_resource(struct acpi_resource *ares, + struct acpi_resource_gpio **agpio) +{ + struct acpi_resource_gpio *gpio; + + if (ares->type != ACPI_RESOURCE_TYPE_GPIO) + return false; + + gpio = &ares->data.gpio; + if (gpio->connection_type != ACPI_RESOURCE_GPIO_TYPE_IO) + return false; + + *agpio = gpio; + return true; +} +EXPORT_SYMBOL_GPL(acpi_gpio_get_io_resource); + static void acpi_gpiochip_request_irq(struct acpi_gpio_chip *acpi_gpio, struct acpi_gpio_event *event) { diff --git a/include/linux/acpi.h b/include/linux/acpi.h index c60745f657e9..a74d37a3b618 100644 --- a/include/linux/acpi.h +++ b/include/linux/acpi.h @@ -1096,6 +1096,8 @@ void __acpi_handle_debug(struct _ddebug *descriptor, acpi_handle handle, const c #if defined(CONFIG_ACPI) && defined(CONFIG_GPIOLIB) bool acpi_gpio_get_irq_resource(struct acpi_resource *ares, struct acpi_resource_gpio **agpio); +bool acpi_gpio_get_io_resource(struct acpi_resource *ares, + struct acpi_resource_gpio **agpio); int acpi_dev_gpio_irq_get_by(struct acpi_device *adev, const char *name, int index); #else static inline bool acpi_gpio_get_irq_resource(struct acpi_resource *ares, @@ -1103,6 +1105,11 @@ static inline bool acpi_gpio_get_irq_resource(struct acpi_resource *ares, { return false; } +static inline bool acpi_gpio_get_io_resource(struct acpi_resource *ares, + struct acpi_resource_gpio **agpio) +{ + return false; +} static inline int acpi_dev_gpio_irq_get_by(struct acpi_device *adev, const char *name, int index) { -- cgit v1.2.3 From 97be86e3fef01736e7a58b4d59a587e471cb1481 Mon Sep 17 00:00:00 2001 From: Prasanth KSR Date: Wed, 12 May 2021 15:55:30 +0530 Subject: platform/x86: dell-wmi-sysman: Make populate_foo_data functions more robust 1. Check acpi type before assignment of each property value 2. Add boundary check for properties count Co-developed-by: Divya Bharathi Signed-off-by: Divya Bharathi Signed-off-by: Prasanth KSR Link: https://lore.kernel.org/r/20210512102530.9704-1-prasanth.ksr@dell.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- .../x86/dell/dell-wmi-sysman/dell-wmi-sysman.h | 5 ++- .../x86/dell/dell-wmi-sysman/enum-attributes.c | 39 +++++++++++++++++++--- .../x86/dell/dell-wmi-sysman/int-attributes.c | 16 +++++++++ .../x86/dell/dell-wmi-sysman/passobj-attributes.c | 6 ++++ .../x86/dell/dell-wmi-sysman/string-attributes.c | 16 ++++++++- drivers/platform/x86/dell/dell-wmi-sysman/sysman.c | 3 +- 6 files changed, 77 insertions(+), 8 deletions(-) diff --git a/drivers/platform/x86/dell/dell-wmi-sysman/dell-wmi-sysman.h b/drivers/platform/x86/dell/dell-wmi-sysman/dell-wmi-sysman.h index b80f2a62ea3f..3ad33a094588 100644 --- a/drivers/platform/x86/dell/dell-wmi-sysman/dell-wmi-sysman.h +++ b/drivers/platform/x86/dell/dell-wmi-sysman/dell-wmi-sysman.h @@ -152,12 +152,15 @@ static ssize_t curr_val##_store(struct kobject *kobj, \ return ret ? ret : count; \ } +#define check_property_type(attr, prop, valuetype) \ + (attr##_obj[prop].type != valuetype) + union acpi_object *get_wmiobj_pointer(int instance_id, const char *guid_string); int get_instance_count(const char *guid_string); void strlcpy_attr(char *dest, char *src); int populate_enum_data(union acpi_object *enumeration_obj, int instance_id, - struct kobject *attr_name_kobj); + struct kobject *attr_name_kobj, u32 enum_property_count); int alloc_enum_data(void); void exit_enum_attributes(void); diff --git a/drivers/platform/x86/dell/dell-wmi-sysman/enum-attributes.c b/drivers/platform/x86/dell/dell-wmi-sysman/enum-attributes.c index 091e48c217ed..8cc212c85266 100644 --- a/drivers/platform/x86/dell/dell-wmi-sysman/enum-attributes.c +++ b/drivers/platform/x86/dell/dell-wmi-sysman/enum-attributes.c @@ -132,39 +132,68 @@ int alloc_enum_data(void) * @enumeration_obj: ACPI object with enumeration data * @instance_id: The instance to enumerate * @attr_name_kobj: The parent kernel object + * @enum_property_count: Total properties count under enumeration type */ int populate_enum_data(union acpi_object *enumeration_obj, int instance_id, - struct kobject *attr_name_kobj) + struct kobject *attr_name_kobj, u32 enum_property_count) { int i, next_obj, value_modifier_count, possible_values_count; wmi_priv.enumeration_data[instance_id].attr_name_kobj = attr_name_kobj; + if (check_property_type(enumeration, ATTR_NAME, ACPI_TYPE_STRING)) + return -EINVAL; strlcpy_attr(wmi_priv.enumeration_data[instance_id].attribute_name, enumeration_obj[ATTR_NAME].string.pointer); + if (check_property_type(enumeration, DISPL_NAME_LANG_CODE, ACPI_TYPE_STRING)) + return -EINVAL; strlcpy_attr(wmi_priv.enumeration_data[instance_id].display_name_language_code, enumeration_obj[DISPL_NAME_LANG_CODE].string.pointer); + if (check_property_type(enumeration, DISPLAY_NAME, ACPI_TYPE_STRING)) + return -EINVAL; strlcpy_attr(wmi_priv.enumeration_data[instance_id].display_name, enumeration_obj[DISPLAY_NAME].string.pointer); + if (check_property_type(enumeration, DEFAULT_VAL, ACPI_TYPE_STRING)) + return -EINVAL; strlcpy_attr(wmi_priv.enumeration_data[instance_id].default_value, enumeration_obj[DEFAULT_VAL].string.pointer); + if (check_property_type(enumeration, MODIFIER, ACPI_TYPE_STRING)) + return -EINVAL; strlcpy_attr(wmi_priv.enumeration_data[instance_id].dell_modifier, enumeration_obj[MODIFIER].string.pointer); next_obj = MODIFIER + 1; - value_modifier_count = (uintptr_t)enumeration_obj[next_obj].string.pointer; + if (next_obj >= enum_property_count) + return -EINVAL; + + if (check_property_type(enumeration, next_obj, ACPI_TYPE_INTEGER)) + return -EINVAL; + value_modifier_count = (uintptr_t)enumeration_obj[next_obj++].string.pointer; for (i = 0; i < value_modifier_count; i++) { + if (next_obj >= enum_property_count) + return -EINVAL; + if (check_property_type(enumeration, next_obj, ACPI_TYPE_STRING)) + return -EINVAL; strcat(wmi_priv.enumeration_data[instance_id].dell_value_modifier, - enumeration_obj[++next_obj].string.pointer); + enumeration_obj[next_obj++].string.pointer); strcat(wmi_priv.enumeration_data[instance_id].dell_value_modifier, ";"); } - possible_values_count = (uintptr_t) enumeration_obj[++next_obj].string.pointer; + if (next_obj >= enum_property_count) + return -EINVAL; + + if (check_property_type(enumeration, next_obj, ACPI_TYPE_INTEGER)) + return -EINVAL; + possible_values_count = (uintptr_t) enumeration_obj[next_obj++].string.pointer; for (i = 0; i < possible_values_count; i++) { + if (next_obj >= enum_property_count) + return -EINVAL; + if (check_property_type(enumeration, next_obj, ACPI_TYPE_STRING)) + return -EINVAL; strcat(wmi_priv.enumeration_data[instance_id].possible_values, - enumeration_obj[++next_obj].string.pointer); + enumeration_obj[next_obj++].string.pointer); strcat(wmi_priv.enumeration_data[instance_id].possible_values, ";"); } diff --git a/drivers/platform/x86/dell/dell-wmi-sysman/int-attributes.c b/drivers/platform/x86/dell/dell-wmi-sysman/int-attributes.c index 8a49ba6e44f9..951e75b538fa 100644 --- a/drivers/platform/x86/dell/dell-wmi-sysman/int-attributes.c +++ b/drivers/platform/x86/dell/dell-wmi-sysman/int-attributes.c @@ -141,20 +141,36 @@ int populate_int_data(union acpi_object *integer_obj, int instance_id, struct kobject *attr_name_kobj) { wmi_priv.integer_data[instance_id].attr_name_kobj = attr_name_kobj; + if (check_property_type(integer, ATTR_NAME, ACPI_TYPE_STRING)) + return -EINVAL; strlcpy_attr(wmi_priv.integer_data[instance_id].attribute_name, integer_obj[ATTR_NAME].string.pointer); + if (check_property_type(integer, DISPL_NAME_LANG_CODE, ACPI_TYPE_STRING)) + return -EINVAL; strlcpy_attr(wmi_priv.integer_data[instance_id].display_name_language_code, integer_obj[DISPL_NAME_LANG_CODE].string.pointer); + if (check_property_type(integer, DISPLAY_NAME, ACPI_TYPE_STRING)) + return -EINVAL; strlcpy_attr(wmi_priv.integer_data[instance_id].display_name, integer_obj[DISPLAY_NAME].string.pointer); + if (check_property_type(integer, DEFAULT_VAL, ACPI_TYPE_INTEGER)) + return -EINVAL; wmi_priv.integer_data[instance_id].default_value = (uintptr_t)integer_obj[DEFAULT_VAL].string.pointer; + if (check_property_type(integer, MODIFIER, ACPI_TYPE_STRING)) + return -EINVAL; strlcpy_attr(wmi_priv.integer_data[instance_id].dell_modifier, integer_obj[MODIFIER].string.pointer); + if (check_property_type(integer, MIN_VALUE, ACPI_TYPE_INTEGER)) + return -EINVAL; wmi_priv.integer_data[instance_id].min_value = (uintptr_t)integer_obj[MIN_VALUE].string.pointer; + if (check_property_type(integer, MAX_VALUE, ACPI_TYPE_INTEGER)) + return -EINVAL; wmi_priv.integer_data[instance_id].max_value = (uintptr_t)integer_obj[MAX_VALUE].string.pointer; + if (check_property_type(integer, SCALAR_INCR, ACPI_TYPE_INTEGER)) + return -EINVAL; wmi_priv.integer_data[instance_id].scalar_increment = (uintptr_t)integer_obj[SCALAR_INCR].string.pointer; diff --git a/drivers/platform/x86/dell/dell-wmi-sysman/passobj-attributes.c b/drivers/platform/x86/dell/dell-wmi-sysman/passobj-attributes.c index 834b3e82ad9f..230e6ee96636 100644 --- a/drivers/platform/x86/dell/dell-wmi-sysman/passobj-attributes.c +++ b/drivers/platform/x86/dell/dell-wmi-sysman/passobj-attributes.c @@ -159,10 +159,16 @@ int alloc_po_data(void) int populate_po_data(union acpi_object *po_obj, int instance_id, struct kobject *attr_name_kobj) { wmi_priv.po_data[instance_id].attr_name_kobj = attr_name_kobj; + if (check_property_type(po, ATTR_NAME, ACPI_TYPE_STRING)) + return -EINVAL; strlcpy_attr(wmi_priv.po_data[instance_id].attribute_name, po_obj[ATTR_NAME].string.pointer); + if (check_property_type(po, MIN_PASS_LEN, ACPI_TYPE_INTEGER)) + return -EINVAL; wmi_priv.po_data[instance_id].min_password_length = (uintptr_t)po_obj[MIN_PASS_LEN].string.pointer; + if (check_property_type(po, MAX_PASS_LEN, ACPI_TYPE_INTEGER)) + return -EINVAL; wmi_priv.po_data[instance_id].max_password_length = (uintptr_t) po_obj[MAX_PASS_LEN].string.pointer; diff --git a/drivers/platform/x86/dell/dell-wmi-sysman/string-attributes.c b/drivers/platform/x86/dell/dell-wmi-sysman/string-attributes.c index 552537852459..c392f0ecf8b5 100644 --- a/drivers/platform/x86/dell/dell-wmi-sysman/string-attributes.c +++ b/drivers/platform/x86/dell/dell-wmi-sysman/string-attributes.c @@ -118,24 +118,38 @@ int alloc_str_data(void) /** * populate_str_data() - Populate all properties of an instance under string attribute - * @str_obj: ACPI object with integer data + * @str_obj: ACPI object with string data * @instance_id: The instance to enumerate * @attr_name_kobj: The parent kernel object */ int populate_str_data(union acpi_object *str_obj, int instance_id, struct kobject *attr_name_kobj) { wmi_priv.str_data[instance_id].attr_name_kobj = attr_name_kobj; + if (check_property_type(str, ATTR_NAME, ACPI_TYPE_STRING)) + return -EINVAL; strlcpy_attr(wmi_priv.str_data[instance_id].attribute_name, str_obj[ATTR_NAME].string.pointer); + if (check_property_type(str, DISPL_NAME_LANG_CODE, ACPI_TYPE_STRING)) + return -EINVAL; strlcpy_attr(wmi_priv.str_data[instance_id].display_name_language_code, str_obj[DISPL_NAME_LANG_CODE].string.pointer); + if (check_property_type(str, DISPLAY_NAME, ACPI_TYPE_STRING)) + return -EINVAL; strlcpy_attr(wmi_priv.str_data[instance_id].display_name, str_obj[DISPLAY_NAME].string.pointer); + if (check_property_type(str, DEFAULT_VAL, ACPI_TYPE_STRING)) + return -EINVAL; strlcpy_attr(wmi_priv.str_data[instance_id].default_value, str_obj[DEFAULT_VAL].string.pointer); + if (check_property_type(str, MODIFIER, ACPI_TYPE_STRING)) + return -EINVAL; strlcpy_attr(wmi_priv.str_data[instance_id].dell_modifier, str_obj[MODIFIER].string.pointer); + if (check_property_type(str, MIN_LEN, ACPI_TYPE_INTEGER)) + return -EINVAL; wmi_priv.str_data[instance_id].min_length = (uintptr_t)str_obj[MIN_LEN].string.pointer; + if (check_property_type(str, MAX_LEN, ACPI_TYPE_INTEGER)) + return -EINVAL; wmi_priv.str_data[instance_id].max_length = (uintptr_t) str_obj[MAX_LEN].string.pointer; return sysfs_create_group(attr_name_kobj, &str_attr_group); diff --git a/drivers/platform/x86/dell/dell-wmi-sysman/sysman.c b/drivers/platform/x86/dell/dell-wmi-sysman/sysman.c index c8d276d78e92..d21e84c7a694 100644 --- a/drivers/platform/x86/dell/dell-wmi-sysman/sysman.c +++ b/drivers/platform/x86/dell/dell-wmi-sysman/sysman.c @@ -481,7 +481,8 @@ static int init_bios_attributes(int attr_type, const char *guid) /* enumerate all of this attribute */ switch (attr_type) { case ENUM: - retval = populate_enum_data(elements, instance_id, attr_name_kobj); + retval = populate_enum_data(elements, instance_id, attr_name_kobj, + obj->package.count); break; case INT: retval = populate_int_data(elements, instance_id, attr_name_kobj); -- cgit v1.2.3 From 842631928afff953d78a71fa762c92db1ab77571 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Mon, 3 May 2021 17:46:47 +0200 Subject: platform/x86: intel_cht_int33fe: Correct "displayport" fwnode reference The Type-C connector on these devices is connected to DP-2 not DP-1, so the reference must be to the DD04 child-node of the GPU, rather then the DD02 child-node. Signed-off-by: Hans de Goede Link: https://lore.kernel.org/r/20210503154647.142551-10-hdegoede@redhat.com --- drivers/platform/x86/intel_cht_int33fe_typec.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/platform/x86/intel_cht_int33fe_typec.c b/drivers/platform/x86/intel_cht_int33fe_typec.c index b61bad9cc8d2..d59544167430 100644 --- a/drivers/platform/x86/intel_cht_int33fe_typec.c +++ b/drivers/platform/x86/intel_cht_int33fe_typec.c @@ -168,8 +168,8 @@ static int cht_int33fe_setup_dp(struct cht_int33fe_data *data) return -ENODEV; } - /* Then the DP child device node */ - data->dp = device_get_named_child_node(&pdev->dev, "DD02"); + /* Then the DP-2 child device node */ + data->dp = device_get_named_child_node(&pdev->dev, "DD04"); pci_dev_put(pdev); if (!data->dp) return -ENODEV; -- cgit v1.2.3 From e48af75dc7f3059c5a18d7176913457eef5765ad Mon Sep 17 00:00:00 2001 From: Yang Li Date: Wed, 12 May 2021 16:57:05 +0800 Subject: platform/x86: dcdbas: drop unneeded assignment in host_control_smi() Making '==' operation with ESM_STATUS_CMD_UNSUCCESSFUL directly after calling the function inb() is more efficient, so assignment to 'cmd_status' is redundant. Eliminate the following clang_analyzer warning: drivers/platform/x86/dell/dcdbas.c:397:11: warning: Although the value stored to 'cmd_status' is used in the enclosing expression, the value is never actually read from 'cmd_status' No functional change. Reported-by: Abaci Robot Signed-off-by: Yang Li Link: https://lore.kernel.org/r/1620809825-84105-1-git-send-email-yang.lee@linux.alibaba.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/dell/dcdbas.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/platform/x86/dell/dcdbas.c b/drivers/platform/x86/dell/dcdbas.c index d513a59a5d47..28447c180be8 100644 --- a/drivers/platform/x86/dell/dcdbas.c +++ b/drivers/platform/x86/dell/dcdbas.c @@ -394,8 +394,7 @@ static int host_control_smi(void) /* wait a few to see if it executed */ num_ticks = TIMEOUT_USEC_SHORT_SEMA_BLOCKING; - while ((cmd_status = inb(PCAT_APM_STATUS_PORT)) - == ESM_STATUS_CMD_UNSUCCESSFUL) { + while ((s8)inb(PCAT_APM_STATUS_PORT) == ESM_STATUS_CMD_UNSUCCESSFUL) { num_ticks--; if (num_ticks == EXPIRED_TIMER) return -ETIME; -- cgit v1.2.3 From 4aebcceb332c74c4a3cca60ca292cf73ce3b100c Mon Sep 17 00:00:00 2001 From: Rajneesh Bhardwaj Date: Thu, 13 May 2021 11:38:25 -0400 Subject: MAINTAINERS: Update info for telemetry - My linux.intel.com email is no longer valid, update it to my gmail id. Signed-off-by: Rajneesh Bhardwaj Link: https://lore.kernel.org/r/20210513153825.77214-1-irenic.rajneesh@gmail.com Signed-off-by: Hans de Goede --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index 81e1edeceae4..6e848b244226 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9398,7 +9398,7 @@ F: include/linux/firmware/intel/stratix10-smc.h F: include/linux/firmware/intel/stratix10-svc-client.h INTEL TELEMETRY DRIVER -M: Rajneesh Bhardwaj +M: Rajneesh Bhardwaj M: "David E. Box" L: platform-driver-x86@vger.kernel.org S: Maintained -- cgit v1.2.3 From 5b6a9a2f839c7e1863606a00f505ac50b8887287 Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Mon, 17 May 2021 12:07:44 +0200 Subject: platform/x86: samsung-laptop: use octal numbers for rwx file permissions Andy asked me to do it before working further on the code. Reviewed-by: Andy Shevchenko Signed-off-by: Wolfram Sang Link: https://lore.kernel.org/r/20210517100746.29663-1-wsa+renesas@sang-engineering.com Signed-off-by: Hans de Goede --- drivers/platform/x86/samsung-laptop.c | 35 ++++++++++++++--------------------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/drivers/platform/x86/samsung-laptop.c b/drivers/platform/x86/samsung-laptop.c index d5cec6e35bb8..763d97cbbe53 100644 --- a/drivers/platform/x86/samsung-laptop.c +++ b/drivers/platform/x86/samsung-laptop.c @@ -388,7 +388,7 @@ MODULE_PARM_DESC(force, "Disable the DMI check and forces the driver to be loaded"); static bool debug; -module_param(debug, bool, S_IRUGO | S_IWUSR); +module_param(debug, bool, 0644); MODULE_PARM_DESC(debug, "Debug enabled or not"); static int sabi_command(struct samsung_laptop *samsung, u16 command, @@ -705,7 +705,7 @@ static ssize_t set_performance_level(struct device *dev, return count; } -static DEVICE_ATTR(performance_level, S_IWUSR | S_IRUGO, +static DEVICE_ATTR(performance_level, 0644, get_performance_level, set_performance_level); static int read_battery_life_extender(struct samsung_laptop *samsung) @@ -774,7 +774,7 @@ static ssize_t set_battery_life_extender(struct device *dev, return count; } -static DEVICE_ATTR(battery_life_extender, S_IWUSR | S_IRUGO, +static DEVICE_ATTR(battery_life_extender, 0644, get_battery_life_extender, set_battery_life_extender); static int read_usb_charge(struct samsung_laptop *samsung) @@ -843,7 +843,7 @@ static ssize_t set_usb_charge(struct device *dev, return count; } -static DEVICE_ATTR(usb_charge, S_IWUSR | S_IRUGO, +static DEVICE_ATTR(usb_charge, 0644, get_usb_charge, set_usb_charge); static int read_lid_handling(struct samsung_laptop *samsung) @@ -908,7 +908,7 @@ static ssize_t set_lid_handling(struct device *dev, return count; } -static DEVICE_ATTR(lid_handling, S_IWUSR | S_IRUGO, +static DEVICE_ATTR(lid_handling, 0644, get_lid_handling, set_lid_handling); static struct attribute *platform_attributes[] = { @@ -1291,24 +1291,17 @@ static void samsung_debugfs_init(struct samsung_laptop *samsung) samsung->debug.sdiag_wrapper.data = samsung->sdiag; samsung->debug.sdiag_wrapper.size = strlen(samsung->sdiag); - 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, + debugfs_create_u16("command", 0644, root, &samsung->debug.command); + debugfs_create_u32("d0", 0644, root, &samsung->debug.data.d0); + debugfs_create_u32("d1", 0644, root, &samsung->debug.data.d1); + debugfs_create_u16("d2", 0644, root, &samsung->debug.data.d2); + debugfs_create_u8("d3", 0644, root, &samsung->debug.data.d3); + debugfs_create_blob("data", 0644, root, &samsung->debug.data_wrapper); + debugfs_create_blob("f0000_segment", 0600, root, &samsung->debug.f0000_wrapper); - debugfs_create_file("call", S_IFREG | S_IRUGO, root, samsung, + debugfs_create_file("call", S_IFREG | 0444, root, samsung, &samsung_laptop_call_fops); - debugfs_create_blob("sdiag", S_IRUGO | S_IWUSR, root, - &samsung->debug.sdiag_wrapper); + debugfs_create_blob("sdiag", 0644, root, &samsung->debug.sdiag_wrapper); } static void samsung_sabi_exit(struct samsung_laptop *samsung) -- cgit v1.2.3 From 1351f1d1e2f7d91d0b1963f7b5bf829a4982c778 Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Mon, 17 May 2021 12:07:45 +0200 Subject: platform/x86: samsung-laptop: set debugfs blobs to read only Those blobs can only be read. So, don't confuse users with 'writable' flags. Also, remove S_IFREG because debugfs takes care of that. Reviewed-by: Andy Shevchenko Signed-off-by: Wolfram Sang Link: https://lore.kernel.org/r/20210517100746.29663-2-wsa+renesas@sang-engineering.com Signed-off-by: Hans de Goede --- drivers/platform/x86/samsung-laptop.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/platform/x86/samsung-laptop.c b/drivers/platform/x86/samsung-laptop.c index 763d97cbbe53..7ee010aa740a 100644 --- a/drivers/platform/x86/samsung-laptop.c +++ b/drivers/platform/x86/samsung-laptop.c @@ -1296,12 +1296,12 @@ static void samsung_debugfs_init(struct samsung_laptop *samsung) debugfs_create_u32("d1", 0644, root, &samsung->debug.data.d1); debugfs_create_u16("d2", 0644, root, &samsung->debug.data.d2); debugfs_create_u8("d3", 0644, root, &samsung->debug.data.d3); - debugfs_create_blob("data", 0644, root, &samsung->debug.data_wrapper); - debugfs_create_blob("f0000_segment", 0600, root, + debugfs_create_blob("data", 0444, root, &samsung->debug.data_wrapper); + debugfs_create_blob("f0000_segment", 0400, root, &samsung->debug.f0000_wrapper); - debugfs_create_file("call", S_IFREG | 0444, root, samsung, + debugfs_create_file("call", 0444, root, samsung, &samsung_laptop_call_fops); - debugfs_create_blob("sdiag", 0644, root, &samsung->debug.sdiag_wrapper); + debugfs_create_blob("sdiag", 0444, root, &samsung->debug.sdiag_wrapper); } static void samsung_sabi_exit(struct samsung_laptop *samsung) -- cgit v1.2.3 From 7dc4a18d017ca26abd1cea197e486fb3e5cd7632 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 19 May 2021 15:56:18 +0200 Subject: platform/x86: toshiba_haps: Fix missing newline in pr_debug call in toshiba_haps_notify The pr_debug() call in toshiba_haps_notify() is missing a newline at the end of the string, add this. BugLink: https://bugs.debian.org/799193 Reported-by: Salvatore Bonaccorso Signed-off-by: Hans de Goede Link: https://lore.kernel.org/r/20210519135618.139701-1-hdegoede@redhat.com --- drivers/platform/x86/toshiba_haps.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/platform/x86/toshiba_haps.c b/drivers/platform/x86/toshiba_haps.c index b237bd6b1ee5..49e84095bb01 100644 --- a/drivers/platform/x86/toshiba_haps.c +++ b/drivers/platform/x86/toshiba_haps.c @@ -131,7 +131,7 @@ static const struct attribute_group haps_attr_group = { */ static void toshiba_haps_notify(struct acpi_device *device, u32 event) { - pr_debug("Received event: 0x%x", event); + pr_debug("Received event: 0x%x\n", event); acpi_bus_generate_netlink_event(device->pnp.device_class, dev_name(&device->dev), -- cgit v1.2.3 From a558ea42c0decd088df1950bb232ac2257929281 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Wed, 19 May 2021 12:44:05 -0500 Subject: platform/x86: Rename hp-wireless to wireless-hotkey This driver was originally intended to support some HP laptops, but later support was added for Xioami and AMD laptops. Rename it to make it clear that it supports a larger variety of systems. Signed-off-by: Mario Limonciello Link: https://lore.kernel.org/r/20210519174405.30155-1-mario.limonciello@amd.com Signed-off-by: Hans de Goede --- drivers/platform/x86/Kconfig | 9 +-- drivers/platform/x86/Makefile | 2 +- drivers/platform/x86/hp-wireless.c | 102 -------------------------------- drivers/platform/x86/wireless-hotkey.c | 103 +++++++++++++++++++++++++++++++++ 4 files changed, 109 insertions(+), 107 deletions(-) delete mode 100644 drivers/platform/x86/hp-wireless.c create mode 100644 drivers/platform/x86/wireless-hotkey.c diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 60592fb88e7a..9a668dae2738 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -415,16 +415,17 @@ config HP_ACCEL To compile this driver as a module, choose M here: the module will be called hp_accel. -config HP_WIRELESS - tristate "HP wireless button" +config WIRELESS_HOTKEY + tristate "Wireless hotkey button" depends on ACPI depends on INPUT help - This driver provides supports for new HP wireless button for Windows 8. + This driver provides supports for the wireless buttons found on some AMD, + HP, & Xioami laptops. On such systems the driver should load automatically (via ACPI alias). To compile this driver as a module, choose M here: the module will - be called hp-wireless. + be called wireless-hotkey. config HP_WMI tristate "HP WMI extras" diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index dcc8cdb95b4d..415bec18ece2 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -52,7 +52,6 @@ obj-$(CONFIG_GPD_POCKET_FAN) += gpd-pocket-fan.o # Hewlett Packard obj-$(CONFIG_HP_ACCEL) += hp_accel.o -obj-$(CONFIG_HP_WIRELESS) += hp-wireless.o obj-$(CONFIG_HP_WMI) += hp-wmi.o obj-$(CONFIG_TC1100_WMI) += tc1100-wmi.o @@ -115,6 +114,7 @@ obj-$(CONFIG_TOPSTAR_LAPTOP) += topstar-laptop.o obj-$(CONFIG_I2C_MULTI_INSTANTIATE) += i2c-multi-instantiate.o obj-$(CONFIG_MLX_PLATFORM) += mlx-platform.o obj-$(CONFIG_TOUCHSCREEN_DMI) += touchscreen_dmi.o +obj-$(CONFIG_WIRELESS_HOTKEY) += wireless-hotkey.o # Intel uncore drivers obj-$(CONFIG_INTEL_IPS) += intel_ips.o diff --git a/drivers/platform/x86/hp-wireless.c b/drivers/platform/x86/hp-wireless.c deleted file mode 100644 index 0753ef18e721..000000000000 --- a/drivers/platform/x86/hp-wireless.c +++ /dev/null @@ -1,102 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Airplane mode button for HP & Xiaomi laptops - * - * Copyright (C) 2014-2017 Alex Hung - */ - -#include -#include -#include -#include -#include -#include -#include - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Alex Hung"); -MODULE_ALIAS("acpi*:HPQ6001:*"); -MODULE_ALIAS("acpi*:WSTADEF:*"); -MODULE_ALIAS("acpi*:AMDI0051:*"); - -static struct input_dev *hpwl_input_dev; - -static const struct acpi_device_id hpwl_ids[] = { - {"HPQ6001", 0}, - {"WSTADEF", 0}, - {"AMDI0051", 0}, - {"", 0}, -}; - -static int hp_wireless_input_setup(void) -{ - int err; - - hpwl_input_dev = input_allocate_device(); - if (!hpwl_input_dev) - return -ENOMEM; - - hpwl_input_dev->name = "HP Wireless hotkeys"; - hpwl_input_dev->phys = "hpq6001/input0"; - hpwl_input_dev->id.bustype = BUS_HOST; - hpwl_input_dev->evbit[0] = BIT(EV_KEY); - set_bit(KEY_RFKILL, hpwl_input_dev->keybit); - - err = input_register_device(hpwl_input_dev); - if (err) - goto err_free_dev; - - return 0; - -err_free_dev: - input_free_device(hpwl_input_dev); - return err; -} - -static void hp_wireless_input_destroy(void) -{ - input_unregister_device(hpwl_input_dev); -} - -static void hpwl_notify(struct acpi_device *acpi_dev, u32 event) -{ - if (event != 0x80) { - pr_info("Received unknown event (0x%x)\n", event); - return; - } - - input_report_key(hpwl_input_dev, KEY_RFKILL, 1); - input_sync(hpwl_input_dev); - input_report_key(hpwl_input_dev, KEY_RFKILL, 0); - input_sync(hpwl_input_dev); -} - -static int hpwl_add(struct acpi_device *device) -{ - int err; - - err = hp_wireless_input_setup(); - if (err) - pr_err("Failed to setup hp wireless hotkeys\n"); - - return err; -} - -static int hpwl_remove(struct acpi_device *device) -{ - hp_wireless_input_destroy(); - return 0; -} - -static struct acpi_driver hpwl_driver = { - .name = "hp-wireless", - .owner = THIS_MODULE, - .ids = hpwl_ids, - .ops = { - .add = hpwl_add, - .remove = hpwl_remove, - .notify = hpwl_notify, - }, -}; - -module_acpi_driver(hpwl_driver); diff --git a/drivers/platform/x86/wireless-hotkey.c b/drivers/platform/x86/wireless-hotkey.c new file mode 100644 index 000000000000..b010e4ca3383 --- /dev/null +++ b/drivers/platform/x86/wireless-hotkey.c @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Airplane mode button for AMD, HP & Xiaomi laptops + * + * Copyright (C) 2014-2017 Alex Hung + * Copyright (C) 2021 Advanced Micro Devices + */ + +#include +#include +#include +#include +#include +#include +#include + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Alex Hung"); +MODULE_ALIAS("acpi*:HPQ6001:*"); +MODULE_ALIAS("acpi*:WSTADEF:*"); +MODULE_ALIAS("acpi*:AMDI0051:*"); + +static struct input_dev *wl_input_dev; + +static const struct acpi_device_id wl_ids[] = { + {"HPQ6001", 0}, + {"WSTADEF", 0}, + {"AMDI0051", 0}, + {"", 0}, +}; + +static int wireless_input_setup(void) +{ + int err; + + wl_input_dev = input_allocate_device(); + if (!wl_input_dev) + return -ENOMEM; + + wl_input_dev->name = "Wireless hotkeys"; + wl_input_dev->phys = "hpq6001/input0"; + wl_input_dev->id.bustype = BUS_HOST; + wl_input_dev->evbit[0] = BIT(EV_KEY); + set_bit(KEY_RFKILL, wl_input_dev->keybit); + + err = input_register_device(wl_input_dev); + if (err) + goto err_free_dev; + + return 0; + +err_free_dev: + input_free_device(wl_input_dev); + return err; +} + +static void wireless_input_destroy(void) +{ + input_unregister_device(wl_input_dev); +} + +static void wl_notify(struct acpi_device *acpi_dev, u32 event) +{ + if (event != 0x80) { + pr_info("Received unknown event (0x%x)\n", event); + return; + } + + input_report_key(wl_input_dev, KEY_RFKILL, 1); + input_sync(wl_input_dev); + input_report_key(wl_input_dev, KEY_RFKILL, 0); + input_sync(wl_input_dev); +} + +static int wl_add(struct acpi_device *device) +{ + int err; + + err = wireless_input_setup(); + if (err) + pr_err("Failed to setup hp wireless hotkeys\n"); + + return err; +} + +static int wl_remove(struct acpi_device *device) +{ + wireless_input_destroy(); + return 0; +} + +static struct acpi_driver wl_driver = { + .name = "wireless-hotkey", + .owner = THIS_MODULE, + .ids = wl_ids, + .ops = { + .add = wl_add, + .remove = wl_remove, + .notify = wl_notify, + }, +}; + +module_acpi_driver(wl_driver); -- cgit v1.2.3 From 98c0c85b1040db24f0d04d3e1d315c6c7b05cc07 Mon Sep 17 00:00:00 2001 From: Luke D. Jones Date: Mon, 19 Apr 2021 19:49:14 +1200 Subject: platform/x86: asus-nb-wmi: Revert "Drop duplicate DMI quirk structures" This is a preparation revert for reverting the "add support for ASUS ROG Zephyrus G14 and G15" change. This reverts commit 67186653c903 ("platform/x86: asus-nb-wmi: Drop duplicate DMI quirk structures") Signed-off-by: Luke D. Jones Link: https://lore.kernel.org/r/20210419074915.393433-2-luke@ljones.dev Signed-off-by: Hans de Goede --- drivers/platform/x86/asus-nb-wmi.c | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/drivers/platform/x86/asus-nb-wmi.c b/drivers/platform/x86/asus-nb-wmi.c index d41d7ad14be0..b07b1288346e 100644 --- a/drivers/platform/x86/asus-nb-wmi.c +++ b/drivers/platform/x86/asus-nb-wmi.c @@ -110,7 +110,12 @@ static struct quirk_entry quirk_asus_forceals = { .wmi_force_als_set = true, }; -static struct quirk_entry quirk_asus_vendor_backlight = { +static struct quirk_entry quirk_asus_ga401i = { + .wmi_backlight_power = true, + .wmi_backlight_set_devstate = true, +}; + +static struct quirk_entry quirk_asus_ga502i = { .wmi_backlight_power = true, .wmi_backlight_set_devstate = true, }; @@ -432,7 +437,7 @@ static const struct dmi_system_id asus_quirks[] = { DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), DMI_MATCH(DMI_PRODUCT_NAME, "GA401IH"), }, - .driver_data = &quirk_asus_vendor_backlight, + .driver_data = &quirk_asus_ga401i, }, { .callback = dmi_matched, @@ -441,7 +446,7 @@ static const struct dmi_system_id asus_quirks[] = { DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), DMI_MATCH(DMI_PRODUCT_NAME, "GA401II"), }, - .driver_data = &quirk_asus_vendor_backlight, + .driver_data = &quirk_asus_ga401i, }, { .callback = dmi_matched, @@ -450,7 +455,7 @@ static const struct dmi_system_id asus_quirks[] = { DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), DMI_MATCH(DMI_PRODUCT_NAME, "GA401IU"), }, - .driver_data = &quirk_asus_vendor_backlight, + .driver_data = &quirk_asus_ga401i, }, { .callback = dmi_matched, @@ -459,7 +464,7 @@ static const struct dmi_system_id asus_quirks[] = { DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), DMI_MATCH(DMI_PRODUCT_NAME, "GA401IV"), }, - .driver_data = &quirk_asus_vendor_backlight, + .driver_data = &quirk_asus_ga401i, }, { .callback = dmi_matched, @@ -468,7 +473,7 @@ static const struct dmi_system_id asus_quirks[] = { DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), DMI_MATCH(DMI_PRODUCT_NAME, "GA401IVC"), }, - .driver_data = &quirk_asus_vendor_backlight, + .driver_data = &quirk_asus_ga401i, }, { .callback = dmi_matched, @@ -477,7 +482,7 @@ static const struct dmi_system_id asus_quirks[] = { DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), DMI_MATCH(DMI_PRODUCT_NAME, "GA502II"), }, - .driver_data = &quirk_asus_vendor_backlight, + .driver_data = &quirk_asus_ga502i, }, { .callback = dmi_matched, @@ -486,7 +491,7 @@ static const struct dmi_system_id asus_quirks[] = { DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), DMI_MATCH(DMI_PRODUCT_NAME, "GA502IU"), }, - .driver_data = &quirk_asus_vendor_backlight, + .driver_data = &quirk_asus_ga502i, }, { .callback = dmi_matched, @@ -495,7 +500,7 @@ static const struct dmi_system_id asus_quirks[] = { DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), DMI_MATCH(DMI_PRODUCT_NAME, "GA502IV"), }, - .driver_data = &quirk_asus_vendor_backlight, + .driver_data = &quirk_asus_ga502i, }, { .callback = dmi_matched, -- cgit v1.2.3 From 28117f3a5c3c8375a3304af76357d5bf9cf30f0b Mon Sep 17 00:00:00 2001 From: Luke D. Jones Date: Mon, 19 Apr 2021 19:49:15 +1200 Subject: platform/x86: asus-nb-wmi: Revert "add support for ASUS ROG Zephyrus G14 and G15" The quirks added to asus-nb-wmi for the ASUS ROG Zephyrus G14 and G15 are wrong, they tell the asus-wmi code to use the vendor specific WMI backlight interface. But there is no such interface on these laptops. As a side effect, these quirks stop the acpi_video driver to register since they make acpi_video_get_backlight_type() return acpi_backlight_vendor, leaving only the native AMD backlight driver in place, which is the one we want. This happy coincidence is being replaced with a new quirk in drivers/acpi/video_detect.c which actually sets the backlight_type to acpi_backlight_native fixinf this properly. This reverts commit 13bceda68fb9 ("platform/x86: asus-nb-wmi: add support for ASUS ROG Zephyrus G14 and G15"). Signed-off-by: Luke D. Jones Link: https://lore.kernel.org/r/20210419074915.393433-3-luke@ljones.dev Signed-off-by: Hans de Goede --- drivers/platform/x86/asus-nb-wmi.c | 82 -------------------------------------- 1 file changed, 82 deletions(-) diff --git a/drivers/platform/x86/asus-nb-wmi.c b/drivers/platform/x86/asus-nb-wmi.c index b07b1288346e..0cb927f0f301 100644 --- a/drivers/platform/x86/asus-nb-wmi.c +++ b/drivers/platform/x86/asus-nb-wmi.c @@ -110,16 +110,6 @@ static struct quirk_entry quirk_asus_forceals = { .wmi_force_als_set = true, }; -static struct quirk_entry quirk_asus_ga401i = { - .wmi_backlight_power = true, - .wmi_backlight_set_devstate = true, -}; - -static struct quirk_entry quirk_asus_ga502i = { - .wmi_backlight_power = true, - .wmi_backlight_set_devstate = true, -}; - static struct quirk_entry quirk_asus_use_kbd_dock_devid = { .use_kbd_dock_devid = true, }; @@ -430,78 +420,6 @@ static const struct dmi_system_id asus_quirks[] = { }, .driver_data = &quirk_asus_forceals, }, - { - .callback = dmi_matched, - .ident = "ASUSTeK COMPUTER INC. GA401IH", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), - DMI_MATCH(DMI_PRODUCT_NAME, "GA401IH"), - }, - .driver_data = &quirk_asus_ga401i, - }, - { - .callback = dmi_matched, - .ident = "ASUSTeK COMPUTER INC. GA401II", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), - DMI_MATCH(DMI_PRODUCT_NAME, "GA401II"), - }, - .driver_data = &quirk_asus_ga401i, - }, - { - .callback = dmi_matched, - .ident = "ASUSTeK COMPUTER INC. GA401IU", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), - DMI_MATCH(DMI_PRODUCT_NAME, "GA401IU"), - }, - .driver_data = &quirk_asus_ga401i, - }, - { - .callback = dmi_matched, - .ident = "ASUSTeK COMPUTER INC. GA401IV", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), - DMI_MATCH(DMI_PRODUCT_NAME, "GA401IV"), - }, - .driver_data = &quirk_asus_ga401i, - }, - { - .callback = dmi_matched, - .ident = "ASUSTeK COMPUTER INC. GA401IVC", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), - DMI_MATCH(DMI_PRODUCT_NAME, "GA401IVC"), - }, - .driver_data = &quirk_asus_ga401i, - }, - { - .callback = dmi_matched, - .ident = "ASUSTeK COMPUTER INC. GA502II", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), - DMI_MATCH(DMI_PRODUCT_NAME, "GA502II"), - }, - .driver_data = &quirk_asus_ga502i, - }, - { - .callback = dmi_matched, - .ident = "ASUSTeK COMPUTER INC. GA502IU", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), - DMI_MATCH(DMI_PRODUCT_NAME, "GA502IU"), - }, - .driver_data = &quirk_asus_ga502i, - }, - { - .callback = dmi_matched, - .ident = "ASUSTeK COMPUTER INC. GA502IV", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."), - DMI_MATCH(DMI_PRODUCT_NAME, "GA502IV"), - }, - .driver_data = &quirk_asus_ga502i, - }, { .callback = dmi_matched, .ident = "Asus Transformer T100TA / T100HA / T100CHI", -- cgit v1.2.3 From e978858b4214b1abde9fa650e3112a438d61791c Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Sun, 23 May 2021 15:45:26 +0200 Subject: platform/surface: aggregator_registry: Update comments for 15" AMD Surface Laptop 4 The 15" AMD version of the Surface Laptop 4 shares its WSID HID with the 15" AMD version of the Surface Laptop 3. Update the comments accordingly. Signed-off-by: Maximilian Luz Link: https://lore.kernel.org/r/20210523134528.798887-2-luzmaximilian@gmail.com Signed-off-by: Hans de Goede --- drivers/platform/surface/surface_aggregator_registry.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c index 685d37a7add1..bdc09305aab7 100644 --- a/drivers/platform/surface/surface_aggregator_registry.c +++ b/drivers/platform/surface/surface_aggregator_registry.c @@ -156,7 +156,7 @@ static const struct software_node *ssam_node_group_sl2[] = { NULL, }; -/* Devices for Surface Laptop 3. */ +/* Devices for Surface Laptop 3 and 4. */ static const struct software_node *ssam_node_group_sl3[] = { &ssam_node_root, &ssam_node_bat_ac, @@ -521,7 +521,7 @@ static const struct acpi_device_id ssam_platform_hub_match[] = { /* Surface Laptop 3 (13", Intel) */ { "MSHW0114", (unsigned long)ssam_node_group_sl3 }, - /* Surface Laptop 3 (15", AMD) */ + /* Surface Laptop 3 (15", AMD) and 4 (15", AMD) */ { "MSHW0110", (unsigned long)ssam_node_group_sl3 }, /* Surface Laptop Go 1 */ -- cgit v1.2.3 From 460d740839a6e786bb61263d47f4daf23b104f55 Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Sun, 23 May 2021 15:45:27 +0200 Subject: platform/surface: aggregator_registry: Add support for 13" Intel Surface Laptop 4 Add support for the 13" Intel version of the Surface Laptop 4. Use the existing node group for the Surface Laptop 3 since the 15" AMD version already shares its WSID HID with its predecessor and there don't seem to be any significant differences with regards to SAM. Signed-off-by: Maximilian Luz Link: https://lore.kernel.org/r/20210523134528.798887-3-luzmaximilian@gmail.com Signed-off-by: Hans de Goede --- drivers/platform/surface/surface_aggregator_registry.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c index bdc09305aab7..ef83461fa536 100644 --- a/drivers/platform/surface/surface_aggregator_registry.c +++ b/drivers/platform/surface/surface_aggregator_registry.c @@ -524,6 +524,9 @@ static const struct acpi_device_id ssam_platform_hub_match[] = { /* Surface Laptop 3 (15", AMD) and 4 (15", AMD) */ { "MSHW0110", (unsigned long)ssam_node_group_sl3 }, + /* Surface Laptop 4 (13", Intel) */ + { "MSHW0250", (unsigned long)ssam_node_group_sl3 }, + /* Surface Laptop Go 1 */ { "MSHW0118", (unsigned long)ssam_node_group_slg1 }, -- cgit v1.2.3 From b6c3c6ff2043c6519b5be38ac259752d19f4a5f9 Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Sun, 23 May 2021 15:45:28 +0200 Subject: platform/surface: aggregator_registry: Consolidate node groups for 5th- and 6th-gen devices 5th- and 6th-generation Surface devices have all SAM clients defined in ACPI, except for the platform profile/performance mode which his handled via the WSID (Windows Surface Integration Device). Thus, the node groups for those devices are the same and we can just use a single one instead of re-defining the same one over and over again. Signed-off-by: Maximilian Luz Link: https://lore.kernel.org/r/20210523134528.798887-4-luzmaximilian@gmail.com Signed-off-by: Hans de Goede --- .../platform/surface/surface_aggregator_registry.c | 47 ++++++---------------- 1 file changed, 12 insertions(+), 35 deletions(-) diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c index ef83461fa536..4428c4330229 100644 --- a/drivers/platform/surface/surface_aggregator_registry.c +++ b/drivers/platform/surface/surface_aggregator_registry.c @@ -119,8 +119,13 @@ static const struct software_node ssam_node_hid_base_iid6 = { .parent = &ssam_node_hub_base, }; -/* Devices for Surface Book 2. */ -static const struct software_node *ssam_node_group_sb2[] = { +/* + * Devices for 5th- and 6th-generations models: + * - Surface Book 2, + * - Surface Laptop 1 and 2, + * - Surface Pro 5 and 6. + */ +static const struct software_node *ssam_node_group_gen5[] = { &ssam_node_root, &ssam_node_tmp_pprof, NULL, @@ -142,20 +147,6 @@ static const struct software_node *ssam_node_group_sb3[] = { NULL, }; -/* Devices for Surface Laptop 1. */ -static const struct software_node *ssam_node_group_sl1[] = { - &ssam_node_root, - &ssam_node_tmp_pprof, - NULL, -}; - -/* Devices for Surface Laptop 2. */ -static const struct software_node *ssam_node_group_sl2[] = { - &ssam_node_root, - &ssam_node_tmp_pprof, - NULL, -}; - /* Devices for Surface Laptop 3 and 4. */ static const struct software_node *ssam_node_group_sl3[] = { &ssam_node_root, @@ -177,20 +168,6 @@ static const struct software_node *ssam_node_group_slg1[] = { NULL, }; -/* Devices for Surface Pro 5. */ -static const struct software_node *ssam_node_group_sp5[] = { - &ssam_node_root, - &ssam_node_tmp_pprof, - NULL, -}; - -/* Devices for Surface Pro 6. */ -static const struct software_node *ssam_node_group_sp6[] = { - &ssam_node_root, - &ssam_node_tmp_pprof, - NULL, -}; - /* Devices for Surface Pro 7 and Surface Pro 7+. */ static const struct software_node *ssam_node_group_sp7[] = { &ssam_node_root, @@ -495,10 +472,10 @@ static struct ssam_device_driver ssam_base_hub_driver = { static const struct acpi_device_id ssam_platform_hub_match[] = { /* Surface Pro 4, 5, and 6 (OMBR < 0x10) */ - { "MSHW0081", (unsigned long)ssam_node_group_sp5 }, + { "MSHW0081", (unsigned long)ssam_node_group_gen5 }, /* Surface Pro 6 (OMBR >= 0x10) */ - { "MSHW0111", (unsigned long)ssam_node_group_sp6 }, + { "MSHW0111", (unsigned long)ssam_node_group_gen5 }, /* Surface Pro 7 */ { "MSHW0116", (unsigned long)ssam_node_group_sp7 }, @@ -507,16 +484,16 @@ static const struct acpi_device_id ssam_platform_hub_match[] = { { "MSHW0119", (unsigned long)ssam_node_group_sp7 }, /* Surface Book 2 */ - { "MSHW0107", (unsigned long)ssam_node_group_sb2 }, + { "MSHW0107", (unsigned long)ssam_node_group_gen5 }, /* Surface Book 3 */ { "MSHW0117", (unsigned long)ssam_node_group_sb3 }, /* Surface Laptop 1 */ - { "MSHW0086", (unsigned long)ssam_node_group_sl1 }, + { "MSHW0086", (unsigned long)ssam_node_group_gen5 }, /* Surface Laptop 2 */ - { "MSHW0112", (unsigned long)ssam_node_group_sl2 }, + { "MSHW0112", (unsigned long)ssam_node_group_gen5 }, /* Surface Laptop 3 (13", Intel) */ { "MSHW0114", (unsigned long)ssam_node_group_sl3 }, -- cgit v1.2.3 From ab66724a230937982d58711302f51aa9b569a5c0 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sun, 23 May 2021 19:23:31 +0200 Subject: platform/x86: ideapad-laptop: Ignore VPC event bit 10 VPC event bit 10 gets set on a Yoga 300-11IBR when the EC believes that the device has changed between laptop/tent/stand/tablet mode. The EC relies on getting angle info from 2 accelerometers through a special windows service calling a DSM on the DUAL250E ACPI-device. Linux does not do this, making the laptop/tent/stand/tablet mode info unreliable. Ignore VPC event bit 10 to avoid the warnings triggered by the default case in ideapad_acpi_notify(). Note that the plan for Linux is to have iio-sensor-proxy read the 2 accelerometers and have it provide info about which mode 360° hinges 2-in-1s to the rest of userspace: https://gitlab.freedesktop.org/hadess/iio-sensor-proxy/-/issues/216 Signed-off-by: Hans de Goede Link: https://lore.kernel.org/r/20210523172331.177834-1-hdegoede@redhat.com --- drivers/platform/x86/ideapad-laptop.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/drivers/platform/x86/ideapad-laptop.c b/drivers/platform/x86/ideapad-laptop.c index 387817290921..784326bd72f0 100644 --- a/drivers/platform/x86/ideapad-laptop.c +++ b/drivers/platform/x86/ideapad-laptop.c @@ -1408,6 +1408,18 @@ static void ideapad_acpi_notify(acpi_handle handle, u32 event, void *data) case 6: ideapad_input_report(priv, bit); break; + case 10: + /* + * This event gets send on a Yoga 300-11IBR when the EC + * believes that the device has changed between laptop/ + * tent/stand/tablet mode. The EC relies on getting + * angle info from 2 accelerometers through a special + * windows service calling a DSM on the DUAL250E ACPI- + * device. Linux does not do this, making the laptop/ + * tent/stand/tablet mode info unreliable, so we simply + * ignore these events. + */ + break; case 9: ideapad_sync_rfk_state(priv); break; -- cgit v1.2.3 From 3fca4b143e49252934d01ee034227c708da5120e Mon Sep 17 00:00:00 2001 From: Jiapeng Chong Date: Mon, 24 May 2021 14:23:58 +0800 Subject: platform/x86: thinkpad_acpi: Fix inconsistent indenting Eliminate the follow smatch warning: drivers/platform/x86/thinkpad_acpi.c:7942 volume_write() warn: inconsistent indenting. Reported-by: Abaci Robot Signed-off-by: Jiapeng Chong Link: https://lore.kernel.org/r/1621837438-70790-1-git-send-email-jiapeng.chong@linux.alibaba.com Signed-off-by: Hans de Goede --- drivers/platform/x86/thinkpad_acpi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index dd60c9397d35..b7fec1b9fffc 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -7938,7 +7938,7 @@ static int volume_write(char *buf) continue; } else if (sscanf(cmd, "level %u", &l) == 1 && l >= 0 && l <= TP_EC_VOLUME_MAX) { - new_level = l; + new_level = l; continue; } } -- cgit v1.2.3 From 25acf21f3a78a1d2815e605e45924393e039b210 Mon Sep 17 00:00:00 2001 From: Til Jasper Ullrich Date: Tue, 25 May 2021 17:09:52 +0200 Subject: platform/x86: thinkpad_acpi: Add X1 Carbon Gen 9 second fan support The X1 Carbon Gen 9 uses two fans instead of one like the previous generation. This adds support for the second fan. It has been tested on my X1 Carbon Gen 9 (20XXS00100) and works fine. Signed-off-by: Til Jasper Ullrich Link: https://lore.kernel.org/r/20210525150950.14805-1-tju@tju.me Signed-off-by: Hans de Goede --- drivers/platform/x86/thinkpad_acpi.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index b7fec1b9fffc..603156a6e3ed 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -8853,6 +8853,7 @@ static const struct tpacpi_quirk fan_quirk_table[] __initconst = { TPACPI_Q_LNV3('N', '2', 'O', TPACPI_FAN_2CTL), /* P1 / X1 Extreme (2nd gen) */ TPACPI_Q_LNV3('N', '2', 'V', TPACPI_FAN_2CTL), /* P1 / X1 Extreme (3nd gen) */ TPACPI_Q_LNV3('N', '3', '0', TPACPI_FAN_2CTL), /* P15 (1st gen) / P15v (1st gen) */ + TPACPI_Q_LNV3('N', '3', '2', TPACPI_FAN_2CTL), /* X1 Carbon (9th gen) */ }; static int __init fan_init(struct ibm_init_struct *iibm) -- cgit v1.2.3 From 8bf388a0a0fe257dd7be9db0352b5b71b4e9138a Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 18 May 2021 11:47:43 +0200 Subject: platform/x86: dell-wmi: Rename dell-wmi.c to dell-wmi-base.c Rename dell-wmi.c to dell-wmi-base.c, so that we can have other dell-wmi-foo.c files which can be added to dell-wmi.ko as "plugins" controlled by separate boolean Kconfig options. Signed-off-by: Hans de Goede --- MAINTAINERS | 2 +- drivers/platform/x86/dell/Makefile | 1 + drivers/platform/x86/dell/dell-wmi-base.c | 763 ++++++++++++++++++++++++++++++ drivers/platform/x86/dell/dell-wmi.c | 763 ------------------------------ 4 files changed, 765 insertions(+), 764 deletions(-) create mode 100644 drivers/platform/x86/dell/dell-wmi-base.c delete mode 100644 drivers/platform/x86/dell/dell-wmi.c diff --git a/MAINTAINERS b/MAINTAINERS index 6e848b244226..7b4a325af65a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5170,7 +5170,7 @@ DELL WMI NOTIFICATIONS DRIVER M: Matthew Garrett M: Pali Rohár S: Maintained -F: drivers/platform/x86/dell/dell-wmi.c +F: drivers/platform/x86/dell/dell-wmi-base.c DELTA ST MEDIA DRIVER M: Hugues Fruchet diff --git a/drivers/platform/x86/dell/Makefile b/drivers/platform/x86/dell/Makefile index d720a3e42ae3..cc45410040cb 100644 --- a/drivers/platform/x86/dell/Makefile +++ b/drivers/platform/x86/dell/Makefile @@ -15,6 +15,7 @@ dell-smbios-$(CONFIG_DELL_SMBIOS_WMI) += dell-smbios-wmi.o dell-smbios-$(CONFIG_DELL_SMBIOS_SMM) += dell-smbios-smm.o obj-$(CONFIG_DELL_SMO8800) += dell-smo8800.o obj-$(CONFIG_DELL_WMI) += dell-wmi.o +dell-wmi-objs := dell-wmi-base.o obj-$(CONFIG_DELL_WMI_AIO) += dell-wmi-aio.o obj-$(CONFIG_DELL_WMI_DESCRIPTOR) += dell-wmi-descriptor.o obj-$(CONFIG_DELL_WMI_LED) += dell-wmi-led.o diff --git a/drivers/platform/x86/dell/dell-wmi-base.c b/drivers/platform/x86/dell/dell-wmi-base.c new file mode 100644 index 000000000000..5e1b7f897df5 --- /dev/null +++ b/drivers/platform/x86/dell/dell-wmi-base.c @@ -0,0 +1,763 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Dell WMI hotkeys + * + * Copyright (C) 2008 Red Hat + * Copyright (C) 2014-2015 Pali Rohár + * + * Portions based on wistron_btns.c: + * Copyright (C) 2005 Miloslav Trmac + * Copyright (C) 2005 Bernhard Rosenkraenzer + * Copyright (C) 2005 Dmitry Torokhov + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "dell-smbios.h" +#include "dell-wmi-descriptor.h" + +MODULE_AUTHOR("Matthew Garrett "); +MODULE_AUTHOR("Pali Rohár "); +MODULE_DESCRIPTION("Dell laptop WMI hotkeys driver"); +MODULE_LICENSE("GPL"); + +#define DELL_EVENT_GUID "9DBB5994-A997-11DA-B012-B622A1EF5492" + +static bool wmi_requires_smbios_request; + +struct dell_wmi_priv { + struct input_dev *input_dev; + u32 interface_version; +}; + +static int __init dmi_matched(const struct dmi_system_id *dmi) +{ + wmi_requires_smbios_request = 1; + return 1; +} + +static const struct dmi_system_id dell_wmi_smbios_list[] __initconst = { + { + .callback = dmi_matched, + .ident = "Dell Inspiron M5110", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron M5110"), + }, + }, + { + .callback = dmi_matched, + .ident = "Dell Vostro V131", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "Vostro V131"), + }, + }, + { } +}; + +/* + * Keymap for WMI events of type 0x0000 + * + * Certain keys are flagged as KE_IGNORE. All of these are either + * notifications (rather than requests for change) or are also sent + * via the keyboard controller so should not be sent again. + */ +static const struct key_entry dell_wmi_keymap_type_0000[] = { + { KE_IGNORE, 0x003a, { KEY_CAPSLOCK } }, + + /* Key code is followed by brightness level */ + { KE_KEY, 0xe005, { KEY_BRIGHTNESSDOWN } }, + { KE_KEY, 0xe006, { KEY_BRIGHTNESSUP } }, + + /* Battery health status button */ + { KE_KEY, 0xe007, { KEY_BATTERY } }, + + /* Radio devices state change, key code is followed by other values */ + { KE_IGNORE, 0xe008, { KEY_RFKILL } }, + + { KE_KEY, 0xe009, { KEY_EJECTCD } }, + + /* Key code is followed by: next, active and attached devices */ + { KE_KEY, 0xe00b, { KEY_SWITCHVIDEOMODE } }, + + /* Key code is followed by keyboard illumination level */ + { KE_IGNORE, 0xe00c, { KEY_KBDILLUMTOGGLE } }, + + /* BIOS error detected */ + { KE_IGNORE, 0xe00d, { KEY_RESERVED } }, + + /* Battery was removed or inserted */ + { KE_IGNORE, 0xe00e, { KEY_RESERVED } }, + + /* Wifi Catcher */ + { KE_KEY, 0xe011, { KEY_WLAN } }, + + /* Ambient light sensor toggle */ + { KE_IGNORE, 0xe013, { KEY_RESERVED } }, + + { KE_IGNORE, 0xe020, { KEY_MUTE } }, + + /* Unknown, defined in ACPI DSDT */ + /* { KE_IGNORE, 0xe023, { KEY_RESERVED } }, */ + + /* Untested, Dell Instant Launch key on Inspiron 7520 */ + /* { KE_IGNORE, 0xe024, { KEY_RESERVED } }, */ + + /* Dell Instant Launch key */ + { KE_KEY, 0xe025, { KEY_PROG4 } }, + + /* Audio panel key */ + { KE_IGNORE, 0xe026, { KEY_RESERVED } }, + + /* LCD Display On/Off Control key */ + { KE_KEY, 0xe027, { KEY_DISPLAYTOGGLE } }, + + /* Untested, Multimedia key on Dell Vostro 3560 */ + /* { KE_IGNORE, 0xe028, { KEY_RESERVED } }, */ + + /* Dell Instant Launch key */ + { KE_KEY, 0xe029, { KEY_PROG4 } }, + + /* Untested, Windows Mobility Center button on Inspiron 7520 */ + /* { KE_IGNORE, 0xe02a, { KEY_RESERVED } }, */ + + /* Unknown, defined in ACPI DSDT */ + /* { KE_IGNORE, 0xe02b, { KEY_RESERVED } }, */ + + /* Untested, Dell Audio With Preset Switch button on Inspiron 7520 */ + /* { KE_IGNORE, 0xe02c, { KEY_RESERVED } }, */ + + { KE_IGNORE, 0xe02e, { KEY_VOLUMEDOWN } }, + { KE_IGNORE, 0xe030, { KEY_VOLUMEUP } }, + { KE_IGNORE, 0xe033, { KEY_KBDILLUMUP } }, + { KE_IGNORE, 0xe034, { KEY_KBDILLUMDOWN } }, + { KE_IGNORE, 0xe03a, { KEY_CAPSLOCK } }, + + /* NIC Link is Up */ + { KE_IGNORE, 0xe043, { KEY_RESERVED } }, + + /* NIC Link is Down */ + { KE_IGNORE, 0xe044, { KEY_RESERVED } }, + + /* + * This entry is very suspicious! + * Originally Matthew Garrett created this dell-wmi driver specially for + * "button with a picture of a battery" which has event code 0xe045. + * Later Mario Limonciello from Dell told us that event code 0xe045 is + * reported by Num Lock and should be ignored because key is send also + * by keyboard controller. + * So for now we will ignore this event to prevent potential double + * Num Lock key press. + */ + { KE_IGNORE, 0xe045, { KEY_NUMLOCK } }, + + /* Scroll lock and also going to tablet mode on portable devices */ + { KE_IGNORE, 0xe046, { KEY_SCROLLLOCK } }, + + /* Untested, going from tablet mode on portable devices */ + /* { KE_IGNORE, 0xe047, { KEY_RESERVED } }, */ + + /* Dell Support Center key */ + { KE_IGNORE, 0xe06e, { KEY_RESERVED } }, + + { KE_IGNORE, 0xe0f7, { KEY_MUTE } }, + { KE_IGNORE, 0xe0f8, { KEY_VOLUMEDOWN } }, + { KE_IGNORE, 0xe0f9, { KEY_VOLUMEUP } }, +}; + +struct dell_bios_keymap_entry { + u16 scancode; + u16 keycode; +}; + +struct dell_bios_hotkey_table { + struct dmi_header header; + struct dell_bios_keymap_entry keymap[]; + +}; + +struct dell_dmi_results { + int err; + int keymap_size; + struct key_entry *keymap; +}; + +/* Uninitialized entries here are KEY_RESERVED == 0. */ +static const u16 bios_to_linux_keycode[256] = { + [0] = KEY_MEDIA, + [1] = KEY_NEXTSONG, + [2] = KEY_PLAYPAUSE, + [3] = KEY_PREVIOUSSONG, + [4] = KEY_STOPCD, + [5] = KEY_UNKNOWN, + [6] = KEY_UNKNOWN, + [7] = KEY_UNKNOWN, + [8] = KEY_WWW, + [9] = KEY_UNKNOWN, + [10] = KEY_VOLUMEDOWN, + [11] = KEY_MUTE, + [12] = KEY_VOLUMEUP, + [13] = KEY_UNKNOWN, + [14] = KEY_BATTERY, + [15] = KEY_EJECTCD, + [16] = KEY_UNKNOWN, + [17] = KEY_SLEEP, + [18] = KEY_PROG1, + [19] = KEY_BRIGHTNESSDOWN, + [20] = KEY_BRIGHTNESSUP, + [21] = KEY_BRIGHTNESS_AUTO, + [22] = KEY_KBDILLUMTOGGLE, + [23] = KEY_UNKNOWN, + [24] = KEY_SWITCHVIDEOMODE, + [25] = KEY_UNKNOWN, + [26] = KEY_UNKNOWN, + [27] = KEY_SWITCHVIDEOMODE, + [28] = KEY_UNKNOWN, + [29] = KEY_UNKNOWN, + [30] = KEY_PROG2, + [31] = KEY_UNKNOWN, + [32] = KEY_UNKNOWN, + [33] = KEY_UNKNOWN, + [34] = KEY_UNKNOWN, + [35] = KEY_UNKNOWN, + [36] = KEY_UNKNOWN, + [37] = KEY_UNKNOWN, + [38] = KEY_MICMUTE, + [255] = KEY_PROG3, +}; + +/* + * Keymap for WMI events of type 0x0010 + * + * These are applied if the 0xB2 DMI hotkey table is present and doesn't + * override them. + */ +static const struct key_entry dell_wmi_keymap_type_0010[] = { + /* Fn-lock switched to function keys */ + { KE_IGNORE, 0x0, { KEY_RESERVED } }, + + /* Fn-lock switched to multimedia keys */ + { KE_IGNORE, 0x1, { KEY_RESERVED } }, + + /* Keyboard backlight change notification */ + { KE_IGNORE, 0x3f, { KEY_RESERVED } }, + + /* Backlight brightness level */ + { KE_KEY, 0x57, { KEY_BRIGHTNESSDOWN } }, + { KE_KEY, 0x58, { KEY_BRIGHTNESSUP } }, + + /* Mic mute */ + { KE_KEY, 0x150, { KEY_MICMUTE } }, + + /* Fn-lock */ + { KE_IGNORE, 0x151, { KEY_RESERVED } }, + + /* Change keyboard illumination */ + { KE_IGNORE, 0x152, { KEY_KBDILLUMTOGGLE } }, + + /* + * Radio disable (notify only -- there is no model for which the + * WMI event is supposed to trigger an action). + */ + { KE_IGNORE, 0x153, { KEY_RFKILL } }, + + /* RGB keyboard backlight control */ + { KE_IGNORE, 0x154, { KEY_RESERVED } }, + + /* + * Stealth mode toggle. This will "disable all lights and sounds". + * The action is performed by the BIOS and EC; the WMI event is just + * a notification. On the XPS 13 9350, this is Fn+F7, and there's + * a BIOS setting to enable and disable the hotkey. + */ + { KE_IGNORE, 0x155, { KEY_RESERVED } }, + + /* Rugged magnetic dock attach/detach events */ + { KE_IGNORE, 0x156, { KEY_RESERVED } }, + { KE_IGNORE, 0x157, { KEY_RESERVED } }, + + /* Rugged programmable (P1/P2/P3 keys) */ + { KE_KEY, 0x850, { KEY_PROG1 } }, + { KE_KEY, 0x851, { KEY_PROG2 } }, + { KE_KEY, 0x852, { KEY_PROG3 } }, + + /* + * Radio disable (notify only -- there is no model for which the + * WMI event is supposed to trigger an action). + */ + { KE_IGNORE, 0xe008, { KEY_RFKILL } }, + + /* Fn-lock */ + { KE_IGNORE, 0xe035, { KEY_RESERVED } }, +}; + +/* + * Keymap for WMI events of type 0x0011 + */ +static const struct key_entry dell_wmi_keymap_type_0011[] = { + /* Battery unplugged */ + { KE_IGNORE, 0xfff0, { KEY_RESERVED } }, + + /* Battery inserted */ + { KE_IGNORE, 0xfff1, { KEY_RESERVED } }, + + /* + * Detachable keyboard detached / undocked + * Note SW_TABLET_MODE is already reported through the intel_vbtn + * driver for this, so we ignore it. + */ + { KE_IGNORE, 0xfff2, { KEY_RESERVED } }, + + /* Detachable keyboard attached / docked */ + { KE_IGNORE, 0xfff3, { KEY_RESERVED } }, + + /* Keyboard backlight level changed */ + { KE_IGNORE, KBD_LED_OFF_TOKEN, { KEY_RESERVED } }, + { KE_IGNORE, KBD_LED_ON_TOKEN, { KEY_RESERVED } }, + { KE_IGNORE, KBD_LED_AUTO_TOKEN, { KEY_RESERVED } }, + { KE_IGNORE, KBD_LED_AUTO_25_TOKEN, { KEY_RESERVED } }, + { KE_IGNORE, KBD_LED_AUTO_50_TOKEN, { KEY_RESERVED } }, + { KE_IGNORE, KBD_LED_AUTO_75_TOKEN, { KEY_RESERVED } }, + { KE_IGNORE, KBD_LED_AUTO_100_TOKEN, { KEY_RESERVED } }, +}; + +/* + * Keymap for WMI events of type 0x0012 + * They are events with extended data + */ +static const struct key_entry dell_wmi_keymap_type_0012[] = { + /* Fn-lock button pressed */ + { KE_IGNORE, 0xe035, { KEY_RESERVED } }, +}; + +static void dell_wmi_process_key(struct wmi_device *wdev, int type, int code) +{ + struct dell_wmi_priv *priv = dev_get_drvdata(&wdev->dev); + const struct key_entry *key; + + key = sparse_keymap_entry_from_scancode(priv->input_dev, + (type << 16) | code); + if (!key) { + pr_info("Unknown key with type 0x%04x and code 0x%04x pressed\n", + type, code); + return; + } + + pr_debug("Key with type 0x%04x and code 0x%04x pressed\n", type, code); + + /* Don't report brightness notifications that will also come via ACPI */ + if ((key->keycode == KEY_BRIGHTNESSUP || + key->keycode == KEY_BRIGHTNESSDOWN) && + acpi_video_handles_brightness_key_presses()) + return; + + if (type == 0x0000 && code == 0xe025 && !wmi_requires_smbios_request) + return; + + if (key->keycode == KEY_KBDILLUMTOGGLE) + dell_laptop_call_notifier( + DELL_LAPTOP_KBD_BACKLIGHT_BRIGHTNESS_CHANGED, NULL); + + sparse_keymap_report_entry(priv->input_dev, key, 1, true); +} + +static void dell_wmi_notify(struct wmi_device *wdev, + union acpi_object *obj) +{ + struct dell_wmi_priv *priv = dev_get_drvdata(&wdev->dev); + u16 *buffer_entry, *buffer_end; + acpi_size buffer_size; + int len, i; + + if (obj->type != ACPI_TYPE_BUFFER) { + pr_warn("bad response type %x\n", obj->type); + return; + } + + pr_debug("Received WMI event (%*ph)\n", + obj->buffer.length, obj->buffer.pointer); + + buffer_entry = (u16 *)obj->buffer.pointer; + buffer_size = obj->buffer.length/2; + buffer_end = buffer_entry + buffer_size; + + /* + * BIOS/ACPI on devices with WMI interface version 0 does not clear + * buffer before filling it. So next time when BIOS/ACPI send WMI event + * which is smaller as previous then it contains garbage in buffer from + * previous event. + * + * BIOS/ACPI on devices with WMI interface version 1 clears buffer and + * sometimes send more events in buffer at one call. + * + * So to prevent reading garbage from buffer we will process only first + * one event on devices with WMI interface version 0. + */ + if (priv->interface_version == 0 && buffer_entry < buffer_end) + if (buffer_end > buffer_entry + buffer_entry[0] + 1) + buffer_end = buffer_entry + buffer_entry[0] + 1; + + while (buffer_entry < buffer_end) { + + len = buffer_entry[0]; + if (len == 0) + break; + + len++; + + if (buffer_entry + len > buffer_end) { + pr_warn("Invalid length of WMI event\n"); + break; + } + + pr_debug("Process buffer (%*ph)\n", len*2, buffer_entry); + + switch (buffer_entry[1]) { + case 0x0000: /* One key pressed or event occurred */ + case 0x0012: /* Event with extended data occurred */ + if (len > 2) + dell_wmi_process_key(wdev, buffer_entry[1], + buffer_entry[2]); + /* Extended data is currently ignored */ + break; + case 0x0010: /* Sequence of keys pressed */ + case 0x0011: /* Sequence of events occurred */ + for (i = 2; i < len; ++i) + dell_wmi_process_key(wdev, buffer_entry[1], + buffer_entry[i]); + break; + default: /* Unknown event */ + pr_info("Unknown WMI event type 0x%x\n", + (int)buffer_entry[1]); + break; + } + + buffer_entry += len; + + } + +} + +static bool have_scancode(u32 scancode, const struct key_entry *keymap, int len) +{ + int i; + + for (i = 0; i < len; i++) + if (keymap[i].code == scancode) + return true; + + return false; +} + +static void handle_dmi_entry(const struct dmi_header *dm, void *opaque) +{ + struct dell_dmi_results *results = opaque; + struct dell_bios_hotkey_table *table; + int hotkey_num, i, pos = 0; + struct key_entry *keymap; + + if (results->err || results->keymap) + return; /* We already found the hotkey table. */ + + /* The Dell hotkey table is type 0xB2. Scan until we find it. */ + if (dm->type != 0xb2) + return; + + table = container_of(dm, struct dell_bios_hotkey_table, header); + + hotkey_num = (table->header.length - + sizeof(struct dell_bios_hotkey_table)) / + sizeof(struct dell_bios_keymap_entry); + if (hotkey_num < 1) { + /* + * Historically, dell-wmi would ignore a DMI entry of + * fewer than 7 bytes. Sizes between 4 and 8 bytes are + * nonsensical (both the header and all entries are 4 + * bytes), so we approximate the old behavior by + * ignoring tables with fewer than one entry. + */ + return; + } + + keymap = kcalloc(hotkey_num, sizeof(struct key_entry), GFP_KERNEL); + if (!keymap) { + results->err = -ENOMEM; + return; + } + + for (i = 0; i < hotkey_num; i++) { + const struct dell_bios_keymap_entry *bios_entry = + &table->keymap[i]; + + /* Uninitialized entries are 0 aka KEY_RESERVED. */ + u16 keycode = (bios_entry->keycode < + ARRAY_SIZE(bios_to_linux_keycode)) ? + bios_to_linux_keycode[bios_entry->keycode] : + (bios_entry->keycode == 0xffff ? KEY_UNKNOWN : KEY_RESERVED); + + /* + * Log if we find an entry in the DMI table that we don't + * understand. If this happens, we should figure out what + * the entry means and add it to bios_to_linux_keycode. + */ + if (keycode == KEY_RESERVED) { + pr_info("firmware scancode 0x%x maps to unrecognized keycode 0x%x\n", + bios_entry->scancode, bios_entry->keycode); + continue; + } + + if (keycode == KEY_KBDILLUMTOGGLE) + keymap[pos].type = KE_IGNORE; + else + keymap[pos].type = KE_KEY; + keymap[pos].code = bios_entry->scancode; + keymap[pos].keycode = keycode; + + pos++; + } + + results->keymap = keymap; + results->keymap_size = pos; +} + +static int dell_wmi_input_setup(struct wmi_device *wdev) +{ + struct dell_wmi_priv *priv = dev_get_drvdata(&wdev->dev); + struct dell_dmi_results dmi_results = {}; + struct key_entry *keymap; + int err, i, pos = 0; + + priv->input_dev = input_allocate_device(); + if (!priv->input_dev) + return -ENOMEM; + + priv->input_dev->name = "Dell WMI hotkeys"; + priv->input_dev->id.bustype = BUS_HOST; + priv->input_dev->dev.parent = &wdev->dev; + + if (dmi_walk(handle_dmi_entry, &dmi_results)) { + /* + * Historically, dell-wmi ignored dmi_walk errors. A failure + * is certainly surprising, but it probably just indicates + * a very old laptop. + */ + pr_warn("no DMI; using the old-style hotkey interface\n"); + } + + if (dmi_results.err) { + err = dmi_results.err; + goto err_free_dev; + } + + keymap = kcalloc(dmi_results.keymap_size + + ARRAY_SIZE(dell_wmi_keymap_type_0000) + + ARRAY_SIZE(dell_wmi_keymap_type_0010) + + ARRAY_SIZE(dell_wmi_keymap_type_0011) + + ARRAY_SIZE(dell_wmi_keymap_type_0012) + + 1, + sizeof(struct key_entry), GFP_KERNEL); + if (!keymap) { + kfree(dmi_results.keymap); + err = -ENOMEM; + goto err_free_dev; + } + + /* Append table with events of type 0x0010 which comes from DMI */ + for (i = 0; i < dmi_results.keymap_size; i++) { + keymap[pos] = dmi_results.keymap[i]; + keymap[pos].code |= (0x0010 << 16); + pos++; + } + + kfree(dmi_results.keymap); + + /* Append table with extra events of type 0x0010 which are not in DMI */ + for (i = 0; i < ARRAY_SIZE(dell_wmi_keymap_type_0010); i++) { + const struct key_entry *entry = &dell_wmi_keymap_type_0010[i]; + + /* + * Check if we've already found this scancode. This takes + * quadratic time, but it doesn't matter unless the list + * of extra keys gets very long. + */ + if (dmi_results.keymap_size && + have_scancode(entry->code | (0x0010 << 16), + keymap, dmi_results.keymap_size) + ) + continue; + + keymap[pos] = *entry; + keymap[pos].code |= (0x0010 << 16); + pos++; + } + + /* Append table with events of type 0x0011 */ + for (i = 0; i < ARRAY_SIZE(dell_wmi_keymap_type_0011); i++) { + keymap[pos] = dell_wmi_keymap_type_0011[i]; + keymap[pos].code |= (0x0011 << 16); + pos++; + } + + /* Append table with events of type 0x0012 */ + for (i = 0; i < ARRAY_SIZE(dell_wmi_keymap_type_0012); i++) { + keymap[pos] = dell_wmi_keymap_type_0012[i]; + keymap[pos].code |= (0x0012 << 16); + pos++; + } + + /* + * Now append also table with "legacy" events of type 0x0000. Some of + * them are reported also on laptops which have scancodes in DMI. + */ + for (i = 0; i < ARRAY_SIZE(dell_wmi_keymap_type_0000); i++) { + keymap[pos] = dell_wmi_keymap_type_0000[i]; + pos++; + } + + keymap[pos].type = KE_END; + + err = sparse_keymap_setup(priv->input_dev, keymap, NULL); + /* + * Sparse keymap library makes a copy of keymap so we don't need the + * original one that was allocated. + */ + kfree(keymap); + if (err) + goto err_free_dev; + + err = input_register_device(priv->input_dev); + if (err) + goto err_free_dev; + + return 0; + + err_free_dev: + input_free_device(priv->input_dev); + return err; +} + +static void dell_wmi_input_destroy(struct wmi_device *wdev) +{ + struct dell_wmi_priv *priv = dev_get_drvdata(&wdev->dev); + + input_unregister_device(priv->input_dev); +} + +/* + * According to Dell SMBIOS documentation: + * + * 17 3 Application Program Registration + * + * cbArg1 Application ID 1 = 0x00010000 + * cbArg2 Application ID 2 + * QUICKSET/DCP = 0x51534554 "QSET" + * ALS Driver = 0x416c7353 "AlsS" + * Latitude ON = 0x4c6f6e52 "LonR" + * cbArg3 Application version or revision number + * cbArg4 0 = Unregister application + * 1 = Register application + * cbRes1 Standard return codes (0, -1, -2) + */ + +static int dell_wmi_events_set_enabled(bool enable) +{ + struct calling_interface_buffer *buffer; + int ret; + + buffer = kzalloc(sizeof(struct calling_interface_buffer), GFP_KERNEL); + if (!buffer) + return -ENOMEM; + buffer->cmd_class = CLASS_INFO; + buffer->cmd_select = SELECT_APP_REGISTRATION; + buffer->input[0] = 0x10000; + buffer->input[1] = 0x51534554; + buffer->input[3] = enable; + ret = dell_smbios_call(buffer); + if (ret == 0) + ret = buffer->output[0]; + kfree(buffer); + + return dell_smbios_error(ret); +} + +static int dell_wmi_probe(struct wmi_device *wdev, const void *context) +{ + struct dell_wmi_priv *priv; + int ret; + + ret = dell_wmi_get_descriptor_valid(); + if (ret) + return ret; + + priv = devm_kzalloc( + &wdev->dev, sizeof(struct dell_wmi_priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + dev_set_drvdata(&wdev->dev, priv); + + if (!dell_wmi_get_interface_version(&priv->interface_version)) + return -EPROBE_DEFER; + + return dell_wmi_input_setup(wdev); +} + +static void dell_wmi_remove(struct wmi_device *wdev) +{ + dell_wmi_input_destroy(wdev); +} +static const struct wmi_device_id dell_wmi_id_table[] = { + { .guid_string = DELL_EVENT_GUID }, + { }, +}; + +static struct wmi_driver dell_wmi_driver = { + .driver = { + .name = "dell-wmi", + }, + .id_table = dell_wmi_id_table, + .probe = dell_wmi_probe, + .remove = dell_wmi_remove, + .notify = dell_wmi_notify, +}; + +static int __init dell_wmi_init(void) +{ + int err; + + dmi_check_system(dell_wmi_smbios_list); + + if (wmi_requires_smbios_request) { + err = dell_wmi_events_set_enabled(true); + if (err) { + pr_err("Failed to enable WMI events\n"); + return err; + } + } + + return wmi_driver_register(&dell_wmi_driver); +} +late_initcall(dell_wmi_init); + +static void __exit dell_wmi_exit(void) +{ + if (wmi_requires_smbios_request) + dell_wmi_events_set_enabled(false); + + wmi_driver_unregister(&dell_wmi_driver); +} +module_exit(dell_wmi_exit); + +MODULE_DEVICE_TABLE(wmi, dell_wmi_id_table); diff --git a/drivers/platform/x86/dell/dell-wmi.c b/drivers/platform/x86/dell/dell-wmi.c deleted file mode 100644 index 5e1b7f897df5..000000000000 --- a/drivers/platform/x86/dell/dell-wmi.c +++ /dev/null @@ -1,763 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Dell WMI hotkeys - * - * Copyright (C) 2008 Red Hat - * Copyright (C) 2014-2015 Pali Rohár - * - * Portions based on wistron_btns.c: - * Copyright (C) 2005 Miloslav Trmac - * Copyright (C) 2005 Bernhard Rosenkraenzer - * Copyright (C) 2005 Dmitry Torokhov - */ - -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "dell-smbios.h" -#include "dell-wmi-descriptor.h" - -MODULE_AUTHOR("Matthew Garrett "); -MODULE_AUTHOR("Pali Rohár "); -MODULE_DESCRIPTION("Dell laptop WMI hotkeys driver"); -MODULE_LICENSE("GPL"); - -#define DELL_EVENT_GUID "9DBB5994-A997-11DA-B012-B622A1EF5492" - -static bool wmi_requires_smbios_request; - -struct dell_wmi_priv { - struct input_dev *input_dev; - u32 interface_version; -}; - -static int __init dmi_matched(const struct dmi_system_id *dmi) -{ - wmi_requires_smbios_request = 1; - return 1; -} - -static const struct dmi_system_id dell_wmi_smbios_list[] __initconst = { - { - .callback = dmi_matched, - .ident = "Dell Inspiron M5110", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron M5110"), - }, - }, - { - .callback = dmi_matched, - .ident = "Dell Vostro V131", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_PRODUCT_NAME, "Vostro V131"), - }, - }, - { } -}; - -/* - * Keymap for WMI events of type 0x0000 - * - * Certain keys are flagged as KE_IGNORE. All of these are either - * notifications (rather than requests for change) or are also sent - * via the keyboard controller so should not be sent again. - */ -static const struct key_entry dell_wmi_keymap_type_0000[] = { - { KE_IGNORE, 0x003a, { KEY_CAPSLOCK } }, - - /* Key code is followed by brightness level */ - { KE_KEY, 0xe005, { KEY_BRIGHTNESSDOWN } }, - { KE_KEY, 0xe006, { KEY_BRIGHTNESSUP } }, - - /* Battery health status button */ - { KE_KEY, 0xe007, { KEY_BATTERY } }, - - /* Radio devices state change, key code is followed by other values */ - { KE_IGNORE, 0xe008, { KEY_RFKILL } }, - - { KE_KEY, 0xe009, { KEY_EJECTCD } }, - - /* Key code is followed by: next, active and attached devices */ - { KE_KEY, 0xe00b, { KEY_SWITCHVIDEOMODE } }, - - /* Key code is followed by keyboard illumination level */ - { KE_IGNORE, 0xe00c, { KEY_KBDILLUMTOGGLE } }, - - /* BIOS error detected */ - { KE_IGNORE, 0xe00d, { KEY_RESERVED } }, - - /* Battery was removed or inserted */ - { KE_IGNORE, 0xe00e, { KEY_RESERVED } }, - - /* Wifi Catcher */ - { KE_KEY, 0xe011, { KEY_WLAN } }, - - /* Ambient light sensor toggle */ - { KE_IGNORE, 0xe013, { KEY_RESERVED } }, - - { KE_IGNORE, 0xe020, { KEY_MUTE } }, - - /* Unknown, defined in ACPI DSDT */ - /* { KE_IGNORE, 0xe023, { KEY_RESERVED } }, */ - - /* Untested, Dell Instant Launch key on Inspiron 7520 */ - /* { KE_IGNORE, 0xe024, { KEY_RESERVED } }, */ - - /* Dell Instant Launch key */ - { KE_KEY, 0xe025, { KEY_PROG4 } }, - - /* Audio panel key */ - { KE_IGNORE, 0xe026, { KEY_RESERVED } }, - - /* LCD Display On/Off Control key */ - { KE_KEY, 0xe027, { KEY_DISPLAYTOGGLE } }, - - /* Untested, Multimedia key on Dell Vostro 3560 */ - /* { KE_IGNORE, 0xe028, { KEY_RESERVED } }, */ - - /* Dell Instant Launch key */ - { KE_KEY, 0xe029, { KEY_PROG4 } }, - - /* Untested, Windows Mobility Center button on Inspiron 7520 */ - /* { KE_IGNORE, 0xe02a, { KEY_RESERVED } }, */ - - /* Unknown, defined in ACPI DSDT */ - /* { KE_IGNORE, 0xe02b, { KEY_RESERVED } }, */ - - /* Untested, Dell Audio With Preset Switch button on Inspiron 7520 */ - /* { KE_IGNORE, 0xe02c, { KEY_RESERVED } }, */ - - { KE_IGNORE, 0xe02e, { KEY_VOLUMEDOWN } }, - { KE_IGNORE, 0xe030, { KEY_VOLUMEUP } }, - { KE_IGNORE, 0xe033, { KEY_KBDILLUMUP } }, - { KE_IGNORE, 0xe034, { KEY_KBDILLUMDOWN } }, - { KE_IGNORE, 0xe03a, { KEY_CAPSLOCK } }, - - /* NIC Link is Up */ - { KE_IGNORE, 0xe043, { KEY_RESERVED } }, - - /* NIC Link is Down */ - { KE_IGNORE, 0xe044, { KEY_RESERVED } }, - - /* - * This entry is very suspicious! - * Originally Matthew Garrett created this dell-wmi driver specially for - * "button with a picture of a battery" which has event code 0xe045. - * Later Mario Limonciello from Dell told us that event code 0xe045 is - * reported by Num Lock and should be ignored because key is send also - * by keyboard controller. - * So for now we will ignore this event to prevent potential double - * Num Lock key press. - */ - { KE_IGNORE, 0xe045, { KEY_NUMLOCK } }, - - /* Scroll lock and also going to tablet mode on portable devices */ - { KE_IGNORE, 0xe046, { KEY_SCROLLLOCK } }, - - /* Untested, going from tablet mode on portable devices */ - /* { KE_IGNORE, 0xe047, { KEY_RESERVED } }, */ - - /* Dell Support Center key */ - { KE_IGNORE, 0xe06e, { KEY_RESERVED } }, - - { KE_IGNORE, 0xe0f7, { KEY_MUTE } }, - { KE_IGNORE, 0xe0f8, { KEY_VOLUMEDOWN } }, - { KE_IGNORE, 0xe0f9, { KEY_VOLUMEUP } }, -}; - -struct dell_bios_keymap_entry { - u16 scancode; - u16 keycode; -}; - -struct dell_bios_hotkey_table { - struct dmi_header header; - struct dell_bios_keymap_entry keymap[]; - -}; - -struct dell_dmi_results { - int err; - int keymap_size; - struct key_entry *keymap; -}; - -/* Uninitialized entries here are KEY_RESERVED == 0. */ -static const u16 bios_to_linux_keycode[256] = { - [0] = KEY_MEDIA, - [1] = KEY_NEXTSONG, - [2] = KEY_PLAYPAUSE, - [3] = KEY_PREVIOUSSONG, - [4] = KEY_STOPCD, - [5] = KEY_UNKNOWN, - [6] = KEY_UNKNOWN, - [7] = KEY_UNKNOWN, - [8] = KEY_WWW, - [9] = KEY_UNKNOWN, - [10] = KEY_VOLUMEDOWN, - [11] = KEY_MUTE, - [12] = KEY_VOLUMEUP, - [13] = KEY_UNKNOWN, - [14] = KEY_BATTERY, - [15] = KEY_EJECTCD, - [16] = KEY_UNKNOWN, - [17] = KEY_SLEEP, - [18] = KEY_PROG1, - [19] = KEY_BRIGHTNESSDOWN, - [20] = KEY_BRIGHTNESSUP, - [21] = KEY_BRIGHTNESS_AUTO, - [22] = KEY_KBDILLUMTOGGLE, - [23] = KEY_UNKNOWN, - [24] = KEY_SWITCHVIDEOMODE, - [25] = KEY_UNKNOWN, - [26] = KEY_UNKNOWN, - [27] = KEY_SWITCHVIDEOMODE, - [28] = KEY_UNKNOWN, - [29] = KEY_UNKNOWN, - [30] = KEY_PROG2, - [31] = KEY_UNKNOWN, - [32] = KEY_UNKNOWN, - [33] = KEY_UNKNOWN, - [34] = KEY_UNKNOWN, - [35] = KEY_UNKNOWN, - [36] = KEY_UNKNOWN, - [37] = KEY_UNKNOWN, - [38] = KEY_MICMUTE, - [255] = KEY_PROG3, -}; - -/* - * Keymap for WMI events of type 0x0010 - * - * These are applied if the 0xB2 DMI hotkey table is present and doesn't - * override them. - */ -static const struct key_entry dell_wmi_keymap_type_0010[] = { - /* Fn-lock switched to function keys */ - { KE_IGNORE, 0x0, { KEY_RESERVED } }, - - /* Fn-lock switched to multimedia keys */ - { KE_IGNORE, 0x1, { KEY_RESERVED } }, - - /* Keyboard backlight change notification */ - { KE_IGNORE, 0x3f, { KEY_RESERVED } }, - - /* Backlight brightness level */ - { KE_KEY, 0x57, { KEY_BRIGHTNESSDOWN } }, - { KE_KEY, 0x58, { KEY_BRIGHTNESSUP } }, - - /* Mic mute */ - { KE_KEY, 0x150, { KEY_MICMUTE } }, - - /* Fn-lock */ - { KE_IGNORE, 0x151, { KEY_RESERVED } }, - - /* Change keyboard illumination */ - { KE_IGNORE, 0x152, { KEY_KBDILLUMTOGGLE } }, - - /* - * Radio disable (notify only -- there is no model for which the - * WMI event is supposed to trigger an action). - */ - { KE_IGNORE, 0x153, { KEY_RFKILL } }, - - /* RGB keyboard backlight control */ - { KE_IGNORE, 0x154, { KEY_RESERVED } }, - - /* - * Stealth mode toggle. This will "disable all lights and sounds". - * The action is performed by the BIOS and EC; the WMI event is just - * a notification. On the XPS 13 9350, this is Fn+F7, and there's - * a BIOS setting to enable and disable the hotkey. - */ - { KE_IGNORE, 0x155, { KEY_RESERVED } }, - - /* Rugged magnetic dock attach/detach events */ - { KE_IGNORE, 0x156, { KEY_RESERVED } }, - { KE_IGNORE, 0x157, { KEY_RESERVED } }, - - /* Rugged programmable (P1/P2/P3 keys) */ - { KE_KEY, 0x850, { KEY_PROG1 } }, - { KE_KEY, 0x851, { KEY_PROG2 } }, - { KE_KEY, 0x852, { KEY_PROG3 } }, - - /* - * Radio disable (notify only -- there is no model for which the - * WMI event is supposed to trigger an action). - */ - { KE_IGNORE, 0xe008, { KEY_RFKILL } }, - - /* Fn-lock */ - { KE_IGNORE, 0xe035, { KEY_RESERVED } }, -}; - -/* - * Keymap for WMI events of type 0x0011 - */ -static const struct key_entry dell_wmi_keymap_type_0011[] = { - /* Battery unplugged */ - { KE_IGNORE, 0xfff0, { KEY_RESERVED } }, - - /* Battery inserted */ - { KE_IGNORE, 0xfff1, { KEY_RESERVED } }, - - /* - * Detachable keyboard detached / undocked - * Note SW_TABLET_MODE is already reported through the intel_vbtn - * driver for this, so we ignore it. - */ - { KE_IGNORE, 0xfff2, { KEY_RESERVED } }, - - /* Detachable keyboard attached / docked */ - { KE_IGNORE, 0xfff3, { KEY_RESERVED } }, - - /* Keyboard backlight level changed */ - { KE_IGNORE, KBD_LED_OFF_TOKEN, { KEY_RESERVED } }, - { KE_IGNORE, KBD_LED_ON_TOKEN, { KEY_RESERVED } }, - { KE_IGNORE, KBD_LED_AUTO_TOKEN, { KEY_RESERVED } }, - { KE_IGNORE, KBD_LED_AUTO_25_TOKEN, { KEY_RESERVED } }, - { KE_IGNORE, KBD_LED_AUTO_50_TOKEN, { KEY_RESERVED } }, - { KE_IGNORE, KBD_LED_AUTO_75_TOKEN, { KEY_RESERVED } }, - { KE_IGNORE, KBD_LED_AUTO_100_TOKEN, { KEY_RESERVED } }, -}; - -/* - * Keymap for WMI events of type 0x0012 - * They are events with extended data - */ -static const struct key_entry dell_wmi_keymap_type_0012[] = { - /* Fn-lock button pressed */ - { KE_IGNORE, 0xe035, { KEY_RESERVED } }, -}; - -static void dell_wmi_process_key(struct wmi_device *wdev, int type, int code) -{ - struct dell_wmi_priv *priv = dev_get_drvdata(&wdev->dev); - const struct key_entry *key; - - key = sparse_keymap_entry_from_scancode(priv->input_dev, - (type << 16) | code); - if (!key) { - pr_info("Unknown key with type 0x%04x and code 0x%04x pressed\n", - type, code); - return; - } - - pr_debug("Key with type 0x%04x and code 0x%04x pressed\n", type, code); - - /* Don't report brightness notifications that will also come via ACPI */ - if ((key->keycode == KEY_BRIGHTNESSUP || - key->keycode == KEY_BRIGHTNESSDOWN) && - acpi_video_handles_brightness_key_presses()) - return; - - if (type == 0x0000 && code == 0xe025 && !wmi_requires_smbios_request) - return; - - if (key->keycode == KEY_KBDILLUMTOGGLE) - dell_laptop_call_notifier( - DELL_LAPTOP_KBD_BACKLIGHT_BRIGHTNESS_CHANGED, NULL); - - sparse_keymap_report_entry(priv->input_dev, key, 1, true); -} - -static void dell_wmi_notify(struct wmi_device *wdev, - union acpi_object *obj) -{ - struct dell_wmi_priv *priv = dev_get_drvdata(&wdev->dev); - u16 *buffer_entry, *buffer_end; - acpi_size buffer_size; - int len, i; - - if (obj->type != ACPI_TYPE_BUFFER) { - pr_warn("bad response type %x\n", obj->type); - return; - } - - pr_debug("Received WMI event (%*ph)\n", - obj->buffer.length, obj->buffer.pointer); - - buffer_entry = (u16 *)obj->buffer.pointer; - buffer_size = obj->buffer.length/2; - buffer_end = buffer_entry + buffer_size; - - /* - * BIOS/ACPI on devices with WMI interface version 0 does not clear - * buffer before filling it. So next time when BIOS/ACPI send WMI event - * which is smaller as previous then it contains garbage in buffer from - * previous event. - * - * BIOS/ACPI on devices with WMI interface version 1 clears buffer and - * sometimes send more events in buffer at one call. - * - * So to prevent reading garbage from buffer we will process only first - * one event on devices with WMI interface version 0. - */ - if (priv->interface_version == 0 && buffer_entry < buffer_end) - if (buffer_end > buffer_entry + buffer_entry[0] + 1) - buffer_end = buffer_entry + buffer_entry[0] + 1; - - while (buffer_entry < buffer_end) { - - len = buffer_entry[0]; - if (len == 0) - break; - - len++; - - if (buffer_entry + len > buffer_end) { - pr_warn("Invalid length of WMI event\n"); - break; - } - - pr_debug("Process buffer (%*ph)\n", len*2, buffer_entry); - - switch (buffer_entry[1]) { - case 0x0000: /* One key pressed or event occurred */ - case 0x0012: /* Event with extended data occurred */ - if (len > 2) - dell_wmi_process_key(wdev, buffer_entry[1], - buffer_entry[2]); - /* Extended data is currently ignored */ - break; - case 0x0010: /* Sequence of keys pressed */ - case 0x0011: /* Sequence of events occurred */ - for (i = 2; i < len; ++i) - dell_wmi_process_key(wdev, buffer_entry[1], - buffer_entry[i]); - break; - default: /* Unknown event */ - pr_info("Unknown WMI event type 0x%x\n", - (int)buffer_entry[1]); - break; - } - - buffer_entry += len; - - } - -} - -static bool have_scancode(u32 scancode, const struct key_entry *keymap, int len) -{ - int i; - - for (i = 0; i < len; i++) - if (keymap[i].code == scancode) - return true; - - return false; -} - -static void handle_dmi_entry(const struct dmi_header *dm, void *opaque) -{ - struct dell_dmi_results *results = opaque; - struct dell_bios_hotkey_table *table; - int hotkey_num, i, pos = 0; - struct key_entry *keymap; - - if (results->err || results->keymap) - return; /* We already found the hotkey table. */ - - /* The Dell hotkey table is type 0xB2. Scan until we find it. */ - if (dm->type != 0xb2) - return; - - table = container_of(dm, struct dell_bios_hotkey_table, header); - - hotkey_num = (table->header.length - - sizeof(struct dell_bios_hotkey_table)) / - sizeof(struct dell_bios_keymap_entry); - if (hotkey_num < 1) { - /* - * Historically, dell-wmi would ignore a DMI entry of - * fewer than 7 bytes. Sizes between 4 and 8 bytes are - * nonsensical (both the header and all entries are 4 - * bytes), so we approximate the old behavior by - * ignoring tables with fewer than one entry. - */ - return; - } - - keymap = kcalloc(hotkey_num, sizeof(struct key_entry), GFP_KERNEL); - if (!keymap) { - results->err = -ENOMEM; - return; - } - - for (i = 0; i < hotkey_num; i++) { - const struct dell_bios_keymap_entry *bios_entry = - &table->keymap[i]; - - /* Uninitialized entries are 0 aka KEY_RESERVED. */ - u16 keycode = (bios_entry->keycode < - ARRAY_SIZE(bios_to_linux_keycode)) ? - bios_to_linux_keycode[bios_entry->keycode] : - (bios_entry->keycode == 0xffff ? KEY_UNKNOWN : KEY_RESERVED); - - /* - * Log if we find an entry in the DMI table that we don't - * understand. If this happens, we should figure out what - * the entry means and add it to bios_to_linux_keycode. - */ - if (keycode == KEY_RESERVED) { - pr_info("firmware scancode 0x%x maps to unrecognized keycode 0x%x\n", - bios_entry->scancode, bios_entry->keycode); - continue; - } - - if (keycode == KEY_KBDILLUMTOGGLE) - keymap[pos].type = KE_IGNORE; - else - keymap[pos].type = KE_KEY; - keymap[pos].code = bios_entry->scancode; - keymap[pos].keycode = keycode; - - pos++; - } - - results->keymap = keymap; - results->keymap_size = pos; -} - -static int dell_wmi_input_setup(struct wmi_device *wdev) -{ - struct dell_wmi_priv *priv = dev_get_drvdata(&wdev->dev); - struct dell_dmi_results dmi_results = {}; - struct key_entry *keymap; - int err, i, pos = 0; - - priv->input_dev = input_allocate_device(); - if (!priv->input_dev) - return -ENOMEM; - - priv->input_dev->name = "Dell WMI hotkeys"; - priv->input_dev->id.bustype = BUS_HOST; - priv->input_dev->dev.parent = &wdev->dev; - - if (dmi_walk(handle_dmi_entry, &dmi_results)) { - /* - * Historically, dell-wmi ignored dmi_walk errors. A failure - * is certainly surprising, but it probably just indicates - * a very old laptop. - */ - pr_warn("no DMI; using the old-style hotkey interface\n"); - } - - if (dmi_results.err) { - err = dmi_results.err; - goto err_free_dev; - } - - keymap = kcalloc(dmi_results.keymap_size + - ARRAY_SIZE(dell_wmi_keymap_type_0000) + - ARRAY_SIZE(dell_wmi_keymap_type_0010) + - ARRAY_SIZE(dell_wmi_keymap_type_0011) + - ARRAY_SIZE(dell_wmi_keymap_type_0012) + - 1, - sizeof(struct key_entry), GFP_KERNEL); - if (!keymap) { - kfree(dmi_results.keymap); - err = -ENOMEM; - goto err_free_dev; - } - - /* Append table with events of type 0x0010 which comes from DMI */ - for (i = 0; i < dmi_results.keymap_size; i++) { - keymap[pos] = dmi_results.keymap[i]; - keymap[pos].code |= (0x0010 << 16); - pos++; - } - - kfree(dmi_results.keymap); - - /* Append table with extra events of type 0x0010 which are not in DMI */ - for (i = 0; i < ARRAY_SIZE(dell_wmi_keymap_type_0010); i++) { - const struct key_entry *entry = &dell_wmi_keymap_type_0010[i]; - - /* - * Check if we've already found this scancode. This takes - * quadratic time, but it doesn't matter unless the list - * of extra keys gets very long. - */ - if (dmi_results.keymap_size && - have_scancode(entry->code | (0x0010 << 16), - keymap, dmi_results.keymap_size) - ) - continue; - - keymap[pos] = *entry; - keymap[pos].code |= (0x0010 << 16); - pos++; - } - - /* Append table with events of type 0x0011 */ - for (i = 0; i < ARRAY_SIZE(dell_wmi_keymap_type_0011); i++) { - keymap[pos] = dell_wmi_keymap_type_0011[i]; - keymap[pos].code |= (0x0011 << 16); - pos++; - } - - /* Append table with events of type 0x0012 */ - for (i = 0; i < ARRAY_SIZE(dell_wmi_keymap_type_0012); i++) { - keymap[pos] = dell_wmi_keymap_type_0012[i]; - keymap[pos].code |= (0x0012 << 16); - pos++; - } - - /* - * Now append also table with "legacy" events of type 0x0000. Some of - * them are reported also on laptops which have scancodes in DMI. - */ - for (i = 0; i < ARRAY_SIZE(dell_wmi_keymap_type_0000); i++) { - keymap[pos] = dell_wmi_keymap_type_0000[i]; - pos++; - } - - keymap[pos].type = KE_END; - - err = sparse_keymap_setup(priv->input_dev, keymap, NULL); - /* - * Sparse keymap library makes a copy of keymap so we don't need the - * original one that was allocated. - */ - kfree(keymap); - if (err) - goto err_free_dev; - - err = input_register_device(priv->input_dev); - if (err) - goto err_free_dev; - - return 0; - - err_free_dev: - input_free_device(priv->input_dev); - return err; -} - -static void dell_wmi_input_destroy(struct wmi_device *wdev) -{ - struct dell_wmi_priv *priv = dev_get_drvdata(&wdev->dev); - - input_unregister_device(priv->input_dev); -} - -/* - * According to Dell SMBIOS documentation: - * - * 17 3 Application Program Registration - * - * cbArg1 Application ID 1 = 0x00010000 - * cbArg2 Application ID 2 - * QUICKSET/DCP = 0x51534554 "QSET" - * ALS Driver = 0x416c7353 "AlsS" - * Latitude ON = 0x4c6f6e52 "LonR" - * cbArg3 Application version or revision number - * cbArg4 0 = Unregister application - * 1 = Register application - * cbRes1 Standard return codes (0, -1, -2) - */ - -static int dell_wmi_events_set_enabled(bool enable) -{ - struct calling_interface_buffer *buffer; - int ret; - - buffer = kzalloc(sizeof(struct calling_interface_buffer), GFP_KERNEL); - if (!buffer) - return -ENOMEM; - buffer->cmd_class = CLASS_INFO; - buffer->cmd_select = SELECT_APP_REGISTRATION; - buffer->input[0] = 0x10000; - buffer->input[1] = 0x51534554; - buffer->input[3] = enable; - ret = dell_smbios_call(buffer); - if (ret == 0) - ret = buffer->output[0]; - kfree(buffer); - - return dell_smbios_error(ret); -} - -static int dell_wmi_probe(struct wmi_device *wdev, const void *context) -{ - struct dell_wmi_priv *priv; - int ret; - - ret = dell_wmi_get_descriptor_valid(); - if (ret) - return ret; - - priv = devm_kzalloc( - &wdev->dev, sizeof(struct dell_wmi_priv), GFP_KERNEL); - if (!priv) - return -ENOMEM; - dev_set_drvdata(&wdev->dev, priv); - - if (!dell_wmi_get_interface_version(&priv->interface_version)) - return -EPROBE_DEFER; - - return dell_wmi_input_setup(wdev); -} - -static void dell_wmi_remove(struct wmi_device *wdev) -{ - dell_wmi_input_destroy(wdev); -} -static const struct wmi_device_id dell_wmi_id_table[] = { - { .guid_string = DELL_EVENT_GUID }, - { }, -}; - -static struct wmi_driver dell_wmi_driver = { - .driver = { - .name = "dell-wmi", - }, - .id_table = dell_wmi_id_table, - .probe = dell_wmi_probe, - .remove = dell_wmi_remove, - .notify = dell_wmi_notify, -}; - -static int __init dell_wmi_init(void) -{ - int err; - - dmi_check_system(dell_wmi_smbios_list); - - if (wmi_requires_smbios_request) { - err = dell_wmi_events_set_enabled(true); - if (err) { - pr_err("Failed to enable WMI events\n"); - return err; - } - } - - return wmi_driver_register(&dell_wmi_driver); -} -late_initcall(dell_wmi_init); - -static void __exit dell_wmi_exit(void) -{ - if (wmi_requires_smbios_request) - dell_wmi_events_set_enabled(false); - - wmi_driver_unregister(&dell_wmi_driver); -} -module_exit(dell_wmi_exit); - -MODULE_DEVICE_TABLE(wmi, dell_wmi_id_table); -- cgit v1.2.3 From 8af9fa37b8a3637832cbf8fdd9bd828bd5f0de66 Mon Sep 17 00:00:00 2001 From: Perry Yuan Date: Thu, 6 May 2021 19:56:05 +0800 Subject: platform/x86: dell-privacy: Add support for Dell hardware privacy add support for Dell privacy driver for the Dell units equipped hardware privacy design, which protect users privacy of audio and camera from hardware level. Once the audio or camera privacy mode activated, any applications will not get any audio or video stream when user pressed ctrl+F4 hotkey, audio privacy mode will be enabled, micmute led will be also changed accordingly The micmute led is fully controlled by hardware & EC(embedded controller) and camera mute hotkey is Ctrl+F9. Currently design only emits SW_CAMERA_LENS_COVER event while the camera lens shutter will be changed by EC & HW(hardware) control *The flow is like this: 1) User presses key. HW does stuff with this key (timeout timer is started) 2) WMI event is emitted from BIOS to kernel 3) WMI event is received by dell-privacy 4) KEY_MICMUTE emitted from dell-privacy 5) Userland picks up key and modifies kcontrol for SW mute 6) Codec kernel driver catches and calls ledtrig_audio_set 7) dell-privacy notifies EC, the timeout is cancelled and the HW mute is activated. If the EC is not notified then the HW mic mute will activate when the timeout triggers, just a bit later than with the active ack. Signed-off-by: Perry Yuan Link: https://lore.kernel.org/r/20210506115605.1504-1-Perry_Yuan@Dell.com [hdegoede@redhat.com: Rework Kconfig/Makefile bits + other small fixups] Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- .../ABI/testing/sysfs-platform-dell-privacy-wmi | 55 +++ MAINTAINERS | 7 + drivers/platform/x86/dell/Kconfig | 9 + drivers/platform/x86/dell/Makefile | 1 + drivers/platform/x86/dell/dell-laptop.c | 13 +- drivers/platform/x86/dell/dell-wmi-base.c | 14 +- drivers/platform/x86/dell/dell-wmi-privacy.c | 391 +++++++++++++++++++++ drivers/platform/x86/dell/dell-wmi-privacy.h | 36 ++ 8 files changed, 522 insertions(+), 4 deletions(-) create mode 100644 Documentation/ABI/testing/sysfs-platform-dell-privacy-wmi create mode 100644 drivers/platform/x86/dell/dell-wmi-privacy.c create mode 100644 drivers/platform/x86/dell/dell-wmi-privacy.h diff --git a/Documentation/ABI/testing/sysfs-platform-dell-privacy-wmi b/Documentation/ABI/testing/sysfs-platform-dell-privacy-wmi new file mode 100644 index 000000000000..7f9e18705861 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-platform-dell-privacy-wmi @@ -0,0 +1,55 @@ +What: /sys/bus/wmi/devices/6932965F-1671-4CEB-B988-D3AB0A901919/dell_privacy_supported_type +Date: Apr 2021 +KernelVersion: 5.13 +Contact: "perry.yuan@dell.com>" +Description: + Display which dell hardware level privacy devices are supported + “Dell Privacy” is a set of HW, FW, and SW features to enhance + Dell’s commitment to platform privacy for MIC, Camera, and + ePrivacy screens. + The supported hardware privacy devices are: +Attributes: + Microphone Mute: + Identifies the local microphone can be muted by hardware, no applications + is available to capture system mic sound + + Camera Shutter: + Identifies camera shutter controlled by hardware, which is a micromechanical + shutter assembly that is built onto the camera module to block capturing images + from outside the laptop + + supported: + The privacy device is supported by this system + + unsupported: + The privacy device is not supported on this system + + For example to check which privacy devices are supported: + + # cat /sys/bus/wmi/drivers/dell-privacy/6932965F-1671-4CEB-B988-D3AB0A901919/dell_privacy_supported_type + [Microphone Mute] [supported] + [Camera Shutter] [supported] + [ePrivacy Screen] [unsupported] + +What: /sys/bus/wmi/devices/6932965F-1671-4CEB-B988-D3AB0A901919/dell_privacy_current_state +Date: Apr 2021 +KernelVersion: 5.13 +Contact: "perry.yuan@dell.com>" +Description: + Allow user space to check current dell privacy device state. + Describes the Device State class exposed by BIOS which can be + consumed by various applications interested in knowing the Privacy + feature capabilities +Attributes: + muted: + Identifies the privacy device is turned off and cannot send stream to OS applications + + unmuted: + Identifies the privacy device is turned on ,audio or camera driver can get + stream from mic and camera module to OS applications + + For example to check all supported current privacy device states: + + # cat /sys/bus/wmi/drivers/dell-privacy/6932965F-1671-4CEB-B988-D3AB0A901919/dell_privacy_current_state + [Microphone] [unmuted] + [Camera Shutter] [unmuted] diff --git a/MAINTAINERS b/MAINTAINERS index 7b4a325af65a..67fc700c9a87 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5172,6 +5172,13 @@ M: Pali Rohár S: Maintained F: drivers/platform/x86/dell/dell-wmi-base.c +DELL WMI HARDWARE PRIVACY SUPPORT +M: Perry Yuan +L: Dell.Client.Kernel@dell.com +L: platform-driver-x86@vger.kernel.org +S: Maintained +F: drivers/platform/x86/dell/dell-wmi-privacy.c + DELTA ST MEDIA DRIVER M: Hugues Fruchet L: linux-media@vger.kernel.org diff --git a/drivers/platform/x86/dell/Kconfig b/drivers/platform/x86/dell/Kconfig index e0a55337f51a..b5c6a6a32bf9 100644 --- a/drivers/platform/x86/dell/Kconfig +++ b/drivers/platform/x86/dell/Kconfig @@ -53,6 +53,7 @@ config DELL_LAPTOP depends on BACKLIGHT_CLASS_DEVICE depends on ACPI_VIDEO || ACPI_VIDEO = n depends on RFKILL || RFKILL = n + depends on DELL_WMI || DELL_WMI = n depends on SERIO_I8042 depends on DELL_SMBIOS select POWER_SUPPLY @@ -164,6 +165,14 @@ config DELL_WMI To compile this driver as a module, choose M here: the module will be called dell-wmi. +config DELL_WMI_PRIVACY + bool "Dell WMI Hardware Privacy Support" + depends on DELL_WMI + depends on LEDS_TRIGGER_AUDIO + help + This option adds integration with the "Dell Hardware Privacy" + feature of Dell laptops to the dell-wmi driver. + config DELL_WMI_AIO tristate "WMI Hotkeys for Dell All-In-One series" default m diff --git a/drivers/platform/x86/dell/Makefile b/drivers/platform/x86/dell/Makefile index cc45410040cb..ddba1df71e80 100644 --- a/drivers/platform/x86/dell/Makefile +++ b/drivers/platform/x86/dell/Makefile @@ -16,6 +16,7 @@ dell-smbios-$(CONFIG_DELL_SMBIOS_SMM) += dell-smbios-smm.o obj-$(CONFIG_DELL_SMO8800) += dell-smo8800.o obj-$(CONFIG_DELL_WMI) += dell-wmi.o dell-wmi-objs := dell-wmi-base.o +dell-wmi-$(CONFIG_DELL_WMI_PRIVACY) += dell-wmi-privacy.o obj-$(CONFIG_DELL_WMI_AIO) += dell-wmi-aio.o obj-$(CONFIG_DELL_WMI_DESCRIPTOR) += dell-wmi-descriptor.o obj-$(CONFIG_DELL_WMI_LED) += dell-wmi-led.o diff --git a/drivers/platform/x86/dell/dell-laptop.c b/drivers/platform/x86/dell/dell-laptop.c index 70edc5bb3a14..8230e7a68a5e 100644 --- a/drivers/platform/x86/dell/dell-laptop.c +++ b/drivers/platform/x86/dell/dell-laptop.c @@ -31,6 +31,8 @@ #include "dell-rbtn.h" #include "dell-smbios.h" +#include "dell-wmi-privacy.h" + struct quirk_entry { bool touchpad_led; bool kbd_led_not_present; @@ -90,6 +92,7 @@ static struct rfkill *wifi_rfkill; static struct rfkill *bluetooth_rfkill; static struct rfkill *wwan_rfkill; static bool force_rfkill; +static bool micmute_led_registered; module_param(force_rfkill, bool, 0444); MODULE_PARM_DESC(force_rfkill, "enable rfkill on non whitelisted models"); @@ -2205,11 +2208,13 @@ static int __init dell_init(void) dell_laptop_register_notifier(&dell_laptop_notifier); if (dell_smbios_find_token(GLOBAL_MIC_MUTE_DISABLE) && - dell_smbios_find_token(GLOBAL_MIC_MUTE_ENABLE)) { + dell_smbios_find_token(GLOBAL_MIC_MUTE_ENABLE) && + !dell_privacy_has_mic_mute()) { micmute_led_cdev.brightness = ledtrig_audio_get(LED_AUDIO_MICMUTE); ret = led_classdev_register(&platform_device->dev, &micmute_led_cdev); if (ret < 0) goto fail_led; + micmute_led_registered = true; } if (acpi_video_get_backlight_type() != acpi_backlight_vendor) @@ -2257,7 +2262,8 @@ static int __init dell_init(void) fail_get_brightness: backlight_device_unregister(dell_backlight_device); fail_backlight: - led_classdev_unregister(&micmute_led_cdev); + if (micmute_led_registered) + led_classdev_unregister(&micmute_led_cdev); fail_led: dell_cleanup_rfkill(); fail_rfkill: @@ -2278,7 +2284,8 @@ static void __exit dell_exit(void) touchpad_led_exit(); kbd_led_exit(); backlight_device_unregister(dell_backlight_device); - led_classdev_unregister(&micmute_led_cdev); + if (micmute_led_registered) + led_classdev_unregister(&micmute_led_cdev); dell_cleanup_rfkill(); if (platform_device) { platform_device_unregister(platform_device); diff --git a/drivers/platform/x86/dell/dell-wmi-base.c b/drivers/platform/x86/dell/dell-wmi-base.c index 5e1b7f897df5..089c125e18f7 100644 --- a/drivers/platform/x86/dell/dell-wmi-base.c +++ b/drivers/platform/x86/dell/dell-wmi-base.c @@ -27,6 +27,7 @@ #include #include "dell-smbios.h" #include "dell-wmi-descriptor.h" +#include "dell-wmi-privacy.h" MODULE_AUTHOR("Matthew Garrett "); MODULE_AUTHOR("Pali Rohár "); @@ -427,7 +428,6 @@ static void dell_wmi_notify(struct wmi_device *wdev, switch (buffer_entry[1]) { case 0x0000: /* One key pressed or event occurred */ - case 0x0012: /* Event with extended data occurred */ if (len > 2) dell_wmi_process_key(wdev, buffer_entry[1], buffer_entry[2]); @@ -439,6 +439,13 @@ static void dell_wmi_notify(struct wmi_device *wdev, dell_wmi_process_key(wdev, buffer_entry[1], buffer_entry[i]); break; + case 0x0012: + if ((len > 4) && dell_privacy_process_event(buffer_entry[1], buffer_entry[3], + buffer_entry[4])) + /* dell_privacy_process_event has handled the event */; + else if (len > 2) + dell_wmi_process_key(wdev, buffer_entry[1], buffer_entry[2]); + break; default: /* Unknown event */ pr_info("Unknown WMI event type 0x%x\n", (int)buffer_entry[1]); @@ -747,6 +754,10 @@ static int __init dell_wmi_init(void) } } + err = dell_privacy_register_driver(); + if (err) + return err; + return wmi_driver_register(&dell_wmi_driver); } late_initcall(dell_wmi_init); @@ -757,6 +768,7 @@ static void __exit dell_wmi_exit(void) dell_wmi_events_set_enabled(false); wmi_driver_unregister(&dell_wmi_driver); + dell_privacy_unregister_driver(); } module_exit(dell_wmi_exit); diff --git a/drivers/platform/x86/dell/dell-wmi-privacy.c b/drivers/platform/x86/dell/dell-wmi-privacy.c new file mode 100644 index 000000000000..074b7e68c227 --- /dev/null +++ b/drivers/platform/x86/dell/dell-wmi-privacy.c @@ -0,0 +1,391 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Dell privacy notification driver + * + * Copyright (C) 2021 Dell Inc. All Rights Reserved. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dell-wmi-privacy.h" + +#define DELL_PRIVACY_GUID "6932965F-1671-4CEB-B988-D3AB0A901919" +#define MICROPHONE_STATUS BIT(0) +#define CAMERA_STATUS BIT(1) +#define DELL_PRIVACY_AUDIO_EVENT 0x1 +#define DELL_PRIVACY_CAMERA_EVENT 0x2 +#define led_to_priv(c) container_of(c, struct privacy_wmi_data, cdev) + +/* + * The wmi_list is used to store the privacy_priv struct with mutex protecting + */ +static LIST_HEAD(wmi_list); +static DEFINE_MUTEX(list_mutex); + +struct privacy_wmi_data { + struct input_dev *input_dev; + struct wmi_device *wdev; + struct list_head list; + struct led_classdev cdev; + u32 features_present; + u32 last_status; +}; + +/* DELL Privacy Type */ +enum dell_hardware_privacy_type { + DELL_PRIVACY_TYPE_AUDIO = 0, + DELL_PRIVACY_TYPE_CAMERA, + DELL_PRIVACY_TYPE_SCREEN, + DELL_PRIVACY_TYPE_MAX, +}; + +static const char * const privacy_types[DELL_PRIVACY_TYPE_MAX] = { + [DELL_PRIVACY_TYPE_AUDIO] = "Microphone", + [DELL_PRIVACY_TYPE_CAMERA] = "Camera Shutter", + [DELL_PRIVACY_TYPE_SCREEN] = "ePrivacy Screen", +}; + +/* + * Keymap for WMI privacy events of type 0x0012 + */ +static const struct key_entry dell_wmi_keymap_type_0012[] = { + /* privacy mic mute */ + { KE_KEY, 0x0001, { KEY_MICMUTE } }, + /* privacy camera mute */ + { KE_SW, 0x0002, { SW_CAMERA_LENS_COVER } }, + { KE_END, 0}, +}; + +bool dell_privacy_has_mic_mute(void) +{ + struct privacy_wmi_data *priv; + + mutex_lock(&list_mutex); + priv = list_first_entry_or_null(&wmi_list, + struct privacy_wmi_data, + list); + mutex_unlock(&list_mutex); + + return priv && (priv->features_present & BIT(DELL_PRIVACY_TYPE_AUDIO)); +} +EXPORT_SYMBOL_GPL(dell_privacy_has_mic_mute); + +/* + * The flow of privacy event: + * 1) User presses key. HW does stuff with this key (timeout is started) + * 2) WMI event is emitted from BIOS + * 3) WMI event is received by dell-privacy + * 4) KEY_MICMUTE emitted from dell-privacy + * 5) Userland picks up key and modifies kcontrol for SW mute + * 6) Codec kernel driver catches and calls ledtrig_audio_set which will call + * led_set_brightness() on the LED registered by dell_privacy_leds_setup() + * 7) dell-privacy notifies EC, the timeout is cancelled and the HW mute activates. + * If the EC is not notified then the HW mic mute will activate when the timeout + * triggers, just a bit later than with the active ack. + */ +bool dell_privacy_process_event(int type, int code, int status) +{ + struct privacy_wmi_data *priv; + const struct key_entry *key; + bool ret = false; + + mutex_lock(&list_mutex); + priv = list_first_entry_or_null(&wmi_list, + struct privacy_wmi_data, + list); + if (!priv) + goto error; + + key = sparse_keymap_entry_from_scancode(priv->input_dev, (type << 16) | code); + if (!key) { + dev_warn(&priv->wdev->dev, "Unknown key with type 0x%04x and code 0x%04x pressed\n", + type, code); + goto error; + } + dev_dbg(&priv->wdev->dev, "Key with type 0x%04x and code 0x%04x pressed\n", type, code); + + switch (code) { + case DELL_PRIVACY_AUDIO_EVENT: /* Mic mute */ + case DELL_PRIVACY_CAMERA_EVENT: /* Camera mute */ + priv->last_status = status; + sparse_keymap_report_entry(priv->input_dev, key, 1, true); + ret = true; + break; + default: + dev_dbg(&priv->wdev->dev, "unknown event type 0x%04x 0x%04x\n", type, code); + } + +error: + mutex_unlock(&list_mutex); + return ret; +} + +static ssize_t dell_privacy_supported_type_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct privacy_wmi_data *priv = dev_get_drvdata(dev); + enum dell_hardware_privacy_type type; + u32 privacy_list; + int len = 0; + + privacy_list = priv->features_present; + for (type = DELL_PRIVACY_TYPE_AUDIO; type < DELL_PRIVACY_TYPE_MAX; type++) { + if (privacy_list & BIT(type)) + len += sysfs_emit_at(buf, len, "[%s] [supported]\n", privacy_types[type]); + else + len += sysfs_emit_at(buf, len, "[%s] [unsupported]\n", privacy_types[type]); + } + + return len; +} + +static ssize_t dell_privacy_current_state_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct privacy_wmi_data *priv = dev_get_drvdata(dev); + u32 privacy_supported = priv->features_present; + enum dell_hardware_privacy_type type; + u32 privacy_state = priv->last_status; + int len = 0; + + for (type = DELL_PRIVACY_TYPE_AUDIO; type < DELL_PRIVACY_TYPE_MAX; type++) { + if (privacy_supported & BIT(type)) { + if (privacy_state & BIT(type)) + len += sysfs_emit_at(buf, len, "[%s] [unmuted]\n", privacy_types[type]); + else + len += sysfs_emit_at(buf, len, "[%s] [muted]\n", privacy_types[type]); + } + } + + return len; +} + +static DEVICE_ATTR_RO(dell_privacy_supported_type); +static DEVICE_ATTR_RO(dell_privacy_current_state); + +static struct attribute *privacy_attributes[] = { + &dev_attr_dell_privacy_supported_type.attr, + &dev_attr_dell_privacy_current_state.attr, + NULL, +}; + +static const struct attribute_group privacy_attribute_group = { + .attrs = privacy_attributes +}; + +/* + * Describes the Device State class exposed by BIOS which can be consumed by + * various applications interested in knowing the Privacy feature capabilities. + * class DeviceState + * { + * [key, read] string InstanceName; + * [read] boolean ReadOnly; + * + * [WmiDataId(1), read] uint32 DevicesSupported; + * 0 - None; 0x1 - Microphone; 0x2 - Camera; 0x4 - ePrivacy Screen + * + * [WmiDataId(2), read] uint32 CurrentState; + * 0 - Off; 1 - On; Bit0 - Microphone; Bit1 - Camera; Bit2 - ePrivacyScreen + * }; + */ +static int get_current_status(struct wmi_device *wdev) +{ + struct privacy_wmi_data *priv = dev_get_drvdata(&wdev->dev); + union acpi_object *obj_present; + u32 *buffer; + int ret = 0; + + if (!priv) { + dev_err(&wdev->dev, "dell privacy priv is NULL\n"); + return -EINVAL; + } + /* check privacy support features and device states */ + obj_present = wmidev_block_query(wdev, 0); + if (!obj_present) { + dev_err(&wdev->dev, "failed to read Binary MOF\n"); + return -EIO; + } + + if (obj_present->type != ACPI_TYPE_BUFFER) { + dev_err(&wdev->dev, "Binary MOF is not a buffer!\n"); + ret = -EIO; + goto obj_free; + } + /* Although it's not technically a failure, this would lead to + * unexpected behavior + */ + if (obj_present->buffer.length != 8) { + dev_err(&wdev->dev, "Dell privacy buffer has unexpected length (%d)!\n", + obj_present->buffer.length); + ret = -EINVAL; + goto obj_free; + } + buffer = (u32 *)obj_present->buffer.pointer; + priv->features_present = buffer[0]; + priv->last_status = buffer[1]; + +obj_free: + kfree(obj_present); + return ret; +} + +static int dell_privacy_micmute_led_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct privacy_wmi_data *priv = led_to_priv(led_cdev); + static char *acpi_method = (char *)"ECAK"; + acpi_status status; + acpi_handle handle; + + handle = ec_get_handle(); + if (!handle) + return -EIO; + + if (!acpi_has_method(handle, acpi_method)) + return -EIO; + + status = acpi_evaluate_object(handle, acpi_method, NULL, NULL); + if (ACPI_FAILURE(status)) { + dev_err(&priv->wdev->dev, "Error setting privacy EC ack value: %s\n", + acpi_format_exception(status)); + return -EIO; + } + + return 0; +} + +/* + * Pressing the mute key activates a time delayed circuit to physically cut + * off the mute. The LED is in the same circuit, so it reflects the true + * state of the HW mute. The reason for the EC "ack" is so that software + * can first invoke a SW mute before the HW circuit is cut off. Without SW + * cutting this off first does not affect the time delayed muting or status + * of the LED but there is a possibility of a "popping" noise. + * + * If the EC receives the SW ack, the circuit will be activated before the + * delay completed. + * + * Exposing as an LED device allows the codec drivers notification path to + * EC ACK to work + */ +static int dell_privacy_leds_setup(struct device *dev) +{ + struct privacy_wmi_data *priv = dev_get_drvdata(dev); + + priv->cdev.name = "dell-privacy::micmute"; + priv->cdev.max_brightness = 1; + priv->cdev.brightness_set_blocking = dell_privacy_micmute_led_set; + priv->cdev.default_trigger = "audio-micmute"; + priv->cdev.brightness = ledtrig_audio_get(LED_AUDIO_MICMUTE); + return devm_led_classdev_register(dev, &priv->cdev); +} + +static int dell_privacy_wmi_probe(struct wmi_device *wdev, const void *context) +{ + struct privacy_wmi_data *priv; + struct key_entry *keymap; + int ret, i; + + ret = wmi_has_guid(DELL_PRIVACY_GUID); + if (!ret) + pr_debug("Unable to detect available Dell privacy devices!\n"); + + priv = devm_kzalloc(&wdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + dev_set_drvdata(&wdev->dev, priv); + priv->wdev = wdev; + /* create evdev passing interface */ + priv->input_dev = devm_input_allocate_device(&wdev->dev); + if (!priv->input_dev) + return -ENOMEM; + + /* remap the wmi keymap event to new keymap */ + keymap = kcalloc(ARRAY_SIZE(dell_wmi_keymap_type_0012), + sizeof(struct key_entry), GFP_KERNEL); + if (!keymap) + return -ENOMEM; + + /* remap the keymap code with Dell privacy key type 0x12 as prefix + * KEY_MICMUTE scancode will be reported as 0x120001 + */ + for (i = 0; i < ARRAY_SIZE(dell_wmi_keymap_type_0012); i++) { + keymap[i] = dell_wmi_keymap_type_0012[i]; + keymap[i].code |= (0x0012 << 16); + } + ret = sparse_keymap_setup(priv->input_dev, keymap, NULL); + kfree(keymap); + if (ret) + return ret; + + priv->input_dev->dev.parent = &wdev->dev; + priv->input_dev->name = "Dell Privacy Driver"; + priv->input_dev->id.bustype = BUS_HOST; + + ret = input_register_device(priv->input_dev); + if (ret) + return ret; + + ret = get_current_status(priv->wdev); + if (ret) + return ret; + + ret = devm_device_add_group(&wdev->dev, &privacy_attribute_group); + if (ret) + return ret; + + if (priv->features_present & BIT(DELL_PRIVACY_TYPE_AUDIO)) { + ret = dell_privacy_leds_setup(&priv->wdev->dev); + if (ret) + return ret; + } + mutex_lock(&list_mutex); + list_add_tail(&priv->list, &wmi_list); + mutex_unlock(&list_mutex); + return 0; +} + +static void dell_privacy_wmi_remove(struct wmi_device *wdev) +{ + struct privacy_wmi_data *priv = dev_get_drvdata(&wdev->dev); + + mutex_lock(&list_mutex); + list_del(&priv->list); + mutex_unlock(&list_mutex); +} + +static const struct wmi_device_id dell_wmi_privacy_wmi_id_table[] = { + { .guid_string = DELL_PRIVACY_GUID }, + { }, +}; + +static struct wmi_driver dell_privacy_wmi_driver = { + .driver = { + .name = "dell-privacy", + }, + .probe = dell_privacy_wmi_probe, + .remove = dell_privacy_wmi_remove, + .id_table = dell_wmi_privacy_wmi_id_table, +}; + +int dell_privacy_register_driver(void) +{ + return wmi_driver_register(&dell_privacy_wmi_driver); +} + +void dell_privacy_unregister_driver(void) +{ + wmi_driver_unregister(&dell_privacy_wmi_driver); +} diff --git a/drivers/platform/x86/dell/dell-wmi-privacy.h b/drivers/platform/x86/dell/dell-wmi-privacy.h new file mode 100644 index 000000000000..50c9b943dd47 --- /dev/null +++ b/drivers/platform/x86/dell/dell-wmi-privacy.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Dell privacy notification driver + * + * Copyright (C) 2021 Dell Inc. All Rights Reserved. + */ + +#ifndef _DELL_PRIVACY_WMI_H_ +#define _DELL_PRIVACY_WMI_H_ + +#if IS_ENABLED(CONFIG_DELL_WMI_PRIVACY) +bool dell_privacy_has_mic_mute(void); +bool dell_privacy_process_event(int type, int code, int status); +int dell_privacy_register_driver(void); +void dell_privacy_unregister_driver(void); +#else /* CONFIG_DELL_PRIVACY */ +static inline bool dell_privacy_has_mic_mute(void) +{ + return false; +} + +static inline bool dell_privacy_process_event(int type, int code, int status) +{ + return false; +} + +static inline int dell_privacy_register_driver(void) +{ + return 0; +} + +static inline void dell_privacy_unregister_driver(void) +{ +} +#endif /* CONFIG_DELL_PRIVACY */ +#endif -- cgit v1.2.3 From f7b056b48029d9f31628a21c5630263775e25793 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sun, 30 May 2021 12:47:44 +0200 Subject: platform/x86: touchscreen_dmi: Fix Chuwi Hi10 Pro comment Fix the comment on the entry for the Chuwi Hi10 Pro tablet: 1. Replace "Prus" type with "Pro". 2. Fix the model number, the Chuwi Hi10 Pro is the CWI529, not the CWI597. Signed-off-by: Hans de Goede Link: https://lore.kernel.org/r/20210530104744.6720-1-hdegoede@redhat.com --- drivers/platform/x86/touchscreen_dmi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/platform/x86/touchscreen_dmi.c b/drivers/platform/x86/touchscreen_dmi.c index bde740d6120e..455a8302773a 100644 --- a/drivers/platform/x86/touchscreen_dmi.c +++ b/drivers/platform/x86/touchscreen_dmi.c @@ -942,7 +942,7 @@ const struct dmi_system_id touchscreen_dmi_table[] = { }, }, { - /* Chuwi Hi10 Prus (CWI597) */ + /* Chuwi Hi10 Pro (CWI529) */ .driver_data = (void *)&chuwi_hi10_pro_data, .matches = { DMI_MATCH(DMI_BOARD_VENDOR, "Hampoo"), -- cgit v1.2.3 From 28e367127718a9cb85d615a71e152f7acee41bfc Mon Sep 17 00:00:00 2001 From: Jiapeng Chong Date: Wed, 2 Jun 2021 18:05:48 +0800 Subject: platform/x86: toshiba_acpi: Fix missing error code in toshiba_acpi_setup_keyboard() The error code is missing in this code scenario, add the error code '-EINVAL' to the return value 'error'. Eliminate the follow smatch warning: drivers/platform/x86/toshiba_acpi.c:2834 toshiba_acpi_setup_keyboard() warn: missing error code 'error'. Reported-by: Abaci Robot Signed-off-by: Jiapeng Chong Link: https://lore.kernel.org/r/1622628348-87035-1-git-send-email-jiapeng.chong@linux.alibaba.com Signed-off-by: Hans de Goede --- drivers/platform/x86/toshiba_acpi.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/platform/x86/toshiba_acpi.c b/drivers/platform/x86/toshiba_acpi.c index fa7232ad8c39..352508d30467 100644 --- a/drivers/platform/x86/toshiba_acpi.c +++ b/drivers/platform/x86/toshiba_acpi.c @@ -2831,6 +2831,7 @@ static int toshiba_acpi_setup_keyboard(struct toshiba_acpi_dev *dev) if (!dev->info_supported && !dev->system_event_supported) { pr_warn("No hotkey query interface found\n"); + error = -EINVAL; goto err_remove_filter; } -- cgit v1.2.3 From 6cbaee2e109ed0f7327a2d3cbb412f36fd8873e0 Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Thu, 3 Jun 2021 02:06:36 +0200 Subject: platform/surface: aggregator: Fix event disable function Disabling events silently fails due to the wrong command ID being used. Instead of the command ID for the disable call, the command ID for the enable call was being used. This causes the disable call to enable the event instead. As the event is already enabled when we call this function, the EC silently drops this command and does nothing. Use the correct command ID for disabling the event to fix this. Fixes: c167b9c7e3d6 ("platform/surface: Add Surface Aggregator subsystem") Signed-off-by: Maximilian Luz Link: https://lore.kernel.org/r/20210603000636.568846-1-luzmaximilian@gmail.com Signed-off-by: Hans de Goede --- drivers/platform/surface/aggregator/controller.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c index 8a70df60142c..a06964aa96e7 100644 --- a/drivers/platform/surface/aggregator/controller.c +++ b/drivers/platform/surface/aggregator/controller.c @@ -1907,7 +1907,7 @@ static int ssam_ssh_event_disable(struct ssam_controller *ctrl, { int status; - status = __ssam_ssh_event_request(ctrl, reg, reg.cid_enable, id, flags); + status = __ssam_ssh_event_request(ctrl, reg, reg.cid_disable, id, flags); if (status < 0 && status != -EINVAL) { ssam_err(ctrl, -- cgit v1.2.3 From 17b707fe5fbd3c019691873c1c11bddb0e0f7225 Mon Sep 17 00:00:00 2001 From: Mark Pearson Date: Sun, 30 May 2021 18:31:09 -0400 Subject: platform/x86: firmware_attributes_class: Create helper file for handling firmware-attributes class registration events This offers shared code for registering the firmware_attributes_class, which is used by the Dell and Lenovo WMI management drivers. Reported-by: kernel test robot Signed-off-by: Mark Pearson Link: https://lore.kernel.org/r/20210530223111.25929-1-markpearson@lenovo.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/Kconfig | 4 ++ drivers/platform/x86/Makefile | 1 + drivers/platform/x86/firmware_attributes_class.c | 52 ++++++++++++++++++++++++ drivers/platform/x86/firmware_attributes_class.h | 11 +++++ 4 files changed, 68 insertions(+) create mode 100644 drivers/platform/x86/firmware_attributes_class.c create mode 100644 drivers/platform/x86/firmware_attributes_class.h diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 9a668dae2738..d6fa071d78de 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -1077,6 +1077,10 @@ config TOUCHSCREEN_DMI the OS-image for the device. This option supplies the missing info. Enable this for x86 tablets with Silead or Chipone touchscreens. +config FW_ATTR_CLASS + tristate + default n + config INTEL_IMR bool "Intel Isolated Memory Region support" depends on X86_INTEL_QUARK && IOSF_MBI diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 415bec18ece2..98c776967fa0 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -111,6 +111,7 @@ obj-$(CONFIG_SYSTEM76_ACPI) += system76_acpi.o obj-$(CONFIG_TOPSTAR_LAPTOP) += topstar-laptop.o # Platform drivers +obj-$(CONFIG_FW_ATTR_CLASS) += firmware_attributes_class.o obj-$(CONFIG_I2C_MULTI_INSTANTIATE) += i2c-multi-instantiate.o obj-$(CONFIG_MLX_PLATFORM) += mlx-platform.o obj-$(CONFIG_TOUCHSCREEN_DMI) += touchscreen_dmi.o diff --git a/drivers/platform/x86/firmware_attributes_class.c b/drivers/platform/x86/firmware_attributes_class.c new file mode 100644 index 000000000000..d62ec3d71ede --- /dev/null +++ b/drivers/platform/x86/firmware_attributes_class.c @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +/* Firmware attributes class helper module */ + +#include +#include +#include +#include "firmware_attributes_class.h" + +static DEFINE_MUTEX(fw_attr_lock); +int fw_attr_inuse; + +static struct class firmware_attributes_class = { + .name = "firmware-attributes", +}; + +int fw_attributes_class_get(struct class **fw_attr_class) +{ + int err; + + mutex_lock(&fw_attr_lock); + if (!fw_attr_inuse) { /*first time class is being used*/ + err = class_register(&firmware_attributes_class); + if (err) { + mutex_unlock(&fw_attr_lock); + return err; + } + } + fw_attr_inuse++; + *fw_attr_class = &firmware_attributes_class; + mutex_unlock(&fw_attr_lock); + return 0; +} +EXPORT_SYMBOL_GPL(fw_attributes_class_get); + +int fw_attributes_class_put(void) +{ + mutex_lock(&fw_attr_lock); + if (!fw_attr_inuse) { + mutex_unlock(&fw_attr_lock); + return -EINVAL; + } + fw_attr_inuse--; + if (!fw_attr_inuse) /* No more consumers */ + class_unregister(&firmware_attributes_class); + mutex_unlock(&fw_attr_lock); + return 0; +} +EXPORT_SYMBOL_GPL(fw_attributes_class_put); + +MODULE_AUTHOR("Mark Pearson "); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/firmware_attributes_class.h b/drivers/platform/x86/firmware_attributes_class.h new file mode 100644 index 000000000000..486485cb1f54 --- /dev/null +++ b/drivers/platform/x86/firmware_attributes_class.h @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +/* Firmware attributes class helper module */ + +#ifndef FW_ATTR_CLASS_H +#define FW_ATTR_CLASS_H + +int fw_attributes_class_get(struct class **fw_attr_class); +int fw_attributes_class_put(void); + +#endif /* FW_ATTR_CLASS_H */ -- cgit v1.2.3 From 8a1c379c5a03281295c436faf21b0e4716e5b8a8 Mon Sep 17 00:00:00 2001 From: Mark Pearson Date: Sun, 30 May 2021 18:31:10 -0400 Subject: platform/x86: dell-wmi-sysman: Use firmware_attributes_class helper Update Dell WMI sysman driver to use newly implemented helper module. Reviewed-by: Hans de Goede Signed-off-by: Mark Pearson Link: https://lore.kernel.org/r/20210530223111.25929-2-markpearson@lenovo.com Signed-off-by: Hans de Goede --- drivers/platform/x86/dell/Kconfig | 1 + drivers/platform/x86/dell/dell-wmi-sysman/sysman.c | 18 ++++++++---------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/drivers/platform/x86/dell/Kconfig b/drivers/platform/x86/dell/Kconfig index b5c6a6a32bf9..9b0a4d080f43 100644 --- a/drivers/platform/x86/dell/Kconfig +++ b/drivers/platform/x86/dell/Kconfig @@ -206,6 +206,7 @@ config DELL_WMI_SYSMAN depends on ACPI_WMI depends on DMI select NLS + select FW_ATTR_CLASS help This driver allows changing BIOS settings on many Dell machines from 2018 and newer without the use of any additional software. diff --git a/drivers/platform/x86/dell/dell-wmi-sysman/sysman.c b/drivers/platform/x86/dell/dell-wmi-sysman/sysman.c index d21e84c7a694..1378c1878658 100644 --- a/drivers/platform/x86/dell/dell-wmi-sysman/sysman.c +++ b/drivers/platform/x86/dell/dell-wmi-sysman/sysman.c @@ -13,14 +13,11 @@ #include #include #include "dell-wmi-sysman.h" +#include "../../firmware_attributes_class.h" #define MAX_TYPES 4 #include -static struct class firmware_attributes_class = { - .name = "firmware-attributes", -}; - struct wmi_sysman_priv wmi_priv = { .mutex = __MUTEX_INITIALIZER(wmi_priv.mutex), }; @@ -28,6 +25,7 @@ struct wmi_sysman_priv wmi_priv = { /* reset bios to defaults */ static const char * const reset_types[] = {"builtinsafe", "lastknowngood", "factory", "custom"}; static int reset_option = -1; +struct class *fw_attr_class; /** @@ -542,11 +540,11 @@ static int __init sysman_init(void) goto err_exit_bios_attr_pass_interface; } - ret = class_register(&firmware_attributes_class); + ret = fw_attributes_class_get(&fw_attr_class); if (ret) goto err_exit_bios_attr_pass_interface; - wmi_priv.class_dev = device_create(&firmware_attributes_class, NULL, MKDEV(0, 0), + wmi_priv.class_dev = device_create(fw_attr_class, NULL, MKDEV(0, 0), NULL, "%s", DRIVER_NAME); if (IS_ERR(wmi_priv.class_dev)) { ret = PTR_ERR(wmi_priv.class_dev); @@ -603,10 +601,10 @@ err_release_attributes_data: release_attributes_data(); err_destroy_classdev: - device_destroy(&firmware_attributes_class, MKDEV(0, 0)); + device_destroy(fw_attr_class, MKDEV(0, 0)); err_unregister_class: - class_unregister(&firmware_attributes_class); + fw_attributes_class_put(); err_exit_bios_attr_pass_interface: exit_bios_attr_pass_interface(); @@ -620,8 +618,8 @@ err_exit_bios_attr_set_interface: static void __exit sysman_exit(void) { release_attributes_data(); - device_destroy(&firmware_attributes_class, MKDEV(0, 0)); - class_unregister(&firmware_attributes_class); + device_destroy(fw_attr_class, MKDEV(0, 0)); + fw_attributes_class_put(); exit_bios_attr_set_interface(); exit_bios_attr_pass_interface(); } -- cgit v1.2.3 From a40cd7ef22fbb11229cf982920f4ec96c1f49282 Mon Sep 17 00:00:00 2001 From: Mark Pearson Date: Sun, 30 May 2021 18:31:11 -0400 Subject: platform/x86: think-lmi: Add WMI interface support on Lenovo platforms For Lenovo platforms that support a WMI interface to the BIOS add support, using the firmware-attributes class, to allow users to access and modify various BIOS related settings. Signed-off-by: Mark Pearson Link: https://lore.kernel.org/r/20210530223111.25929-3-markpearson@lenovo.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- .../ABI/testing/sysfs-class-firmware-attributes | 18 +- MAINTAINERS | 7 + drivers/platform/x86/Kconfig | 11 + drivers/platform/x86/Makefile | 1 + drivers/platform/x86/think-lmi.c | 891 +++++++++++++++++++++ drivers/platform/x86/think-lmi.h | 81 ++ 6 files changed, 1008 insertions(+), 1 deletion(-) create mode 100644 drivers/platform/x86/think-lmi.c create mode 100644 drivers/platform/x86/think-lmi.h diff --git a/Documentation/ABI/testing/sysfs-class-firmware-attributes b/Documentation/ABI/testing/sysfs-class-firmware-attributes index 8ea59fea4709..3348bf80a37c 100644 --- a/Documentation/ABI/testing/sysfs-class-firmware-attributes +++ b/Documentation/ABI/testing/sysfs-class-firmware-attributes @@ -197,8 +197,24 @@ Description: Drivers may emit a CHANGE uevent when a password is set or unset userspace may check it again. - On Dell systems, if Admin password is set, then all BIOS attributes + On Dell and Lenovo systems, if Admin password is set, then all BIOS attributes require password validation. + On Lenovo systems if you change the Admin password the new password is not active until + the next boot. + + Lenovo specific class extensions + ------------------------------ + + On Lenovo systems the following additional settings are available: + + lenovo_encoding: + The encoding method that is used. This can be either "ascii" + or "scancode". Default is set to "ascii" + + lenovo_kbdlang: + The keyboard language method that is used. This is generally a + two char code (e.g. "us", "fr", "gr") and may vary per platform. + Default is set to "us" What: /sys/class/firmware-attributes/*/attributes/pending_reboot Date: February 2021 diff --git a/MAINTAINERS b/MAINTAINERS index 67fc700c9a87..771ea74e7e2d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -18163,6 +18163,13 @@ W: http://thinkwiki.org/wiki/Ibm-acpi T: git git://repo.or.cz/linux-2.6/linux-acpi-2.6/ibm-acpi-2.6.git F: drivers/platform/x86/thinkpad_acpi.c +THINKPAD LMI DRIVER +M: Mark Pearson +L: platform-driver-x86@vger.kernel.org +S: Maintained +F: Documentation/ABI/testing/sysfs-class-firmware-attributes +F: drivers/platform/x86/think-lmi.? + THUNDERBOLT DMA TRAFFIC TEST DRIVER M: Isaac Hazan L: linux-usb@vger.kernel.org diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index d6fa071d78de..1e538ce8feaf 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -640,6 +640,17 @@ config THINKPAD_ACPI_HOTKEY_POLL If you are not sure, say Y here. The driver enables polling only if it is strictly necessary to do so. +config THINKPAD_LMI + tristate "Lenovo WMI-based systems management driver" + depends on ACPI_WMI + select FW_ATTR_CLASS + help + This driver allows changing BIOS settings on Lenovo machines whose + BIOS support the WMI interface. + + To compile this driver as a module, choose M here: the module will + be called think-lmi. + config INTEL_ATOMISP2_LED tristate "Intel AtomISP2 camera LED driver" depends on GPIOLIB && LEDS_GPIO diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 98c776967fa0..ff620d653d39 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -63,6 +63,7 @@ obj-$(CONFIG_IBM_RTL) += ibm_rtl.o obj-$(CONFIG_IDEAPAD_LAPTOP) += ideapad-laptop.o obj-$(CONFIG_SENSORS_HDAPS) += hdaps.o obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o +obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o # Intel obj-$(CONFIG_INTEL_ATOMISP2_LED) += intel_atomisp2_led.o diff --git a/drivers/platform/x86/think-lmi.c b/drivers/platform/x86/think-lmi.c new file mode 100644 index 000000000000..854427fed1a9 --- /dev/null +++ b/drivers/platform/x86/think-lmi.c @@ -0,0 +1,891 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Think LMI BIOS configuration driver + * + * Copyright(C) 2019-2021 Lenovo + * + * Original code from Thinkpad-wmi project https://github.com/iksaif/thinkpad-wmi + * Copyright(C) 2017 Corentin Chary + * Distributed under the GPL-2.0 license + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include "firmware_attributes_class.h" +#include "think-lmi.h" + +/* + * Name: + * Lenovo_BiosSetting + * Description: + * Get item name and settings for current LMI instance. + * Type: + * Query + * Returns: + * "Item,Value" + * Example: + * "WakeOnLAN,Enable" + */ +#define LENOVO_BIOS_SETTING_GUID "51F5230E-9677-46CD-A1CF-C0B23EE34DB7" + +/* + * Name: + * Lenovo_SetBiosSetting + * Description: + * Change the BIOS setting to the desired value using the Lenovo_SetBiosSetting + * class. To save the settings, use the Lenovo_SaveBiosSetting class. + * BIOS settings and values are case sensitive. + * After making changes to the BIOS settings, you must reboot the computer + * before the changes will take effect. + * Type: + * Method + * Arguments: + * "Item,Value,Password,Encoding,KbdLang;" + * Example: + * "WakeOnLAN,Disable,pa55w0rd,ascii,us;" + */ +#define LENOVO_SET_BIOS_SETTINGS_GUID "98479A64-33F5-4E33-A707-8E251EBBC3A1" + +/* + * Name: + * Lenovo_SaveBiosSettings + * Description: + * Save any pending changes in settings. + * Type: + * Method + * Arguments: + * "Password,Encoding,KbdLang;" + * Example: + * "pa55w0rd,ascii,us;" + */ +#define LENOVO_SAVE_BIOS_SETTINGS_GUID "6A4B54EF-A5ED-4D33-9455-B0D9B48DF4B3" + +/* + * Name: + * Lenovo_BiosPasswordSettings + * Description: + * Return BIOS Password settings + * Type: + * Query + * Returns: + * PasswordMode, PasswordState, MinLength, MaxLength, + * SupportedEncoding, SupportedKeyboard + */ +#define LENOVO_BIOS_PASSWORD_SETTINGS_GUID "8ADB159E-1E32-455C-BC93-308A7ED98246" + +/* + * Name: + * Lenovo_SetBiosPassword + * Description: + * Change a specific password. + * - BIOS settings cannot be changed at the same boot as power-on + * passwords (POP) and hard disk passwords (HDP). If you want to change + * BIOS settings and POP or HDP, you must reboot the system after changing + * one of them. + * - A password cannot be set using this method when one does not already + * exist. Passwords can only be updated or cleared. + * Type: + * Method + * Arguments: + * "PasswordType,CurrentPassword,NewPassword,Encoding,KbdLang;" + * Example: + * "pop,pa55w0rd,newpa55w0rd,ascii,us;” + */ +#define LENOVO_SET_BIOS_PASSWORD_GUID "2651D9FD-911C-4B69-B94E-D0DED5963BD7" + +/* + * Name: + * Lenovo_GetBiosSelections + * Description: + * Return a list of valid settings for a given item. + * Type: + * Method + * Arguments: + * "Item" + * Returns: + * "Value1,Value2,Value3,..." + * Example: + * -> "FlashOverLAN" + * <- "Enabled,Disabled" + */ +#define LENOVO_GET_BIOS_SELECTIONS_GUID "7364651A-132F-4FE7-ADAA-40C6C7EE2E3B" + +#define TLMI_POP_PWD (1 << 0) +#define TLMI_PAP_PWD (1 << 1) +#define to_tlmi_pwd_setting(kobj) container_of(kobj, struct tlmi_pwd_setting, kobj) +#define to_tlmi_attr_setting(kobj) container_of(kobj, struct tlmi_attr_setting, kobj) + +static const struct tlmi_err_codes tlmi_errs[] = { + {"Success", 0}, + {"Not Supported", -EOPNOTSUPP}, + {"Invalid Parameter", -EINVAL}, + {"Access Denied", -EACCES}, + {"System Busy", -EBUSY}, +}; + +static const char * const encoding_options[] = { + [TLMI_ENCODING_ASCII] = "ascii", + [TLMI_ENCODING_SCANCODE] = "scancode", +}; +static struct think_lmi tlmi_priv; +struct class *fw_attr_class; + +/* ------ Utility functions ------------*/ +/* Convert BIOS WMI error string to suitable error code */ +static int tlmi_errstr_to_err(const char *errstr) +{ + int i; + + for (i = 0; i < sizeof(tlmi_errs)/sizeof(struct tlmi_err_codes); i++) { + if (!strcmp(tlmi_errs[i].err_str, errstr)) + return tlmi_errs[i].err_code; + } + return -EPERM; +} + +/* Extract error string from WMI return buffer */ +static int tlmi_extract_error(const struct acpi_buffer *output) +{ + const union acpi_object *obj; + + obj = output->pointer; + if (!obj) + return -ENOMEM; + if (obj->type != ACPI_TYPE_STRING || !obj->string.pointer) + return -EIO; + + return tlmi_errstr_to_err(obj->string.pointer); +} + +/* Utility function to execute WMI call to BIOS */ +static int tlmi_simple_call(const char *guid, const char *arg) +{ + const struct acpi_buffer input = { strlen(arg), (char *)arg }; + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; + acpi_status status; + int i, err; + + /* + * Duplicated call required to match BIOS workaround for behavior + * seen when WMI accessed via scripting on other OS. + */ + for (i = 0; i < 2; i++) { + /* (re)initialize output buffer to default state */ + output.length = ACPI_ALLOCATE_BUFFER; + output.pointer = NULL; + + status = wmi_evaluate_method(guid, 0, 0, &input, &output); + if (ACPI_FAILURE(status)) { + kfree(output.pointer); + return -EIO; + } + err = tlmi_extract_error(&output); + kfree(output.pointer); + if (err) + return err; + } + return 0; +} + +/* Extract output string from WMI return buffer */ +static int tlmi_extract_output_string(const struct acpi_buffer *output, + char **string) +{ + const union acpi_object *obj; + char *s; + + obj = output->pointer; + if (!obj) + return -ENOMEM; + if (obj->type != ACPI_TYPE_STRING || !obj->string.pointer) + return -EIO; + + s = kstrdup(obj->string.pointer, GFP_KERNEL); + if (!s) + return -ENOMEM; + *string = s; + return 0; +} + +/* ------ Core interface functions ------------*/ + +/* Get password settings from BIOS */ +static int tlmi_get_pwd_settings(struct tlmi_pwdcfg *pwdcfg) +{ + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; + const union acpi_object *obj; + acpi_status status; + + if (!tlmi_priv.can_get_password_settings) + return -EOPNOTSUPP; + + status = wmi_query_block(LENOVO_BIOS_PASSWORD_SETTINGS_GUID, 0, + &output); + if (ACPI_FAILURE(status)) + return -EIO; + + obj = output.pointer; + if (!obj) + return -ENOMEM; + if (obj->type != ACPI_TYPE_BUFFER || !obj->buffer.pointer) { + kfree(obj); + return -EIO; + } + /* + * The size of thinkpad_wmi_pcfg on ThinkStation is larger than ThinkPad. + * To make the driver compatible on different brands, we permit it to get + * the data in below case. + */ + if (obj->buffer.length < sizeof(struct tlmi_pwdcfg)) { + pr_warn("Unknown pwdcfg buffer length %d\n", obj->buffer.length); + kfree(obj); + return -EIO; + } + memcpy(pwdcfg, obj->buffer.pointer, sizeof(struct tlmi_pwdcfg)); + kfree(obj); + return 0; +} + +static int tlmi_save_bios_settings(const char *password) +{ + return tlmi_simple_call(LENOVO_SAVE_BIOS_SETTINGS_GUID, + password); +} + +static int tlmi_setting(int item, char **value, const char *guid_string) +{ + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; + acpi_status status; + int ret; + + status = wmi_query_block(guid_string, item, &output); + if (ACPI_FAILURE(status)) { + kfree(output.pointer); + return -EIO; + } + + ret = tlmi_extract_output_string(&output, value); + kfree(output.pointer); + return ret; +} + +static int tlmi_get_bios_selections(const char *item, char **value) +{ + const struct acpi_buffer input = { strlen(item), (char *)item }; + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; + acpi_status status; + int ret; + + status = wmi_evaluate_method(LENOVO_GET_BIOS_SELECTIONS_GUID, + 0, 0, &input, &output); + + if (ACPI_FAILURE(status)) { + kfree(output.pointer); + return -EIO; + } + + ret = tlmi_extract_output_string(&output, value); + kfree(output.pointer); + return ret; +} + +/* ---- Authentication sysfs --------------------------------------------------------- */ +static ssize_t is_enabled_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj); + + return sysfs_emit(buf, "%d\n", setting->valid); +} + +static struct kobj_attribute auth_is_pass_set = __ATTR_RO(is_enabled); + +static ssize_t current_password_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count) +{ + struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj); + size_t pwdlen; + char *p; + + pwdlen = strlen(buf); + /* pwdlen == 0 is allowed to clear the password */ + if (pwdlen && ((pwdlen < setting->minlen) || (pwdlen > setting->maxlen))) + return -EINVAL; + + strscpy(setting->password, buf, setting->maxlen); + /* Strip out CR if one is present, setting password won't work if it is present */ + p = strchrnul(setting->password, '\n'); + *p = '\0'; + return count; +} + +static struct kobj_attribute auth_current_password = __ATTR_WO(current_password); + +static ssize_t new_password_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count) +{ + struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj); + char *auth_str, *new_pwd, *p; + size_t pwdlen; + int ret; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + if (!tlmi_priv.can_set_bios_password) + return -EOPNOTSUPP; + + new_pwd = kstrdup(buf, GFP_KERNEL); + if (!new_pwd) + return -ENOMEM; + + /* Strip out CR if one is present, setting password won't work if it is present */ + p = strchrnul(new_pwd, '\n'); + *p = '\0'; + + pwdlen = strlen(new_pwd); + /* pwdlen == 0 is allowed to clear the password */ + if (pwdlen && ((pwdlen < setting->minlen) || (pwdlen > setting->maxlen))) { + ret = -EINVAL; + goto out; + } + + /* Format: 'PasswordType,CurrentPw,NewPw,Encoding,KbdLang;' */ + auth_str = kasprintf(GFP_KERNEL, "%s,%s,%s,%s,%s;", + setting->pwd_type, setting->password, new_pwd, + encoding_options[setting->encoding], setting->kbdlang); + if (!auth_str) { + ret = -ENOMEM; + goto out; + } + ret = tlmi_simple_call(LENOVO_SET_BIOS_PASSWORD_GUID, auth_str); + kfree(auth_str); +out: + kfree(new_pwd); + return ret ?: count; +} + +static struct kobj_attribute auth_new_password = __ATTR_WO(new_password); + +static ssize_t min_password_length_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj); + + return sysfs_emit(buf, "%d\n", setting->minlen); +} + +static struct kobj_attribute auth_min_pass_length = __ATTR_RO(min_password_length); + +static ssize_t max_password_length_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj); + + return sysfs_emit(buf, "%d\n", setting->maxlen); +} +static struct kobj_attribute auth_max_pass_length = __ATTR_RO(max_password_length); + +static ssize_t mechanism_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + return sysfs_emit(buf, "password\n"); +} +static struct kobj_attribute auth_mechanism = __ATTR_RO(mechanism); + +static ssize_t encoding_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj); + + return sysfs_emit(buf, "%s\n", encoding_options[setting->encoding]); +} + +static ssize_t encoding_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count) +{ + struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj); + int i; + + /* Scan for a matching profile */ + i = sysfs_match_string(encoding_options, buf); + if (i < 0) + return -EINVAL; + + setting->encoding = i; + return count; +} + +static struct kobj_attribute auth_encoding = __ATTR_RW(encoding); + +static ssize_t kbdlang_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj); + + return sysfs_emit(buf, "%s\n", setting->kbdlang); +} + +static ssize_t kbdlang_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count) +{ + struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj); + int length; + + length = strlen(buf); + if (buf[length-1] == '\n') + length--; + + if (!length || (length >= TLMI_LANG_MAXLEN)) + return -EINVAL; + + memcpy(setting->kbdlang, buf, length); + setting->kbdlang[length] = '\0'; + return count; +} + +static struct kobj_attribute auth_kbdlang = __ATTR_RW(kbdlang); + +static ssize_t role_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj); + + return sysfs_emit(buf, "%s\n", setting->role); +} +static struct kobj_attribute auth_role = __ATTR_RO(role); + +static struct attribute *auth_attrs[] = { + &auth_is_pass_set.attr, + &auth_min_pass_length.attr, + &auth_max_pass_length.attr, + &auth_current_password.attr, + &auth_new_password.attr, + &auth_role.attr, + &auth_mechanism.attr, + &auth_encoding.attr, + &auth_kbdlang.attr, + NULL +}; + +static const struct attribute_group auth_attr_group = { + .attrs = auth_attrs, +}; + +/* ---- Attributes sysfs --------------------------------------------------------- */ +static ssize_t display_name_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + struct tlmi_attr_setting *setting = to_tlmi_attr_setting(kobj); + + return sysfs_emit(buf, "%s\n", setting->display_name); +} + +static ssize_t current_value_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) +{ + struct tlmi_attr_setting *setting = to_tlmi_attr_setting(kobj); + char *item; + int ret; + + ret = tlmi_setting(setting->index, &item, LENOVO_BIOS_SETTING_GUID); + if (ret) + return ret; + + ret = sysfs_emit(buf, "%s\n", item); + kfree(item); + return ret; +} + +static ssize_t possible_values_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) +{ + struct tlmi_attr_setting *setting = to_tlmi_attr_setting(kobj); + + if (!tlmi_priv.can_get_bios_selections) + return -EOPNOTSUPP; + + return sysfs_emit(buf, "%s\n", setting->possible_values); +} + +static ssize_t current_value_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count) +{ + struct tlmi_attr_setting *setting = to_tlmi_attr_setting(kobj); + char *set_str = NULL, *new_setting = NULL; + char *auth_str = NULL; + char *p; + int ret; + + if (!tlmi_priv.can_set_bios_settings) + return -EOPNOTSUPP; + + new_setting = kstrdup(buf, GFP_KERNEL); + if (!new_setting) + return -ENOMEM; + + /* Strip out CR if one is present */ + p = strchrnul(new_setting, '\n'); + *p = '\0'; + + if (tlmi_priv.pwd_admin->valid && tlmi_priv.pwd_admin->password) { + auth_str = kasprintf(GFP_KERNEL, "%s,%s,%s;", + tlmi_priv.pwd_admin->password, + encoding_options[tlmi_priv.pwd_admin->encoding], + tlmi_priv.pwd_admin->kbdlang); + if (!auth_str) { + ret = -ENOMEM; + goto out; + } + } + + if (auth_str) + set_str = kasprintf(GFP_KERNEL, "%s,%s,%s", setting->display_name, + new_setting, auth_str); + else + set_str = kasprintf(GFP_KERNEL, "%s,%s;", setting->display_name, + new_setting); + if (!set_str) { + ret = -ENOMEM; + goto out; + } + + ret = tlmi_simple_call(LENOVO_SET_BIOS_SETTINGS_GUID, set_str); + if (ret) + goto out; + + if (tlmi_priv.pwd_admin->valid && tlmi_priv.pwd_admin->password) + ret = tlmi_save_bios_settings(auth_str); + else + ret = tlmi_save_bios_settings(""); + +out: + kfree(auth_str); + kfree(set_str); + kfree(new_setting); + return ret ?: count; +} + +static struct kobj_attribute attr_displ_name = __ATTR_RO(display_name); + +static struct kobj_attribute attr_possible_values = __ATTR_RO(possible_values); + +static struct kobj_attribute attr_current_val = __ATTR_RW_MODE(current_value, 0600); + +static struct attribute *tlmi_attrs[] = { + &attr_displ_name.attr, + &attr_current_val.attr, + &attr_possible_values.attr, + NULL +}; + +static const struct attribute_group tlmi_attr_group = { + .attrs = tlmi_attrs, +}; + +static ssize_t tlmi_attr_show(struct kobject *kobj, struct attribute *attr, + char *buf) +{ + struct kobj_attribute *kattr; + + kattr = container_of(attr, struct kobj_attribute, attr); + if (kattr->show) + return kattr->show(kobj, kattr, buf); + return -EIO; +} + +static ssize_t tlmi_attr_store(struct kobject *kobj, struct attribute *attr, + const char *buf, size_t count) +{ + struct kobj_attribute *kattr; + + kattr = container_of(attr, struct kobj_attribute, attr); + if (kattr->store) + return kattr->store(kobj, kattr, buf, count); + return -EIO; +} + +static const struct sysfs_ops tlmi_kobj_sysfs_ops = { + .show = tlmi_attr_show, + .store = tlmi_attr_store, +}; + +static void tlmi_attr_setting_release(struct kobject *kobj) +{ + struct tlmi_attr_setting *setting = to_tlmi_attr_setting(kobj); + + kfree(setting); +} + +static void tlmi_pwd_setting_release(struct kobject *kobj) +{ + struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj); + + kfree(setting); +} + +static struct kobj_type tlmi_attr_setting_ktype = { + .release = &tlmi_attr_setting_release, + .sysfs_ops = &tlmi_kobj_sysfs_ops, +}; + +static struct kobj_type tlmi_pwd_setting_ktype = { + .release = &tlmi_pwd_setting_release, + .sysfs_ops = &tlmi_kobj_sysfs_ops, +}; + +/* ---- Initialisation --------------------------------------------------------- */ +static void tlmi_release_attr(void) +{ + int i; + + /* Attribute structures */ + for (i = 0; i < TLMI_SETTINGS_COUNT; i++) { + if (tlmi_priv.setting[i]) { + kfree(tlmi_priv.setting[i]->possible_values); + sysfs_remove_group(&tlmi_priv.setting[i]->kobj, &tlmi_attr_group); + kobject_put(&tlmi_priv.setting[i]->kobj); + } + } + kset_unregister(tlmi_priv.attribute_kset); + + /* Authentication structures */ + sysfs_remove_group(&tlmi_priv.pwd_admin->kobj, &auth_attr_group); + kobject_put(&tlmi_priv.pwd_admin->kobj); + sysfs_remove_group(&tlmi_priv.pwd_power->kobj, &auth_attr_group); + kobject_put(&tlmi_priv.pwd_power->kobj); + kset_unregister(tlmi_priv.authentication_kset); +} + +static int tlmi_sysfs_init(void) +{ + int i, ret; + + ret = fw_attributes_class_get(&fw_attr_class); + if (ret) + return ret; + + tlmi_priv.class_dev = device_create(fw_attr_class, NULL, MKDEV(0, 0), + NULL, "%s", "thinklmi"); + if (IS_ERR(tlmi_priv.class_dev)) { + ret = PTR_ERR(tlmi_priv.class_dev); + goto fail_class_created; + } + + tlmi_priv.attribute_kset = kset_create_and_add("attributes", NULL, + &tlmi_priv.class_dev->kobj); + if (!tlmi_priv.attribute_kset) { + ret = -ENOMEM; + goto fail_device_created; + } + + for (i = 0; i < TLMI_SETTINGS_COUNT; i++) { + /* Check if index is a valid setting - skip if it isn't */ + if (!tlmi_priv.setting[i]) + continue; + + /* Build attribute */ + tlmi_priv.setting[i]->kobj.kset = tlmi_priv.attribute_kset; + ret = kobject_init_and_add(&tlmi_priv.setting[i]->kobj, &tlmi_attr_setting_ktype, + NULL, "%s", tlmi_priv.setting[i]->display_name); + if (ret) + goto fail_create_attr; + + ret = sysfs_create_group(&tlmi_priv.setting[i]->kobj, &tlmi_attr_group); + if (ret) + goto fail_create_attr; + } + + /* Create authentication entries */ + tlmi_priv.authentication_kset = kset_create_and_add("authentication", NULL, + &tlmi_priv.class_dev->kobj); + if (!tlmi_priv.authentication_kset) { + ret = -ENOMEM; + goto fail_create_attr; + } + tlmi_priv.pwd_admin->kobj.kset = tlmi_priv.authentication_kset; + ret = kobject_init_and_add(&tlmi_priv.pwd_admin->kobj, &tlmi_pwd_setting_ktype, + NULL, "%s", "Admin"); + if (ret) + goto fail_create_attr; + + ret = sysfs_create_group(&tlmi_priv.pwd_admin->kobj, &auth_attr_group); + if (ret) + goto fail_create_attr; + + tlmi_priv.pwd_power->kobj.kset = tlmi_priv.authentication_kset; + ret = kobject_init_and_add(&tlmi_priv.pwd_power->kobj, &tlmi_pwd_setting_ktype, + NULL, "%s", "System"); + if (ret) + goto fail_create_attr; + + ret = sysfs_create_group(&tlmi_priv.pwd_power->kobj, &auth_attr_group); + if (ret) + goto fail_create_attr; + + return ret; + +fail_create_attr: + tlmi_release_attr(); +fail_device_created: + device_destroy(fw_attr_class, MKDEV(0, 0)); +fail_class_created: + fw_attributes_class_put(); + return ret; +} + +/* ---- Base Driver -------------------------------------------------------- */ +static int tlmi_analyze(void) +{ + struct tlmi_pwdcfg pwdcfg; + acpi_status status; + int i, ret; + + if (wmi_has_guid(LENOVO_SET_BIOS_SETTINGS_GUID) && + wmi_has_guid(LENOVO_SAVE_BIOS_SETTINGS_GUID)) + tlmi_priv.can_set_bios_settings = true; + + if (wmi_has_guid(LENOVO_GET_BIOS_SELECTIONS_GUID)) + tlmi_priv.can_get_bios_selections = true; + + if (wmi_has_guid(LENOVO_SET_BIOS_PASSWORD_GUID)) + tlmi_priv.can_set_bios_password = true; + + if (wmi_has_guid(LENOVO_BIOS_PASSWORD_SETTINGS_GUID)) + tlmi_priv.can_get_password_settings = true; + + /* + * Try to find the number of valid settings of this machine + * and use it to create sysfs attributes. + */ + for (i = 0; i < TLMI_SETTINGS_COUNT; ++i) { + struct tlmi_attr_setting *setting; + char *item = NULL; + char *p; + + tlmi_priv.setting[i] = NULL; + status = tlmi_setting(i, &item, LENOVO_BIOS_SETTING_GUID); + if (ACPI_FAILURE(status)) + break; + if (!item) + break; + if (!*item) + continue; + + /* It is not allowed to have '/' for file name. Convert it into '\'. */ + strreplace(item, '/', '\\'); + + /* Remove the value part */ + p = strchrnul(item, ','); + *p = '\0'; + + /* Create a setting entry */ + setting = kzalloc(sizeof(*setting), GFP_KERNEL); + if (!setting) { + ret = -ENOMEM; + goto fail_clear_attr; + } + setting->index = i; + strscpy(setting->display_name, item, TLMI_SETTINGS_MAXLEN); + /* If BIOS selections supported, load those */ + if (tlmi_priv.can_get_bios_selections) { + ret = tlmi_get_bios_selections(setting->display_name, + &setting->possible_values); + if (ret || !setting->possible_values) + pr_info("Error retrieving possible values for %d : %s\n", + i, setting->display_name); + } + tlmi_priv.setting[i] = setting; + tlmi_priv.settings_count++; + kfree(item); + } + + /* Create password setting structure */ + ret = tlmi_get_pwd_settings(&pwdcfg); + if (ret) + goto fail_clear_attr; + + tlmi_priv.pwd_admin = kzalloc(sizeof(struct tlmi_pwd_setting), GFP_KERNEL); + if (!tlmi_priv.pwd_admin) { + ret = -ENOMEM; + goto fail_clear_attr; + } + strscpy(tlmi_priv.pwd_admin->display_name, "admin", TLMI_PWDTYPE_MAXLEN); + strscpy(tlmi_priv.pwd_admin->kbdlang, "us", TLMI_LANG_MAXLEN); + tlmi_priv.pwd_admin->encoding = TLMI_ENCODING_ASCII; + tlmi_priv.pwd_admin->pwd_type = "pap"; + tlmi_priv.pwd_admin->role = "bios-admin"; + tlmi_priv.pwd_admin->minlen = pwdcfg.min_length; + if (WARN_ON(pwdcfg.max_length >= TLMI_PWD_BUFSIZE)) + pwdcfg.max_length = TLMI_PWD_BUFSIZE - 1; + tlmi_priv.pwd_admin->maxlen = pwdcfg.max_length; + if (pwdcfg.password_state & TLMI_PAP_PWD) + tlmi_priv.pwd_admin->valid = true; + + tlmi_priv.pwd_power = kzalloc(sizeof(struct tlmi_pwd_setting), GFP_KERNEL); + if (!tlmi_priv.pwd_power) { + ret = -ENOMEM; + goto fail_clear_attr; + } + strscpy(tlmi_priv.pwd_power->display_name, "power-on", TLMI_PWDTYPE_MAXLEN); + strscpy(tlmi_priv.pwd_power->kbdlang, "us", TLMI_LANG_MAXLEN); + tlmi_priv.pwd_power->encoding = TLMI_ENCODING_ASCII; + tlmi_priv.pwd_power->pwd_type = "pop"; + tlmi_priv.pwd_power->role = "power-on"; + tlmi_priv.pwd_power->minlen = pwdcfg.min_length; + tlmi_priv.pwd_power->maxlen = pwdcfg.max_length; + + if (pwdcfg.password_state & TLMI_POP_PWD) + tlmi_priv.pwd_power->valid = true; + + return 0; + +fail_clear_attr: + for (i = 0; i < TLMI_SETTINGS_COUNT; ++i) + kfree(tlmi_priv.setting[i]); + return ret; +} + +static void tlmi_remove(struct wmi_device *wdev) +{ + tlmi_release_attr(); + device_destroy(fw_attr_class, MKDEV(0, 0)); + fw_attributes_class_put(); +} + +static int tlmi_probe(struct wmi_device *wdev, const void *context) +{ + tlmi_analyze(); + return tlmi_sysfs_init(); +} + +static const struct wmi_device_id tlmi_id_table[] = { + { .guid_string = LENOVO_BIOS_SETTING_GUID }, + { } +}; + +static struct wmi_driver tlmi_driver = { + .driver = { + .name = "think-lmi", + }, + .id_table = tlmi_id_table, + .probe = tlmi_probe, + .remove = tlmi_remove, +}; + +MODULE_AUTHOR("Sugumaran L "); +MODULE_AUTHOR("Mark Pearson "); +MODULE_AUTHOR("Corentin Chary "); +MODULE_DESCRIPTION("ThinkLMI Driver"); +MODULE_LICENSE("GPL"); + +module_wmi_driver(tlmi_driver); diff --git a/drivers/platform/x86/think-lmi.h b/drivers/platform/x86/think-lmi.h new file mode 100644 index 000000000000..6cd5325cc50e --- /dev/null +++ b/drivers/platform/x86/think-lmi.h @@ -0,0 +1,81 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#ifndef _THINK_LMI_H_ +#define _THINK_LMI_H_ + +#include + +#define TLMI_SETTINGS_COUNT 256 +#define TLMI_SETTINGS_MAXLEN 512 +#define TLMI_PWD_BUFSIZE 129 +#define TLMI_PWDTYPE_MAXLEN 64 +#define TLMI_ENC_MAXLEN 64 +#define TLMI_LANG_MAXLEN 4 +#define TLMI_PWDTYPE_LEN 4 +/* + * Longest string should be in the set command: allow size of BIOS + * option and choice + */ +#define TLMI_GETSET_MAXLEN (TLMI_SETTINGS_MAXLEN + TLMI_SETTINGS_MAXLEN) + +/* Possible error values */ +struct tlmi_err_codes { + const char *err_str; + int err_code; +}; + +enum encoding_option { + TLMI_ENCODING_ASCII, + TLMI_ENCODING_SCANCODE, +}; + +/* password configuration details */ +struct tlmi_pwdcfg { + uint32_t password_mode; + uint32_t password_state; + uint32_t min_length; + uint32_t max_length; + uint32_t supported_encodings; + uint32_t supported_keyboard; +}; + +/* password setting details */ +struct tlmi_pwd_setting { + struct kobject kobj; + bool valid; + char display_name[TLMI_PWDTYPE_MAXLEN]; + char password[TLMI_PWD_BUFSIZE]; + const char *pwd_type; + const char *role; + int minlen; + int maxlen; + enum encoding_option encoding; + char kbdlang[TLMI_LANG_MAXLEN]; +}; + +/* Attribute setting details */ +struct tlmi_attr_setting { + struct kobject kobj; + int index; + char display_name[TLMI_SETTINGS_MAXLEN]; + char *possible_values; +}; + +struct think_lmi { + struct wmi_device *wmi_device; + + int settings_count; + bool can_set_bios_settings; + bool can_get_bios_selections; + bool can_set_bios_password; + bool can_get_password_settings; + + struct tlmi_attr_setting *setting[TLMI_SETTINGS_COUNT]; + struct device *class_dev; + struct kset *attribute_kset; + struct kset *authentication_kset; + struct tlmi_pwd_setting *pwd_admin; + struct tlmi_pwd_setting *pwd_power; +}; + +#endif /* !_THINK_LMI_H_ */ -- cgit v1.2.3 From 14227ce92a402f7a3d51d05dae14d9d22211e501 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Mon, 31 May 2021 15:59:11 +0200 Subject: platform/x86: thinkpad-lmi: Remove unused display_name member from struct tlmi_pwd_setting The struct tlmi_pwd_setting display_name member is initialized, but never read. Remove it and the TLMI_PWDTYPE_MAXLEN define. While at it also remove some other unused [MAX]LEN defines. Signed-off-by: Hans de Goede Link: https://lore.kernel.org/r/20210531135911.82582-1-hdegoede@redhat.com --- drivers/platform/x86/think-lmi.c | 2 -- drivers/platform/x86/think-lmi.h | 9 --------- 2 files changed, 11 deletions(-) diff --git a/drivers/platform/x86/think-lmi.c b/drivers/platform/x86/think-lmi.c index 854427fed1a9..782d8e3fe7a1 100644 --- a/drivers/platform/x86/think-lmi.c +++ b/drivers/platform/x86/think-lmi.c @@ -819,7 +819,6 @@ static int tlmi_analyze(void) ret = -ENOMEM; goto fail_clear_attr; } - strscpy(tlmi_priv.pwd_admin->display_name, "admin", TLMI_PWDTYPE_MAXLEN); strscpy(tlmi_priv.pwd_admin->kbdlang, "us", TLMI_LANG_MAXLEN); tlmi_priv.pwd_admin->encoding = TLMI_ENCODING_ASCII; tlmi_priv.pwd_admin->pwd_type = "pap"; @@ -836,7 +835,6 @@ static int tlmi_analyze(void) ret = -ENOMEM; goto fail_clear_attr; } - strscpy(tlmi_priv.pwd_power->display_name, "power-on", TLMI_PWDTYPE_MAXLEN); strscpy(tlmi_priv.pwd_power->kbdlang, "us", TLMI_LANG_MAXLEN); tlmi_priv.pwd_power->encoding = TLMI_ENCODING_ASCII; tlmi_priv.pwd_power->pwd_type = "pop"; diff --git a/drivers/platform/x86/think-lmi.h b/drivers/platform/x86/think-lmi.h index 6cd5325cc50e..6fa8da7af6c7 100644 --- a/drivers/platform/x86/think-lmi.h +++ b/drivers/platform/x86/think-lmi.h @@ -8,15 +8,7 @@ #define TLMI_SETTINGS_COUNT 256 #define TLMI_SETTINGS_MAXLEN 512 #define TLMI_PWD_BUFSIZE 129 -#define TLMI_PWDTYPE_MAXLEN 64 -#define TLMI_ENC_MAXLEN 64 #define TLMI_LANG_MAXLEN 4 -#define TLMI_PWDTYPE_LEN 4 -/* - * Longest string should be in the set command: allow size of BIOS - * option and choice - */ -#define TLMI_GETSET_MAXLEN (TLMI_SETTINGS_MAXLEN + TLMI_SETTINGS_MAXLEN) /* Possible error values */ struct tlmi_err_codes { @@ -43,7 +35,6 @@ struct tlmi_pwdcfg { struct tlmi_pwd_setting { struct kobject kobj; bool valid; - char display_name[TLMI_PWDTYPE_MAXLEN]; char password[TLMI_PWD_BUFSIZE]; const char *pwd_type; const char *role; -- cgit v1.2.3 From ae8ee4c1e43af131088bb2da1163fdb864f6f6a2 Mon Sep 17 00:00:00 2001 From: kernel test robot Date: Thu, 3 Jun 2021 23:39:36 +0800 Subject: platform/x86: dell-wmi-sysman: fw_attr_inuse can be static drivers/platform/x86/firmware_attributes_class.c:11:5: warning: symbol 'fw_attr_inuse' was not declared. Should it be static? Reported-by: kernel test robot Signed-off-by: kernel test robot Link: https://lore.kernel.org/r/20210603153936.GA65404@7832cb195c0b Signed-off-by: Hans de Goede --- drivers/platform/x86/firmware_attributes_class.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/platform/x86/firmware_attributes_class.c b/drivers/platform/x86/firmware_attributes_class.c index d62ec3d71ede..fafe8eaf6e3e 100644 --- a/drivers/platform/x86/firmware_attributes_class.c +++ b/drivers/platform/x86/firmware_attributes_class.c @@ -8,7 +8,7 @@ #include "firmware_attributes_class.h" static DEFINE_MUTEX(fw_attr_lock); -int fw_attr_inuse; +static int fw_attr_inuse; static struct class firmware_attributes_class = { .name = "firmware-attributes", -- cgit v1.2.3 From a32348b743eec51ac01334735a0b6c979157b132 Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Fri, 4 Jun 2021 15:25:40 +0200 Subject: platform/surface: dtx: Add missing mutex_destroy() call in failure path When we fail to open the device file due to DTX being shut down, the mutex is initialized but never destroyed. We are destroying it when releasing the file, so add the missing call in the failure path as well. Fixes: 1d609992832e ("platform/surface: Add DTX driver") Signed-off-by: Maximilian Luz Link: https://lore.kernel.org/r/20210604132540.533036-1-luzmaximilian@gmail.com Signed-off-by: Hans de Goede --- drivers/platform/surface/surface_dtx.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/platform/surface/surface_dtx.c b/drivers/platform/surface/surface_dtx.c index 5d9b758a99bb..1203b9a82993 100644 --- a/drivers/platform/surface/surface_dtx.c +++ b/drivers/platform/surface/surface_dtx.c @@ -427,6 +427,7 @@ static int surface_dtx_open(struct inode *inode, struct file *file) */ if (test_bit(SDTX_DEVICE_SHUTDOWN_BIT, &ddev->flags)) { up_write(&ddev->client_lock); + mutex_destroy(&client->read_lock); sdtx_device_put(client->ddev); kfree(client); return -ENODEV; -- cgit v1.2.3 From 3d9907e181de05a32420db46b068b2557173a9f7 Mon Sep 17 00:00:00 2001 From: Mykola Kostenok Date: Thu, 3 Jun 2021 20:28:27 +0300 Subject: platform/mellanox: mlxreg-hotplug: Revert "move to use request_irq by IRQF_NO_AUTOEN flag" It causes mlxreg-hotplug probing failure: request_threaded_irq() returns -EINVAL due to true value of condition: ((irqflags & IRQF_SHARED) && (irqflags & IRQF_NO_AUTOEN)) after flag "IRQF_NO_AUTOEN" has been added to: err = devm_request_irq(&pdev->dev, priv->irq, mlxreg_hotplug_irq_handler, IRQF_TRIGGER_FALLING | IRQF_SHARED | IRQF_NO_AUTOEN, "mlxreg-hotplug", priv); This reverts commit bee3ecfed0fc ("platform/mellanox: mlxreg-hotplug: move to use request_irq by IRQF_NO_AUTOEN flag"). Signed-off-by: Mykola Kostenok Acked-by: Vadim Pasternak Link: https://lore.kernel.org/r/20210603172827.2599908-1-c_mykolak@nvidia.com Signed-off-by: Hans de Goede --- drivers/platform/mellanox/mlxreg-hotplug.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/platform/mellanox/mlxreg-hotplug.c b/drivers/platform/mellanox/mlxreg-hotplug.c index a9db2f32658f..b013445147dd 100644 --- a/drivers/platform/mellanox/mlxreg-hotplug.c +++ b/drivers/platform/mellanox/mlxreg-hotplug.c @@ -683,13 +683,13 @@ static int mlxreg_hotplug_probe(struct platform_device *pdev) err = devm_request_irq(&pdev->dev, priv->irq, mlxreg_hotplug_irq_handler, IRQF_TRIGGER_FALLING - | IRQF_SHARED | IRQF_NO_AUTOEN, - "mlxreg-hotplug", priv); + | IRQF_SHARED, "mlxreg-hotplug", priv); if (err) { dev_err(&pdev->dev, "Failed to request irq: %d\n", err); return err; } + disable_irq(priv->irq); spin_lock_init(&priv->lock); INIT_DELAYED_WORK(&priv->dwork_irq, mlxreg_hotplug_work_handler); dev_set_drvdata(&pdev->dev, priv); -- cgit v1.2.3 From 0e8512fab9fd6d78e88931c02a43b04d15566d6b Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Fri, 4 Jun 2021 15:47:49 +0200 Subject: platform/surface: aggregator: Allow registering notifiers without enabling events Currently, each SSAM event notifier is directly tied to one group of events. This makes sense as registering a notifier will automatically take care of enabling the corresponding event group and normally drivers only need notifications for a very limited number of events, associated with different callbacks for each group. However, there are rare cases, especially for debugging, when we want to get notifications for a whole event target category instead of just a single group of events in that category. Registering multiple notifiers, i.e. one per group, may be infeasible due to two issues: a) we might not know every event enable/disable specification as some events are auto-enabled by the EC and b) forwarding this to the same callback will lead to duplicate events as we might not know the full event specification to perform the appropriate filtering. This commit introduces observer-notifiers, which are notifiers that are not tied to a specific event group and do not attempt to manage any events. In other words, they can be registered without enabling any event group or incrementing the corresponding reference count and just act as silent observers, listening to all currently/previously enabled events based on their match-specification. Essentially, this allows us to register one single notifier for a full event target category, meaning that we can process all events of that target category in a single callback without duplication. Specifically, this will be used in the cdev debug interface to forward events to user-space via a device file from which the events can be read. Signed-off-by: Maximilian Luz Reviewed-by: Hans de Goede Link: https://lore.kernel.org/r/20210604134755.535590-2-luzmaximilian@gmail.com Signed-off-by: Hans de Goede --- drivers/platform/surface/aggregator/controller.c | 69 ++++++++++++++---------- include/linux/surface_aggregator/controller.h | 17 ++++++ 2 files changed, 58 insertions(+), 28 deletions(-) diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c index a06964aa96e7..cd3a6b77f48d 100644 --- a/drivers/platform/surface/aggregator/controller.c +++ b/drivers/platform/surface/aggregator/controller.c @@ -2127,9 +2127,15 @@ int ssam_ctrl_notif_d0_entry(struct ssam_controller *ctrl) * @ctrl: The controller to register the notifier on. * @n: The event notifier to register. * - * Register an event notifier and increment the usage counter of the - * associated SAM event. If the event was previously not enabled, it will be - * enabled during this call. + * Register an event notifier. Increment the usage counter of the associated + * SAM event if the notifier is not marked as an observer. If the event is not + * marked as an observer and is currently not enabled, it will be enabled + * during this call. If the notifier is marked as an observer, no attempt will + * be made at enabling any event and no reference count will be modified. + * + * Notifiers marked as observers do not need to be associated with one specific + * event, i.e. as long as no event matching is performed, only the event target + * category needs to be set. * * Return: Returns zero on success, %-ENOSPC if there have already been * %INT_MAX notifiers for the event ID/type associated with the notifier block @@ -2138,11 +2144,10 @@ int ssam_ctrl_notif_d0_entry(struct ssam_controller *ctrl) * for the specific associated event, returns the status of the event-enable * EC-command. */ -int ssam_notifier_register(struct ssam_controller *ctrl, - struct ssam_event_notifier *n) +int ssam_notifier_register(struct ssam_controller *ctrl, struct ssam_event_notifier *n) { u16 rqid = ssh_tc_to_rqid(n->event.id.target_category); - struct ssam_nf_refcount_entry *entry; + struct ssam_nf_refcount_entry *entry = NULL; struct ssam_nf_head *nf_head; struct ssam_nf *nf; int status; @@ -2155,29 +2160,32 @@ int ssam_notifier_register(struct ssam_controller *ctrl, mutex_lock(&nf->lock); - entry = ssam_nf_refcount_inc(nf, n->event.reg, n->event.id); - if (IS_ERR(entry)) { - mutex_unlock(&nf->lock); - return PTR_ERR(entry); - } + if (!(n->flags & SSAM_EVENT_NOTIFIER_OBSERVER)) { + entry = ssam_nf_refcount_inc(nf, n->event.reg, n->event.id); + if (IS_ERR(entry)) { + mutex_unlock(&nf->lock); + return PTR_ERR(entry); + } - ssam_dbg(ctrl, "enabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n", - n->event.reg.target_category, n->event.id.target_category, - n->event.id.instance, entry->refcount); + ssam_dbg(ctrl, "enabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n", + n->event.reg.target_category, n->event.id.target_category, + n->event.id.instance, entry->refcount); + } status = ssam_nfblk_insert(nf_head, &n->base); if (status) { - entry = ssam_nf_refcount_dec(nf, n->event.reg, n->event.id); - if (entry->refcount == 0) - kfree(entry); + if (entry) { + entry = ssam_nf_refcount_dec(nf, n->event.reg, n->event.id); + if (entry->refcount == 0) + kfree(entry); + } mutex_unlock(&nf->lock); return status; } - if (entry->refcount == 1) { - status = ssam_ssh_event_enable(ctrl, n->event.reg, n->event.id, - n->event.flags); + if (entry && entry->refcount == 1) { + status = ssam_ssh_event_enable(ctrl, n->event.reg, n->event.id, n->event.flags); if (status) { ssam_nfblk_remove(&n->base); kfree(ssam_nf_refcount_dec(nf, n->event.reg, n->event.id)); @@ -2188,7 +2196,7 @@ int ssam_notifier_register(struct ssam_controller *ctrl, entry->flags = n->event.flags; - } else if (entry->flags != n->event.flags) { + } else if (entry && entry->flags != n->event.flags) { ssam_warn(ctrl, "inconsistent flags when enabling event: got %#04x, expected %#04x (reg: %#04x, tc: %#04x, iid: %#04x)\n", n->event.flags, entry->flags, n->event.reg.target_category, @@ -2205,17 +2213,16 @@ EXPORT_SYMBOL_GPL(ssam_notifier_register); * @ctrl: The controller the notifier has been registered on. * @n: The event notifier to unregister. * - * Unregister an event notifier and decrement the usage counter of the - * associated SAM event. If the usage counter reaches zero, the event will be - * disabled. + * Unregister an event notifier. Decrement the usage counter of the associated + * SAM event if the notifier is not marked as an observer. If the usage counter + * reaches zero, the event will be disabled. * * Return: Returns zero on success, %-ENOENT if the given notifier block has * not been registered on the controller. If the given notifier block was the * last one associated with its specific event, returns the status of the * event-disable EC-command. */ -int ssam_notifier_unregister(struct ssam_controller *ctrl, - struct ssam_event_notifier *n) +int ssam_notifier_unregister(struct ssam_controller *ctrl, struct ssam_event_notifier *n) { u16 rqid = ssh_tc_to_rqid(n->event.id.target_category); struct ssam_nf_refcount_entry *entry; @@ -2236,6 +2243,13 @@ int ssam_notifier_unregister(struct ssam_controller *ctrl, return -ENOENT; } + /* + * If this is an observer notifier, do not attempt to disable the + * event, just remove it. + */ + if (n->flags & SSAM_EVENT_NOTIFIER_OBSERVER) + goto remove; + entry = ssam_nf_refcount_dec(nf, n->event.reg, n->event.id); if (WARN_ON(!entry)) { /* @@ -2260,8 +2274,7 @@ int ssam_notifier_unregister(struct ssam_controller *ctrl, } if (entry->refcount == 0) { - status = ssam_ssh_event_disable(ctrl, n->event.reg, n->event.id, - n->event.flags); + status = ssam_ssh_event_disable(ctrl, n->event.reg, n->event.id, n->event.flags); kfree(entry); } diff --git a/include/linux/surface_aggregator/controller.h b/include/linux/surface_aggregator/controller.h index 0806796eabcb..cf4bb48a850e 100644 --- a/include/linux/surface_aggregator/controller.h +++ b/include/linux/surface_aggregator/controller.h @@ -795,6 +795,20 @@ enum ssam_event_mask { #define SSAM_EVENT_REGISTRY_REG \ SSAM_EVENT_REGISTRY(SSAM_SSH_TC_REG, 0x02, 0x01, 0x02) +/** + * enum ssam_event_notifier_flags - Flags for event notifiers. + * @SSAM_EVENT_NOTIFIER_OBSERVER: + * The corresponding notifier acts as observer. Registering a notifier + * with this flag set will not attempt to enable any event. Equally, + * unregistering will not attempt to disable any event. Note that a + * notifier with this flag may not even correspond to a certain event at + * all, only to a specific event target category. Event matching will not + * be influenced by this flag. + */ +enum ssam_event_notifier_flags { + SSAM_EVENT_NOTIFIER_OBSERVER = BIT(0), +}; + /** * struct ssam_event_notifier - Notifier block for SSAM events. * @base: The base notifier block with callback function and priority. @@ -803,6 +817,7 @@ enum ssam_event_mask { * @event.id: ID specifying the event. * @event.mask: Flags determining how events are matched to the notifier. * @event.flags: Flags used for enabling the event. + * @flags: Notifier flags (see &enum ssam_event_notifier_flags). */ struct ssam_event_notifier { struct ssam_notifier_block base; @@ -813,6 +828,8 @@ struct ssam_event_notifier { enum ssam_event_mask mask; u8 flags; } event; + + unsigned long flags; }; int ssam_notifier_register(struct ssam_controller *ctrl, -- cgit v1.2.3 From 4b38a1dcf378f5075884b54dc5afeb9d0dfe7681 Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Fri, 4 Jun 2021 15:47:50 +0200 Subject: platform/surface: aggregator: Allow enabling of events without notifiers We can already enable and disable SAM events via one of two ways: either via a (non-observer) notifier tied to a specific event group, or a generic event enable/disable request. In some instances, however, neither method may be desirable. The first method will tie the event enable request to a specific notifier, however, when we want to receive notifications for multiple event groups of the same target category and forward this to the same notifier callback, we may receive duplicate events, i.e. one event per registered notifier. The second method will bypass the internal reference counting mechanism, meaning that a disable request will disable the event regardless of any other client driver using it, which may break the functionality of that driver. To address this problem, add new functions that allow enabling and disabling of events via the event reference counting mechanism built into the controller, without needing to register a notifier. This can then be used in combination with observer notifiers to process multiple events of the same target category without duplication in the same callback function. Signed-off-by: Maximilian Luz Link: https://lore.kernel.org/r/20210604134755.535590-3-luzmaximilian@gmail.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/surface/aggregator/controller.c | 293 +++++++++++++++++++---- include/linux/surface_aggregator/controller.h | 8 + 2 files changed, 253 insertions(+), 48 deletions(-) diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c index cd3a6b77f48d..cedd0f779f7a 100644 --- a/drivers/platform/surface/aggregator/controller.c +++ b/drivers/platform/surface/aggregator/controller.c @@ -407,6 +407,31 @@ ssam_nf_refcount_dec(struct ssam_nf *nf, struct ssam_event_registry reg, return NULL; } +/** + * ssam_nf_refcount_dec_free() - Decrement reference-/activation-count of the + * given event and free its entry if the reference count reaches zero. + * @nf: The notifier system reference. + * @reg: The registry used to enable/disable the event. + * @id: The event ID. + * + * Decrements the reference-/activation-count of the specified event, freeing + * its entry if it reaches zero. + * + * Note: ``nf->lock`` must be held when calling this function. + */ +static void ssam_nf_refcount_dec_free(struct ssam_nf *nf, + struct ssam_event_registry reg, + struct ssam_event_id id) +{ + struct ssam_nf_refcount_entry *entry; + + lockdep_assert_held(&nf->lock); + + entry = ssam_nf_refcount_dec(nf, reg, id); + if (entry && entry->refcount == 0) + kfree(entry); +} + /** * ssam_nf_refcount_empty() - Test if the notification system has any * enabled/active events. @@ -2122,6 +2147,109 @@ int ssam_ctrl_notif_d0_entry(struct ssam_controller *ctrl) /* -- Top-level event registry interface. ----------------------------------- */ +/** + * ssam_nf_refcount_enable() - Enable event for reference count entry if it has + * not already been enabled. + * @ctrl: The controller to enable the event on. + * @entry: The reference count entry for the event to be enabled. + * @flags: The flags used for enabling the event on the EC. + * + * Enable the event associated with the given reference count entry if the + * reference count equals one, i.e. the event has not previously been enabled. + * If the event has already been enabled (i.e. reference count not equal to + * one), check that the flags used for enabling match and warn about this if + * they do not. + * + * This does not modify the reference count itself, which is done with + * ssam_nf_refcount_inc() / ssam_nf_refcount_dec(). + * + * Note: ``nf->lock`` must be held when calling this function. + * + * Return: Returns zero on success. If the event is enabled by this call, + * returns the status of the event-enable EC command. + */ +static int ssam_nf_refcount_enable(struct ssam_controller *ctrl, + struct ssam_nf_refcount_entry *entry, u8 flags) +{ + const struct ssam_event_registry reg = entry->key.reg; + const struct ssam_event_id id = entry->key.id; + struct ssam_nf *nf = &ctrl->cplt.event.notif; + int status; + + lockdep_assert_held(&nf->lock); + + ssam_dbg(ctrl, "enabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n", + reg.target_category, id.target_category, id.instance, entry->refcount); + + if (entry->refcount == 1) { + status = ssam_ssh_event_enable(ctrl, reg, id, flags); + if (status) + return status; + + entry->flags = flags; + + } else if (entry->flags != flags) { + ssam_warn(ctrl, + "inconsistent flags when enabling event: got %#04x, expected %#04x (reg: %#04x, tc: %#04x, iid: %#04x)\n", + flags, entry->flags, reg.target_category, id.target_category, + id.instance); + } + + return 0; +} + +/** + * ssam_nf_refcount_disable_free() - Disable event for reference count entry if it is + * no longer in use and free the corresponding entry. + * @ctrl: The controller to disable the event on. + * @entry: The reference count entry for the event to be disabled. + * @flags: The flags used for enabling the event on the EC. + * + * If the reference count equals zero, i.e. the event is no longer requested by + * any client, the event will be disabled and the corresponding reference count + * entry freed. The reference count entry must not be used any more after a + * call to this function. + * + * Also checks if the flags used for disabling the event match the flags used + * for enabling the event and warns if they do not (regardless of reference + * count). + * + * This does not modify the reference count itself, which is done with + * ssam_nf_refcount_inc() / ssam_nf_refcount_dec(). + * + * Note: ``nf->lock`` must be held when calling this function. + * + * Return: Returns zero on success. If the event is disabled by this call, + * returns the status of the event-enable EC command. + */ +static int ssam_nf_refcount_disable_free(struct ssam_controller *ctrl, + struct ssam_nf_refcount_entry *entry, u8 flags) +{ + const struct ssam_event_registry reg = entry->key.reg; + const struct ssam_event_id id = entry->key.id; + struct ssam_nf *nf = &ctrl->cplt.event.notif; + int status; + + lockdep_assert_held(&nf->lock); + + ssam_dbg(ctrl, "disabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n", + reg.target_category, id.target_category, id.instance, entry->refcount); + + if (entry->flags != flags) { + ssam_warn(ctrl, + "inconsistent flags when disabling event: got %#04x, expected %#04x (reg: %#04x, tc: %#04x, iid: %#04x)\n", + flags, entry->flags, reg.target_category, id.target_category, + id.instance); + } + + if (entry->refcount == 0) { + status = ssam_ssh_event_disable(ctrl, reg, id, flags); + kfree(entry); + } + + return status; +} + /** * ssam_notifier_register() - Register an event notifier. * @ctrl: The controller to register the notifier on. @@ -2166,41 +2294,26 @@ int ssam_notifier_register(struct ssam_controller *ctrl, struct ssam_event_notif mutex_unlock(&nf->lock); return PTR_ERR(entry); } - - ssam_dbg(ctrl, "enabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n", - n->event.reg.target_category, n->event.id.target_category, - n->event.id.instance, entry->refcount); } status = ssam_nfblk_insert(nf_head, &n->base); if (status) { - if (entry) { - entry = ssam_nf_refcount_dec(nf, n->event.reg, n->event.id); - if (entry->refcount == 0) - kfree(entry); - } + if (entry) + ssam_nf_refcount_dec_free(nf, n->event.reg, n->event.id); mutex_unlock(&nf->lock); return status; } - if (entry && entry->refcount == 1) { - status = ssam_ssh_event_enable(ctrl, n->event.reg, n->event.id, n->event.flags); + if (entry) { + status = ssam_nf_refcount_enable(ctrl, entry, n->event.flags); if (status) { ssam_nfblk_remove(&n->base); - kfree(ssam_nf_refcount_dec(nf, n->event.reg, n->event.id)); + ssam_nf_refcount_dec_free(nf, n->event.reg, n->event.id); mutex_unlock(&nf->lock); synchronize_srcu(&nf_head->srcu); return status; } - - entry->flags = n->event.flags; - - } else if (entry && entry->flags != n->event.flags) { - ssam_warn(ctrl, - "inconsistent flags when enabling event: got %#04x, expected %#04x (reg: %#04x, tc: %#04x, iid: %#04x)\n", - n->event.flags, entry->flags, n->event.reg.target_category, - n->event.id.target_category, n->event.id.instance); } mutex_unlock(&nf->lock); @@ -2247,35 +2360,20 @@ int ssam_notifier_unregister(struct ssam_controller *ctrl, struct ssam_event_not * If this is an observer notifier, do not attempt to disable the * event, just remove it. */ - if (n->flags & SSAM_EVENT_NOTIFIER_OBSERVER) - goto remove; - - entry = ssam_nf_refcount_dec(nf, n->event.reg, n->event.id); - if (WARN_ON(!entry)) { - /* - * If this does not return an entry, there's a logic error - * somewhere: The notifier block is registered, but the event - * refcount entry is not there. Remove the notifier block - * anyways. - */ - status = -ENOENT; - goto remove; - } - - ssam_dbg(ctrl, "disabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n", - n->event.reg.target_category, n->event.id.target_category, - n->event.id.instance, entry->refcount); - - if (entry->flags != n->event.flags) { - ssam_warn(ctrl, - "inconsistent flags when disabling event: got %#04x, expected %#04x (reg: %#04x, tc: %#04x, iid: %#04x)\n", - n->event.flags, entry->flags, n->event.reg.target_category, - n->event.id.target_category, n->event.id.instance); - } + if (!(n->flags & SSAM_EVENT_NOTIFIER_OBSERVER)) { + entry = ssam_nf_refcount_dec(nf, n->event.reg, n->event.id); + if (WARN_ON(!entry)) { + /* + * If this does not return an entry, there's a logic + * error somewhere: The notifier block is registered, + * but the event refcount entry is not there. Remove + * the notifier block anyways. + */ + status = -ENOENT; + goto remove; + } - if (entry->refcount == 0) { - status = ssam_ssh_event_disable(ctrl, n->event.reg, n->event.id, n->event.flags); - kfree(entry); + status = ssam_nf_refcount_disable_free(ctrl, entry, n->event.flags); } remove: @@ -2287,6 +2385,105 @@ remove: } EXPORT_SYMBOL_GPL(ssam_notifier_unregister); +/** + * ssam_controller_event_enable() - Enable the specified event. + * @ctrl: The controller to enable the event for. + * @reg: The event registry to use for enabling the event. + * @id: The event ID specifying the event to be enabled. + * @flags: The SAM event flags used for enabling the event. + * + * Increment the event reference count of the specified event. If the event has + * not been enabled previously, it will be enabled by this call. + * + * Note: In general, ssam_notifier_register() with a non-observer notifier + * should be preferred for enabling/disabling events, as this will guarantee + * proper ordering and event forwarding in case of errors during event + * enabling/disabling. + * + * Return: Returns zero on success, %-ENOSPC if the reference count for the + * specified event has reached its maximum, %-ENOMEM if the corresponding event + * entry could not be allocated. If this is the first time that this event has + * been enabled (i.e. the reference count was incremented from zero to one by + * this call), returns the status of the event-enable EC-command. + */ +int ssam_controller_event_enable(struct ssam_controller *ctrl, + struct ssam_event_registry reg, + struct ssam_event_id id, u8 flags) +{ + u16 rqid = ssh_tc_to_rqid(id.target_category); + struct ssam_nf *nf = &ctrl->cplt.event.notif; + struct ssam_nf_refcount_entry *entry; + int status; + + if (!ssh_rqid_is_event(rqid)) + return -EINVAL; + + mutex_lock(&nf->lock); + + entry = ssam_nf_refcount_inc(nf, reg, id); + if (IS_ERR(entry)) { + mutex_unlock(&nf->lock); + return PTR_ERR(entry); + } + + status = ssam_nf_refcount_enable(ctrl, entry, flags); + if (status) { + ssam_nf_refcount_dec_free(nf, reg, id); + mutex_unlock(&nf->lock); + return status; + } + + mutex_unlock(&nf->lock); + return 0; +} +EXPORT_SYMBOL_GPL(ssam_controller_event_enable); + +/** + * ssam_controller_event_disable() - Disable the specified event. + * @ctrl: The controller to disable the event for. + * @reg: The event registry to use for disabling the event. + * @id: The event ID specifying the event to be disabled. + * @flags: The flags used when enabling the event. + * + * Decrement the reference count of the specified event. If the reference count + * reaches zero, the event will be disabled. + * + * Note: In general, ssam_notifier_register()/ssam_notifier_unregister() with a + * non-observer notifier should be preferred for enabling/disabling events, as + * this will guarantee proper ordering and event forwarding in case of errors + * during event enabling/disabling. + * + * Return: Returns zero on success, %-ENOENT if the given event has not been + * enabled on the controller. If the reference count of the event reaches zero + * during this call, returns the status of the event-disable EC-command. + */ +int ssam_controller_event_disable(struct ssam_controller *ctrl, + struct ssam_event_registry reg, + struct ssam_event_id id, u8 flags) +{ + u16 rqid = ssh_tc_to_rqid(id.target_category); + struct ssam_nf *nf = &ctrl->cplt.event.notif; + struct ssam_nf_refcount_entry *entry; + int status = 0; + + if (!ssh_rqid_is_event(rqid)) + return -EINVAL; + + mutex_lock(&nf->lock); + + entry = ssam_nf_refcount_dec(nf, reg, id); + if (!entry) { + mutex_unlock(&nf->lock); + return -ENOENT; + } + + status = ssam_nf_refcount_disable_free(ctrl, entry, flags); + + mutex_unlock(&nf->lock); + return status; +} +EXPORT_SYMBOL_GPL(ssam_controller_event_disable); + /** * ssam_notifier_disable_registered() - Disable events for all registered * notifiers. diff --git a/include/linux/surface_aggregator/controller.h b/include/linux/surface_aggregator/controller.h index cf4bb48a850e..7965bdc669c5 100644 --- a/include/linux/surface_aggregator/controller.h +++ b/include/linux/surface_aggregator/controller.h @@ -838,4 +838,12 @@ int ssam_notifier_register(struct ssam_controller *ctrl, int ssam_notifier_unregister(struct ssam_controller *ctrl, struct ssam_event_notifier *n); +int ssam_controller_event_enable(struct ssam_controller *ctrl, + struct ssam_event_registry reg, + struct ssam_event_id id, u8 flags); + +int ssam_controller_event_disable(struct ssam_controller *ctrl, + struct ssam_event_registry reg, + struct ssam_event_id id, u8 flags); + #endif /* _LINUX_SURFACE_AGGREGATOR_CONTROLLER_H */ -- cgit v1.2.3 From b2763358feb28590f6b52a4c95c94a645dadfb26 Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Fri, 4 Jun 2021 15:47:51 +0200 Subject: platform/surface: aggregator: Update copyright It's 2021, update the copyright accordingly. Signed-off-by: Maximilian Luz Reviewed-by: Hans de Goede Link: https://lore.kernel.org/r/20210604134755.535590-4-luzmaximilian@gmail.com Signed-off-by: Hans de Goede --- drivers/platform/surface/aggregator/Kconfig | 2 +- drivers/platform/surface/aggregator/Makefile | 2 +- drivers/platform/surface/aggregator/bus.c | 2 +- drivers/platform/surface/aggregator/bus.h | 2 +- drivers/platform/surface/aggregator/controller.c | 2 +- drivers/platform/surface/aggregator/controller.h | 2 +- drivers/platform/surface/aggregator/core.c | 2 +- drivers/platform/surface/aggregator/ssh_msgb.h | 2 +- drivers/platform/surface/aggregator/ssh_packet_layer.c | 2 +- drivers/platform/surface/aggregator/ssh_packet_layer.h | 2 +- drivers/platform/surface/aggregator/ssh_parser.c | 2 +- drivers/platform/surface/aggregator/ssh_parser.h | 2 +- drivers/platform/surface/aggregator/ssh_request_layer.c | 2 +- drivers/platform/surface/aggregator/ssh_request_layer.h | 2 +- drivers/platform/surface/aggregator/trace.h | 2 +- include/linux/surface_aggregator/controller.h | 2 +- include/linux/surface_aggregator/device.h | 2 +- include/linux/surface_aggregator/serial_hub.h | 2 +- 18 files changed, 18 insertions(+), 18 deletions(-) diff --git a/drivers/platform/surface/aggregator/Kconfig b/drivers/platform/surface/aggregator/Kconfig index 3aaeea9f0433..fd6dc452f3e8 100644 --- a/drivers/platform/surface/aggregator/Kconfig +++ b/drivers/platform/surface/aggregator/Kconfig @@ -1,5 +1,5 @@ # SPDX-License-Identifier: GPL-2.0+ -# Copyright (C) 2019-2020 Maximilian Luz +# Copyright (C) 2019-2021 Maximilian Luz menuconfig SURFACE_AGGREGATOR tristate "Microsoft Surface System Aggregator Module Subsystem and Drivers" diff --git a/drivers/platform/surface/aggregator/Makefile b/drivers/platform/surface/aggregator/Makefile index c112e2c7112b..c8498c41e758 100644 --- a/drivers/platform/surface/aggregator/Makefile +++ b/drivers/platform/surface/aggregator/Makefile @@ -1,5 +1,5 @@ # SPDX-License-Identifier: GPL-2.0+ -# Copyright (C) 2019-2020 Maximilian Luz +# Copyright (C) 2019-2021 Maximilian Luz # For include/trace/define_trace.h to include trace.h CFLAGS_core.o = -I$(src) diff --git a/drivers/platform/surface/aggregator/bus.c b/drivers/platform/surface/aggregator/bus.c index a9b660af0917..0169677c243e 100644 --- a/drivers/platform/surface/aggregator/bus.c +++ b/drivers/platform/surface/aggregator/bus.c @@ -2,7 +2,7 @@ /* * Surface System Aggregator Module bus and device integration. * - * Copyright (C) 2019-2020 Maximilian Luz + * Copyright (C) 2019-2021 Maximilian Luz */ #include diff --git a/drivers/platform/surface/aggregator/bus.h b/drivers/platform/surface/aggregator/bus.h index 7712baaed6a5..ed032c2cbdb2 100644 --- a/drivers/platform/surface/aggregator/bus.h +++ b/drivers/platform/surface/aggregator/bus.h @@ -2,7 +2,7 @@ /* * Surface System Aggregator Module bus and device integration. * - * Copyright (C) 2019-2020 Maximilian Luz + * Copyright (C) 2019-2021 Maximilian Luz */ #ifndef _SURFACE_AGGREGATOR_BUS_H diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c index cedd0f779f7a..6646f4d6e10d 100644 --- a/drivers/platform/surface/aggregator/controller.c +++ b/drivers/platform/surface/aggregator/controller.c @@ -2,7 +2,7 @@ /* * Main SSAM/SSH controller structure and functionality. * - * Copyright (C) 2019-2020 Maximilian Luz + * Copyright (C) 2019-2021 Maximilian Luz */ #include diff --git a/drivers/platform/surface/aggregator/controller.h b/drivers/platform/surface/aggregator/controller.h index 8297d34e7489..a0963c3562ff 100644 --- a/drivers/platform/surface/aggregator/controller.h +++ b/drivers/platform/surface/aggregator/controller.h @@ -2,7 +2,7 @@ /* * Main SSAM/SSH controller structure and functionality. * - * Copyright (C) 2019-2020 Maximilian Luz + * Copyright (C) 2019-2021 Maximilian Luz */ #ifndef _SURFACE_AGGREGATOR_CONTROLLER_H diff --git a/drivers/platform/surface/aggregator/core.c b/drivers/platform/surface/aggregator/core.c index 8dc2c267bcd6..5d780e55f4a1 100644 --- a/drivers/platform/surface/aggregator/core.c +++ b/drivers/platform/surface/aggregator/core.c @@ -7,7 +7,7 @@ * Handles communication via requests as well as enabling, disabling, and * relaying of events. * - * Copyright (C) 2019-2020 Maximilian Luz + * Copyright (C) 2019-2021 Maximilian Luz */ #include diff --git a/drivers/platform/surface/aggregator/ssh_msgb.h b/drivers/platform/surface/aggregator/ssh_msgb.h index 1221f642dda1..e562958ffdf0 100644 --- a/drivers/platform/surface/aggregator/ssh_msgb.h +++ b/drivers/platform/surface/aggregator/ssh_msgb.h @@ -2,7 +2,7 @@ /* * SSH message builder functions. * - * Copyright (C) 2019-2020 Maximilian Luz + * Copyright (C) 2019-2021 Maximilian Luz */ #ifndef _SURFACE_AGGREGATOR_SSH_MSGB_H diff --git a/drivers/platform/surface/aggregator/ssh_packet_layer.c b/drivers/platform/surface/aggregator/ssh_packet_layer.c index 15d96eac6811..5e08049fc3ac 100644 --- a/drivers/platform/surface/aggregator/ssh_packet_layer.c +++ b/drivers/platform/surface/aggregator/ssh_packet_layer.c @@ -2,7 +2,7 @@ /* * SSH packet transport layer. * - * Copyright (C) 2019-2020 Maximilian Luz + * Copyright (C) 2019-2021 Maximilian Luz */ #include diff --git a/drivers/platform/surface/aggregator/ssh_packet_layer.h b/drivers/platform/surface/aggregator/ssh_packet_layer.h index e8757d03f279..2eb329f0b91a 100644 --- a/drivers/platform/surface/aggregator/ssh_packet_layer.h +++ b/drivers/platform/surface/aggregator/ssh_packet_layer.h @@ -2,7 +2,7 @@ /* * SSH packet transport layer. * - * Copyright (C) 2019-2020 Maximilian Luz + * Copyright (C) 2019-2021 Maximilian Luz */ #ifndef _SURFACE_AGGREGATOR_SSH_PACKET_LAYER_H diff --git a/drivers/platform/surface/aggregator/ssh_parser.c b/drivers/platform/surface/aggregator/ssh_parser.c index e2dead8de94a..b77912f8f13b 100644 --- a/drivers/platform/surface/aggregator/ssh_parser.c +++ b/drivers/platform/surface/aggregator/ssh_parser.c @@ -2,7 +2,7 @@ /* * SSH message parser. * - * Copyright (C) 2019-2020 Maximilian Luz + * Copyright (C) 2019-2021 Maximilian Luz */ #include diff --git a/drivers/platform/surface/aggregator/ssh_parser.h b/drivers/platform/surface/aggregator/ssh_parser.h index 63c38d350988..3bd6e180fd16 100644 --- a/drivers/platform/surface/aggregator/ssh_parser.h +++ b/drivers/platform/surface/aggregator/ssh_parser.h @@ -2,7 +2,7 @@ /* * SSH message parser. * - * Copyright (C) 2019-2020 Maximilian Luz + * Copyright (C) 2019-2021 Maximilian Luz */ #ifndef _SURFACE_AGGREGATOR_SSH_PARSER_H diff --git a/drivers/platform/surface/aggregator/ssh_request_layer.c b/drivers/platform/surface/aggregator/ssh_request_layer.c index 52a83a8fcf82..bfe1aaf38065 100644 --- a/drivers/platform/surface/aggregator/ssh_request_layer.c +++ b/drivers/platform/surface/aggregator/ssh_request_layer.c @@ -2,7 +2,7 @@ /* * SSH request transport layer. * - * Copyright (C) 2019-2020 Maximilian Luz + * Copyright (C) 2019-2021 Maximilian Luz */ #include diff --git a/drivers/platform/surface/aggregator/ssh_request_layer.h b/drivers/platform/surface/aggregator/ssh_request_layer.h index cb35815858d1..9c3cbae2d4bd 100644 --- a/drivers/platform/surface/aggregator/ssh_request_layer.h +++ b/drivers/platform/surface/aggregator/ssh_request_layer.h @@ -2,7 +2,7 @@ /* * SSH request transport layer. * - * Copyright (C) 2019-2020 Maximilian Luz + * Copyright (C) 2019-2021 Maximilian Luz */ #ifndef _SURFACE_AGGREGATOR_SSH_REQUEST_LAYER_H diff --git a/drivers/platform/surface/aggregator/trace.h b/drivers/platform/surface/aggregator/trace.h index eb332bb53ae4..de64cf169060 100644 --- a/drivers/platform/surface/aggregator/trace.h +++ b/drivers/platform/surface/aggregator/trace.h @@ -2,7 +2,7 @@ /* * Trace points for SSAM/SSH. * - * Copyright (C) 2020 Maximilian Luz + * Copyright (C) 2020-2021 Maximilian Luz */ #undef TRACE_SYSTEM diff --git a/include/linux/surface_aggregator/controller.h b/include/linux/surface_aggregator/controller.h index 7965bdc669c5..068e1982ad37 100644 --- a/include/linux/surface_aggregator/controller.h +++ b/include/linux/surface_aggregator/controller.h @@ -6,7 +6,7 @@ * managing access and communication to and from the SSAM EC, as well as main * communication structures and definitions. * - * Copyright (C) 2019-2020 Maximilian Luz + * Copyright (C) 2019-2021 Maximilian Luz */ #ifndef _LINUX_SURFACE_AGGREGATOR_CONTROLLER_H diff --git a/include/linux/surface_aggregator/device.h b/include/linux/surface_aggregator/device.h index 6ff9c58b3e17..f636c5310321 100644 --- a/include/linux/surface_aggregator/device.h +++ b/include/linux/surface_aggregator/device.h @@ -7,7 +7,7 @@ * Provides support for non-platform/non-ACPI SSAM clients via dedicated * subsystem. * - * Copyright (C) 2019-2020 Maximilian Luz + * Copyright (C) 2019-2021 Maximilian Luz */ #ifndef _LINUX_SURFACE_AGGREGATOR_DEVICE_H diff --git a/include/linux/surface_aggregator/serial_hub.h b/include/linux/surface_aggregator/serial_hub.h index 64276fbfa1d5..c3de43edcffa 100644 --- a/include/linux/surface_aggregator/serial_hub.h +++ b/include/linux/surface_aggregator/serial_hub.h @@ -6,7 +6,7 @@ * Surface System Aggregator Module (SSAM). Provides the interface for basic * packet- and request-based communication with the SSAM EC via SSH. * - * Copyright (C) 2019-2020 Maximilian Luz + * Copyright (C) 2019-2021 Maximilian Luz */ #ifndef _LINUX_SURFACE_AGGREGATOR_SERIAL_HUB_H -- cgit v1.2.3 From 776c53c6a448905d8b9b161805b67f82301bfe91 Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Fri, 4 Jun 2021 15:47:52 +0200 Subject: platform/surface: aggregator_cdev: Add support for forwarding events to user-space Currently, debugging unknown events requires writing a custom driver. This is somewhat difficult, slow to adapt, and not entirely user-friendly for quickly trying to figure out things on devices of some third-party user. We can do better. We already have a user-space interface intended for debugging SAM EC requests, so let's add support for receiving events to that. This commit provides support for receiving events by reading from the controller file. It additionally introduces two new IOCTLs to control which event categories will be forwarded. Specifically, a user-space client can specify which target categories it wants to receive events from by registering the corresponding notifier(s) via the IOCTLs and after that, read the received events by reading from the controller device. Signed-off-by: Maximilian Luz Reviewed-by: Hans de Goede Link: https://lore.kernel.org/r/20210604134755.535590-5-luzmaximilian@gmail.com Signed-off-by: Hans de Goede --- Documentation/userspace-api/ioctl/ioctl-number.rst | 2 +- drivers/platform/surface/surface_aggregator_cdev.c | 460 +++++++++++++++++++-- include/uapi/linux/surface_aggregator/cdev.h | 41 +- 3 files changed, 477 insertions(+), 26 deletions(-) diff --git a/Documentation/userspace-api/ioctl/ioctl-number.rst b/Documentation/userspace-api/ioctl/ioctl-number.rst index 9bfc2b510c64..1409e40e6345 100644 --- a/Documentation/userspace-api/ioctl/ioctl-number.rst +++ b/Documentation/userspace-api/ioctl/ioctl-number.rst @@ -325,7 +325,7 @@ Code Seq# Include File Comments 0xA3 90-9F linux/dtlk.h 0xA4 00-1F uapi/linux/tee.h Generic TEE subsystem 0xA4 00-1F uapi/asm/sgx.h -0xA5 01 linux/surface_aggregator/cdev.h Microsoft Surface Platform System Aggregator +0xA5 01-05 linux/surface_aggregator/cdev.h Microsoft Surface Platform System Aggregator 0xA5 20-2F linux/surface_aggregator/dtx.h Microsoft Surface DTX driver diff --git a/drivers/platform/surface/surface_aggregator_cdev.c b/drivers/platform/surface/surface_aggregator_cdev.c index 79e28fab7e40..dcda377896b7 100644 --- a/drivers/platform/surface/surface_aggregator_cdev.c +++ b/drivers/platform/surface/surface_aggregator_cdev.c @@ -3,29 +3,69 @@ * Provides user-space access to the SSAM EC via the /dev/surface/aggregator * misc device. Intended for debugging and development. * - * Copyright (C) 2020 Maximilian Luz + * Copyright (C) 2020-2021 Maximilian Luz */ #include +#include #include +#include #include #include #include #include +#include #include #include #include +#include #include #include +#include #define SSAM_CDEV_DEVICE_NAME "surface_aggregator_cdev" + +/* -- Main structures. ------------------------------------------------------ */ + +enum ssam_cdev_device_state { + SSAM_CDEV_DEVICE_SHUTDOWN_BIT = BIT(0), +}; + struct ssam_cdev { struct kref kref; struct rw_semaphore lock; + + struct device *dev; struct ssam_controller *ctrl; struct miscdevice mdev; + unsigned long flags; + + struct rw_semaphore client_lock; /* Guards client list. */ + struct list_head client_list; +}; + +struct ssam_cdev_client; + +struct ssam_cdev_notifier { + struct ssam_cdev_client *client; + struct ssam_event_notifier nf; +}; + +struct ssam_cdev_client { + struct ssam_cdev *cdev; + struct list_head node; + + struct mutex notifier_lock; /* Guards notifier access for registration */ + struct ssam_cdev_notifier *notifier[SSH_NUM_EVENTS]; + + struct mutex read_lock; /* Guards FIFO buffer read access */ + struct mutex write_lock; /* Guards FIFO buffer write access */ + DECLARE_KFIFO(buffer, u8, 4096); + + wait_queue_head_t waitq; + struct fasync_struct *fasync; }; static void __ssam_cdev_release(struct kref *kref) @@ -47,24 +87,169 @@ static void ssam_cdev_put(struct ssam_cdev *cdev) kref_put(&cdev->kref, __ssam_cdev_release); } -static int ssam_cdev_device_open(struct inode *inode, struct file *filp) + +/* -- Notifier handling. ---------------------------------------------------- */ + +static u32 ssam_cdev_notifier(struct ssam_event_notifier *nf, const struct ssam_event *in) { - struct miscdevice *mdev = filp->private_data; - struct ssam_cdev *cdev = container_of(mdev, struct ssam_cdev, mdev); + struct ssam_cdev_notifier *cdev_nf = container_of(nf, struct ssam_cdev_notifier, nf); + struct ssam_cdev_client *client = cdev_nf->client; + struct ssam_cdev_event event; + size_t n = struct_size(&event, data, in->length); + + /* Translate event. */ + event.target_category = in->target_category; + event.target_id = in->target_id; + event.command_id = in->command_id; + event.instance_id = in->instance_id; + event.length = in->length; + + mutex_lock(&client->write_lock); + + /* Make sure we have enough space. */ + if (kfifo_avail(&client->buffer) < n) { + dev_warn(client->cdev->dev, + "buffer full, dropping event (tc: %#04x, tid: %#04x, cid: %#04x, iid: %#04x)\n", + in->target_category, in->target_id, in->command_id, in->instance_id); + mutex_unlock(&client->write_lock); + return 0; + } - filp->private_data = ssam_cdev_get(cdev); - return stream_open(inode, filp); + /* Copy event header and payload. */ + kfifo_in(&client->buffer, (const u8 *)&event, struct_size(&event, data, 0)); + kfifo_in(&client->buffer, &in->data[0], in->length); + + mutex_unlock(&client->write_lock); + + /* Notify waiting readers. */ + kill_fasync(&client->fasync, SIGIO, POLL_IN); + wake_up_interruptible(&client->waitq); + + /* + * Don't mark events as handled, this is the job of a proper driver and + * not the debugging interface. + */ + return 0; } -static int ssam_cdev_device_release(struct inode *inode, struct file *filp) +static int ssam_cdev_notifier_register(struct ssam_cdev_client *client, u8 tc, int priority) { - ssam_cdev_put(filp->private_data); - return 0; + const u16 rqid = ssh_tc_to_rqid(tc); + const u16 event = ssh_rqid_to_event(rqid); + struct ssam_cdev_notifier *nf; + int status; + + /* Validate notifier target category. */ + if (!ssh_rqid_is_event(rqid)) + return -EINVAL; + + mutex_lock(&client->notifier_lock); + + /* Check if the notifier has already been registered. */ + if (client->notifier[event]) { + mutex_unlock(&client->notifier_lock); + return -EEXIST; + } + + /* Allocate new notifier. */ + nf = kzalloc(sizeof(*nf), GFP_KERNEL); + if (!nf) { + mutex_unlock(&client->notifier_lock); + return -ENOMEM; + } + + /* + * Create a dummy notifier with the minimal required fields for + * observer registration. Note that we can skip fully specifying event + * and registry here as we do not need any matching and use silent + * registration, which does not enable the corresponding event. + */ + nf->client = client; + nf->nf.base.fn = ssam_cdev_notifier; + nf->nf.base.priority = priority; + nf->nf.event.id.target_category = tc; + nf->nf.event.mask = 0; /* Do not do any matching. */ + nf->nf.flags = SSAM_EVENT_NOTIFIER_OBSERVER; + + /* Register notifier. */ + status = ssam_notifier_register(client->cdev->ctrl, &nf->nf); + if (status) + kfree(nf); + else + client->notifier[event] = nf; + + mutex_unlock(&client->notifier_lock); + return status; } -static long ssam_cdev_request(struct ssam_cdev *cdev, unsigned long arg) +static int ssam_cdev_notifier_unregister(struct ssam_cdev_client *client, u8 tc) +{ + const u16 rqid = ssh_tc_to_rqid(tc); + const u16 event = ssh_rqid_to_event(rqid); + int status; + + /* Validate notifier target category. */ + if (!ssh_rqid_is_event(rqid)) + return -EINVAL; + + mutex_lock(&client->notifier_lock); + + /* Check if the notifier is currently registered. */ + if (!client->notifier[event]) { + mutex_unlock(&client->notifier_lock); + return -ENOENT; + } + + /* Unregister and free notifier. */ + status = ssam_notifier_unregister(client->cdev->ctrl, &client->notifier[event]->nf); + kfree(client->notifier[event]); + client->notifier[event] = NULL; + + mutex_unlock(&client->notifier_lock); + return status; +} + +static void ssam_cdev_notifier_unregister_all(struct ssam_cdev_client *client) +{ + int i; + + down_read(&client->cdev->lock); + + /* + * This function may be used during shutdown, thus we need to test for + * cdev->ctrl instead of the SSAM_CDEV_DEVICE_SHUTDOWN_BIT bit. + */ + if (client->cdev->ctrl) { + for (i = 0; i < SSH_NUM_EVENTS; i++) + ssam_cdev_notifier_unregister(client, i + 1); + + } else { + int count = 0; + + /* + * Device has been shut down. Any notifier remaining is a bug, + * so warn about that as this would otherwise hardly be + * noticeable. Nevertheless, free them as well. + */ + mutex_lock(&client->notifier_lock); + for (i = 0; i < SSH_NUM_EVENTS; i++) { + count += !!(client->notifier[i]); + kfree(client->notifier[i]); + client->notifier[i] = NULL; + } + mutex_unlock(&client->notifier_lock); + + WARN_ON(count > 0); + } + + up_read(&client->cdev->lock); +} + + +/* -- IOCTL functions. ------------------------------------------------------ */ + +static long ssam_cdev_request(struct ssam_cdev_client *client, struct ssam_cdev_request __user *r) { - struct ssam_cdev_request __user *r; struct ssam_cdev_request rqst; struct ssam_request spec = {}; struct ssam_response rsp = {}; @@ -72,7 +257,6 @@ static long ssam_cdev_request(struct ssam_cdev *cdev, unsigned long arg) void __user *rspdata; int status = 0, ret = 0, tmp; - r = (struct ssam_cdev_request __user *)arg; ret = copy_struct_from_user(&rqst, sizeof(rqst), r, sizeof(*r)); if (ret) goto out; @@ -152,7 +336,7 @@ static long ssam_cdev_request(struct ssam_cdev *cdev, unsigned long arg) } /* Perform request. */ - status = ssam_request_sync(cdev->ctrl, &spec, &rsp); + status = ssam_request_sync(client->cdev->ctrl, &spec, &rsp); if (status) goto out; @@ -177,48 +361,247 @@ out: return ret; } -static long __ssam_cdev_device_ioctl(struct ssam_cdev *cdev, unsigned int cmd, +static long ssam_cdev_notif_register(struct ssam_cdev_client *client, + const struct ssam_cdev_notifier_desc __user *d) +{ + struct ssam_cdev_notifier_desc desc; + long ret; + + ret = copy_struct_from_user(&desc, sizeof(desc), d, sizeof(*d)); + if (ret) + return ret; + + return ssam_cdev_notifier_register(client, desc.target_category, desc.priority); +} + +static long ssam_cdev_notif_unregister(struct ssam_cdev_client *client, + const struct ssam_cdev_notifier_desc __user *d) +{ + struct ssam_cdev_notifier_desc desc; + long ret; + + ret = copy_struct_from_user(&desc, sizeof(desc), d, sizeof(*d)); + if (ret) + return ret; + + return ssam_cdev_notifier_unregister(client, desc.target_category); +} + + +/* -- File operations. ------------------------------------------------------ */ + +static int ssam_cdev_device_open(struct inode *inode, struct file *filp) +{ + struct miscdevice *mdev = filp->private_data; + struct ssam_cdev_client *client; + struct ssam_cdev *cdev = container_of(mdev, struct ssam_cdev, mdev); + + /* Initialize client */ + client = vzalloc(sizeof(*client)); + if (!client) + return -ENOMEM; + + client->cdev = ssam_cdev_get(cdev); + + INIT_LIST_HEAD(&client->node); + + mutex_init(&client->notifier_lock); + + mutex_init(&client->read_lock); + mutex_init(&client->write_lock); + INIT_KFIFO(client->buffer); + init_waitqueue_head(&client->waitq); + + filp->private_data = client; + + /* Attach client. */ + down_write(&cdev->client_lock); + + if (test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &cdev->flags)) { + up_write(&cdev->client_lock); + mutex_destroy(&client->write_lock); + mutex_destroy(&client->read_lock); + mutex_destroy(&client->notifier_lock); + ssam_cdev_put(client->cdev); + vfree(client); + return -ENODEV; + } + list_add_tail(&client->node, &cdev->client_list); + + up_write(&cdev->client_lock); + + stream_open(inode, filp); + return 0; +} + +static int ssam_cdev_device_release(struct inode *inode, struct file *filp) +{ + struct ssam_cdev_client *client = filp->private_data; + + /* Force-unregister all remaining notifiers of this client. */ + ssam_cdev_notifier_unregister_all(client); + + /* Detach client. */ + down_write(&client->cdev->client_lock); + list_del(&client->node); + up_write(&client->cdev->client_lock); + + /* Free client. */ + mutex_destroy(&client->write_lock); + mutex_destroy(&client->read_lock); + + mutex_destroy(&client->notifier_lock); + + ssam_cdev_put(client->cdev); + vfree(client); + + return 0; +} + +static long __ssam_cdev_device_ioctl(struct ssam_cdev_client *client, unsigned int cmd, unsigned long arg) { switch (cmd) { case SSAM_CDEV_REQUEST: - return ssam_cdev_request(cdev, arg); + return ssam_cdev_request(client, (struct ssam_cdev_request __user *)arg); + + case SSAM_CDEV_NOTIF_REGISTER: + return ssam_cdev_notif_register(client, + (struct ssam_cdev_notifier_desc __user *)arg); + + case SSAM_CDEV_NOTIF_UNREGISTER: + return ssam_cdev_notif_unregister(client, + (struct ssam_cdev_notifier_desc __user *)arg); default: return -ENOTTY; } } -static long ssam_cdev_device_ioctl(struct file *file, unsigned int cmd, - unsigned long arg) +static long ssam_cdev_device_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { - struct ssam_cdev *cdev = file->private_data; + struct ssam_cdev_client *client = file->private_data; long status; /* Ensure that controller is valid for as long as we need it. */ + if (down_read_killable(&client->cdev->lock)) + return -ERESTARTSYS; + + if (test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &client->cdev->flags)) { + up_read(&client->cdev->lock); + return -ENODEV; + } + + status = __ssam_cdev_device_ioctl(client, cmd, arg); + + up_read(&client->cdev->lock); + return status; +} + +static ssize_t ssam_cdev_read(struct file *file, char __user *buf, size_t count, loff_t *offs) +{ + struct ssam_cdev_client *client = file->private_data; + struct ssam_cdev *cdev = client->cdev; + unsigned int copied; + int status = 0; + if (down_read_killable(&cdev->lock)) return -ERESTARTSYS; - if (!cdev->ctrl) { + /* Make sure we're not shut down. */ + if (test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &cdev->flags)) { up_read(&cdev->lock); return -ENODEV; } - status = __ssam_cdev_device_ioctl(cdev, cmd, arg); + do { + /* Check availability, wait if necessary. */ + if (kfifo_is_empty(&client->buffer)) { + up_read(&cdev->lock); + + if (file->f_flags & O_NONBLOCK) + return -EAGAIN; + + status = wait_event_interruptible(client->waitq, + !kfifo_is_empty(&client->buffer) || + test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, + &cdev->flags)); + if (status < 0) + return status; + + if (down_read_killable(&cdev->lock)) + return -ERESTARTSYS; + + /* Need to check that we're not shut down again. */ + if (test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &cdev->flags)) { + up_read(&cdev->lock); + return -ENODEV; + } + } + + /* Try to read from FIFO. */ + if (mutex_lock_interruptible(&client->read_lock)) { + up_read(&cdev->lock); + return -ERESTARTSYS; + } + + status = kfifo_to_user(&client->buffer, buf, count, &copied); + mutex_unlock(&client->read_lock); + + if (status < 0) { + up_read(&cdev->lock); + return status; + } + + /* We might not have gotten anything, check this here. */ + if (copied == 0 && (file->f_flags & O_NONBLOCK)) { + up_read(&cdev->lock); + return -EAGAIN; + } + } while (copied == 0); up_read(&cdev->lock); - return status; + return copied; +} + +static __poll_t ssam_cdev_poll(struct file *file, struct poll_table_struct *pt) +{ + struct ssam_cdev_client *client = file->private_data; + __poll_t events = 0; + + if (test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &client->cdev->flags)) + return EPOLLHUP | EPOLLERR; + + poll_wait(file, &client->waitq, pt); + + if (!kfifo_is_empty(&client->buffer)) + events |= EPOLLIN | EPOLLRDNORM; + + return events; +} + +static int ssam_cdev_fasync(int fd, struct file *file, int on) +{ + struct ssam_cdev_client *client = file->private_data; + + return fasync_helper(fd, file, on, &client->fasync); } static const struct file_operations ssam_controller_fops = { .owner = THIS_MODULE, .open = ssam_cdev_device_open, .release = ssam_cdev_device_release, + .read = ssam_cdev_read, + .poll = ssam_cdev_poll, + .fasync = ssam_cdev_fasync, .unlocked_ioctl = ssam_cdev_device_ioctl, .compat_ioctl = ssam_cdev_device_ioctl, - .llseek = noop_llseek, + .llseek = no_llseek, }; + +/* -- Device and driver setup ----------------------------------------------- */ + static int ssam_dbg_device_probe(struct platform_device *pdev) { struct ssam_controller *ctrl; @@ -236,6 +619,7 @@ static int ssam_dbg_device_probe(struct platform_device *pdev) kref_init(&cdev->kref); init_rwsem(&cdev->lock); cdev->ctrl = ctrl; + cdev->dev = &pdev->dev; cdev->mdev.parent = &pdev->dev; cdev->mdev.minor = MISC_DYNAMIC_MINOR; @@ -243,6 +627,9 @@ static int ssam_dbg_device_probe(struct platform_device *pdev) cdev->mdev.nodename = "surface/aggregator"; cdev->mdev.fops = &ssam_controller_fops; + init_rwsem(&cdev->client_lock); + INIT_LIST_HEAD(&cdev->client_list); + status = misc_register(&cdev->mdev); if (status) { kfree(cdev); @@ -256,8 +643,32 @@ static int ssam_dbg_device_probe(struct platform_device *pdev) static int ssam_dbg_device_remove(struct platform_device *pdev) { struct ssam_cdev *cdev = platform_get_drvdata(pdev); + struct ssam_cdev_client *client; - misc_deregister(&cdev->mdev); + /* + * Mark device as shut-down. Prevent new clients from being added and + * new operations from being executed. + */ + set_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &cdev->flags); + + down_write(&cdev->client_lock); + + /* Remove all notifiers registered by us. */ + list_for_each_entry(client, &cdev->client_list, node) { + ssam_cdev_notifier_unregister_all(client); + } + + /* Wake up async clients. */ + list_for_each_entry(client, &cdev->client_list, node) { + kill_fasync(&client->fasync, SIGIO, POLL_HUP); + } + + /* Wake up blocking clients. */ + list_for_each_entry(client, &cdev->client_list, node) { + wake_up_interruptible(&client->waitq); + } + + up_write(&cdev->client_lock); /* * The controller is only guaranteed to be valid for as long as the @@ -266,8 +677,11 @@ static int ssam_dbg_device_remove(struct platform_device *pdev) */ down_write(&cdev->lock); cdev->ctrl = NULL; + cdev->dev = NULL; up_write(&cdev->lock); + misc_deregister(&cdev->mdev); + ssam_cdev_put(cdev); return 0; } diff --git a/include/uapi/linux/surface_aggregator/cdev.h b/include/uapi/linux/surface_aggregator/cdev.h index fbcce04abfe9..4f393fafc235 100644 --- a/include/uapi/linux/surface_aggregator/cdev.h +++ b/include/uapi/linux/surface_aggregator/cdev.h @@ -6,7 +6,7 @@ * device. This device provides direct user-space access to the SSAM EC. * Intended for debugging and development. * - * Copyright (C) 2020 Maximilian Luz + * Copyright (C) 2020-2021 Maximilian Luz */ #ifndef _UAPI_LINUX_SURFACE_AGGREGATOR_CDEV_H @@ -73,6 +73,43 @@ struct ssam_cdev_request { } response; } __attribute__((__packed__)); -#define SSAM_CDEV_REQUEST _IOWR(0xA5, 1, struct ssam_cdev_request) +/** + * struct ssam_cdev_notifier_desc - Notifier descriptor. + * @priority: Priority value determining the order in which notifier + * callbacks will be called. A higher value means higher + * priority, i.e. the associated callback will be executed + * earlier than other (lower priority) callbacks. + * @target_category: The event target category for which this notifier should + * receive events. + * + * Specifies the notifier that should be registered or unregistered, + * specifically with which priority and for which target category of events. + */ +struct ssam_cdev_notifier_desc { + __s32 priority; + __u8 target_category; +} __attribute__((__packed__)); + +/** + * struct ssam_cdev_event - SSAM event sent by the EC. + * @target_category: Target category of the event source. See &enum ssam_ssh_tc. + * @target_id: Target ID of the event source. + * @command_id: Command ID of the event. + * @instance_id: Instance ID of the event source. + * @length: Length of the event payload in bytes. + * @data: Event payload data. + */ +struct ssam_cdev_event { + __u8 target_category; + __u8 target_id; + __u8 command_id; + __u8 instance_id; + __u16 length; + __u8 data[]; +} __attribute__((__packed__)); + +#define SSAM_CDEV_REQUEST _IOWR(0xA5, 1, struct ssam_cdev_request) +#define SSAM_CDEV_NOTIF_REGISTER _IOW(0xA5, 2, struct ssam_cdev_notifier_desc) +#define SSAM_CDEV_NOTIF_UNREGISTER _IOW(0xA5, 3, struct ssam_cdev_notifier_desc) #endif /* _UAPI_LINUX_SURFACE_AGGREGATOR_CDEV_H */ -- cgit v1.2.3 From e8e298a653856b1f3a2bb7b1fe31d3faa93cc7dc Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Fri, 4 Jun 2021 15:47:53 +0200 Subject: platform/surface: aggregator_cdev: Allow enabling of events from user-space While events can already be enabled and disabled via the generic request IOCTL, this bypasses the internal reference counting mechanism of the controller. Due to that, disabling an event will turn it off regardless of any other client having requested said event, which may break functionality of that client. To solve this, add IOCTLs wrapping the ssam_controller_event_enable() and ssam_controller_event_disable() functions, which have been previously introduced for this specific purpose. Signed-off-by: Maximilian Luz Reviewed-by: Hans de Goede Link: https://lore.kernel.org/r/20210604134755.535590-6-luzmaximilian@gmail.com Signed-off-by: Hans de Goede --- drivers/platform/surface/surface_aggregator_cdev.c | 58 ++++++++++++++++++++++ include/uapi/linux/surface_aggregator/cdev.h | 32 ++++++++++++ 2 files changed, 90 insertions(+) diff --git a/drivers/platform/surface/surface_aggregator_cdev.c b/drivers/platform/surface/surface_aggregator_cdev.c index dcda377896b7..7b86b36eaaa0 100644 --- a/drivers/platform/surface/surface_aggregator_cdev.c +++ b/drivers/platform/surface/surface_aggregator_cdev.c @@ -387,6 +387,58 @@ static long ssam_cdev_notif_unregister(struct ssam_cdev_client *client, return ssam_cdev_notifier_unregister(client, desc.target_category); } +static long ssam_cdev_event_enable(struct ssam_cdev_client *client, + const struct ssam_cdev_event_desc __user *d) +{ + struct ssam_cdev_event_desc desc; + struct ssam_event_registry reg; + struct ssam_event_id id; + long ret; + + /* Read descriptor from user-space. */ + ret = copy_struct_from_user(&desc, sizeof(desc), d, sizeof(*d)); + if (ret) + return ret; + + /* Translate descriptor. */ + reg.target_category = desc.reg.target_category; + reg.target_id = desc.reg.target_id; + reg.cid_enable = desc.reg.cid_enable; + reg.cid_disable = desc.reg.cid_disable; + + id.target_category = desc.id.target_category; + id.instance = desc.id.instance; + + /* Disable event. */ + return ssam_controller_event_enable(client->cdev->ctrl, reg, id, desc.flags); +} + +static long ssam_cdev_event_disable(struct ssam_cdev_client *client, + const struct ssam_cdev_event_desc __user *d) +{ + struct ssam_cdev_event_desc desc; + struct ssam_event_registry reg; + struct ssam_event_id id; + long ret; + + /* Read descriptor from user-space. */ + ret = copy_struct_from_user(&desc, sizeof(desc), d, sizeof(*d)); + if (ret) + return ret; + + /* Translate descriptor. */ + reg.target_category = desc.reg.target_category; + reg.target_id = desc.reg.target_id; + reg.cid_enable = desc.reg.cid_enable; + reg.cid_disable = desc.reg.cid_disable; + + id.target_category = desc.id.target_category; + id.instance = desc.id.instance; + + /* Disable event. */ + return ssam_controller_event_disable(client->cdev->ctrl, reg, id, desc.flags); +} + /* -- File operations. ------------------------------------------------------ */ @@ -473,6 +525,12 @@ static long __ssam_cdev_device_ioctl(struct ssam_cdev_client *client, unsigned i return ssam_cdev_notif_unregister(client, (struct ssam_cdev_notifier_desc __user *)arg); + case SSAM_CDEV_EVENT_ENABLE: + return ssam_cdev_event_enable(client, (struct ssam_cdev_event_desc __user *)arg); + + case SSAM_CDEV_EVENT_DISABLE: + return ssam_cdev_event_disable(client, (struct ssam_cdev_event_desc __user *)arg); + default: return -ENOTTY; } diff --git a/include/uapi/linux/surface_aggregator/cdev.h b/include/uapi/linux/surface_aggregator/cdev.h index 4f393fafc235..08f46b60b151 100644 --- a/include/uapi/linux/surface_aggregator/cdev.h +++ b/include/uapi/linux/surface_aggregator/cdev.h @@ -90,6 +90,36 @@ struct ssam_cdev_notifier_desc { __u8 target_category; } __attribute__((__packed__)); +/** + * struct ssam_cdev_event_desc - Event descriptor. + * @reg: Registry via which the event will be enabled/disabled. + * @reg.target_category: Target category for the event registry requests. + * @reg.target_id: Target ID for the event registry requests. + * @reg.cid_enable: Command ID for the event-enable request. + * @reg.cid_disable: Command ID for the event-disable request. + * @id: ID specifying the event. + * @id.target_category: Target category of the event source. + * @id.instance: Instance ID of the event source. + * @flags: Flags used for enabling the event. + * + * Specifies which event should be enabled/disabled and how to do that. + */ +struct ssam_cdev_event_desc { + struct { + __u8 target_category; + __u8 target_id; + __u8 cid_enable; + __u8 cid_disable; + } reg; + + struct { + __u8 target_category; + __u8 instance; + } id; + + __u8 flags; +} __attribute__((__packed__)); + /** * struct ssam_cdev_event - SSAM event sent by the EC. * @target_category: Target category of the event source. See &enum ssam_ssh_tc. @@ -111,5 +141,7 @@ struct ssam_cdev_event { #define SSAM_CDEV_REQUEST _IOWR(0xA5, 1, struct ssam_cdev_request) #define SSAM_CDEV_NOTIF_REGISTER _IOW(0xA5, 2, struct ssam_cdev_notifier_desc) #define SSAM_CDEV_NOTIF_UNREGISTER _IOW(0xA5, 3, struct ssam_cdev_notifier_desc) +#define SSAM_CDEV_EVENT_ENABLE _IOW(0xA5, 4, struct ssam_cdev_event_desc) +#define SSAM_CDEV_EVENT_DISABLE _IOW(0xA5, 5, struct ssam_cdev_event_desc) #endif /* _UAPI_LINUX_SURFACE_AGGREGATOR_CDEV_H */ -- cgit v1.2.3 From cbd224e0ddfe59eb1eb92e436825f3eca4de3c10 Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Fri, 4 Jun 2021 15:47:54 +0200 Subject: platform/surface: aggregator_cdev: Add lockdep support Mark functions with locking requirements via the corresponding lockdep calls for debugging and documentary purposes. Signed-off-by: Maximilian Luz Reviewed-by: Hans de Goede Link: https://lore.kernel.org/r/20210604134755.535590-7-luzmaximilian@gmail.com Signed-off-by: Hans de Goede --- drivers/platform/surface/surface_aggregator_cdev.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/drivers/platform/surface/surface_aggregator_cdev.c b/drivers/platform/surface/surface_aggregator_cdev.c index 7b86b36eaaa0..30fb50fde450 100644 --- a/drivers/platform/surface/surface_aggregator_cdev.c +++ b/drivers/platform/surface/surface_aggregator_cdev.c @@ -139,6 +139,8 @@ static int ssam_cdev_notifier_register(struct ssam_cdev_client *client, u8 tc, i struct ssam_cdev_notifier *nf; int status; + lockdep_assert_held_read(&client->cdev->lock); + /* Validate notifier target category. */ if (!ssh_rqid_is_event(rqid)) return -EINVAL; @@ -188,6 +190,8 @@ static int ssam_cdev_notifier_unregister(struct ssam_cdev_client *client, u8 tc) const u16 event = ssh_rqid_to_event(rqid); int status; + lockdep_assert_held_read(&client->cdev->lock); + /* Validate notifier target category. */ if (!ssh_rqid_is_event(rqid)) return -EINVAL; @@ -257,6 +261,8 @@ static long ssam_cdev_request(struct ssam_cdev_client *client, struct ssam_cdev_ void __user *rspdata; int status = 0, ret = 0, tmp; + lockdep_assert_held_read(&client->cdev->lock); + ret = copy_struct_from_user(&rqst, sizeof(rqst), r, sizeof(*r)); if (ret) goto out; @@ -367,6 +373,8 @@ static long ssam_cdev_notif_register(struct ssam_cdev_client *client, struct ssam_cdev_notifier_desc desc; long ret; + lockdep_assert_held_read(&client->cdev->lock); + ret = copy_struct_from_user(&desc, sizeof(desc), d, sizeof(*d)); if (ret) return ret; @@ -380,6 +388,8 @@ static long ssam_cdev_notif_unregister(struct ssam_cdev_client *client, struct ssam_cdev_notifier_desc desc; long ret; + lockdep_assert_held_read(&client->cdev->lock); + ret = copy_struct_from_user(&desc, sizeof(desc), d, sizeof(*d)); if (ret) return ret; @@ -395,6 +405,8 @@ static long ssam_cdev_event_enable(struct ssam_cdev_client *client, struct ssam_event_id id; long ret; + lockdep_assert_held_read(&client->cdev->lock); + /* Read descriptor from user-space. */ ret = copy_struct_from_user(&desc, sizeof(desc), d, sizeof(*d)); if (ret) @@ -421,6 +433,8 @@ static long ssam_cdev_event_disable(struct ssam_cdev_client *client, struct ssam_event_id id; long ret; + lockdep_assert_held_read(&client->cdev->lock); + /* Read descriptor from user-space. */ ret = copy_struct_from_user(&desc, sizeof(desc), d, sizeof(*d)); if (ret) @@ -513,6 +527,8 @@ static int ssam_cdev_device_release(struct inode *inode, struct file *filp) static long __ssam_cdev_device_ioctl(struct ssam_cdev_client *client, unsigned int cmd, unsigned long arg) { + lockdep_assert_held_read(&client->cdev->lock); + switch (cmd) { case SSAM_CDEV_REQUEST: return ssam_cdev_request(client, (struct ssam_cdev_request __user *)arg); -- cgit v1.2.3 From 8ae200547aa9dbb1001c22325d251b825113bdb3 Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Fri, 4 Jun 2021 15:47:55 +0200 Subject: docs: driver-api: Update Surface Aggregator user-space interface documentation Update the controller-device user-space interface (cdev) documentation for the newly introduced IOCTLs and event interface. Signed-off-by: Maximilian Luz Reviewed-by: Hans de Goede Link: https://lore.kernel.org/r/20210604134755.535590-8-luzmaximilian@gmail.com Signed-off-by: Hans de Goede --- .../driver-api/surface_aggregator/clients/cdev.rst | 127 ++++++++++++++++++++- 1 file changed, 122 insertions(+), 5 deletions(-) diff --git a/Documentation/driver-api/surface_aggregator/clients/cdev.rst b/Documentation/driver-api/surface_aggregator/clients/cdev.rst index 248c1372d879..0134a841a079 100644 --- a/Documentation/driver-api/surface_aggregator/clients/cdev.rst +++ b/Documentation/driver-api/surface_aggregator/clients/cdev.rst @@ -1,9 +1,8 @@ .. SPDX-License-Identifier: GPL-2.0+ -.. |u8| replace:: :c:type:`u8 ` -.. |u16| replace:: :c:type:`u16 ` .. |ssam_cdev_request| replace:: :c:type:`struct ssam_cdev_request ` .. |ssam_cdev_request_flags| replace:: :c:type:`enum ssam_cdev_request_flags ` +.. |ssam_cdev_event| replace:: :c:type:`struct ssam_cdev_event ` ============================== User-Space EC Interface (cdev) @@ -23,6 +22,40 @@ These IOCTLs and their respective input/output parameter structs are defined in A small python library and scripts for accessing this interface can be found at https://github.com/linux-surface/surface-aggregator-module/tree/master/scripts/ssam. +.. contents:: + + +Receiving Events +================ + +Events can be received by reading from the device-file. The are represented by +the |ssam_cdev_event| datatype. + +Before events are available to be read, however, the desired notifiers must be +registered via the ``SSAM_CDEV_NOTIF_REGISTER`` IOCTL. Notifiers are, in +essence, callbacks, called when the EC sends an event. They are, in this +interface, associated with a specific target category and device-file-instance. +They forward any event of this category to the buffer of the corresponding +instance, from which it can then be read. + +Notifiers themselves do not enable events on the EC. Thus, it may additionally +be necessary to enable events via the ``SSAM_CDEV_EVENT_ENABLE`` IOCTL. While +notifiers work per-client (i.e. per-device-file-instance), events are enabled +globally, for the EC and all of its clients (regardless of userspace or +non-userspace). The ``SSAM_CDEV_EVENT_ENABLE`` and ``SSAM_CDEV_EVENT_DISABLE`` +IOCTLs take care of reference counting the events, such that an event is +enabled as long as there is a client that has requested it. + +Note that enabled events are not automatically disabled once the client +instance is closed. Therefore any client process (or group of processes) should +balance their event enable calls with the corresponding event disable calls. It +is, however, perfectly valid to enable and disable events on different client +instances. For example, it is valid to set up notifiers and read events on +client instance ``A``, enable those events on instance ``B`` (note that these +will also be received by A since events are enabled/disabled globally), and +after no more events are desired, disable the previously enabled events via +instance ``C``. + Controller IOCTLs ================= @@ -45,9 +78,33 @@ The following IOCTLs are provided: - ``REQUEST`` - Perform synchronous SAM request. + * - ``0xA5`` + - ``2`` + - ``W`` + - ``NOTIF_REGISTER`` + - Register event notifier. -``REQUEST`` ------------ + * - ``0xA5`` + - ``3`` + - ``W`` + - ``NOTIF_UNREGISTER`` + - Unregister event notifier. + + * - ``0xA5`` + - ``4`` + - ``W`` + - ``EVENT_ENABLE`` + - Enable event source. + + * - ``0xA5`` + - ``5`` + - ``W`` + - ``EVENT_DISABLE`` + - Disable event source. + + +``SSAM_CDEV_REQUEST`` +--------------------- Defined as ``_IOWR(0xA5, 1, struct ssam_cdev_request)``. @@ -82,6 +139,66 @@ submitted, and completed (i.e. handed back to user-space) successfully from inside the IOCTL, but the request ``status`` member may still be negative in case the actual execution of the request failed after it has been submitted. -A full definition of the argument struct is provided below: +A full definition of the argument struct is provided below. + +``SSAM_CDEV_NOTIF_REGISTER`` +---------------------------- + +Defined as ``_IOW(0xA5, 2, struct ssam_cdev_notifier_desc)``. + +Register a notifier for the event target category specified in the given +notifier description with the specified priority. Notifiers registration is +required to receive events, but does not enable events themselves. After a +notifier for a specific target category has been registered, all events of that +category will be forwarded to the userspace client and can then be read from +the device file instance. Note that events may have to be enabled, e.g. via the +``SSAM_CDEV_EVENT_ENABLE`` IOCTL, before the EC will send them. + +Only one notifier can be registered per target category and client instance. If +a notifier has already been registered, this IOCTL will fail with ``-EEXIST``. + +Notifiers will automatically be removed when the device file instance is +closed. + +``SSAM_CDEV_NOTIF_UNREGISTER`` +------------------------------ + +Defined as ``_IOW(0xA5, 3, struct ssam_cdev_notifier_desc)``. + +Unregisters the notifier associated with the specified target category. The +priority field will be ignored by this IOCTL. If no notifier has been +registered for this client instance and the given category, this IOCTL will +fail with ``-ENOENT``. + +``SSAM_CDEV_EVENT_ENABLE`` +-------------------------- + +Defined as ``_IOW(0xA5, 4, struct ssam_cdev_event_desc)``. + +Enable the event associated with the given event descriptor. + +Note that this call will not register a notifier itself, it will only enable +events on the controller. If you want to receive events by reading from the +device file, you will need to register the corresponding notifier(s) on that +instance. + +Events are not automatically disabled when the device file is closed. This must +be done manually, via a call to the ``SSAM_CDEV_EVENT_DISABLE`` IOCTL. + +``SSAM_CDEV_EVENT_DISABLE`` +--------------------------- + +Defined as ``_IOW(0xA5, 5, struct ssam_cdev_event_desc)``. + +Disable the event associated with the given event descriptor. + +Note that this will not unregister any notifiers. Events may still be received +and forwarded to user-space after this call. The only safe way of stopping +events from being received is unregistering all previously registered +notifiers. + + +Structures and Enums +==================== .. kernel-doc:: include/uapi/linux/surface_aggregator/cdev.h -- cgit v1.2.3 From 37ed76a745b099565b4ae7915f0441b1316bf108 Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Fri, 4 Jun 2021 23:09:06 +0200 Subject: platform/surface: aggregator: Do not return uninitialized value The status variable in ssam_nf_refcount_disable_free() is only set when the reference count equals zero. Otherwise, it is returned uninitialized. Fix this by always initializing status to zero. Reported-by: kernel test robot Fixes: 640ee17199e4 ("platform/surface: aggregator: Allow enabling of events without notifiers") Signed-off-by: Maximilian Luz Link: https://lore.kernel.org/r/20210604210907.25738-2-luzmaximilian@gmail.com Signed-off-by: Hans de Goede --- drivers/platform/surface/aggregator/controller.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c index 6646f4d6e10d..634399387d76 100644 --- a/drivers/platform/surface/aggregator/controller.c +++ b/drivers/platform/surface/aggregator/controller.c @@ -2228,7 +2228,7 @@ static int ssam_nf_refcount_disable_free(struct ssam_controller *ctrl, const struct ssam_event_registry reg = entry->key.reg; const struct ssam_event_id id = entry->key.id; struct ssam_nf *nf = &ctrl->cplt.event.notif; - int status; + int status = 0; lockdep_assert_held(&nf->lock); -- cgit v1.2.3 From f9e7f9a2b2a0d76c03ebdbb8ffc7940017b326b9 Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Fri, 4 Jun 2021 23:09:07 +0200 Subject: platform/surface: aggregator: Drop unnecessary variable initialization The status variable in ssam_controller_event_disable() is always set, no need to initialize it. Signed-off-by: Maximilian Luz Link: https://lore.kernel.org/r/20210604210907.25738-3-luzmaximilian@gmail.com Signed-off-by: Hans de Goede --- drivers/platform/surface/aggregator/controller.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c index 634399387d76..b8c377b3f932 100644 --- a/drivers/platform/surface/aggregator/controller.c +++ b/drivers/platform/surface/aggregator/controller.c @@ -2464,7 +2464,7 @@ int ssam_controller_event_disable(struct ssam_controller *ctrl, u16 rqid = ssh_tc_to_rqid(id.target_category); struct ssam_nf *nf = &ctrl->cplt.event.notif; struct ssam_nf_refcount_entry *entry; - int status = 0; + int status; if (!ssh_rqid_is_event(rqid)) return -EINVAL; -- cgit v1.2.3 From a8aedd45d7dd7d3b6136c90bd755cb68743d930e Mon Sep 17 00:00:00 2001 From: Baokun Li Date: Wed, 9 Jun 2021 15:26:38 +0800 Subject: platform/surface: aggregator: Use list_move_tail instead of list_del/list_add_tail in ssh_request_layer.c Using list_move_tail() instead of list_del() + list_add_tail() in ssh_request_layer.c. Reported-by: Hulk Robot Signed-off-by: Baokun Li Reviewed-by: Maximilian Luz Link: https://lore.kernel.org/r/20210609072638.1358174-1-libaokun1@huawei.com Signed-off-by: Hans de Goede --- drivers/platform/surface/aggregator/ssh_request_layer.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/drivers/platform/surface/aggregator/ssh_request_layer.c b/drivers/platform/surface/aggregator/ssh_request_layer.c index bfe1aaf38065..790f7f0eee98 100644 --- a/drivers/platform/surface/aggregator/ssh_request_layer.c +++ b/drivers/platform/surface/aggregator/ssh_request_layer.c @@ -863,9 +863,7 @@ static void ssh_rtl_timeout_reap(struct work_struct *work) clear_bit(SSH_REQUEST_SF_PENDING_BIT, &r->state); atomic_dec(&rtl->pending.count); - list_del(&r->node); - - list_add_tail(&r->node, &claimed); + list_move_tail(&r->node, &claimed); } spin_unlock(&rtl->pending.lock); @@ -1204,8 +1202,7 @@ void ssh_rtl_shutdown(struct ssh_rtl *rtl) smp_mb__before_atomic(); clear_bit(SSH_REQUEST_SF_QUEUED_BIT, &r->state); - list_del(&r->node); - list_add_tail(&r->node, &claimed); + list_move_tail(&r->node, &claimed); } spin_unlock(&rtl->queue.lock); @@ -1238,8 +1235,7 @@ void ssh_rtl_shutdown(struct ssh_rtl *rtl) smp_mb__before_atomic(); clear_bit(SSH_REQUEST_SF_PENDING_BIT, &r->state); - list_del(&r->node); - list_add_tail(&r->node, &claimed); + list_move_tail(&r->node, &claimed); } spin_unlock(&rtl->pending.lock); } -- cgit v1.2.3 From be9c4fa236e24af5cc3271a16e209eab098566c4 Mon Sep 17 00:00:00 2001 From: Baokun Li Date: Wed, 9 Jun 2021 15:24:48 +0800 Subject: platform/surface: aggregator: Use list_move_tail instead of list_del/list_add_tail in ssh_packet_layer.c Using list_move_tail() instead of list_del() + list_add_tail() in ssh_packet_layer.c. Reported-by: Hulk Robot Signed-off-by: Baokun Li Reviewed-by: Maximilian Luz Link: https://lore.kernel.org/r/20210609072448.1357524-1-libaokun1@huawei.com Signed-off-by: Hans de Goede --- drivers/platform/surface/aggregator/ssh_packet_layer.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/drivers/platform/surface/aggregator/ssh_packet_layer.c b/drivers/platform/surface/aggregator/ssh_packet_layer.c index 5e08049fc3ac..8a4451c1ffe5 100644 --- a/drivers/platform/surface/aggregator/ssh_packet_layer.c +++ b/drivers/platform/surface/aggregator/ssh_packet_layer.c @@ -1567,9 +1567,7 @@ static void ssh_ptl_timeout_reap(struct work_struct *work) clear_bit(SSH_PACKET_SF_PENDING_BIT, &p->state); atomic_dec(&ptl->pending.count); - list_del(&p->pending_node); - - list_add_tail(&p->pending_node, &claimed); + list_move_tail(&p->pending_node, &claimed); } spin_unlock(&ptl->pending.lock); @@ -1957,8 +1955,7 @@ void ssh_ptl_shutdown(struct ssh_ptl *ptl) smp_mb__before_atomic(); clear_bit(SSH_PACKET_SF_QUEUED_BIT, &p->state); - list_del(&p->queue_node); - list_add_tail(&p->queue_node, &complete_q); + list_move_tail(&p->queue_node, &complete_q); } spin_unlock(&ptl->queue.lock); @@ -1970,8 +1967,7 @@ void ssh_ptl_shutdown(struct ssh_ptl *ptl) smp_mb__before_atomic(); clear_bit(SSH_PACKET_SF_PENDING_BIT, &p->state); - list_del(&p->pending_node); - list_add_tail(&p->pending_node, &complete_q); + list_move_tail(&p->pending_node, &complete_q); } atomic_set(&ptl->pending.count, 0); spin_unlock(&ptl->pending.lock); -- cgit v1.2.3 From c297937fbb50edd6c5ebc80fa4aa1d59246fb0ed Mon Sep 17 00:00:00 2001 From: Rikard Falkeborn Date: Sat, 5 Jun 2021 22:38:04 +0200 Subject: platform/x86: hdaps: Constify static attribute_group struct The only use of hdaps_attribute_group is to pass its address to sysfs_create_group() and sysfs_remove_group(), both which takes pointers to const attribute_group structs. Make it const to allow the compiler to put it in read-only memory. Signed-off-by: Rikard Falkeborn Reviewed-by: Frank Seidel Link: https://lore.kernel.org/r/20210605203807.60547-2-rikard.falkeborn@gmail.com Signed-off-by: Hans de Goede --- drivers/platform/x86/hdaps.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/platform/x86/hdaps.c b/drivers/platform/x86/hdaps.c index a72270932ec3..9996485f5295 100644 --- a/drivers/platform/x86/hdaps.c +++ b/drivers/platform/x86/hdaps.c @@ -462,7 +462,7 @@ static struct attribute *hdaps_attributes[] = { NULL, }; -static struct attribute_group hdaps_attribute_group = { +static const struct attribute_group hdaps_attribute_group = { .attrs = hdaps_attributes, }; -- cgit v1.2.3 From d24023e375704860c6c8b91c3af3034669aa1bc5 Mon Sep 17 00:00:00 2001 From: Rikard Falkeborn Date: Sat, 5 Jun 2021 22:38:05 +0200 Subject: platform/x86: intel_pmt_crashlog: Constify static attribute_group struct The only use of pmt_crashlog_group is to assign its address to the attr_grp field in the intel_pmt_namespace struct, which is a pointer to const attribute_group. Make it const to allow the compiler to put it in read-only memory. Signed-off-by: Rikard Falkeborn Link: https://lore.kernel.org/r/20210605203807.60547-3-rikard.falkeborn@gmail.com Signed-off-by: Hans de Goede --- drivers/platform/x86/intel_pmt_crashlog.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/platform/x86/intel_pmt_crashlog.c b/drivers/platform/x86/intel_pmt_crashlog.c index 92d315a16cfd..56963ceb6345 100644 --- a/drivers/platform/x86/intel_pmt_crashlog.c +++ b/drivers/platform/x86/intel_pmt_crashlog.c @@ -218,7 +218,7 @@ static struct attribute *pmt_crashlog_attrs[] = { NULL }; -static struct attribute_group pmt_crashlog_group = { +static const struct attribute_group pmt_crashlog_group = { .attrs = pmt_crashlog_attrs, }; -- cgit v1.2.3 From 62ef96919720b30d5e84a193e64490da1d30d776 Mon Sep 17 00:00:00 2001 From: Rikard Falkeborn Date: Sat, 5 Jun 2021 22:38:06 +0200 Subject: platform/x86: tc1100-wmi: Constify static attribute_group struct The only use of tc1100_attribute_group is to pass its address to sysfs_create_group() and sysfs_remove_group(), both which takes pointer to const attribute_group structs. Make it const to allow the compiler to put it in read-only memory. Signed-off-by: Rikard Falkeborn Link: https://lore.kernel.org/r/20210605203807.60547-4-rikard.falkeborn@gmail.com Signed-off-by: Hans de Goede --- drivers/platform/x86/tc1100-wmi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/platform/x86/tc1100-wmi.c b/drivers/platform/x86/tc1100-wmi.c index 803920b6f01d..9072eb302618 100644 --- a/drivers/platform/x86/tc1100-wmi.c +++ b/drivers/platform/x86/tc1100-wmi.c @@ -156,7 +156,7 @@ static struct attribute *tc1100_attributes[] = { NULL }; -static struct attribute_group tc1100_attribute_group = { +static const struct attribute_group tc1100_attribute_group = { .attrs = tc1100_attributes, }; -- cgit v1.2.3 From 77d06ec65a281c5382f4ea2398a267dc3bd7bfe3 Mon Sep 17 00:00:00 2001 From: Rikard Falkeborn Date: Sat, 5 Jun 2021 22:38:07 +0200 Subject: x86/platform/uv: Constify static attribute_group struct The only use of base_attr_group and hubless_base_attr_group is to pass their addresses to sysfs_create_group() and sysfs_remove_group(), both which takes pointers to const attribute_group structs. Make them const to allow the compiler to put them in read-only memory. Signed-off-by: Rikard Falkeborn Reviewed-by: Justin Ernst Link: https://lore.kernel.org/r/20210605203807.60547-5-rikard.falkeborn@gmail.com Signed-off-by: Hans de Goede --- drivers/platform/x86/uv_sysfs.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/platform/x86/uv_sysfs.c b/drivers/platform/x86/uv_sysfs.c index 7badcfa3f384..956a354b57c1 100644 --- a/drivers/platform/x86/uv_sysfs.c +++ b/drivers/platform/x86/uv_sysfs.c @@ -778,7 +778,7 @@ static struct attribute *base_attrs[] = { NULL, }; -static struct attribute_group base_attr_group = { +static const struct attribute_group base_attr_group = { .attrs = base_attrs }; @@ -823,7 +823,7 @@ static struct attribute *hubless_base_attrs[] = { NULL, }; -static struct attribute_group hubless_base_attr_group = { +static const struct attribute_group hubless_base_attr_group = { .attrs = hubless_base_attrs }; -- cgit v1.2.3 From 8f44f316d1da2ad521e62028a812284bb72ef3d4 Mon Sep 17 00:00:00 2001 From: yangerkun Date: Mon, 7 Jun 2021 09:47:02 +0800 Subject: platform/x86: intel_ips: fix set but unused warning in read_mgtv drivers/platform/x86/intel_ips.c:832:6: warning: variable ‘ret’ set but not used [-Wunused-but-set-variable] 832 | u16 ret; | ^~~ Fix it by mark ret as '__maybe_unused'. Reported-by: Hulk Robot Signed-off-by: yangerkun Link: https://lore.kernel.org/r/20210607014702.2981097-1-yangerkun@huawei.com Signed-off-by: Hans de Goede --- drivers/platform/x86/intel_ips.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/platform/x86/intel_ips.c b/drivers/platform/x86/intel_ips.c index bffe548187ee..4dfdbfca6841 100644 --- a/drivers/platform/x86/intel_ips.c +++ b/drivers/platform/x86/intel_ips.c @@ -829,7 +829,7 @@ static u16 calc_avg_temp(struct ips_driver *ips, u16 *array) static u16 read_mgtv(struct ips_driver *ips) { - u16 ret; + u16 __maybe_unused ret; u64 slope, offset; u64 val; -- cgit v1.2.3 From cb58c277ff1a35432cd84a6cc9768c60ce4c2cad Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 9 Jun 2021 16:59:52 +0200 Subject: platform/x86: dell-wmi-sysman/think-lmi: Make fw_attr_class global static The dell-wmi-sysman and think-lmi kernel modules both have a global struct class *fw_attr_class variable, leading to the following compile errors when both are builtin: ld: drivers/platform/x86/think-lmi.o:(.bss+0x0): multiple definition of `fw_attr_class'; drivers/platform/x86/dell/dell-wmi-sysman/sysman.o:(.bss+0x0): first defined here In both cases the variable is only used in the file where it is declared. Make both declarations static to avoid the linker error. Cc: Mark Pearson Cc: Dell.Client.Kernel@dell.com Reported-by: Nathan Chancellor Signed-off-by: Hans de Goede Link: https://lore.kernel.org/r/20210609145952.113393-1-hdegoede@redhat.com --- drivers/platform/x86/dell/dell-wmi-sysman/sysman.c | 2 +- drivers/platform/x86/think-lmi.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/platform/x86/dell/dell-wmi-sysman/sysman.c b/drivers/platform/x86/dell/dell-wmi-sysman/sysman.c index 1378c1878658..636bdfa83284 100644 --- a/drivers/platform/x86/dell/dell-wmi-sysman/sysman.c +++ b/drivers/platform/x86/dell/dell-wmi-sysman/sysman.c @@ -25,7 +25,7 @@ struct wmi_sysman_priv wmi_priv = { /* reset bios to defaults */ static const char * const reset_types[] = {"builtinsafe", "lastknowngood", "factory", "custom"}; static int reset_option = -1; -struct class *fw_attr_class; +static struct class *fw_attr_class; /** diff --git a/drivers/platform/x86/think-lmi.c b/drivers/platform/x86/think-lmi.c index 782d8e3fe7a1..c6413b906e4a 100644 --- a/drivers/platform/x86/think-lmi.c +++ b/drivers/platform/x86/think-lmi.c @@ -134,7 +134,7 @@ static const char * const encoding_options[] = { [TLMI_ENCODING_SCANCODE] = "scancode", }; static struct think_lmi tlmi_priv; -struct class *fw_attr_class; +static struct class *fw_attr_class; /* ------ Utility functions ------------*/ /* Convert BIOS WMI error string to suitable error code */ -- cgit v1.2.3 From 86bb2e3daf5d84c02ef40da8bf26f7b851aaa8a7 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 9 Jun 2021 17:17:51 +0200 Subject: platform/x86: think-lmi: Fix check for admin password being set tlmi_priv.pwd_admin->password is an array (not a pointer), so the correct way to check for the password being set is to check for tlmi_priv.pwd_admin->password[0] != 0. For the second check, replace the check with checking that auth_str is set instead. Cc: Mark Pearson Reported-by: Dan Carpenter Reported-by: coverity-bot Addresses-Coverity-ID: 1505158 ("NO_EFFECT") Fixes: a7314b3b1d8a ("platform/x86: think-lmi: Add WMI interface support on Lenovo platforms") Signed-off-by: Hans de Goede Link: https://lore.kernel.org/r/20210609151752.156902-1-hdegoede@redhat.com --- drivers/platform/x86/think-lmi.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/platform/x86/think-lmi.c b/drivers/platform/x86/think-lmi.c index c6413b906e4a..4881de4e669d 100644 --- a/drivers/platform/x86/think-lmi.c +++ b/drivers/platform/x86/think-lmi.c @@ -537,7 +537,7 @@ static ssize_t current_value_store(struct kobject *kobj, p = strchrnul(new_setting, '\n'); *p = '\0'; - if (tlmi_priv.pwd_admin->valid && tlmi_priv.pwd_admin->password) { + if (tlmi_priv.pwd_admin->valid && tlmi_priv.pwd_admin->password[0]) { auth_str = kasprintf(GFP_KERNEL, "%s,%s,%s;", tlmi_priv.pwd_admin->password, encoding_options[tlmi_priv.pwd_admin->encoding], @@ -563,7 +563,7 @@ static ssize_t current_value_store(struct kobject *kobj, if (ret) goto out; - if (tlmi_priv.pwd_admin->valid && tlmi_priv.pwd_admin->password) + if (auth_str) ret = tlmi_save_bios_settings(auth_str); else ret = tlmi_save_bios_settings(""); -- cgit v1.2.3 From 0ddcf3a6b44209e73fb21b3c53e258884ea90cef Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 9 Jun 2021 17:17:52 +0200 Subject: platform/x86: think-lmi: Avoid potential read before start of the buffer If length equals 0 then reading buf[length-1] will read before the start of the buffer. Avoid this by moving the length == 0 check up. Cc: Mark Pearson Reported-by: Dan Carpenter Signed-off-by: Hans de Goede Link: https://lore.kernel.org/r/20210609151752.156902-2-hdegoede@redhat.com --- drivers/platform/x86/think-lmi.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/platform/x86/think-lmi.c b/drivers/platform/x86/think-lmi.c index 4881de4e669d..7771c9359449 100644 --- a/drivers/platform/x86/think-lmi.c +++ b/drivers/platform/x86/think-lmi.c @@ -443,10 +443,13 @@ static ssize_t kbdlang_store(struct kobject *kobj, int length; length = strlen(buf); + if (!length) + return -EINVAL; + if (buf[length-1] == '\n') length--; - if (!length || (length >= TLMI_LANG_MAXLEN)) + if (length >= TLMI_LANG_MAXLEN) return -EINVAL; memcpy(setting->kbdlang, buf, length); -- cgit v1.2.3 From 039e6a3117b0f4c4c4884a560f68cb13d55ad0c4 Mon Sep 17 00:00:00 2001 From: Zou Wei Date: Wed, 16 Jun 2021 10:50:09 +0800 Subject: platform/x86: think-lmi: Add missing MODULE_DEVICE_TABLE This patch adds missing MODULE_DEVICE_TABLE definition which generates correct modalias for automatic loading of this driver when it is built as an external module. Reported-by: Hulk Robot Signed-off-by: Zou Wei Link: https://lore.kernel.org/r/1623811809-65099-1-git-send-email-zou_wei@huawei.com Signed-off-by: Hans de Goede --- drivers/platform/x86/think-lmi.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/platform/x86/think-lmi.c b/drivers/platform/x86/think-lmi.c index 7771c9359449..c6c9fbb8a53e 100644 --- a/drivers/platform/x86/think-lmi.c +++ b/drivers/platform/x86/think-lmi.c @@ -873,6 +873,7 @@ static const struct wmi_device_id tlmi_id_table[] = { { .guid_string = LENOVO_BIOS_SETTING_GUID }, { } }; +MODULE_DEVICE_TABLE(wmi, tlmi_id_table); static struct wmi_driver tlmi_driver = { .driver = { -- cgit v1.2.3 From 33ec58bd640a62a242d2e3e5f98ff7c478f1466c Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Mon, 14 Jun 2021 21:46:35 +0200 Subject: MAINTAINERS: Update IRC link for Surface System Aggregator subsystem We have moved to libera.chat. Update the link accordingly. Signed-off-by: Maximilian Luz Link: https://lore.kernel.org/r/20210614194635.1681519-1-luzmaximilian@gmail.com Signed-off-by: Hans de Goede --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index 771ea74e7e2d..fecb2e42f956 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -12190,7 +12190,7 @@ M: Maximilian Luz L: platform-driver-x86@vger.kernel.org S: Maintained W: https://github.com/linux-surface/surface-aggregator-module -C: irc://chat.freenode.net/##linux-surface +C: irc://irc.libera.chat/linux-surface F: Documentation/driver-api/surface_aggregator/ F: drivers/platform/surface/aggregator/ F: drivers/platform/surface/surface_acpi_notify.c -- cgit v1.2.3 From 5de691bffe57fd0fc2b4dcdcf13815c56d11db10 Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Thu, 3 Jun 2021 23:40:06 +0100 Subject: platform/x86: Add intel_skl_int3472 driver ACPI devices with _HID INT3472 are currently matched to the tps68470 driver, however this does not cover all situations in which that _HID occurs. We've encountered three possibilities: 1. On Chrome OS devices, an ACPI device with _HID INT3472 (representing a physical TPS68470 device) that requires a GPIO and OpRegion driver 2. On devices designed for Windows, an ACPI device with _HID INT3472 (again representing a physical TPS68470 device) which requires GPIO, Clock and Regulator drivers. 3. On other devices designed for Windows, an ACPI device with _HID INT3472 which does **not** represent a physical TPS68470, and is instead used as a dummy device to group some system GPIO lines which are meant to be consumed by the sensor that is dependent on this entry. This commit adds a new module, registering a platform driver to deal with the 3rd scenario plus an i2c driver to deal with #1 and #2, by querying the CLDB buffer found against INT3472 entries to determine which is most appropriate. Suggested-by: Laurent Pinchart Signed-off-by: Daniel Scally Link: https://lore.kernel.org/r/20210603224007.120560-6-djrscally@gmail.com [hdegoede@redhat.com Make skl_int3472_tps68470_calc_type() static] Signed-off-by: Hans de Goede --- MAINTAINERS | 5 + drivers/platform/x86/Kconfig | 2 + drivers/platform/x86/Makefile | 1 + drivers/platform/x86/intel-int3472/Kconfig | 30 ++ drivers/platform/x86/intel-int3472/Makefile | 5 + .../intel_skl_int3472_clk_and_regulator.c | 196 ++++++++++ .../x86/intel-int3472/intel_skl_int3472_common.c | 106 ++++++ .../x86/intel-int3472/intel_skl_int3472_common.h | 118 ++++++ .../x86/intel-int3472/intel_skl_int3472_discrete.c | 417 +++++++++++++++++++++ .../x86/intel-int3472/intel_skl_int3472_tps68470.c | 137 +++++++ 10 files changed, 1017 insertions(+) create mode 100644 drivers/platform/x86/intel-int3472/Kconfig create mode 100644 drivers/platform/x86/intel-int3472/Makefile create mode 100644 drivers/platform/x86/intel-int3472/intel_skl_int3472_clk_and_regulator.c create mode 100644 drivers/platform/x86/intel-int3472/intel_skl_int3472_common.c create mode 100644 drivers/platform/x86/intel-int3472/intel_skl_int3472_common.h create mode 100644 drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c create mode 100644 drivers/platform/x86/intel-int3472/intel_skl_int3472_tps68470.c diff --git a/MAINTAINERS b/MAINTAINERS index fecb2e42f956..7da4c07364fa 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9385,6 +9385,11 @@ S: Maintained F: arch/x86/include/asm/intel_scu_ipc.h F: drivers/platform/x86/intel_scu_* +INTEL SKYLAKE INT3472 ACPI DEVICE DRIVER +M: Daniel Scally +S: Maintained +F: drivers/platform/x86/intel-int3472/ + INTEL SPEED SELECT TECHNOLOGY M: Srinivas Pandruvada L: platform-driver-x86@vger.kernel.org diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 1e538ce8feaf..4fd792f2a10a 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -709,6 +709,8 @@ config INTEL_CHT_INT33FE device and CONFIG_TYPEC_FUSB302=m and CONFIG_BATTERY_MAX17042=m for Type-C device. +source "drivers/platform/x86/intel-int3472/Kconfig" + config INTEL_HID_EVENT tristate "INTEL HID Event" depends on ACPI diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index ff620d653d39..a1f64613af71 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -76,6 +76,7 @@ obj-$(CONFIG_INTEL_HID_EVENT) += intel-hid.o obj-$(CONFIG_INTEL_INT0002_VGPIO) += intel_int0002_vgpio.o obj-$(CONFIG_INTEL_MENLOW) += intel_menlow.o obj-$(CONFIG_INTEL_OAKTRAIL) += intel_oaktrail.o +obj-$(CONFIG_INTEL_SKL_INT3472) += intel-int3472/ obj-$(CONFIG_INTEL_VBTN) += intel-vbtn.o # MSI diff --git a/drivers/platform/x86/intel-int3472/Kconfig b/drivers/platform/x86/intel-int3472/Kconfig new file mode 100644 index 000000000000..c112878e833b --- /dev/null +++ b/drivers/platform/x86/intel-int3472/Kconfig @@ -0,0 +1,30 @@ +config INTEL_SKL_INT3472 + tristate "Intel SkyLake ACPI INT3472 Driver" + depends on ACPI + depends on COMMON_CLK && CLKDEV_LOOKUP + depends on I2C + depends on GPIOLIB + depends on REGULATOR + select MFD_CORE + select REGMAP_I2C + help + This driver adds power controller support for the Intel SkyCam + devices found on the Intel SkyLake platforms. + + The INT3472 is a camera power controller, a logical device found on + Intel Skylake-based systems that can map to different hardware + devices depending on the platform. On machines designed for Chrome OS + it maps to a TPS68470 camera PMIC. On machines designed for Windows, + it maps to either a TP68470 camera PMIC, a uP6641Q sensor PMIC, or a + set of discrete GPIOs and power gates. + + If your device was designed for Chrome OS, this driver will provide + an ACPI OpRegion, which must be available before any of the devices + using it are probed. For this reason, you should select Y if your + device was designed for ChromeOS. For the same reason the + I2C_DESIGNWARE_PLATFORM option must be set to Y too. + + Say Y or M here if you have a SkyLake device designed for use + with Windows or ChromeOS. Say N here if you are not sure. + + The module will be named "intel-skl-int3472". diff --git a/drivers/platform/x86/intel-int3472/Makefile b/drivers/platform/x86/intel-int3472/Makefile new file mode 100644 index 000000000000..48bd97f0a04e --- /dev/null +++ b/drivers/platform/x86/intel-int3472/Makefile @@ -0,0 +1,5 @@ +obj-$(CONFIG_INTEL_SKL_INT3472) += intel_skl_int3472.o +intel_skl_int3472-objs := intel_skl_int3472_common.o \ + intel_skl_int3472_discrete.o \ + intel_skl_int3472_tps68470.o \ + intel_skl_int3472_clk_and_regulator.o diff --git a/drivers/platform/x86/intel-int3472/intel_skl_int3472_clk_and_regulator.c b/drivers/platform/x86/intel-int3472/intel_skl_int3472_clk_and_regulator.c new file mode 100644 index 000000000000..ceee860e2c07 --- /dev/null +++ b/drivers/platform/x86/intel-int3472/intel_skl_int3472_clk_and_regulator.c @@ -0,0 +1,196 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Author: Dan Scally */ + +#include +#include +#include +#include +#include +#include +#include + +#include "intel_skl_int3472_common.h" + +/* + * The regulators have to have .ops to be valid, but the only ops we actually + * support are .enable and .disable which are handled via .ena_gpiod. Pass an + * empty struct to clear the check without lying about capabilities. + */ +static const struct regulator_ops int3472_gpio_regulator_ops; + +static int skl_int3472_clk_prepare(struct clk_hw *hw) +{ + struct int3472_gpio_clock *clk = to_int3472_clk(hw); + + gpiod_set_value_cansleep(clk->ena_gpio, 1); + gpiod_set_value_cansleep(clk->led_gpio, 1); + + return 0; +} + +static void skl_int3472_clk_unprepare(struct clk_hw *hw) +{ + struct int3472_gpio_clock *clk = to_int3472_clk(hw); + + gpiod_set_value_cansleep(clk->ena_gpio, 0); + gpiod_set_value_cansleep(clk->led_gpio, 0); +} + +static int skl_int3472_clk_enable(struct clk_hw *hw) +{ + /* + * We're just turning a GPIO on to enable the clock, which operation + * has the potential to sleep. Given .enable() cannot sleep, but + * .prepare() can, we toggle the GPIO in .prepare() instead. Thus, + * nothing to do here. + */ + return 0; +} + +static void skl_int3472_clk_disable(struct clk_hw *hw) +{ + /* Likewise, nothing to do here... */ +} + +static unsigned int skl_int3472_get_clk_frequency(struct int3472_discrete_device *int3472) +{ + union acpi_object *obj; + unsigned int freq; + + obj = skl_int3472_get_acpi_buffer(int3472->sensor, "SSDB"); + if (IS_ERR(obj)) + return 0; /* report rate as 0 on error */ + + if (obj->buffer.length < CIO2_SENSOR_SSDB_MCLKSPEED_OFFSET + sizeof(u32)) { + dev_err(int3472->dev, "The buffer is too small\n"); + kfree(obj); + return 0; + } + + freq = *(u32 *)(obj->buffer.pointer + CIO2_SENSOR_SSDB_MCLKSPEED_OFFSET); + + kfree(obj); + return freq; +} + +static unsigned long skl_int3472_clk_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct int3472_gpio_clock *clk = to_int3472_clk(hw); + + return clk->frequency; +} + +static const struct clk_ops skl_int3472_clock_ops = { + .prepare = skl_int3472_clk_prepare, + .unprepare = skl_int3472_clk_unprepare, + .enable = skl_int3472_clk_enable, + .disable = skl_int3472_clk_disable, + .recalc_rate = skl_int3472_clk_recalc_rate, +}; + +int skl_int3472_register_clock(struct int3472_discrete_device *int3472) +{ + struct clk_init_data init = { + .ops = &skl_int3472_clock_ops, + .flags = CLK_GET_RATE_NOCACHE, + }; + int ret; + + init.name = kasprintf(GFP_KERNEL, "%s-clk", + acpi_dev_name(int3472->adev)); + if (!init.name) + return -ENOMEM; + + int3472->clock.frequency = skl_int3472_get_clk_frequency(int3472); + + int3472->clock.clk_hw.init = &init; + int3472->clock.clk = clk_register(&int3472->adev->dev, + &int3472->clock.clk_hw); + if (IS_ERR(int3472->clock.clk)) { + ret = PTR_ERR(int3472->clock.clk); + goto out_free_init_name; + } + + int3472->clock.cl = clkdev_create(int3472->clock.clk, NULL, + int3472->sensor_name); + if (!int3472->clock.cl) { + ret = -ENOMEM; + goto err_unregister_clk; + } + + kfree(init.name); + return 0; + +err_unregister_clk: + clk_unregister(int3472->clock.clk); +out_free_init_name: + kfree(init.name); + + return ret; +} + +int skl_int3472_register_regulator(struct int3472_discrete_device *int3472, + struct acpi_resource *ares) +{ + char *path = ares->data.gpio.resource_source.string_ptr; + const struct int3472_sensor_config *sensor_config; + struct regulator_consumer_supply supply_map; + struct regulator_init_data init_data = { }; + struct regulator_config cfg = { }; + int ret; + + sensor_config = int3472->sensor_config; + if (IS_ERR(sensor_config)) { + dev_err(int3472->dev, "No sensor module config\n"); + return PTR_ERR(sensor_config); + } + + if (!sensor_config->supply_map.supply) { + dev_err(int3472->dev, "No supply name defined\n"); + return -ENODEV; + } + + init_data.constraints.valid_ops_mask = REGULATOR_CHANGE_STATUS; + init_data.num_consumer_supplies = 1; + supply_map = sensor_config->supply_map; + supply_map.dev_name = int3472->sensor_name; + init_data.consumer_supplies = &supply_map; + + snprintf(int3472->regulator.regulator_name, + sizeof(int3472->regulator.regulator_name), "%s-regulator", + acpi_dev_name(int3472->adev)); + snprintf(int3472->regulator.supply_name, + GPIO_REGULATOR_SUPPLY_NAME_LENGTH, "supply-0"); + + int3472->regulator.rdesc = INT3472_REGULATOR( + int3472->regulator.regulator_name, + int3472->regulator.supply_name, + &int3472_gpio_regulator_ops); + + int3472->regulator.gpio = acpi_get_and_request_gpiod(path, + ares->data.gpio.pin_table[0], + "int3472,regulator"); + if (IS_ERR(int3472->regulator.gpio)) { + dev_err(int3472->dev, "Failed to get regulator GPIO line\n"); + return PTR_ERR(int3472->regulator.gpio); + } + + cfg.dev = &int3472->adev->dev; + cfg.init_data = &init_data; + cfg.ena_gpiod = int3472->regulator.gpio; + + int3472->regulator.rdev = regulator_register(&int3472->regulator.rdesc, + &cfg); + if (IS_ERR(int3472->regulator.rdev)) { + ret = PTR_ERR(int3472->regulator.rdev); + goto err_free_gpio; + } + + return 0; + +err_free_gpio: + gpiod_put(int3472->regulator.gpio); + + return ret; +} diff --git a/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.c b/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.c new file mode 100644 index 000000000000..497e74fba75f --- /dev/null +++ b/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.c @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Author: Dan Scally */ + +#include +#include +#include +#include + +#include "intel_skl_int3472_common.h" + +union acpi_object *skl_int3472_get_acpi_buffer(struct acpi_device *adev, char *id) +{ + struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; + acpi_handle handle = adev->handle; + union acpi_object *obj; + acpi_status status; + + status = acpi_evaluate_object(handle, id, NULL, &buffer); + if (ACPI_FAILURE(status)) + return ERR_PTR(-ENODEV); + + obj = buffer.pointer; + if (!obj) + return ERR_PTR(-ENODEV); + + if (obj->type != ACPI_TYPE_BUFFER) { + acpi_handle_err(handle, "%s object is not an ACPI buffer\n", id); + kfree(obj); + return ERR_PTR(-EINVAL); + } + + return obj; +} + +int skl_int3472_fill_cldb(struct acpi_device *adev, struct int3472_cldb *cldb) +{ + union acpi_object *obj; + int ret; + + obj = skl_int3472_get_acpi_buffer(adev, "CLDB"); + if (IS_ERR(obj)) + return PTR_ERR(obj); + + if (obj->buffer.length > sizeof(*cldb)) { + acpi_handle_err(adev->handle, "The CLDB buffer is too large\n"); + ret = -EINVAL; + goto out_free_obj; + } + + memcpy(cldb, obj->buffer.pointer, obj->buffer.length); + ret = 0; + +out_free_obj: + kfree(obj); + return ret; +} + +static const struct acpi_device_id int3472_device_id[] = { + { "INT3472", 0 }, + { } +}; +MODULE_DEVICE_TABLE(acpi, int3472_device_id); + +static struct platform_driver int3472_discrete = { + .driver = { + .name = "int3472-discrete", + .acpi_match_table = int3472_device_id, + }, + .probe = skl_int3472_discrete_probe, + .remove = skl_int3472_discrete_remove, +}; + +static struct i2c_driver int3472_tps68470 = { + .driver = { + .name = "int3472-tps68470", + .acpi_match_table = int3472_device_id, + }, + .probe_new = skl_int3472_tps68470_probe, +}; + +static int skl_int3472_init(void) +{ + int ret; + + ret = platform_driver_register(&int3472_discrete); + if (ret) + return ret; + + ret = i2c_register_driver(THIS_MODULE, &int3472_tps68470); + if (ret) + platform_driver_unregister(&int3472_discrete); + + return ret; +} +module_init(skl_int3472_init); + +static void skl_int3472_exit(void) +{ + platform_driver_unregister(&int3472_discrete); + i2c_del_driver(&int3472_tps68470); +} +module_exit(skl_int3472_exit); + +MODULE_DESCRIPTION("Intel SkyLake INT3472 ACPI Device Driver"); +MODULE_AUTHOR("Daniel Scally "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.h b/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.h new file mode 100644 index 000000000000..6fdf78584219 --- /dev/null +++ b/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.h @@ -0,0 +1,118 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Author: Dan Scally */ + +#ifndef _INTEL_SKL_INT3472_H +#define _INTEL_SKL_INT3472_H + +#include +#include +#include +#include +#include + +/* FIXME drop this once the I2C_DEV_NAME_FORMAT macro has been added to include/linux/i2c.h */ +#ifndef I2C_DEV_NAME_FORMAT +#define I2C_DEV_NAME_FORMAT "i2c-%s" +#endif + +/* PMIC GPIO Types */ +#define INT3472_GPIO_TYPE_RESET 0x00 +#define INT3472_GPIO_TYPE_POWERDOWN 0x01 +#define INT3472_GPIO_TYPE_POWER_ENABLE 0x0b +#define INT3472_GPIO_TYPE_CLK_ENABLE 0x0c +#define INT3472_GPIO_TYPE_PRIVACY_LED 0x0d + +#define INT3472_PDEV_MAX_NAME_LEN 23 +#define INT3472_MAX_SENSOR_GPIOS 3 + +#define GPIO_REGULATOR_NAME_LENGTH 21 +#define GPIO_REGULATOR_SUPPLY_NAME_LENGTH 9 + +#define CIO2_SENSOR_SSDB_MCLKSPEED_OFFSET 86 + +#define INT3472_REGULATOR(_name, _supply, _ops) \ + (const struct regulator_desc) { \ + .name = _name, \ + .supply_name = _supply, \ + .type = REGULATOR_VOLTAGE, \ + .ops = _ops, \ + .owner = THIS_MODULE, \ + } + +#define to_int3472_clk(hw) \ + container_of(hw, struct int3472_gpio_clock, clk_hw) + +#define to_int3472_device(clk) \ + container_of(clk, struct int3472_discrete_device, clock) + +struct acpi_device; +struct i2c_client; +struct platform_device; + +struct int3472_cldb { + u8 version; + /* + * control logic type + * 0: UNKNOWN + * 1: DISCRETE(CRD-D) + * 2: PMIC TPS68470 + * 3: PMIC uP6641 + */ + u8 control_logic_type; + u8 control_logic_id; + u8 sensor_card_sku; + u8 reserved[28]; +}; + +struct int3472_gpio_function_remap { + const char *documented; + const char *actual; +}; + +struct int3472_sensor_config { + const char *sensor_module_name; + struct regulator_consumer_supply supply_map; + const struct int3472_gpio_function_remap *function_maps; +}; + +struct int3472_discrete_device { + struct acpi_device *adev; + struct device *dev; + struct acpi_device *sensor; + const char *sensor_name; + + const struct int3472_sensor_config *sensor_config; + + struct int3472_gpio_regulator { + char regulator_name[GPIO_REGULATOR_NAME_LENGTH]; + char supply_name[GPIO_REGULATOR_SUPPLY_NAME_LENGTH]; + struct gpio_desc *gpio; + struct regulator_dev *rdev; + struct regulator_desc rdesc; + } regulator; + + struct int3472_gpio_clock { + struct clk *clk; + struct clk_hw clk_hw; + struct clk_lookup *cl; + struct gpio_desc *ena_gpio; + struct gpio_desc *led_gpio; + u32 frequency; + } clock; + + unsigned int ngpios; /* how many GPIOs have we seen */ + unsigned int n_sensor_gpios; /* how many have we mapped to sensor */ + struct gpiod_lookup_table gpios; +}; + +int skl_int3472_discrete_probe(struct platform_device *pdev); +int skl_int3472_discrete_remove(struct platform_device *pdev); +int skl_int3472_tps68470_probe(struct i2c_client *client); +union acpi_object *skl_int3472_get_acpi_buffer(struct acpi_device *adev, + char *id); +int skl_int3472_fill_cldb(struct acpi_device *adev, struct int3472_cldb *cldb); +int skl_int3472_register_clock(struct int3472_discrete_device *int3472); +int skl_int3472_register_regulator(struct int3472_discrete_device *int3472, + struct acpi_resource *ares); + +#endif diff --git a/drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c b/drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c new file mode 100644 index 000000000000..8c18dbff1c43 --- /dev/null +++ b/drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c @@ -0,0 +1,417 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Author: Dan Scally */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "intel_skl_int3472_common.h" + +/* + * 79234640-9e10-4fea-a5c1-b5aa8b19756f + * This _DSM GUID returns information about the GPIO lines mapped to a + * discrete INT3472 device. Function number 1 returns a count of the GPIO + * lines that are mapped. Subsequent functions return 32 bit ints encoding + * information about the GPIO line, including its purpose. + */ +static const guid_t int3472_gpio_guid = + GUID_INIT(0x79234640, 0x9e10, 0x4fea, + 0xa5, 0xc1, 0xb5, 0xaa, 0x8b, 0x19, 0x75, 0x6f); + +/* + * 822ace8f-2814-4174-a56b-5f029fe079ee + * This _DSM GUID returns a string from the sensor device, which acts as a + * module identifier. + */ +static const guid_t cio2_sensor_module_guid = + GUID_INIT(0x822ace8f, 0x2814, 0x4174, + 0xa5, 0x6b, 0x5f, 0x02, 0x9f, 0xe0, 0x79, 0xee); + +/* + * Here follows platform specific mapping information that we can pass to + * the functions mapping resources to the sensors. Where the sensors have + * a power enable pin defined in DSDT we need to provide a supply name so + * the sensor drivers can find the regulator. The device name will be derived + * from the sensor's ACPI device within the code. Optionally, we can provide a + * NULL terminated array of function name mappings to deal with any platform + * specific deviations from the documented behaviour of GPIOs. + * + * Map a GPIO function name to NULL to prevent the driver from mapping that + * GPIO at all. + */ + +static const struct int3472_gpio_function_remap ov2680_gpio_function_remaps[] = { + { "reset", NULL }, + { "powerdown", "reset" }, + { } +}; + +static const struct int3472_sensor_config int3472_sensor_configs[] = { + /* Lenovo Miix 510-12ISK - OV2680, Front */ + { "GNDF140809R", { 0 }, ov2680_gpio_function_remaps }, + /* Lenovo Miix 510-12ISK - OV5648, Rear */ + { "GEFF150023R", REGULATOR_SUPPLY("avdd", NULL), NULL }, + /* Surface Go 1&2 - OV5693, Front */ + { "YHCU", REGULATOR_SUPPLY("avdd", NULL), NULL }, +}; + +static const struct int3472_sensor_config * +skl_int3472_get_sensor_module_config(struct int3472_discrete_device *int3472) +{ + union acpi_object *obj; + unsigned int i; + + obj = acpi_evaluate_dsm_typed(int3472->sensor->handle, + &cio2_sensor_module_guid, 0x00, + 0x01, NULL, ACPI_TYPE_STRING); + + if (!obj) { + dev_err(int3472->dev, + "Failed to get sensor module string from _DSM\n"); + return ERR_PTR(-ENODEV); + } + + if (obj->string.type != ACPI_TYPE_STRING) { + dev_err(int3472->dev, + "Sensor _DSM returned a non-string value\n"); + + ACPI_FREE(obj); + return ERR_PTR(-EINVAL); + } + + for (i = 0; i < ARRAY_SIZE(int3472_sensor_configs); i++) { + if (!strcmp(int3472_sensor_configs[i].sensor_module_name, + obj->string.pointer)) + break; + } + + ACPI_FREE(obj); + + if (i >= ARRAY_SIZE(int3472_sensor_configs)) + return ERR_PTR(-EINVAL); + + return &int3472_sensor_configs[i]; +} + +static int skl_int3472_map_gpio_to_sensor(struct int3472_discrete_device *int3472, + struct acpi_resource *ares, + const char *func, u32 polarity) +{ + char *path = ares->data.gpio.resource_source.string_ptr; + const struct int3472_sensor_config *sensor_config; + struct gpiod_lookup *table_entry; + struct acpi_device *adev; + acpi_handle handle; + acpi_status status; + int ret; + + if (int3472->n_sensor_gpios >= INT3472_MAX_SENSOR_GPIOS) { + dev_warn(int3472->dev, "Too many GPIOs mapped\n"); + return -EINVAL; + } + + sensor_config = int3472->sensor_config; + if (!IS_ERR(sensor_config) && sensor_config->function_maps) { + const struct int3472_gpio_function_remap *remap; + + for (remap = sensor_config->function_maps; remap->documented; remap++) { + if (!strcmp(func, remap->documented)) { + func = remap->actual; + break; + } + } + } + + /* Functions mapped to NULL should not be mapped to the sensor */ + if (!func) + return 0; + + status = acpi_get_handle(NULL, path, &handle); + if (ACPI_FAILURE(status)) + return -EINVAL; + + ret = acpi_bus_get_device(handle, &adev); + if (ret) + return -ENODEV; + + table_entry = &int3472->gpios.table[int3472->n_sensor_gpios]; + table_entry->key = acpi_dev_name(adev); + table_entry->chip_hwnum = ares->data.gpio.pin_table[0]; + table_entry->con_id = func; + table_entry->idx = 0; + table_entry->flags = polarity; + + int3472->n_sensor_gpios++; + + return 0; +} + +static int skl_int3472_map_gpio_to_clk(struct int3472_discrete_device *int3472, + struct acpi_resource *ares, u8 type) +{ + char *path = ares->data.gpio.resource_source.string_ptr; + struct gpio_desc *gpio; + + switch (type) { + case INT3472_GPIO_TYPE_CLK_ENABLE: + gpio = acpi_get_and_request_gpiod(path, ares->data.gpio.pin_table[0], + "int3472,clk-enable"); + if (IS_ERR(gpio)) + return (PTR_ERR(gpio)); + + int3472->clock.ena_gpio = gpio; + break; + case INT3472_GPIO_TYPE_PRIVACY_LED: + gpio = acpi_get_and_request_gpiod(path, ares->data.gpio.pin_table[0], + "int3472,privacy-led"); + if (IS_ERR(gpio)) + return (PTR_ERR(gpio)); + + int3472->clock.led_gpio = gpio; + break; + default: + dev_err(int3472->dev, "Invalid GPIO type 0x%02x for clock\n", type); + break; + } + + return 0; +} + +/** + * skl_int3472_handle_gpio_resources: Map PMIC resources to consuming sensor + * @ares: A pointer to a &struct acpi_resource + * @data: A pointer to a &struct int3472_discrete_device + * + * This function handles GPIO resources that are against an INT3472 + * ACPI device, by checking the value of the corresponding _DSM entry. + * This will return a 32bit int, where the lowest byte represents the + * function of the GPIO pin: + * + * 0x00 Reset + * 0x01 Power down + * 0x0b Power enable + * 0x0c Clock enable + * 0x0d Privacy LED + * + * There are some known platform specific quirks where that does not quite + * hold up; for example where a pin with type 0x01 (Power down) is mapped to + * a sensor pin that performs a reset function or entries in _CRS and _DSM that + * do not actually correspond to a physical connection. These will be handled + * by the mapping sub-functions. + * + * GPIOs will either be mapped directly to the sensor device or else used + * to create clocks and regulators via the usual frameworks. + * + * Return: + * * 1 - To continue the loop + * * 0 - When all resources found are handled properly. + * * -EINVAL - If the resource is not a GPIO IO resource + * * -ENODEV - If the resource has no corresponding _DSM entry + * * -Other - Errors propagated from one of the sub-functions. + */ +static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares, + void *data) +{ + struct int3472_discrete_device *int3472 = data; + struct acpi_resource_gpio *agpio; + union acpi_object *obj; + const char *err_msg; + int ret; + u8 type; + + if (!acpi_gpio_get_io_resource(ares, &agpio)) + return 1; + + /* + * ngpios + 2 because the index of this _DSM function is 1-based and + * the first function is just a count. + */ + obj = acpi_evaluate_dsm_typed(int3472->adev->handle, + &int3472_gpio_guid, 0x00, + int3472->ngpios + 2, + NULL, ACPI_TYPE_INTEGER); + + if (!obj) { + dev_warn(int3472->dev, "No _DSM entry for GPIO pin %u\n", + ares->data.gpio.pin_table[0]); + return 1; + } + + type = obj->integer.value & 0xff; + + switch (type) { + case INT3472_GPIO_TYPE_RESET: + ret = skl_int3472_map_gpio_to_sensor(int3472, ares, "reset", + GPIO_ACTIVE_LOW); + if (ret) + err_msg = "Failed to map reset pin to sensor\n"; + + break; + case INT3472_GPIO_TYPE_POWERDOWN: + ret = skl_int3472_map_gpio_to_sensor(int3472, ares, + "powerdown", + GPIO_ACTIVE_LOW); + if (ret) + err_msg = "Failed to map powerdown pin to sensor\n"; + + break; + case INT3472_GPIO_TYPE_CLK_ENABLE: + case INT3472_GPIO_TYPE_PRIVACY_LED: + ret = skl_int3472_map_gpio_to_clk(int3472, ares, type); + if (ret) + err_msg = "Failed to map GPIO to clock\n"; + + break; + case INT3472_GPIO_TYPE_POWER_ENABLE: + ret = skl_int3472_register_regulator(int3472, ares); + if (ret) + err_msg = "Failed to map regulator to sensor\n"; + + break; + default: + dev_warn(int3472->dev, + "GPIO type 0x%02x unknown; the sensor may not work\n", + type); + ret = 1; + break; + } + + int3472->ngpios++; + ACPI_FREE(obj); + + if (ret) + return dev_err_probe(int3472->dev, ret, err_msg); + + return 0; +} + +static int skl_int3472_parse_crs(struct int3472_discrete_device *int3472) +{ + LIST_HEAD(resource_list); + int ret; + + /* + * No error check, because not having a sensor config is not necessarily + * a failure mode. + */ + int3472->sensor_config = skl_int3472_get_sensor_module_config(int3472); + + ret = acpi_dev_get_resources(int3472->adev, &resource_list, + skl_int3472_handle_gpio_resources, + int3472); + if (ret) + goto out_free_res_list; + + /* + * If we find no clock enable GPIO pin then the privacy LED won't work. + * We've never seen that situation, but it's possible. Warn the user so + * it's clear what's happened. + */ + if (int3472->clock.ena_gpio) { + ret = skl_int3472_register_clock(int3472); + if (ret) + goto out_free_res_list; + } else { + if (int3472->clock.led_gpio) + dev_warn(int3472->dev, + "No clk GPIO. The privacy LED won't work\n"); + } + + int3472->gpios.dev_id = int3472->sensor_name; + gpiod_add_lookup_table(&int3472->gpios); + +out_free_res_list: + acpi_dev_free_resource_list(&resource_list); + + return ret; +} + +int skl_int3472_discrete_probe(struct platform_device *pdev) +{ + struct acpi_device *adev = ACPI_COMPANION(&pdev->dev); + struct int3472_discrete_device *int3472; + struct int3472_cldb cldb; + int ret; + + ret = skl_int3472_fill_cldb(adev, &cldb); + if (ret) { + dev_err(&pdev->dev, "Couldn't fill CLDB structure\n"); + return ret; + } + + if (cldb.control_logic_type != 1) { + dev_err(&pdev->dev, "Unsupported control logic type %u\n", + cldb.control_logic_type); + return -EINVAL; + } + + /* Max num GPIOs we've seen plus a terminator */ + int3472 = devm_kzalloc(&pdev->dev, struct_size(int3472, gpios.table, + INT3472_MAX_SENSOR_GPIOS + 1), GFP_KERNEL); + if (!int3472) + return -ENOMEM; + + int3472->adev = adev; + int3472->dev = &pdev->dev; + platform_set_drvdata(pdev, int3472); + + int3472->sensor = acpi_dev_get_first_consumer_dev(adev); + if (!int3472->sensor) { + dev_err(&pdev->dev, "INT3472 seems to have no dependents.\n"); + return -ENODEV; + } + + int3472->sensor_name = devm_kasprintf(int3472->dev, GFP_KERNEL, + I2C_DEV_NAME_FORMAT, + acpi_dev_name(int3472->sensor)); + if (!int3472->sensor_name) { + ret = -ENOMEM; + goto err_put_sensor; + } + + /* + * Initialising this list means we can call gpiod_remove_lookup_table() + * in failure paths without issue. + */ + INIT_LIST_HEAD(&int3472->gpios.list); + + ret = skl_int3472_parse_crs(int3472); + if (ret) { + skl_int3472_discrete_remove(pdev); + return ret; + } + + return 0; + +err_put_sensor: + acpi_dev_put(int3472->sensor); + + return ret; +} + +int skl_int3472_discrete_remove(struct platform_device *pdev) +{ + struct int3472_discrete_device *int3472 = platform_get_drvdata(pdev); + + gpiod_remove_lookup_table(&int3472->gpios); + regulator_unregister(int3472->regulator.rdev); + clk_unregister(int3472->clock.clk); + + if (int3472->clock.cl) + clkdev_drop(int3472->clock.cl); + + gpiod_put(int3472->regulator.gpio); + gpiod_put(int3472->clock.ena_gpio); + gpiod_put(int3472->clock.led_gpio); + + return 0; +} diff --git a/drivers/platform/x86/intel-int3472/intel_skl_int3472_tps68470.c b/drivers/platform/x86/intel-int3472/intel_skl_int3472_tps68470.c new file mode 100644 index 000000000000..c05b4cf502fe --- /dev/null +++ b/drivers/platform/x86/intel-int3472/intel_skl_int3472_tps68470.c @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Author: Dan Scally */ + +#include +#include +#include +#include +#include + +#include "intel_skl_int3472_common.h" + +#define DESIGNED_FOR_CHROMEOS 1 +#define DESIGNED_FOR_WINDOWS 2 + +static const struct mfd_cell tps68470_cros[] = { + { .name = "tps68470-gpio" }, + { .name = "tps68470_pmic_opregion" }, +}; + +static const struct mfd_cell tps68470_win[] = { + { .name = "tps68470-gpio" }, + { .name = "tps68470-clk" }, + { .name = "tps68470-regulator" }, +}; + +static const struct regmap_config tps68470_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = TPS68470_REG_MAX, +}; + +static int tps68470_chip_init(struct device *dev, struct regmap *regmap) +{ + unsigned int version; + int ret; + + /* Force software reset */ + ret = regmap_write(regmap, TPS68470_REG_RESET, TPS68470_REG_RESET_MASK); + if (ret) + return ret; + + ret = regmap_read(regmap, TPS68470_REG_REVID, &version); + if (ret) { + dev_err(dev, "Failed to read revision register: %d\n", ret); + return ret; + } + + dev_info(dev, "TPS68470 REVID: 0x%02x\n", version); + + return 0; +} + +/** skl_int3472_tps68470_calc_type: Check what platform a device is designed for + * @adev: A pointer to a &struct acpi_device + * + * Check CLDB buffer against the PMIC's adev. If present, then we check + * the value of control_logic_type field and follow one of the + * following scenarios: + * + * 1. No CLDB - likely ACPI tables designed for ChromeOS. We + * create platform devices for the GPIOs and OpRegion drivers. + * + * 2. CLDB, with control_logic_type = 2 - probably ACPI tables + * made for Windows 2-in-1 platforms. Register pdevs for GPIO, + * Clock and Regulator drivers to bind to. + * + * 3. Any other value in control_logic_type, we should never have + * gotten to this point; fail probe and return. + * + * Return: + * * 1 Device intended for ChromeOS + * * 2 Device intended for Windows + * * -EINVAL Where @adev has an object named CLDB but it does not conform to + * our expectations + */ +static int skl_int3472_tps68470_calc_type(struct acpi_device *adev) +{ + struct int3472_cldb cldb = { 0 }; + int ret; + + /* + * A CLDB buffer that exists, but which does not match our expectations + * should trigger an error so we don't blindly continue. + */ + ret = skl_int3472_fill_cldb(adev, &cldb); + if (ret && ret != -ENODEV) + return ret; + + if (ret) + return DESIGNED_FOR_CHROMEOS; + + if (cldb.control_logic_type != 2) + return -EINVAL; + + return DESIGNED_FOR_WINDOWS; +} + +int skl_int3472_tps68470_probe(struct i2c_client *client) +{ + struct acpi_device *adev = ACPI_COMPANION(&client->dev); + struct regmap *regmap; + int device_type; + int ret; + + regmap = devm_regmap_init_i2c(client, &tps68470_regmap_config); + if (IS_ERR(regmap)) { + dev_err(&client->dev, "Failed to create regmap: %ld\n", PTR_ERR(regmap)); + return PTR_ERR(regmap); + } + + i2c_set_clientdata(client, regmap); + + ret = tps68470_chip_init(&client->dev, regmap); + if (ret < 0) { + dev_err(&client->dev, "TPS68470 init error %d\n", ret); + return ret; + } + + device_type = skl_int3472_tps68470_calc_type(adev); + switch (device_type) { + case DESIGNED_FOR_WINDOWS: + ret = devm_mfd_add_devices(&client->dev, PLATFORM_DEVID_NONE, + tps68470_win, ARRAY_SIZE(tps68470_win), + NULL, 0, NULL); + break; + case DESIGNED_FOR_CHROMEOS: + ret = devm_mfd_add_devices(&client->dev, PLATFORM_DEVID_NONE, + tps68470_cros, ARRAY_SIZE(tps68470_cros), + NULL, 0, NULL); + break; + default: + dev_err(&client->dev, "Failed to add MFD devices\n"); + return device_type; + } + + return ret; +} -- cgit v1.2.3 From 24700e1f41f0dcbe389b8d9e5830aaca2192093c Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Thu, 3 Jun 2021 23:40:07 +0100 Subject: mfd: tps68470: Remove tps68470 MFD driver This driver only covered one scenario in which ACPI devices with _HID INT3472 are found, and its functionality has been taken over by the intel-skl-int3472 module, so remove it. Acked-by: Andy Shevchenko Acked-by: Lee Jones Reviewed-by: Laurent Pinchart Signed-off-by: Daniel Scally Link: https://lore.kernel.org/r/20210603224007.120560-7-djrscally@gmail.com Signed-off-by: Hans de Goede --- drivers/acpi/pmic/Kconfig | 2 +- drivers/gpio/Kconfig | 2 +- drivers/mfd/Kconfig | 18 --------- drivers/mfd/Makefile | 1 - drivers/mfd/tps68470.c | 97 ----------------------------------------------- 5 files changed, 2 insertions(+), 118 deletions(-) delete mode 100644 drivers/mfd/tps68470.c diff --git a/drivers/acpi/pmic/Kconfig b/drivers/acpi/pmic/Kconfig index 56bbcb2ce61b..f84b8f6038dc 100644 --- a/drivers/acpi/pmic/Kconfig +++ b/drivers/acpi/pmic/Kconfig @@ -52,7 +52,7 @@ endif # PMIC_OPREGION config TPS68470_PMIC_OPREGION bool "ACPI operation region support for TPS68470 PMIC" - depends on MFD_TPS68470 + depends on INTEL_SKL_INT3472 help This config adds ACPI operation region support for TI TPS68470 PMIC. TPS68470 device is an advanced power management unit that powers diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 1dd0ec6727fd..10228abeee56 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -1367,7 +1367,7 @@ config GPIO_TPS65912 config GPIO_TPS68470 bool "TPS68470 GPIO" - depends on MFD_TPS68470 + depends on INTEL_SKL_INT3472 help Select this option to enable GPIO driver for the TPS68470 chip family. diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 5c7f2b100191..99c4e1a80ae0 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -1499,24 +1499,6 @@ config MFD_TPS65217 This driver can also be built as a module. If so, the module will be called tps65217. -config MFD_TPS68470 - bool "TI TPS68470 Power Management / LED chips" - depends on ACPI && PCI && I2C=y - depends on I2C_DESIGNWARE_PLATFORM=y - select MFD_CORE - select REGMAP_I2C - help - If you say yes here you get support for the TPS68470 series of - Power Management / LED chips. - - These include voltage regulators, LEDs and other features - that are often used in portable devices. - - This option is a bool as it provides an ACPI operation - region, which must be available before any of the devices - using this are probed. This option also configures the - designware-i2c driver to be built-in, for the same reason. - config MFD_TI_LP873X tristate "TI LP873X Power Management IC" depends on I2C diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index 4f6d2b8a5f76..8b322d89a0c5 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -105,7 +105,6 @@ obj-$(CONFIG_MFD_TPS65910) += tps65910.o obj-$(CONFIG_MFD_TPS65912) += tps65912-core.o obj-$(CONFIG_MFD_TPS65912_I2C) += tps65912-i2c.o obj-$(CONFIG_MFD_TPS65912_SPI) += tps65912-spi.o -obj-$(CONFIG_MFD_TPS68470) += tps68470.o obj-$(CONFIG_MFD_TPS80031) += tps80031.o obj-$(CONFIG_MENELAUS) += menelaus.o diff --git a/drivers/mfd/tps68470.c b/drivers/mfd/tps68470.c deleted file mode 100644 index 4a4df4ffd18c..000000000000 --- a/drivers/mfd/tps68470.c +++ /dev/null @@ -1,97 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * TPS68470 chip Parent driver - * - * Copyright (C) 2017 Intel Corporation - * - * Authors: - * Rajmohan Mani - * Tianshu Qiu - * Jian Xu Zheng - * Yuning Pu - */ - -#include -#include -#include -#include -#include -#include -#include - -static const struct mfd_cell tps68470s[] = { - { .name = "tps68470-gpio" }, - { .name = "tps68470_pmic_opregion" }, -}; - -static const struct regmap_config tps68470_regmap_config = { - .reg_bits = 8, - .val_bits = 8, - .max_register = TPS68470_REG_MAX, -}; - -static int tps68470_chip_init(struct device *dev, struct regmap *regmap) -{ - unsigned int version; - int ret; - - /* Force software reset */ - ret = regmap_write(regmap, TPS68470_REG_RESET, TPS68470_REG_RESET_MASK); - if (ret) - return ret; - - ret = regmap_read(regmap, TPS68470_REG_REVID, &version); - if (ret) { - dev_err(dev, "Failed to read revision register: %d\n", ret); - return ret; - } - - dev_info(dev, "TPS68470 REVID: 0x%x\n", version); - - return 0; -} - -static int tps68470_probe(struct i2c_client *client) -{ - struct device *dev = &client->dev; - struct regmap *regmap; - int ret; - - regmap = devm_regmap_init_i2c(client, &tps68470_regmap_config); - if (IS_ERR(regmap)) { - dev_err(dev, "devm_regmap_init_i2c Error %ld\n", - PTR_ERR(regmap)); - return PTR_ERR(regmap); - } - - i2c_set_clientdata(client, regmap); - - ret = tps68470_chip_init(dev, regmap); - if (ret < 0) { - dev_err(dev, "TPS68470 Init Error %d\n", ret); - return ret; - } - - ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, tps68470s, - ARRAY_SIZE(tps68470s), NULL, 0, NULL); - if (ret < 0) { - dev_err(dev, "devm_mfd_add_devices failed: %d\n", ret); - return ret; - } - - return 0; -} - -static const struct acpi_device_id tps68470_acpi_ids[] = { - {"INT3472"}, - {}, -}; - -static struct i2c_driver tps68470_driver = { - .driver = { - .name = "tps68470", - .acpi_match_table = tps68470_acpi_ids, - }, - .probe_new = tps68470_probe, -}; -builtin_i2c_driver(tps68470_driver); -- cgit v1.2.3 From 7a2c4cc537fa9f05fe90812e7d789b9faf7eb869 Mon Sep 17 00:00:00 2001 From: Matti Vaittinen Date: Tue, 8 Jun 2021 13:09:34 +0300 Subject: devm-helpers: Add resource managed version of work init A few drivers which need a work-queue must cancel work at driver detach. Some of those implement remove() solely for this purpose. Help drivers to avoid unnecessary remove and error-branch implementation by adding managed verision of work initialization. This will also help drivers to avoid mixing manual and devm based unwinding when other resources are handled by devm. Signed-off-by: Matti Vaittinen Reviewed-by: Krzysztof Kozlowski Reviewed-by: Hans de Goede Link: https://lore.kernel.org/r/94ff4175e7f2ff134ed2fa7d6e7641005cc9784b.1623146580.git.matti.vaittinen@fi.rohmeurope.com Signed-off-by: Hans de Goede --- include/linux/devm-helpers.h | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/include/linux/devm-helpers.h b/include/linux/devm-helpers.h index f40f77717a24..74891802200d 100644 --- a/include/linux/devm-helpers.h +++ b/include/linux/devm-helpers.h @@ -51,4 +51,29 @@ static inline int devm_delayed_work_autocancel(struct device *dev, return devm_add_action(dev, devm_delayed_work_drop, w); } +static inline void devm_work_drop(void *res) +{ + cancel_work_sync(res); +} + +/** + * devm_work_autocancel - Resource-managed work allocation + * @dev: Device which lifetime work is bound to + * @w: Work to be added (and automatically cancelled) + * @worker: Worker function + * + * Initialize work which is automatically cancelled when driver is detached. + * A few drivers need to queue work which must be cancelled before driver + * is detached to avoid accessing removed resources. + * devm_work_autocancel() can be used to omit the explicit + * cancelleation when driver is detached. + */ +static inline int devm_work_autocancel(struct device *dev, + struct work_struct *w, + work_func_t worker) +{ + INIT_WORK(w, worker); + return devm_add_action(dev, devm_work_drop, w); +} + #endif -- cgit v1.2.3 From 14ad76825f00b1471a7ec2eff30528d21ee2772b Mon Sep 17 00:00:00 2001 From: Matti Vaittinen Date: Tue, 8 Jun 2021 13:09:55 +0300 Subject: extcon: extcon-max14577: Fix potential work-queue cancellation race The extcon IRQ schedules a work item. IRQ is requested using devm while WQ is cancelld at remove(). This mixing of devm and manual unwinding has potential case where the WQ has been emptied (.remove() was ran) but devm unwinding of IRQ was not yet done. It is possible the IRQ is triggered at this point scheduling new work item to the already flushed queue. Use new devm_work_autocancel() to remove the remove() and to kill the bug. Signed-off-by: Matti Vaittinen Reviewed-by: Krzysztof Kozlowski Reviewed-by: Hans de Goede Acked-by: Chanwoo Choi Link: https://lore.kernel.org/r/ee8545f59ae3a93f0a70f640ecbd7e31cfadbcb9.1623146580.git.matti.vaittinen@fi.rohmeurope.com Signed-off-by: Hans de Goede --- drivers/extcon/extcon-max14577.c | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/drivers/extcon/extcon-max14577.c b/drivers/extcon/extcon-max14577.c index ace523924e58..5476f48ed74b 100644 --- a/drivers/extcon/extcon-max14577.c +++ b/drivers/extcon/extcon-max14577.c @@ -6,6 +6,7 @@ // Chanwoo Choi // Krzysztof Kozlowski +#include #include #include #include @@ -673,7 +674,10 @@ static int max14577_muic_probe(struct platform_device *pdev) platform_set_drvdata(pdev, info); mutex_init(&info->mutex); - INIT_WORK(&info->irq_work, max14577_muic_irq_work); + ret = devm_work_autocancel(&pdev->dev, &info->irq_work, + max14577_muic_irq_work); + if (ret) + return ret; switch (max14577->dev_type) { case MAXIM_DEVICE_TYPE_MAX77836: @@ -766,15 +770,6 @@ static int max14577_muic_probe(struct platform_device *pdev) return ret; } -static int max14577_muic_remove(struct platform_device *pdev) -{ - struct max14577_muic_info *info = platform_get_drvdata(pdev); - - cancel_work_sync(&info->irq_work); - - return 0; -} - static const struct platform_device_id max14577_muic_id[] = { { "max14577-muic", MAXIM_DEVICE_TYPE_MAX14577, }, { "max77836-muic", MAXIM_DEVICE_TYPE_MAX77836, }, @@ -797,7 +792,6 @@ static struct platform_driver max14577_muic_driver = { .of_match_table = of_max14577_muic_dt_match, }, .probe = max14577_muic_probe, - .remove = max14577_muic_remove, .id_table = max14577_muic_id, }; -- cgit v1.2.3 From 74047eaa2281982853afa144463ebe18d49022f2 Mon Sep 17 00:00:00 2001 From: Matti Vaittinen Date: Tue, 8 Jun 2021 13:10:11 +0300 Subject: extcon: extcon-max77693.c: Fix potential work-queue cancellation race The extcon IRQ schedules a work item. IRQ is requested using devm while WQ is cancelld at remove(). This mixing of devm and manual unwinding has potential case where the WQ has been emptied (.remove() was ran) but devm unwinding of IRQ was not yet done. It may be possible the IRQ is triggered at this point scheduling new work item to the already flushed queue. According to the input documentation the input device allocated by devm_input_allocate_device() does not need to be explicitly unregistered. Use the new devm_work_autocancel() and remove the remove() to simplify the code. Signed-off-by: Matti Vaittinen Reviewed-by: Krzysztof Kozlowski Reviewed-by: Hans de Goede Acked-by: Chanwoo Choi Link: https://lore.kernel.org/r/cbe8205eed8276f6e6db5003cfe51b8b0d4ac966.1623146580.git.matti.vaittinen@fi.rohmeurope.com Signed-off-by: Hans de Goede --- drivers/extcon/extcon-max77693.c | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/drivers/extcon/extcon-max77693.c b/drivers/extcon/extcon-max77693.c index 92af97e00828..1f1d9ab0c5c7 100644 --- a/drivers/extcon/extcon-max77693.c +++ b/drivers/extcon/extcon-max77693.c @@ -5,6 +5,7 @@ // Copyright (C) 2012 Samsung Electrnoics // Chanwoo Choi +#include #include #include #include @@ -1127,7 +1128,10 @@ static int max77693_muic_probe(struct platform_device *pdev) platform_set_drvdata(pdev, info); mutex_init(&info->mutex); - INIT_WORK(&info->irq_work, max77693_muic_irq_work); + ret = devm_work_autocancel(&pdev->dev, &info->irq_work, + max77693_muic_irq_work); + if (ret) + return ret; /* Support irq domain for MAX77693 MUIC device */ for (i = 0; i < ARRAY_SIZE(muic_irqs); i++) { @@ -1254,22 +1258,11 @@ static int max77693_muic_probe(struct platform_device *pdev) return ret; } -static int max77693_muic_remove(struct platform_device *pdev) -{ - struct max77693_muic_info *info = platform_get_drvdata(pdev); - - cancel_work_sync(&info->irq_work); - input_unregister_device(info->dock); - - return 0; -} - static struct platform_driver max77693_muic_driver = { .driver = { .name = DEV_NAME, }, .probe = max77693_muic_probe, - .remove = max77693_muic_remove, }; module_platform_driver(max77693_muic_driver); -- cgit v1.2.3 From 610bdc04830a864115e6928fc944f1171dfff6f3 Mon Sep 17 00:00:00 2001 From: Matti Vaittinen Date: Tue, 8 Jun 2021 13:10:31 +0300 Subject: extcon: extcon-max8997: Fix IRQ freeing at error path If reading MAX8997_MUIC_REG_STATUS1 fails at probe the driver exits without freeing the requested IRQs. Free the IRQs prior returning if reading the status fails. Fixes: 3e34c8198960 ("extcon: max8997: Avoid forcing UART path on drive probe") Signed-off-by: Matti Vaittinen Reviewed-by: Hans de Goede Acked-by: Chanwoo Choi Link: https://lore.kernel.org/r/27ee4a48ee775c3f8c9d90459c18b6f2b15edc76.1623146580.git.matti.vaittinen@fi.rohmeurope.com Signed-off-by: Hans de Goede --- drivers/extcon/extcon-max8997.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/extcon/extcon-max8997.c b/drivers/extcon/extcon-max8997.c index e1408075ef7d..c15a612067af 100644 --- a/drivers/extcon/extcon-max8997.c +++ b/drivers/extcon/extcon-max8997.c @@ -733,7 +733,7 @@ static int max8997_muic_probe(struct platform_device *pdev) 2, info->status); if (ret) { dev_err(info->dev, "failed to read MUIC register\n"); - return ret; + goto err_irq; } cable_type = max8997_muic_get_cable_type(info, MAX8997_CABLE_GROUP_ADC, &attached); -- cgit v1.2.3 From 87ee8de23c9df3a368504f34cf3d7f9be9207717 Mon Sep 17 00:00:00 2001 From: Matti Vaittinen Date: Tue, 8 Jun 2021 13:10:53 +0300 Subject: extcon: extcon-max8997: Simplify driver using devm Simplify driver by switching to use the resource managed IRQ requesting and resource managed work-queue initialization. Signed-off-by: Matti Vaittinen Reviewed-by: Hans de Goede Acked-by: Chanwoo Choi Link: https://lore.kernel.org/r/61190cc280a63baeb05ec570282bb3677bee8e7b.1623146580.git.matti.vaittinen@fi.rohmeurope.com Signed-off-by: Hans de Goede --- drivers/extcon/extcon-max8997.c | 47 ++++++++++++++--------------------------- 1 file changed, 16 insertions(+), 31 deletions(-) diff --git a/drivers/extcon/extcon-max8997.c b/drivers/extcon/extcon-max8997.c index c15a612067af..bbc592823570 100644 --- a/drivers/extcon/extcon-max8997.c +++ b/drivers/extcon/extcon-max8997.c @@ -5,6 +5,7 @@ // Copyright (C) 2012 Samsung Electronics // Donggeun Kim +#include #include #include #include @@ -650,27 +651,30 @@ static int max8997_muic_probe(struct platform_device *pdev) mutex_init(&info->mutex); INIT_WORK(&info->irq_work, max8997_muic_irq_work); + ret = devm_work_autocancel(&pdev->dev, &info->irq_work, + max8997_muic_irq_work); + if (ret) + return ret; for (i = 0; i < ARRAY_SIZE(muic_irqs); i++) { struct max8997_muic_irq *muic_irq = &muic_irqs[i]; unsigned int virq = 0; virq = irq_create_mapping(max8997->irq_domain, muic_irq->irq); - if (!virq) { - ret = -EINVAL; - goto err_irq; - } + if (!virq) + return -EINVAL; + muic_irq->virq = virq; - ret = request_threaded_irq(virq, NULL, - max8997_muic_irq_handler, - IRQF_NO_SUSPEND, - muic_irq->name, info); + ret = devm_request_threaded_irq(&pdev->dev, virq, NULL, + max8997_muic_irq_handler, + IRQF_NO_SUSPEND, + muic_irq->name, info); if (ret) { dev_err(&pdev->dev, "failed: irq request (IRQ: %d, error :%d)\n", muic_irq->irq, ret); - goto err_irq; + return ret; } } @@ -678,14 +682,13 @@ static int max8997_muic_probe(struct platform_device *pdev) info->edev = devm_extcon_dev_allocate(&pdev->dev, max8997_extcon_cable); if (IS_ERR(info->edev)) { dev_err(&pdev->dev, "failed to allocate memory for extcon\n"); - ret = PTR_ERR(info->edev); - goto err_irq; + return PTR_ERR(info->edev); } ret = devm_extcon_dev_register(&pdev->dev, info->edev); if (ret) { dev_err(&pdev->dev, "failed to register extcon device\n"); - goto err_irq; + return ret; } if (pdata && pdata->muic_pdata) { @@ -733,7 +736,7 @@ static int max8997_muic_probe(struct platform_device *pdev) 2, info->status); if (ret) { dev_err(info->dev, "failed to read MUIC register\n"); - goto err_irq; + return ret; } cable_type = max8997_muic_get_cable_type(info, MAX8997_CABLE_GROUP_ADC, &attached); @@ -756,23 +759,6 @@ static int max8997_muic_probe(struct platform_device *pdev) delay_jiffies); return 0; - -err_irq: - while (--i >= 0) - free_irq(muic_irqs[i].virq, info); - return ret; -} - -static int max8997_muic_remove(struct platform_device *pdev) -{ - struct max8997_muic_info *info = platform_get_drvdata(pdev); - int i; - - for (i = 0; i < ARRAY_SIZE(muic_irqs); i++) - free_irq(muic_irqs[i].virq, info); - cancel_work_sync(&info->irq_work); - - return 0; } static struct platform_driver max8997_muic_driver = { @@ -780,7 +766,6 @@ static struct platform_driver max8997_muic_driver = { .name = DEV_NAME, }, .probe = max8997_muic_probe, - .remove = max8997_muic_remove, }; module_platform_driver(max8997_muic_driver); -- cgit v1.2.3 From 159f130f60f402273b235801d1fde3fc115c6795 Mon Sep 17 00:00:00 2001 From: Srinivas Pandruvada Date: Wed, 12 May 2021 03:17:32 -0700 Subject: tools/power/x86/intel-speed-select: Fix uncore memory frequency display The uncore memory frequency value from the mailbox command CONFIG_TDP_GET_MEM_FREQ needs to be scaled based on the platform for display. There is no single constant multiplier. This change introduces CPU model specific memory frequency multiplier. Signed-off-by: Srinivas Pandruvada Signed-off-by: Hans de Goede --- tools/power/x86/intel-speed-select/isst-config.c | 16 ++++++++++++++++ tools/power/x86/intel-speed-select/isst-core.c | 15 +++++++++++++++ tools/power/x86/intel-speed-select/isst-display.c | 2 +- tools/power/x86/intel-speed-select/isst.h | 2 ++ 4 files changed, 34 insertions(+), 1 deletion(-) diff --git a/tools/power/x86/intel-speed-select/isst-config.c b/tools/power/x86/intel-speed-select/isst-config.c index ab940c508ef0..d4f0a7872e49 100644 --- a/tools/power/x86/intel-speed-select/isst-config.c +++ b/tools/power/x86/intel-speed-select/isst-config.c @@ -106,6 +106,22 @@ int is_skx_based_platform(void) return 0; } +int is_spr_platform(void) +{ + if (cpu_model == 0x8F) + return 1; + + return 0; +} + +int is_icx_platform(void) +{ + if (cpu_model == 0x6A || cpu_model == 0x6C) + return 1; + + return 0; +} + static int update_cpu_model(void) { unsigned int ebx, ecx, edx; diff --git a/tools/power/x86/intel-speed-select/isst-core.c b/tools/power/x86/intel-speed-select/isst-core.c index 6a26d5769984..4431c8a0d40a 100644 --- a/tools/power/x86/intel-speed-select/isst-core.c +++ b/tools/power/x86/intel-speed-select/isst-core.c @@ -201,6 +201,7 @@ void isst_get_uncore_mem_freq(int cpu, int config_index, { unsigned int resp; int ret; + ret = isst_send_mbox_command(cpu, CONFIG_TDP, CONFIG_TDP_GET_MEM_FREQ, 0, config_index, &resp); if (ret) { @@ -209,6 +210,20 @@ void isst_get_uncore_mem_freq(int cpu, int config_index, } ctdp_level->mem_freq = resp & GENMASK(7, 0); + if (is_spr_platform()) { + ctdp_level->mem_freq *= 200; + } else if (is_icx_platform()) { + if (ctdp_level->mem_freq < 7) { + ctdp_level->mem_freq = (12 - ctdp_level->mem_freq) * 133.33 * 2 * 10; + ctdp_level->mem_freq /= 10; + if (ctdp_level->mem_freq % 10 > 5) + ctdp_level->mem_freq++; + } else { + ctdp_level->mem_freq = 0; + } + } else { + ctdp_level->mem_freq = 0; + } debug_printf( "cpu:%d ctdp:%d CONFIG_TDP_GET_MEM_FREQ resp:%x uncore mem_freq:%d\n", cpu, config_index, resp, ctdp_level->mem_freq); diff --git a/tools/power/x86/intel-speed-select/isst-display.c b/tools/power/x86/intel-speed-select/isst-display.c index 3bf1820c0da1..f97d8859ada7 100644 --- a/tools/power/x86/intel-speed-select/isst-display.c +++ b/tools/power/x86/intel-speed-select/isst-display.c @@ -446,7 +446,7 @@ void isst_ctdp_display_information(int cpu, FILE *outf, int tdp_level, if (ctdp_level->mem_freq) { snprintf(header, sizeof(header), "mem-frequency(MHz)"); snprintf(value, sizeof(value), "%d", - ctdp_level->mem_freq * DISP_FREQ_MULTIPLIER); + ctdp_level->mem_freq); format_and_print(outf, level + 2, header, value); } diff --git a/tools/power/x86/intel-speed-select/isst.h b/tools/power/x86/intel-speed-select/isst.h index 0cac6c54be87..1aa15d5ea57c 100644 --- a/tools/power/x86/intel-speed-select/isst.h +++ b/tools/power/x86/intel-speed-select/isst.h @@ -257,5 +257,7 @@ extern int get_cpufreq_base_freq(int cpu); extern int isst_read_pm_config(int cpu, int *cp_state, int *cp_cap); extern void isst_display_error_info_message(int error, char *msg, int arg_valid, int arg); extern int is_skx_based_platform(void); +extern int is_spr_platform(void); +extern int is_icx_platform(void); extern void isst_trl_display_information(int cpu, FILE *outf, unsigned long long trl); #endif -- cgit v1.2.3 From 307722e872658ee8cfa4ee0f9a7aa9a1b2207417 Mon Sep 17 00:00:00 2001 From: Srinivas Pandruvada Date: Thu, 10 Jun 2021 14:36:35 -0700 Subject: tools/power/x86/intel-speed-select: v1.10 release This release adds following change: - Fix reporting of memory frequency Signed-off-by: Srinivas Pandruvada Signed-off-by: Hans de Goede --- tools/power/x86/intel-speed-select/isst-config.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/power/x86/intel-speed-select/isst-config.c b/tools/power/x86/intel-speed-select/isst-config.c index d4f0a7872e49..bf9fd3549a1d 100644 --- a/tools/power/x86/intel-speed-select/isst-config.c +++ b/tools/power/x86/intel-speed-select/isst-config.c @@ -15,7 +15,7 @@ struct process_cmd_struct { int arg; }; -static const char *version_str = "v1.9"; +static const char *version_str = "v1.10"; static const int supported_api_ver = 1; static struct isst_if_platform_info isst_platform_info; static char *progname; -- cgit v1.2.3 From 1e42de8e53d32bbd7a732df49d872a30b4f888b4 Mon Sep 17 00:00:00 2001 From: Srinivas Pandruvada Date: Wed, 16 Jun 2021 15:13:28 -0700 Subject: platform/x86: ISST: Optimize CPU to PCI device mapping It was observed that some of the high performance benchmarks are spending more time in kernel depending on which CPU package they are executing. The difference is significant and benchmark scores varies more than 10%. These benchmarks adjust class of service to improve thread performance which run in parallel. This class of service change causes access to MMIO region of Intel Speed Select PCI devices depending on the CPU package they are executing. This mapping from CPU to PCI device instance uses a standard Linux PCI interface "pci_get_domain_bus_and_slot()". This function does a linear search to get to a PCI device. Since these platforms have 100+ PCI devices, this search can be expensive in fast path for benchmarks. Since the device and function of PCI device is fixed for Intel Speed Select PCI devices, the CPU to PCI device information can be cached at the same time when bus number for the CPU is read. In this way during runtime the cached information can be used. This improves performance of these benchmarks significantly. Signed-off-by: Srinivas Pandruvada Link: https://lore.kernel.org/r/20210616221329.1909276-1-srinivas.pandruvada@linux.intel.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- .../x86/intel_speed_select_if/isst_if_common.c | 29 ++++++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) 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 0c2aa22c7a12..bbd46b1d9c10 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 @@ -281,11 +281,27 @@ static int isst_if_get_platform_info(void __user *argp) struct isst_if_cpu_info { /* For BUS 0 and BUS 1 only, which we need for PUNIT interface */ int bus_info[2]; + struct pci_dev *pci_dev[2]; int punit_cpu_id; }; static struct isst_if_cpu_info *isst_cpu_info; +static 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)); +} + /** * isst_if_get_pci_dev() - Get the PCI device instance for a CPU * @cpu: Logical CPU number. @@ -300,17 +316,18 @@ static struct isst_if_cpu_info *isst_cpu_info; */ struct pci_dev *isst_if_get_pci_dev(int cpu, int bus_no, int dev, int fn) { - int bus_number; + struct pci_dev *pci_dev; 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; + pci_dev = isst_cpu_info[cpu].pci_dev[bus_no]; - return pci_get_domain_bus_and_slot(0, bus_number, PCI_DEVFN(dev, fn)); + if (pci_dev && pci_dev->devfn == PCI_DEVFN(dev, fn)) + return pci_dev; + + return _isst_if_get_pci_dev(cpu, bus_no, dev, fn); } EXPORT_SYMBOL_GPL(isst_if_get_pci_dev); @@ -327,6 +344,8 @@ static int isst_if_cpu_online(unsigned int cpu) } else { isst_cpu_info[cpu].bus_info[0] = data & 0xff; isst_cpu_info[cpu].bus_info[1] = (data >> 8) & 0xff; + isst_cpu_info[cpu].pci_dev[0] = _isst_if_get_pci_dev(cpu, 0, 0, 1); + isst_cpu_info[cpu].pci_dev[1] = _isst_if_get_pci_dev(cpu, 1, 30, 1); } ret = rdmsrl_safe(MSR_THREAD_ID_INFO, &data); -- cgit v1.2.3 From aa2ddd24257213bdfd2f65058531810ac57455dc Mon Sep 17 00:00:00 2001 From: Srinivas Pandruvada Date: Wed, 16 Jun 2021 15:13:29 -0700 Subject: platform/x86: ISST: Use numa node id for cpu pci dev mapping There is a problem in mapping CPU to a PCI device instance when the bus numbers are reused in different packages. This was observed on some Sapphire Rapids systems. The current implementation reads bus number assigned to a CPU package via MSR 0x128. This allows to establish relationship between a CPU and a PCI device. This allows to update power related parameters to a MMIO offset in a PCI device space which is unique to a CPU. But if two packages uses same bus number then this mapping will not be unique. When bus number is reused, PCI device will use different domain number or segment number. So we need to be aware of this domain information while matching CPU to PCI bus number. This domain information is not available via any MSR. So need to use ACPI numa node information. There is an interface already available in the Linux to read numa node for a CPU and a PCI device. This change uses this interface to check the numa node of a match PCI device with bus number. If the bus number and numa node matches with the CPU's assigned bus number and numa node, the matched PCI device instance will be returned to the caller. It is possible that before Sapphire Rapids, the numa node is not defined for the Speed Select PCI device in some OEM systems. In this case to restore old behavior, return the last matched PCI device for domain 0 unlsess there are more than one matches. Signed-off-by: Srinivas Pandruvada Link: https://lore.kernel.org/r/20210616221329.1909276-2-srinivas.pandruvada@linux.intel.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- .../x86/intel_speed_select_if/isst_if_common.c | 48 +++++++++++++++++++++- 1 file changed, 46 insertions(+), 2 deletions(-) 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 bbd46b1d9c10..6f0cc679c8e5 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 @@ -283,13 +283,18 @@ struct isst_if_cpu_info { int bus_info[2]; struct pci_dev *pci_dev[2]; int punit_cpu_id; + int numa_node; }; static struct isst_if_cpu_info *isst_cpu_info; +#define ISST_MAX_PCI_DOMAINS 8 static struct pci_dev *_isst_if_get_pci_dev(int cpu, int bus_no, int dev, int fn) { - int bus_number; + struct pci_dev *matched_pci_dev = NULL; + struct pci_dev *pci_dev = NULL; + int no_matches = 0; + int i, bus_number; if (bus_no < 0 || bus_no > 1 || cpu < 0 || cpu >= nr_cpu_ids || cpu >= num_possible_cpus()) @@ -299,7 +304,45 @@ static struct pci_dev *_isst_if_get_pci_dev(int cpu, int bus_no, int dev, int fn if (bus_number < 0) return NULL; - return pci_get_domain_bus_and_slot(0, bus_number, PCI_DEVFN(dev, fn)); + for (i = 0; i < ISST_MAX_PCI_DOMAINS; ++i) { + struct pci_dev *_pci_dev; + int node; + + _pci_dev = pci_get_domain_bus_and_slot(i, bus_number, PCI_DEVFN(dev, fn)); + if (!_pci_dev) + continue; + + ++no_matches; + if (!matched_pci_dev) + matched_pci_dev = _pci_dev; + + node = dev_to_node(&_pci_dev->dev); + if (node == NUMA_NO_NODE) { + pr_info("Fail to get numa node for CPU:%d bus:%d dev:%d fn:%d\n", + cpu, bus_no, dev, fn); + continue; + } + + if (node == isst_cpu_info[cpu].numa_node) { + pci_dev = _pci_dev; + break; + } + } + + /* + * If there is no numa matched pci_dev, then there can be following cases: + * 1. CONFIG_NUMA is not defined: In this case if there is only single device + * match, then we don't need numa information. Simply return last match. + * Othewise return NULL. + * 2. NUMA information is not exposed via _SEG method. In this case it is similar + * to case 1. + * 3. Numa information doesn't match with CPU numa node and more than one match + * return NULL. + */ + if (!pci_dev && no_matches == 1) + pci_dev = matched_pci_dev; + + return pci_dev; } /** @@ -354,6 +397,7 @@ static int isst_if_cpu_online(unsigned int cpu) return ret; } isst_cpu_info[cpu].punit_cpu_id = data; + isst_cpu_info[cpu].numa_node = cpu_to_node(cpu); isst_restore_msr_local(cpu); -- cgit v1.2.3 From 3ece696c1acaa2ecac2e55143fc0c3ac413369c0 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 18 Jun 2021 15:55:09 +0300 Subject: platform/x86: Remove "default n" entries Linus already once did that for PDx86, don't repeat our mistakes. TL;DR: 'n' *is* the default 'default'. Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20210618125516.53510-1-andriy.shevchenko@linux.intel.com Signed-off-by: Hans de Goede --- drivers/platform/x86/Kconfig | 1 - drivers/platform/x86/dell/Kconfig | 1 - 2 files changed, 2 deletions(-) diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 4fd792f2a10a..ccb827b57f1f 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -1092,7 +1092,6 @@ config TOUCHSCREEN_DMI config FW_ATTR_CLASS tristate - default n config INTEL_IMR bool "Intel Isolated Memory Region support" diff --git a/drivers/platform/x86/dell/Kconfig b/drivers/platform/x86/dell/Kconfig index 9b0a4d080f43..9e7314d90bea 100644 --- a/drivers/platform/x86/dell/Kconfig +++ b/drivers/platform/x86/dell/Kconfig @@ -5,7 +5,6 @@ menuconfig X86_PLATFORM_DRIVERS_DELL bool "Dell X86 Platform Specific Device Drivers" - default n depends on X86_PLATFORM_DEVICES help Say Y here to get to see options for device drivers for various -- cgit v1.2.3 From a4310246430096a876a8d54ca7ad52d6603b9cda Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 18 Jun 2021 15:55:10 +0300 Subject: platform/x86: intel_skl_int3472: Free ACPI device resources after use We may free ACPI device resources immediately after use. Refactor skl_int3472_parse_crs() accordingly. Signed-off-by: Andy Shevchenko Reviewed-by: Daniel Scally Tested-by: Daniel Scally Link: https://lore.kernel.org/r/20210618125516.53510-2-andriy.shevchenko@linux.intel.com Signed-off-by: Hans de Goede --- .../platform/x86/intel-int3472/intel_skl_int3472_discrete.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c b/drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c index 8c18dbff1c43..48a00a1f4fb6 100644 --- a/drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c +++ b/drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c @@ -308,8 +308,10 @@ static int skl_int3472_parse_crs(struct int3472_discrete_device *int3472) ret = acpi_dev_get_resources(int3472->adev, &resource_list, skl_int3472_handle_gpio_resources, int3472); - if (ret) - goto out_free_res_list; + if (ret < 0) + return ret; + + acpi_dev_free_resource_list(&resource_list); /* * If we find no clock enable GPIO pin then the privacy LED won't work. @@ -319,7 +321,7 @@ static int skl_int3472_parse_crs(struct int3472_discrete_device *int3472) if (int3472->clock.ena_gpio) { ret = skl_int3472_register_clock(int3472); if (ret) - goto out_free_res_list; + return ret; } else { if (int3472->clock.led_gpio) dev_warn(int3472->dev, @@ -329,10 +331,7 @@ static int skl_int3472_parse_crs(struct int3472_discrete_device *int3472) int3472->gpios.dev_id = int3472->sensor_name; gpiod_add_lookup_table(&int3472->gpios); -out_free_res_list: - acpi_dev_free_resource_list(&resource_list); - - return ret; + return 0; } int skl_int3472_discrete_probe(struct platform_device *pdev) -- cgit v1.2.3 From a438dd11081a6ff1b8aa13cc96f07e2ca2f33a36 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 18 Jun 2021 15:55:11 +0300 Subject: platform/x86: intel_skl_int3472: Fix dependencies (drop CLKDEV_LOOKUP) Besides the fact that COMMON_CLK selects CLKDEV_LOOKUP, the latter is going to be removed from clock framework. Reviewed-by: Daniel Scally Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20210618125516.53510-3-andriy.shevchenko@linux.intel.com Signed-off-by: Hans de Goede --- drivers/platform/x86/intel-int3472/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/platform/x86/intel-int3472/Kconfig b/drivers/platform/x86/intel-int3472/Kconfig index c112878e833b..62e5d4cf9ee5 100644 --- a/drivers/platform/x86/intel-int3472/Kconfig +++ b/drivers/platform/x86/intel-int3472/Kconfig @@ -1,7 +1,7 @@ config INTEL_SKL_INT3472 tristate "Intel SkyLake ACPI INT3472 Driver" depends on ACPI - depends on COMMON_CLK && CLKDEV_LOOKUP + depends on COMMON_CLK depends on I2C depends on GPIOLIB depends on REGULATOR -- cgit v1.2.3 From 719941878bc95af5e1368eca56fd4dcbd3633f10 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 18 Jun 2021 15:55:12 +0300 Subject: platform/x86: intel_skl_int3472: Use ACPI GPIO resource directly When we call acpi_gpio_get_io_resource(), the output will be the pointer to the ACPI GPIO resource. Use it directly instead of dereferencing the generic resource. Signed-off-by: Andy Shevchenko Reviewed-by: Daniel Scally Tested-by: Daniel Scally Link: https://lore.kernel.org/r/20210618125516.53510-4-andriy.shevchenko@linux.intel.com Signed-off-by: Hans de Goede --- .../intel_skl_int3472_clk_and_regulator.c | 7 +++--- .../x86/intel-int3472/intel_skl_int3472_common.h | 2 +- .../x86/intel-int3472/intel_skl_int3472_discrete.c | 28 ++++++++++------------ 3 files changed, 17 insertions(+), 20 deletions(-) diff --git a/drivers/platform/x86/intel-int3472/intel_skl_int3472_clk_and_regulator.c b/drivers/platform/x86/intel-int3472/intel_skl_int3472_clk_and_regulator.c index ceee860e2c07..49ea1e86c193 100644 --- a/drivers/platform/x86/intel-int3472/intel_skl_int3472_clk_and_regulator.c +++ b/drivers/platform/x86/intel-int3472/intel_skl_int3472_clk_and_regulator.c @@ -131,10 +131,10 @@ out_free_init_name: } int skl_int3472_register_regulator(struct int3472_discrete_device *int3472, - struct acpi_resource *ares) + struct acpi_resource_gpio *agpio) { - char *path = ares->data.gpio.resource_source.string_ptr; const struct int3472_sensor_config *sensor_config; + char *path = agpio->resource_source.string_ptr; struct regulator_consumer_supply supply_map; struct regulator_init_data init_data = { }; struct regulator_config cfg = { }; @@ -168,8 +168,7 @@ int skl_int3472_register_regulator(struct int3472_discrete_device *int3472, int3472->regulator.supply_name, &int3472_gpio_regulator_ops); - int3472->regulator.gpio = acpi_get_and_request_gpiod(path, - ares->data.gpio.pin_table[0], + int3472->regulator.gpio = acpi_get_and_request_gpiod(path, agpio->pin_table[0], "int3472,regulator"); if (IS_ERR(int3472->regulator.gpio)) { dev_err(int3472->dev, "Failed to get regulator GPIO line\n"); diff --git a/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.h b/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.h index 6fdf78584219..765e01ec1604 100644 --- a/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.h +++ b/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.h @@ -113,6 +113,6 @@ union acpi_object *skl_int3472_get_acpi_buffer(struct acpi_device *adev, int skl_int3472_fill_cldb(struct acpi_device *adev, struct int3472_cldb *cldb); int skl_int3472_register_clock(struct int3472_discrete_device *int3472); int skl_int3472_register_regulator(struct int3472_discrete_device *int3472, - struct acpi_resource *ares); + struct acpi_resource_gpio *agpio); #endif diff --git a/drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c b/drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c index 48a00a1f4fb6..fd681d2a73fe 100644 --- a/drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c +++ b/drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c @@ -103,11 +103,11 @@ skl_int3472_get_sensor_module_config(struct int3472_discrete_device *int3472) } static int skl_int3472_map_gpio_to_sensor(struct int3472_discrete_device *int3472, - struct acpi_resource *ares, + struct acpi_resource_gpio *agpio, const char *func, u32 polarity) { - char *path = ares->data.gpio.resource_source.string_ptr; const struct int3472_sensor_config *sensor_config; + char *path = agpio->resource_source.string_ptr; struct gpiod_lookup *table_entry; struct acpi_device *adev; acpi_handle handle; @@ -145,7 +145,7 @@ static int skl_int3472_map_gpio_to_sensor(struct int3472_discrete_device *int347 table_entry = &int3472->gpios.table[int3472->n_sensor_gpios]; table_entry->key = acpi_dev_name(adev); - table_entry->chip_hwnum = ares->data.gpio.pin_table[0]; + table_entry->chip_hwnum = agpio->pin_table[0]; table_entry->con_id = func; table_entry->idx = 0; table_entry->flags = polarity; @@ -156,23 +156,22 @@ static int skl_int3472_map_gpio_to_sensor(struct int3472_discrete_device *int347 } static int skl_int3472_map_gpio_to_clk(struct int3472_discrete_device *int3472, - struct acpi_resource *ares, u8 type) + struct acpi_resource_gpio *agpio, u8 type) { - char *path = ares->data.gpio.resource_source.string_ptr; + char *path = agpio->resource_source.string_ptr; + u16 pin = agpio->pin_table[0]; struct gpio_desc *gpio; switch (type) { case INT3472_GPIO_TYPE_CLK_ENABLE: - gpio = acpi_get_and_request_gpiod(path, ares->data.gpio.pin_table[0], - "int3472,clk-enable"); + gpio = acpi_get_and_request_gpiod(path, pin, "int3472,clk-enable"); if (IS_ERR(gpio)) return (PTR_ERR(gpio)); int3472->clock.ena_gpio = gpio; break; case INT3472_GPIO_TYPE_PRIVACY_LED: - gpio = acpi_get_and_request_gpiod(path, ares->data.gpio.pin_table[0], - "int3472,privacy-led"); + gpio = acpi_get_and_request_gpiod(path, pin, "int3472,privacy-led"); if (IS_ERR(gpio)) return (PTR_ERR(gpio)); @@ -242,7 +241,7 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares, if (!obj) { dev_warn(int3472->dev, "No _DSM entry for GPIO pin %u\n", - ares->data.gpio.pin_table[0]); + agpio->pin_table[0]); return 1; } @@ -250,15 +249,14 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares, switch (type) { case INT3472_GPIO_TYPE_RESET: - ret = skl_int3472_map_gpio_to_sensor(int3472, ares, "reset", + ret = skl_int3472_map_gpio_to_sensor(int3472, agpio, "reset", GPIO_ACTIVE_LOW); if (ret) err_msg = "Failed to map reset pin to sensor\n"; break; case INT3472_GPIO_TYPE_POWERDOWN: - ret = skl_int3472_map_gpio_to_sensor(int3472, ares, - "powerdown", + ret = skl_int3472_map_gpio_to_sensor(int3472, agpio, "powerdown", GPIO_ACTIVE_LOW); if (ret) err_msg = "Failed to map powerdown pin to sensor\n"; @@ -266,13 +264,13 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares, break; case INT3472_GPIO_TYPE_CLK_ENABLE: case INT3472_GPIO_TYPE_PRIVACY_LED: - ret = skl_int3472_map_gpio_to_clk(int3472, ares, type); + ret = skl_int3472_map_gpio_to_clk(int3472, agpio, type); if (ret) err_msg = "Failed to map GPIO to clock\n"; break; case INT3472_GPIO_TYPE_POWER_ENABLE: - ret = skl_int3472_register_regulator(int3472, ares); + ret = skl_int3472_register_regulator(int3472, agpio); if (ret) err_msg = "Failed to map regulator to sensor\n"; -- cgit v1.2.3 From 7b2baa407c3c9e6f74c7edfa181eeb001e75ed3e Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 18 Jun 2021 15:55:13 +0300 Subject: platform/x86: intel_skl_int3472: Provide skl_int3472_unregister_regulator() For the sake of APIs to be properly layered provide skl_int3472_unregister_regulator(). Signed-off-by: Andy Shevchenko Reviewed-by: Daniel Scally Tested-by: Daniel Scally Link: https://lore.kernel.org/r/20210618125516.53510-5-andriy.shevchenko@linux.intel.com Signed-off-by: Hans de Goede --- .../x86/intel-int3472/intel_skl_int3472_clk_and_regulator.c | 6 ++++++ drivers/platform/x86/intel-int3472/intel_skl_int3472_common.h | 2 ++ drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c | 4 ++-- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/drivers/platform/x86/intel-int3472/intel_skl_int3472_clk_and_regulator.c b/drivers/platform/x86/intel-int3472/intel_skl_int3472_clk_and_regulator.c index 49ea1e86c193..60c7128f44ee 100644 --- a/drivers/platform/x86/intel-int3472/intel_skl_int3472_clk_and_regulator.c +++ b/drivers/platform/x86/intel-int3472/intel_skl_int3472_clk_and_regulator.c @@ -193,3 +193,9 @@ err_free_gpio: return ret; } + +void skl_int3472_unregister_regulator(struct int3472_discrete_device *int3472) +{ + regulator_unregister(int3472->regulator.rdev); + gpiod_put(int3472->regulator.gpio); +} diff --git a/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.h b/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.h index 765e01ec1604..50f73c6eab44 100644 --- a/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.h +++ b/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.h @@ -112,7 +112,9 @@ union acpi_object *skl_int3472_get_acpi_buffer(struct acpi_device *adev, char *id); int skl_int3472_fill_cldb(struct acpi_device *adev, struct int3472_cldb *cldb); int skl_int3472_register_clock(struct int3472_discrete_device *int3472); + int skl_int3472_register_regulator(struct int3472_discrete_device *int3472, struct acpi_resource_gpio *agpio); +void skl_int3472_unregister_regulator(struct int3472_discrete_device *int3472); #endif diff --git a/drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c b/drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c index fd681d2a73fe..2638d375e226 100644 --- a/drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c +++ b/drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c @@ -400,15 +400,15 @@ int skl_int3472_discrete_remove(struct platform_device *pdev) struct int3472_discrete_device *int3472 = platform_get_drvdata(pdev); gpiod_remove_lookup_table(&int3472->gpios); - regulator_unregister(int3472->regulator.rdev); clk_unregister(int3472->clock.clk); if (int3472->clock.cl) clkdev_drop(int3472->clock.cl); - gpiod_put(int3472->regulator.gpio); gpiod_put(int3472->clock.ena_gpio); gpiod_put(int3472->clock.led_gpio); + skl_int3472_unregister_regulator(int3472); + return 0; } -- cgit v1.2.3 From 7540599a5ef1cbe8d20993ca0c3202d8409338e1 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 18 Jun 2021 15:55:14 +0300 Subject: platform/x86: intel_skl_int3472: Provide skl_int3472_unregister_clock() For the sake of APIs to be properly layered provide skl_int3472_unregister_clock(). Signed-off-by: Andy Shevchenko Reviewed-by: Daniel Scally Tested-by: Daniel Scally Link: https://lore.kernel.org/r/20210618125516.53510-6-andriy.shevchenko@linux.intel.com Signed-off-by: Hans de Goede --- .../x86/intel-int3472/intel_skl_int3472_clk_and_regulator.c | 6 ++++++ drivers/platform/x86/intel-int3472/intel_skl_int3472_common.h | 2 ++ drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c | 5 ++--- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/drivers/platform/x86/intel-int3472/intel_skl_int3472_clk_and_regulator.c b/drivers/platform/x86/intel-int3472/intel_skl_int3472_clk_and_regulator.c index 60c7128f44ee..1700e7557a82 100644 --- a/drivers/platform/x86/intel-int3472/intel_skl_int3472_clk_and_regulator.c +++ b/drivers/platform/x86/intel-int3472/intel_skl_int3472_clk_and_regulator.c @@ -130,6 +130,12 @@ out_free_init_name: return ret; } +void skl_int3472_unregister_clock(struct int3472_discrete_device *int3472) +{ + clkdev_drop(int3472->clock.cl); + clk_unregister(int3472->clock.clk); +} + int skl_int3472_register_regulator(struct int3472_discrete_device *int3472, struct acpi_resource_gpio *agpio) { diff --git a/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.h b/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.h index 50f73c6eab44..714fde73b524 100644 --- a/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.h +++ b/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.h @@ -111,7 +111,9 @@ int skl_int3472_tps68470_probe(struct i2c_client *client); union acpi_object *skl_int3472_get_acpi_buffer(struct acpi_device *adev, char *id); int skl_int3472_fill_cldb(struct acpi_device *adev, struct int3472_cldb *cldb); + int skl_int3472_register_clock(struct int3472_discrete_device *int3472); +void skl_int3472_unregister_clock(struct int3472_discrete_device *int3472); int skl_int3472_register_regulator(struct int3472_discrete_device *int3472, struct acpi_resource_gpio *agpio); diff --git a/drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c b/drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c index 2638d375e226..17c6fe830765 100644 --- a/drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c +++ b/drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c @@ -400,10 +400,9 @@ int skl_int3472_discrete_remove(struct platform_device *pdev) struct int3472_discrete_device *int3472 = platform_get_drvdata(pdev); gpiod_remove_lookup_table(&int3472->gpios); - clk_unregister(int3472->clock.clk); - if (int3472->clock.cl) - clkdev_drop(int3472->clock.cl); + if (int3472->clock.ena_gpio) + skl_int3472_unregister_clock(int3472); gpiod_put(int3472->clock.ena_gpio); gpiod_put(int3472->clock.led_gpio); -- cgit v1.2.3 From 8bd836feb6cad6bd746da09a86bda0f5ee5c4b01 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 18 Jun 2021 15:55:15 +0300 Subject: platform/x86: intel_skl_int3472: Move to intel/ subfolder Start collecting Intel x86 related drivers in its own subfolder. Move intel_skl_int3472 first. Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20210618125516.53510-7-andriy.shevchenko@linux.intel.com Signed-off-by: Hans de Goede --- MAINTAINERS | 2 +- drivers/platform/x86/Kconfig | 4 +- drivers/platform/x86/Makefile | 3 +- drivers/platform/x86/intel-int3472/Kconfig | 30 -- drivers/platform/x86/intel-int3472/Makefile | 5 - .../intel_skl_int3472_clk_and_regulator.c | 207 ----------- .../x86/intel-int3472/intel_skl_int3472_common.c | 106 ------ .../x86/intel-int3472/intel_skl_int3472_common.h | 122 ------ .../x86/intel-int3472/intel_skl_int3472_discrete.c | 413 --------------------- .../x86/intel-int3472/intel_skl_int3472_tps68470.c | 137 ------- drivers/platform/x86/intel/Kconfig | 21 ++ drivers/platform/x86/intel/Makefile | 7 + drivers/platform/x86/intel/int3472/Kconfig | 30 ++ drivers/platform/x86/intel/int3472/Makefile | 5 + .../int3472/intel_skl_int3472_clk_and_regulator.c | 207 +++++++++++ .../x86/intel/int3472/intel_skl_int3472_common.c | 106 ++++++ .../x86/intel/int3472/intel_skl_int3472_common.h | 122 ++++++ .../x86/intel/int3472/intel_skl_int3472_discrete.c | 413 +++++++++++++++++++++ .../x86/intel/int3472/intel_skl_int3472_tps68470.c | 137 +++++++ 19 files changed, 1053 insertions(+), 1024 deletions(-) delete mode 100644 drivers/platform/x86/intel-int3472/Kconfig delete mode 100644 drivers/platform/x86/intel-int3472/Makefile delete mode 100644 drivers/platform/x86/intel-int3472/intel_skl_int3472_clk_and_regulator.c delete mode 100644 drivers/platform/x86/intel-int3472/intel_skl_int3472_common.c delete mode 100644 drivers/platform/x86/intel-int3472/intel_skl_int3472_common.h delete mode 100644 drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c delete mode 100644 drivers/platform/x86/intel-int3472/intel_skl_int3472_tps68470.c create mode 100644 drivers/platform/x86/intel/Kconfig create mode 100644 drivers/platform/x86/intel/Makefile create mode 100644 drivers/platform/x86/intel/int3472/Kconfig create mode 100644 drivers/platform/x86/intel/int3472/Makefile create mode 100644 drivers/platform/x86/intel/int3472/intel_skl_int3472_clk_and_regulator.c create mode 100644 drivers/platform/x86/intel/int3472/intel_skl_int3472_common.c create mode 100644 drivers/platform/x86/intel/int3472/intel_skl_int3472_common.h create mode 100644 drivers/platform/x86/intel/int3472/intel_skl_int3472_discrete.c create mode 100644 drivers/platform/x86/intel/int3472/intel_skl_int3472_tps68470.c diff --git a/MAINTAINERS b/MAINTAINERS index 7da4c07364fa..5ca79321b9c5 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9388,7 +9388,7 @@ F: drivers/platform/x86/intel_scu_* INTEL SKYLAKE INT3472 ACPI DEVICE DRIVER M: Daniel Scally S: Maintained -F: drivers/platform/x86/intel-int3472/ +F: drivers/platform/x86/intel/int3472/ INTEL SPEED SELECT TECHNOLOGY M: Srinivas Pandruvada diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index ccb827b57f1f..79d095c0ab61 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -651,6 +651,8 @@ config THINKPAD_LMI To compile this driver as a module, choose M here: the module will be called think-lmi. +source "drivers/platform/x86/intel/Kconfig" + config INTEL_ATOMISP2_LED tristate "Intel AtomISP2 camera LED driver" depends on GPIOLIB && LEDS_GPIO @@ -709,8 +711,6 @@ config INTEL_CHT_INT33FE device and CONFIG_TYPEC_FUSB302=m and CONFIG_BATTERY_MAX17042=m for Type-C device. -source "drivers/platform/x86/intel-int3472/Kconfig" - config INTEL_HID_EVENT tristate "INTEL HID Event" depends on ACPI diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index a1f64613af71..e03b59ce3f9f 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -66,6 +66,8 @@ obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o # Intel +obj-$(CONFIG_X86_PLATFORM_DRIVERS_INTEL) += intel/ + obj-$(CONFIG_INTEL_ATOMISP2_LED) += intel_atomisp2_led.o obj-$(CONFIG_INTEL_ATOMISP2_PM) += intel_atomisp2_pm.o obj-$(CONFIG_INTEL_CHT_INT33FE) += intel_cht_int33fe.o @@ -76,7 +78,6 @@ obj-$(CONFIG_INTEL_HID_EVENT) += intel-hid.o obj-$(CONFIG_INTEL_INT0002_VGPIO) += intel_int0002_vgpio.o obj-$(CONFIG_INTEL_MENLOW) += intel_menlow.o obj-$(CONFIG_INTEL_OAKTRAIL) += intel_oaktrail.o -obj-$(CONFIG_INTEL_SKL_INT3472) += intel-int3472/ obj-$(CONFIG_INTEL_VBTN) += intel-vbtn.o # MSI diff --git a/drivers/platform/x86/intel-int3472/Kconfig b/drivers/platform/x86/intel-int3472/Kconfig deleted file mode 100644 index 62e5d4cf9ee5..000000000000 --- a/drivers/platform/x86/intel-int3472/Kconfig +++ /dev/null @@ -1,30 +0,0 @@ -config INTEL_SKL_INT3472 - tristate "Intel SkyLake ACPI INT3472 Driver" - depends on ACPI - depends on COMMON_CLK - depends on I2C - depends on GPIOLIB - depends on REGULATOR - select MFD_CORE - select REGMAP_I2C - help - This driver adds power controller support for the Intel SkyCam - devices found on the Intel SkyLake platforms. - - The INT3472 is a camera power controller, a logical device found on - Intel Skylake-based systems that can map to different hardware - devices depending on the platform. On machines designed for Chrome OS - it maps to a TPS68470 camera PMIC. On machines designed for Windows, - it maps to either a TP68470 camera PMIC, a uP6641Q sensor PMIC, or a - set of discrete GPIOs and power gates. - - If your device was designed for Chrome OS, this driver will provide - an ACPI OpRegion, which must be available before any of the devices - using it are probed. For this reason, you should select Y if your - device was designed for ChromeOS. For the same reason the - I2C_DESIGNWARE_PLATFORM option must be set to Y too. - - Say Y or M here if you have a SkyLake device designed for use - with Windows or ChromeOS. Say N here if you are not sure. - - The module will be named "intel-skl-int3472". diff --git a/drivers/platform/x86/intel-int3472/Makefile b/drivers/platform/x86/intel-int3472/Makefile deleted file mode 100644 index 48bd97f0a04e..000000000000 --- a/drivers/platform/x86/intel-int3472/Makefile +++ /dev/null @@ -1,5 +0,0 @@ -obj-$(CONFIG_INTEL_SKL_INT3472) += intel_skl_int3472.o -intel_skl_int3472-objs := intel_skl_int3472_common.o \ - intel_skl_int3472_discrete.o \ - intel_skl_int3472_tps68470.o \ - intel_skl_int3472_clk_and_regulator.o diff --git a/drivers/platform/x86/intel-int3472/intel_skl_int3472_clk_and_regulator.c b/drivers/platform/x86/intel-int3472/intel_skl_int3472_clk_and_regulator.c deleted file mode 100644 index 1700e7557a82..000000000000 --- a/drivers/platform/x86/intel-int3472/intel_skl_int3472_clk_and_regulator.c +++ /dev/null @@ -1,207 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* Author: Dan Scally */ - -#include -#include -#include -#include -#include -#include -#include - -#include "intel_skl_int3472_common.h" - -/* - * The regulators have to have .ops to be valid, but the only ops we actually - * support are .enable and .disable which are handled via .ena_gpiod. Pass an - * empty struct to clear the check without lying about capabilities. - */ -static const struct regulator_ops int3472_gpio_regulator_ops; - -static int skl_int3472_clk_prepare(struct clk_hw *hw) -{ - struct int3472_gpio_clock *clk = to_int3472_clk(hw); - - gpiod_set_value_cansleep(clk->ena_gpio, 1); - gpiod_set_value_cansleep(clk->led_gpio, 1); - - return 0; -} - -static void skl_int3472_clk_unprepare(struct clk_hw *hw) -{ - struct int3472_gpio_clock *clk = to_int3472_clk(hw); - - gpiod_set_value_cansleep(clk->ena_gpio, 0); - gpiod_set_value_cansleep(clk->led_gpio, 0); -} - -static int skl_int3472_clk_enable(struct clk_hw *hw) -{ - /* - * We're just turning a GPIO on to enable the clock, which operation - * has the potential to sleep. Given .enable() cannot sleep, but - * .prepare() can, we toggle the GPIO in .prepare() instead. Thus, - * nothing to do here. - */ - return 0; -} - -static void skl_int3472_clk_disable(struct clk_hw *hw) -{ - /* Likewise, nothing to do here... */ -} - -static unsigned int skl_int3472_get_clk_frequency(struct int3472_discrete_device *int3472) -{ - union acpi_object *obj; - unsigned int freq; - - obj = skl_int3472_get_acpi_buffer(int3472->sensor, "SSDB"); - if (IS_ERR(obj)) - return 0; /* report rate as 0 on error */ - - if (obj->buffer.length < CIO2_SENSOR_SSDB_MCLKSPEED_OFFSET + sizeof(u32)) { - dev_err(int3472->dev, "The buffer is too small\n"); - kfree(obj); - return 0; - } - - freq = *(u32 *)(obj->buffer.pointer + CIO2_SENSOR_SSDB_MCLKSPEED_OFFSET); - - kfree(obj); - return freq; -} - -static unsigned long skl_int3472_clk_recalc_rate(struct clk_hw *hw, - unsigned long parent_rate) -{ - struct int3472_gpio_clock *clk = to_int3472_clk(hw); - - return clk->frequency; -} - -static const struct clk_ops skl_int3472_clock_ops = { - .prepare = skl_int3472_clk_prepare, - .unprepare = skl_int3472_clk_unprepare, - .enable = skl_int3472_clk_enable, - .disable = skl_int3472_clk_disable, - .recalc_rate = skl_int3472_clk_recalc_rate, -}; - -int skl_int3472_register_clock(struct int3472_discrete_device *int3472) -{ - struct clk_init_data init = { - .ops = &skl_int3472_clock_ops, - .flags = CLK_GET_RATE_NOCACHE, - }; - int ret; - - init.name = kasprintf(GFP_KERNEL, "%s-clk", - acpi_dev_name(int3472->adev)); - if (!init.name) - return -ENOMEM; - - int3472->clock.frequency = skl_int3472_get_clk_frequency(int3472); - - int3472->clock.clk_hw.init = &init; - int3472->clock.clk = clk_register(&int3472->adev->dev, - &int3472->clock.clk_hw); - if (IS_ERR(int3472->clock.clk)) { - ret = PTR_ERR(int3472->clock.clk); - goto out_free_init_name; - } - - int3472->clock.cl = clkdev_create(int3472->clock.clk, NULL, - int3472->sensor_name); - if (!int3472->clock.cl) { - ret = -ENOMEM; - goto err_unregister_clk; - } - - kfree(init.name); - return 0; - -err_unregister_clk: - clk_unregister(int3472->clock.clk); -out_free_init_name: - kfree(init.name); - - return ret; -} - -void skl_int3472_unregister_clock(struct int3472_discrete_device *int3472) -{ - clkdev_drop(int3472->clock.cl); - clk_unregister(int3472->clock.clk); -} - -int skl_int3472_register_regulator(struct int3472_discrete_device *int3472, - struct acpi_resource_gpio *agpio) -{ - const struct int3472_sensor_config *sensor_config; - char *path = agpio->resource_source.string_ptr; - struct regulator_consumer_supply supply_map; - struct regulator_init_data init_data = { }; - struct regulator_config cfg = { }; - int ret; - - sensor_config = int3472->sensor_config; - if (IS_ERR(sensor_config)) { - dev_err(int3472->dev, "No sensor module config\n"); - return PTR_ERR(sensor_config); - } - - if (!sensor_config->supply_map.supply) { - dev_err(int3472->dev, "No supply name defined\n"); - return -ENODEV; - } - - init_data.constraints.valid_ops_mask = REGULATOR_CHANGE_STATUS; - init_data.num_consumer_supplies = 1; - supply_map = sensor_config->supply_map; - supply_map.dev_name = int3472->sensor_name; - init_data.consumer_supplies = &supply_map; - - snprintf(int3472->regulator.regulator_name, - sizeof(int3472->regulator.regulator_name), "%s-regulator", - acpi_dev_name(int3472->adev)); - snprintf(int3472->regulator.supply_name, - GPIO_REGULATOR_SUPPLY_NAME_LENGTH, "supply-0"); - - int3472->regulator.rdesc = INT3472_REGULATOR( - int3472->regulator.regulator_name, - int3472->regulator.supply_name, - &int3472_gpio_regulator_ops); - - int3472->regulator.gpio = acpi_get_and_request_gpiod(path, agpio->pin_table[0], - "int3472,regulator"); - if (IS_ERR(int3472->regulator.gpio)) { - dev_err(int3472->dev, "Failed to get regulator GPIO line\n"); - return PTR_ERR(int3472->regulator.gpio); - } - - cfg.dev = &int3472->adev->dev; - cfg.init_data = &init_data; - cfg.ena_gpiod = int3472->regulator.gpio; - - int3472->regulator.rdev = regulator_register(&int3472->regulator.rdesc, - &cfg); - if (IS_ERR(int3472->regulator.rdev)) { - ret = PTR_ERR(int3472->regulator.rdev); - goto err_free_gpio; - } - - return 0; - -err_free_gpio: - gpiod_put(int3472->regulator.gpio); - - return ret; -} - -void skl_int3472_unregister_regulator(struct int3472_discrete_device *int3472) -{ - regulator_unregister(int3472->regulator.rdev); - gpiod_put(int3472->regulator.gpio); -} diff --git a/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.c b/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.c deleted file mode 100644 index 497e74fba75f..000000000000 --- a/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.c +++ /dev/null @@ -1,106 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* Author: Dan Scally */ - -#include -#include -#include -#include - -#include "intel_skl_int3472_common.h" - -union acpi_object *skl_int3472_get_acpi_buffer(struct acpi_device *adev, char *id) -{ - struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; - acpi_handle handle = adev->handle; - union acpi_object *obj; - acpi_status status; - - status = acpi_evaluate_object(handle, id, NULL, &buffer); - if (ACPI_FAILURE(status)) - return ERR_PTR(-ENODEV); - - obj = buffer.pointer; - if (!obj) - return ERR_PTR(-ENODEV); - - if (obj->type != ACPI_TYPE_BUFFER) { - acpi_handle_err(handle, "%s object is not an ACPI buffer\n", id); - kfree(obj); - return ERR_PTR(-EINVAL); - } - - return obj; -} - -int skl_int3472_fill_cldb(struct acpi_device *adev, struct int3472_cldb *cldb) -{ - union acpi_object *obj; - int ret; - - obj = skl_int3472_get_acpi_buffer(adev, "CLDB"); - if (IS_ERR(obj)) - return PTR_ERR(obj); - - if (obj->buffer.length > sizeof(*cldb)) { - acpi_handle_err(adev->handle, "The CLDB buffer is too large\n"); - ret = -EINVAL; - goto out_free_obj; - } - - memcpy(cldb, obj->buffer.pointer, obj->buffer.length); - ret = 0; - -out_free_obj: - kfree(obj); - return ret; -} - -static const struct acpi_device_id int3472_device_id[] = { - { "INT3472", 0 }, - { } -}; -MODULE_DEVICE_TABLE(acpi, int3472_device_id); - -static struct platform_driver int3472_discrete = { - .driver = { - .name = "int3472-discrete", - .acpi_match_table = int3472_device_id, - }, - .probe = skl_int3472_discrete_probe, - .remove = skl_int3472_discrete_remove, -}; - -static struct i2c_driver int3472_tps68470 = { - .driver = { - .name = "int3472-tps68470", - .acpi_match_table = int3472_device_id, - }, - .probe_new = skl_int3472_tps68470_probe, -}; - -static int skl_int3472_init(void) -{ - int ret; - - ret = platform_driver_register(&int3472_discrete); - if (ret) - return ret; - - ret = i2c_register_driver(THIS_MODULE, &int3472_tps68470); - if (ret) - platform_driver_unregister(&int3472_discrete); - - return ret; -} -module_init(skl_int3472_init); - -static void skl_int3472_exit(void) -{ - platform_driver_unregister(&int3472_discrete); - i2c_del_driver(&int3472_tps68470); -} -module_exit(skl_int3472_exit); - -MODULE_DESCRIPTION("Intel SkyLake INT3472 ACPI Device Driver"); -MODULE_AUTHOR("Daniel Scally "); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.h b/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.h deleted file mode 100644 index 714fde73b524..000000000000 --- a/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.h +++ /dev/null @@ -1,122 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* Author: Dan Scally */ - -#ifndef _INTEL_SKL_INT3472_H -#define _INTEL_SKL_INT3472_H - -#include -#include -#include -#include -#include - -/* FIXME drop this once the I2C_DEV_NAME_FORMAT macro has been added to include/linux/i2c.h */ -#ifndef I2C_DEV_NAME_FORMAT -#define I2C_DEV_NAME_FORMAT "i2c-%s" -#endif - -/* PMIC GPIO Types */ -#define INT3472_GPIO_TYPE_RESET 0x00 -#define INT3472_GPIO_TYPE_POWERDOWN 0x01 -#define INT3472_GPIO_TYPE_POWER_ENABLE 0x0b -#define INT3472_GPIO_TYPE_CLK_ENABLE 0x0c -#define INT3472_GPIO_TYPE_PRIVACY_LED 0x0d - -#define INT3472_PDEV_MAX_NAME_LEN 23 -#define INT3472_MAX_SENSOR_GPIOS 3 - -#define GPIO_REGULATOR_NAME_LENGTH 21 -#define GPIO_REGULATOR_SUPPLY_NAME_LENGTH 9 - -#define CIO2_SENSOR_SSDB_MCLKSPEED_OFFSET 86 - -#define INT3472_REGULATOR(_name, _supply, _ops) \ - (const struct regulator_desc) { \ - .name = _name, \ - .supply_name = _supply, \ - .type = REGULATOR_VOLTAGE, \ - .ops = _ops, \ - .owner = THIS_MODULE, \ - } - -#define to_int3472_clk(hw) \ - container_of(hw, struct int3472_gpio_clock, clk_hw) - -#define to_int3472_device(clk) \ - container_of(clk, struct int3472_discrete_device, clock) - -struct acpi_device; -struct i2c_client; -struct platform_device; - -struct int3472_cldb { - u8 version; - /* - * control logic type - * 0: UNKNOWN - * 1: DISCRETE(CRD-D) - * 2: PMIC TPS68470 - * 3: PMIC uP6641 - */ - u8 control_logic_type; - u8 control_logic_id; - u8 sensor_card_sku; - u8 reserved[28]; -}; - -struct int3472_gpio_function_remap { - const char *documented; - const char *actual; -}; - -struct int3472_sensor_config { - const char *sensor_module_name; - struct regulator_consumer_supply supply_map; - const struct int3472_gpio_function_remap *function_maps; -}; - -struct int3472_discrete_device { - struct acpi_device *adev; - struct device *dev; - struct acpi_device *sensor; - const char *sensor_name; - - const struct int3472_sensor_config *sensor_config; - - struct int3472_gpio_regulator { - char regulator_name[GPIO_REGULATOR_NAME_LENGTH]; - char supply_name[GPIO_REGULATOR_SUPPLY_NAME_LENGTH]; - struct gpio_desc *gpio; - struct regulator_dev *rdev; - struct regulator_desc rdesc; - } regulator; - - struct int3472_gpio_clock { - struct clk *clk; - struct clk_hw clk_hw; - struct clk_lookup *cl; - struct gpio_desc *ena_gpio; - struct gpio_desc *led_gpio; - u32 frequency; - } clock; - - unsigned int ngpios; /* how many GPIOs have we seen */ - unsigned int n_sensor_gpios; /* how many have we mapped to sensor */ - struct gpiod_lookup_table gpios; -}; - -int skl_int3472_discrete_probe(struct platform_device *pdev); -int skl_int3472_discrete_remove(struct platform_device *pdev); -int skl_int3472_tps68470_probe(struct i2c_client *client); -union acpi_object *skl_int3472_get_acpi_buffer(struct acpi_device *adev, - char *id); -int skl_int3472_fill_cldb(struct acpi_device *adev, struct int3472_cldb *cldb); - -int skl_int3472_register_clock(struct int3472_discrete_device *int3472); -void skl_int3472_unregister_clock(struct int3472_discrete_device *int3472); - -int skl_int3472_register_regulator(struct int3472_discrete_device *int3472, - struct acpi_resource_gpio *agpio); -void skl_int3472_unregister_regulator(struct int3472_discrete_device *int3472); - -#endif diff --git a/drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c b/drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c deleted file mode 100644 index 17c6fe830765..000000000000 --- a/drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c +++ /dev/null @@ -1,413 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* Author: Dan Scally */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "intel_skl_int3472_common.h" - -/* - * 79234640-9e10-4fea-a5c1-b5aa8b19756f - * This _DSM GUID returns information about the GPIO lines mapped to a - * discrete INT3472 device. Function number 1 returns a count of the GPIO - * lines that are mapped. Subsequent functions return 32 bit ints encoding - * information about the GPIO line, including its purpose. - */ -static const guid_t int3472_gpio_guid = - GUID_INIT(0x79234640, 0x9e10, 0x4fea, - 0xa5, 0xc1, 0xb5, 0xaa, 0x8b, 0x19, 0x75, 0x6f); - -/* - * 822ace8f-2814-4174-a56b-5f029fe079ee - * This _DSM GUID returns a string from the sensor device, which acts as a - * module identifier. - */ -static const guid_t cio2_sensor_module_guid = - GUID_INIT(0x822ace8f, 0x2814, 0x4174, - 0xa5, 0x6b, 0x5f, 0x02, 0x9f, 0xe0, 0x79, 0xee); - -/* - * Here follows platform specific mapping information that we can pass to - * the functions mapping resources to the sensors. Where the sensors have - * a power enable pin defined in DSDT we need to provide a supply name so - * the sensor drivers can find the regulator. The device name will be derived - * from the sensor's ACPI device within the code. Optionally, we can provide a - * NULL terminated array of function name mappings to deal with any platform - * specific deviations from the documented behaviour of GPIOs. - * - * Map a GPIO function name to NULL to prevent the driver from mapping that - * GPIO at all. - */ - -static const struct int3472_gpio_function_remap ov2680_gpio_function_remaps[] = { - { "reset", NULL }, - { "powerdown", "reset" }, - { } -}; - -static const struct int3472_sensor_config int3472_sensor_configs[] = { - /* Lenovo Miix 510-12ISK - OV2680, Front */ - { "GNDF140809R", { 0 }, ov2680_gpio_function_remaps }, - /* Lenovo Miix 510-12ISK - OV5648, Rear */ - { "GEFF150023R", REGULATOR_SUPPLY("avdd", NULL), NULL }, - /* Surface Go 1&2 - OV5693, Front */ - { "YHCU", REGULATOR_SUPPLY("avdd", NULL), NULL }, -}; - -static const struct int3472_sensor_config * -skl_int3472_get_sensor_module_config(struct int3472_discrete_device *int3472) -{ - union acpi_object *obj; - unsigned int i; - - obj = acpi_evaluate_dsm_typed(int3472->sensor->handle, - &cio2_sensor_module_guid, 0x00, - 0x01, NULL, ACPI_TYPE_STRING); - - if (!obj) { - dev_err(int3472->dev, - "Failed to get sensor module string from _DSM\n"); - return ERR_PTR(-ENODEV); - } - - if (obj->string.type != ACPI_TYPE_STRING) { - dev_err(int3472->dev, - "Sensor _DSM returned a non-string value\n"); - - ACPI_FREE(obj); - return ERR_PTR(-EINVAL); - } - - for (i = 0; i < ARRAY_SIZE(int3472_sensor_configs); i++) { - if (!strcmp(int3472_sensor_configs[i].sensor_module_name, - obj->string.pointer)) - break; - } - - ACPI_FREE(obj); - - if (i >= ARRAY_SIZE(int3472_sensor_configs)) - return ERR_PTR(-EINVAL); - - return &int3472_sensor_configs[i]; -} - -static int skl_int3472_map_gpio_to_sensor(struct int3472_discrete_device *int3472, - struct acpi_resource_gpio *agpio, - const char *func, u32 polarity) -{ - const struct int3472_sensor_config *sensor_config; - char *path = agpio->resource_source.string_ptr; - struct gpiod_lookup *table_entry; - struct acpi_device *adev; - acpi_handle handle; - acpi_status status; - int ret; - - if (int3472->n_sensor_gpios >= INT3472_MAX_SENSOR_GPIOS) { - dev_warn(int3472->dev, "Too many GPIOs mapped\n"); - return -EINVAL; - } - - sensor_config = int3472->sensor_config; - if (!IS_ERR(sensor_config) && sensor_config->function_maps) { - const struct int3472_gpio_function_remap *remap; - - for (remap = sensor_config->function_maps; remap->documented; remap++) { - if (!strcmp(func, remap->documented)) { - func = remap->actual; - break; - } - } - } - - /* Functions mapped to NULL should not be mapped to the sensor */ - if (!func) - return 0; - - status = acpi_get_handle(NULL, path, &handle); - if (ACPI_FAILURE(status)) - return -EINVAL; - - ret = acpi_bus_get_device(handle, &adev); - if (ret) - return -ENODEV; - - table_entry = &int3472->gpios.table[int3472->n_sensor_gpios]; - table_entry->key = acpi_dev_name(adev); - table_entry->chip_hwnum = agpio->pin_table[0]; - table_entry->con_id = func; - table_entry->idx = 0; - table_entry->flags = polarity; - - int3472->n_sensor_gpios++; - - return 0; -} - -static int skl_int3472_map_gpio_to_clk(struct int3472_discrete_device *int3472, - struct acpi_resource_gpio *agpio, u8 type) -{ - char *path = agpio->resource_source.string_ptr; - u16 pin = agpio->pin_table[0]; - struct gpio_desc *gpio; - - switch (type) { - case INT3472_GPIO_TYPE_CLK_ENABLE: - gpio = acpi_get_and_request_gpiod(path, pin, "int3472,clk-enable"); - if (IS_ERR(gpio)) - return (PTR_ERR(gpio)); - - int3472->clock.ena_gpio = gpio; - break; - case INT3472_GPIO_TYPE_PRIVACY_LED: - gpio = acpi_get_and_request_gpiod(path, pin, "int3472,privacy-led"); - if (IS_ERR(gpio)) - return (PTR_ERR(gpio)); - - int3472->clock.led_gpio = gpio; - break; - default: - dev_err(int3472->dev, "Invalid GPIO type 0x%02x for clock\n", type); - break; - } - - return 0; -} - -/** - * skl_int3472_handle_gpio_resources: Map PMIC resources to consuming sensor - * @ares: A pointer to a &struct acpi_resource - * @data: A pointer to a &struct int3472_discrete_device - * - * This function handles GPIO resources that are against an INT3472 - * ACPI device, by checking the value of the corresponding _DSM entry. - * This will return a 32bit int, where the lowest byte represents the - * function of the GPIO pin: - * - * 0x00 Reset - * 0x01 Power down - * 0x0b Power enable - * 0x0c Clock enable - * 0x0d Privacy LED - * - * There are some known platform specific quirks where that does not quite - * hold up; for example where a pin with type 0x01 (Power down) is mapped to - * a sensor pin that performs a reset function or entries in _CRS and _DSM that - * do not actually correspond to a physical connection. These will be handled - * by the mapping sub-functions. - * - * GPIOs will either be mapped directly to the sensor device or else used - * to create clocks and regulators via the usual frameworks. - * - * Return: - * * 1 - To continue the loop - * * 0 - When all resources found are handled properly. - * * -EINVAL - If the resource is not a GPIO IO resource - * * -ENODEV - If the resource has no corresponding _DSM entry - * * -Other - Errors propagated from one of the sub-functions. - */ -static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares, - void *data) -{ - struct int3472_discrete_device *int3472 = data; - struct acpi_resource_gpio *agpio; - union acpi_object *obj; - const char *err_msg; - int ret; - u8 type; - - if (!acpi_gpio_get_io_resource(ares, &agpio)) - return 1; - - /* - * ngpios + 2 because the index of this _DSM function is 1-based and - * the first function is just a count. - */ - obj = acpi_evaluate_dsm_typed(int3472->adev->handle, - &int3472_gpio_guid, 0x00, - int3472->ngpios + 2, - NULL, ACPI_TYPE_INTEGER); - - if (!obj) { - dev_warn(int3472->dev, "No _DSM entry for GPIO pin %u\n", - agpio->pin_table[0]); - return 1; - } - - type = obj->integer.value & 0xff; - - switch (type) { - case INT3472_GPIO_TYPE_RESET: - ret = skl_int3472_map_gpio_to_sensor(int3472, agpio, "reset", - GPIO_ACTIVE_LOW); - if (ret) - err_msg = "Failed to map reset pin to sensor\n"; - - break; - case INT3472_GPIO_TYPE_POWERDOWN: - ret = skl_int3472_map_gpio_to_sensor(int3472, agpio, "powerdown", - GPIO_ACTIVE_LOW); - if (ret) - err_msg = "Failed to map powerdown pin to sensor\n"; - - break; - case INT3472_GPIO_TYPE_CLK_ENABLE: - case INT3472_GPIO_TYPE_PRIVACY_LED: - ret = skl_int3472_map_gpio_to_clk(int3472, agpio, type); - if (ret) - err_msg = "Failed to map GPIO to clock\n"; - - break; - case INT3472_GPIO_TYPE_POWER_ENABLE: - ret = skl_int3472_register_regulator(int3472, agpio); - if (ret) - err_msg = "Failed to map regulator to sensor\n"; - - break; - default: - dev_warn(int3472->dev, - "GPIO type 0x%02x unknown; the sensor may not work\n", - type); - ret = 1; - break; - } - - int3472->ngpios++; - ACPI_FREE(obj); - - if (ret) - return dev_err_probe(int3472->dev, ret, err_msg); - - return 0; -} - -static int skl_int3472_parse_crs(struct int3472_discrete_device *int3472) -{ - LIST_HEAD(resource_list); - int ret; - - /* - * No error check, because not having a sensor config is not necessarily - * a failure mode. - */ - int3472->sensor_config = skl_int3472_get_sensor_module_config(int3472); - - ret = acpi_dev_get_resources(int3472->adev, &resource_list, - skl_int3472_handle_gpio_resources, - int3472); - if (ret < 0) - return ret; - - acpi_dev_free_resource_list(&resource_list); - - /* - * If we find no clock enable GPIO pin then the privacy LED won't work. - * We've never seen that situation, but it's possible. Warn the user so - * it's clear what's happened. - */ - if (int3472->clock.ena_gpio) { - ret = skl_int3472_register_clock(int3472); - if (ret) - return ret; - } else { - if (int3472->clock.led_gpio) - dev_warn(int3472->dev, - "No clk GPIO. The privacy LED won't work\n"); - } - - int3472->gpios.dev_id = int3472->sensor_name; - gpiod_add_lookup_table(&int3472->gpios); - - return 0; -} - -int skl_int3472_discrete_probe(struct platform_device *pdev) -{ - struct acpi_device *adev = ACPI_COMPANION(&pdev->dev); - struct int3472_discrete_device *int3472; - struct int3472_cldb cldb; - int ret; - - ret = skl_int3472_fill_cldb(adev, &cldb); - if (ret) { - dev_err(&pdev->dev, "Couldn't fill CLDB structure\n"); - return ret; - } - - if (cldb.control_logic_type != 1) { - dev_err(&pdev->dev, "Unsupported control logic type %u\n", - cldb.control_logic_type); - return -EINVAL; - } - - /* Max num GPIOs we've seen plus a terminator */ - int3472 = devm_kzalloc(&pdev->dev, struct_size(int3472, gpios.table, - INT3472_MAX_SENSOR_GPIOS + 1), GFP_KERNEL); - if (!int3472) - return -ENOMEM; - - int3472->adev = adev; - int3472->dev = &pdev->dev; - platform_set_drvdata(pdev, int3472); - - int3472->sensor = acpi_dev_get_first_consumer_dev(adev); - if (!int3472->sensor) { - dev_err(&pdev->dev, "INT3472 seems to have no dependents.\n"); - return -ENODEV; - } - - int3472->sensor_name = devm_kasprintf(int3472->dev, GFP_KERNEL, - I2C_DEV_NAME_FORMAT, - acpi_dev_name(int3472->sensor)); - if (!int3472->sensor_name) { - ret = -ENOMEM; - goto err_put_sensor; - } - - /* - * Initialising this list means we can call gpiod_remove_lookup_table() - * in failure paths without issue. - */ - INIT_LIST_HEAD(&int3472->gpios.list); - - ret = skl_int3472_parse_crs(int3472); - if (ret) { - skl_int3472_discrete_remove(pdev); - return ret; - } - - return 0; - -err_put_sensor: - acpi_dev_put(int3472->sensor); - - return ret; -} - -int skl_int3472_discrete_remove(struct platform_device *pdev) -{ - struct int3472_discrete_device *int3472 = platform_get_drvdata(pdev); - - gpiod_remove_lookup_table(&int3472->gpios); - - if (int3472->clock.ena_gpio) - skl_int3472_unregister_clock(int3472); - - gpiod_put(int3472->clock.ena_gpio); - gpiod_put(int3472->clock.led_gpio); - - skl_int3472_unregister_regulator(int3472); - - return 0; -} diff --git a/drivers/platform/x86/intel-int3472/intel_skl_int3472_tps68470.c b/drivers/platform/x86/intel-int3472/intel_skl_int3472_tps68470.c deleted file mode 100644 index c05b4cf502fe..000000000000 --- a/drivers/platform/x86/intel-int3472/intel_skl_int3472_tps68470.c +++ /dev/null @@ -1,137 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* Author: Dan Scally */ - -#include -#include -#include -#include -#include - -#include "intel_skl_int3472_common.h" - -#define DESIGNED_FOR_CHROMEOS 1 -#define DESIGNED_FOR_WINDOWS 2 - -static const struct mfd_cell tps68470_cros[] = { - { .name = "tps68470-gpio" }, - { .name = "tps68470_pmic_opregion" }, -}; - -static const struct mfd_cell tps68470_win[] = { - { .name = "tps68470-gpio" }, - { .name = "tps68470-clk" }, - { .name = "tps68470-regulator" }, -}; - -static const struct regmap_config tps68470_regmap_config = { - .reg_bits = 8, - .val_bits = 8, - .max_register = TPS68470_REG_MAX, -}; - -static int tps68470_chip_init(struct device *dev, struct regmap *regmap) -{ - unsigned int version; - int ret; - - /* Force software reset */ - ret = regmap_write(regmap, TPS68470_REG_RESET, TPS68470_REG_RESET_MASK); - if (ret) - return ret; - - ret = regmap_read(regmap, TPS68470_REG_REVID, &version); - if (ret) { - dev_err(dev, "Failed to read revision register: %d\n", ret); - return ret; - } - - dev_info(dev, "TPS68470 REVID: 0x%02x\n", version); - - return 0; -} - -/** skl_int3472_tps68470_calc_type: Check what platform a device is designed for - * @adev: A pointer to a &struct acpi_device - * - * Check CLDB buffer against the PMIC's adev. If present, then we check - * the value of control_logic_type field and follow one of the - * following scenarios: - * - * 1. No CLDB - likely ACPI tables designed for ChromeOS. We - * create platform devices for the GPIOs and OpRegion drivers. - * - * 2. CLDB, with control_logic_type = 2 - probably ACPI tables - * made for Windows 2-in-1 platforms. Register pdevs for GPIO, - * Clock and Regulator drivers to bind to. - * - * 3. Any other value in control_logic_type, we should never have - * gotten to this point; fail probe and return. - * - * Return: - * * 1 Device intended for ChromeOS - * * 2 Device intended for Windows - * * -EINVAL Where @adev has an object named CLDB but it does not conform to - * our expectations - */ -static int skl_int3472_tps68470_calc_type(struct acpi_device *adev) -{ - struct int3472_cldb cldb = { 0 }; - int ret; - - /* - * A CLDB buffer that exists, but which does not match our expectations - * should trigger an error so we don't blindly continue. - */ - ret = skl_int3472_fill_cldb(adev, &cldb); - if (ret && ret != -ENODEV) - return ret; - - if (ret) - return DESIGNED_FOR_CHROMEOS; - - if (cldb.control_logic_type != 2) - return -EINVAL; - - return DESIGNED_FOR_WINDOWS; -} - -int skl_int3472_tps68470_probe(struct i2c_client *client) -{ - struct acpi_device *adev = ACPI_COMPANION(&client->dev); - struct regmap *regmap; - int device_type; - int ret; - - regmap = devm_regmap_init_i2c(client, &tps68470_regmap_config); - if (IS_ERR(regmap)) { - dev_err(&client->dev, "Failed to create regmap: %ld\n", PTR_ERR(regmap)); - return PTR_ERR(regmap); - } - - i2c_set_clientdata(client, regmap); - - ret = tps68470_chip_init(&client->dev, regmap); - if (ret < 0) { - dev_err(&client->dev, "TPS68470 init error %d\n", ret); - return ret; - } - - device_type = skl_int3472_tps68470_calc_type(adev); - switch (device_type) { - case DESIGNED_FOR_WINDOWS: - ret = devm_mfd_add_devices(&client->dev, PLATFORM_DEVID_NONE, - tps68470_win, ARRAY_SIZE(tps68470_win), - NULL, 0, NULL); - break; - case DESIGNED_FOR_CHROMEOS: - ret = devm_mfd_add_devices(&client->dev, PLATFORM_DEVID_NONE, - tps68470_cros, ARRAY_SIZE(tps68470_cros), - NULL, 0, NULL); - break; - default: - dev_err(&client->dev, "Failed to add MFD devices\n"); - return device_type; - } - - return ret; -} diff --git a/drivers/platform/x86/intel/Kconfig b/drivers/platform/x86/intel/Kconfig new file mode 100644 index 000000000000..33f2dab03d3d --- /dev/null +++ b/drivers/platform/x86/intel/Kconfig @@ -0,0 +1,21 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Intel x86 Platform Specific Drivers +# + +menuconfig X86_PLATFORM_DRIVERS_INTEL + bool "Intel x86 Platform Specific Device Drivers" + default y + help + Say Y here to get to see options for device drivers for + various Intel x86 platforms, including vendor-specific + drivers. This option alone does not add any kernel code. + + If you say N, all options in this submenu will be skipped + and disabled. + +if X86_PLATFORM_DRIVERS_INTEL + +source "drivers/platform/x86/intel/int3472/Kconfig" + +endif # X86_PLATFORM_DRIVERS_INTEL diff --git a/drivers/platform/x86/intel/Makefile b/drivers/platform/x86/intel/Makefile new file mode 100644 index 000000000000..3ac795d810f1 --- /dev/null +++ b/drivers/platform/x86/intel/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for drivers/platform/x86/intel +# Intel x86 Platform-Specific Drivers +# + +obj-$(CONFIG_INTEL_SKL_INT3472) += int3472/ diff --git a/drivers/platform/x86/intel/int3472/Kconfig b/drivers/platform/x86/intel/int3472/Kconfig new file mode 100644 index 000000000000..62e5d4cf9ee5 --- /dev/null +++ b/drivers/platform/x86/intel/int3472/Kconfig @@ -0,0 +1,30 @@ +config INTEL_SKL_INT3472 + tristate "Intel SkyLake ACPI INT3472 Driver" + depends on ACPI + depends on COMMON_CLK + depends on I2C + depends on GPIOLIB + depends on REGULATOR + select MFD_CORE + select REGMAP_I2C + help + This driver adds power controller support for the Intel SkyCam + devices found on the Intel SkyLake platforms. + + The INT3472 is a camera power controller, a logical device found on + Intel Skylake-based systems that can map to different hardware + devices depending on the platform. On machines designed for Chrome OS + it maps to a TPS68470 camera PMIC. On machines designed for Windows, + it maps to either a TP68470 camera PMIC, a uP6641Q sensor PMIC, or a + set of discrete GPIOs and power gates. + + If your device was designed for Chrome OS, this driver will provide + an ACPI OpRegion, which must be available before any of the devices + using it are probed. For this reason, you should select Y if your + device was designed for ChromeOS. For the same reason the + I2C_DESIGNWARE_PLATFORM option must be set to Y too. + + Say Y or M here if you have a SkyLake device designed for use + with Windows or ChromeOS. Say N here if you are not sure. + + The module will be named "intel-skl-int3472". diff --git a/drivers/platform/x86/intel/int3472/Makefile b/drivers/platform/x86/intel/int3472/Makefile new file mode 100644 index 000000000000..48bd97f0a04e --- /dev/null +++ b/drivers/platform/x86/intel/int3472/Makefile @@ -0,0 +1,5 @@ +obj-$(CONFIG_INTEL_SKL_INT3472) += intel_skl_int3472.o +intel_skl_int3472-objs := intel_skl_int3472_common.o \ + intel_skl_int3472_discrete.o \ + intel_skl_int3472_tps68470.o \ + intel_skl_int3472_clk_and_regulator.o diff --git a/drivers/platform/x86/intel/int3472/intel_skl_int3472_clk_and_regulator.c b/drivers/platform/x86/intel/int3472/intel_skl_int3472_clk_and_regulator.c new file mode 100644 index 000000000000..1700e7557a82 --- /dev/null +++ b/drivers/platform/x86/intel/int3472/intel_skl_int3472_clk_and_regulator.c @@ -0,0 +1,207 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Author: Dan Scally */ + +#include +#include +#include +#include +#include +#include +#include + +#include "intel_skl_int3472_common.h" + +/* + * The regulators have to have .ops to be valid, but the only ops we actually + * support are .enable and .disable which are handled via .ena_gpiod. Pass an + * empty struct to clear the check without lying about capabilities. + */ +static const struct regulator_ops int3472_gpio_regulator_ops; + +static int skl_int3472_clk_prepare(struct clk_hw *hw) +{ + struct int3472_gpio_clock *clk = to_int3472_clk(hw); + + gpiod_set_value_cansleep(clk->ena_gpio, 1); + gpiod_set_value_cansleep(clk->led_gpio, 1); + + return 0; +} + +static void skl_int3472_clk_unprepare(struct clk_hw *hw) +{ + struct int3472_gpio_clock *clk = to_int3472_clk(hw); + + gpiod_set_value_cansleep(clk->ena_gpio, 0); + gpiod_set_value_cansleep(clk->led_gpio, 0); +} + +static int skl_int3472_clk_enable(struct clk_hw *hw) +{ + /* + * We're just turning a GPIO on to enable the clock, which operation + * has the potential to sleep. Given .enable() cannot sleep, but + * .prepare() can, we toggle the GPIO in .prepare() instead. Thus, + * nothing to do here. + */ + return 0; +} + +static void skl_int3472_clk_disable(struct clk_hw *hw) +{ + /* Likewise, nothing to do here... */ +} + +static unsigned int skl_int3472_get_clk_frequency(struct int3472_discrete_device *int3472) +{ + union acpi_object *obj; + unsigned int freq; + + obj = skl_int3472_get_acpi_buffer(int3472->sensor, "SSDB"); + if (IS_ERR(obj)) + return 0; /* report rate as 0 on error */ + + if (obj->buffer.length < CIO2_SENSOR_SSDB_MCLKSPEED_OFFSET + sizeof(u32)) { + dev_err(int3472->dev, "The buffer is too small\n"); + kfree(obj); + return 0; + } + + freq = *(u32 *)(obj->buffer.pointer + CIO2_SENSOR_SSDB_MCLKSPEED_OFFSET); + + kfree(obj); + return freq; +} + +static unsigned long skl_int3472_clk_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct int3472_gpio_clock *clk = to_int3472_clk(hw); + + return clk->frequency; +} + +static const struct clk_ops skl_int3472_clock_ops = { + .prepare = skl_int3472_clk_prepare, + .unprepare = skl_int3472_clk_unprepare, + .enable = skl_int3472_clk_enable, + .disable = skl_int3472_clk_disable, + .recalc_rate = skl_int3472_clk_recalc_rate, +}; + +int skl_int3472_register_clock(struct int3472_discrete_device *int3472) +{ + struct clk_init_data init = { + .ops = &skl_int3472_clock_ops, + .flags = CLK_GET_RATE_NOCACHE, + }; + int ret; + + init.name = kasprintf(GFP_KERNEL, "%s-clk", + acpi_dev_name(int3472->adev)); + if (!init.name) + return -ENOMEM; + + int3472->clock.frequency = skl_int3472_get_clk_frequency(int3472); + + int3472->clock.clk_hw.init = &init; + int3472->clock.clk = clk_register(&int3472->adev->dev, + &int3472->clock.clk_hw); + if (IS_ERR(int3472->clock.clk)) { + ret = PTR_ERR(int3472->clock.clk); + goto out_free_init_name; + } + + int3472->clock.cl = clkdev_create(int3472->clock.clk, NULL, + int3472->sensor_name); + if (!int3472->clock.cl) { + ret = -ENOMEM; + goto err_unregister_clk; + } + + kfree(init.name); + return 0; + +err_unregister_clk: + clk_unregister(int3472->clock.clk); +out_free_init_name: + kfree(init.name); + + return ret; +} + +void skl_int3472_unregister_clock(struct int3472_discrete_device *int3472) +{ + clkdev_drop(int3472->clock.cl); + clk_unregister(int3472->clock.clk); +} + +int skl_int3472_register_regulator(struct int3472_discrete_device *int3472, + struct acpi_resource_gpio *agpio) +{ + const struct int3472_sensor_config *sensor_config; + char *path = agpio->resource_source.string_ptr; + struct regulator_consumer_supply supply_map; + struct regulator_init_data init_data = { }; + struct regulator_config cfg = { }; + int ret; + + sensor_config = int3472->sensor_config; + if (IS_ERR(sensor_config)) { + dev_err(int3472->dev, "No sensor module config\n"); + return PTR_ERR(sensor_config); + } + + if (!sensor_config->supply_map.supply) { + dev_err(int3472->dev, "No supply name defined\n"); + return -ENODEV; + } + + init_data.constraints.valid_ops_mask = REGULATOR_CHANGE_STATUS; + init_data.num_consumer_supplies = 1; + supply_map = sensor_config->supply_map; + supply_map.dev_name = int3472->sensor_name; + init_data.consumer_supplies = &supply_map; + + snprintf(int3472->regulator.regulator_name, + sizeof(int3472->regulator.regulator_name), "%s-regulator", + acpi_dev_name(int3472->adev)); + snprintf(int3472->regulator.supply_name, + GPIO_REGULATOR_SUPPLY_NAME_LENGTH, "supply-0"); + + int3472->regulator.rdesc = INT3472_REGULATOR( + int3472->regulator.regulator_name, + int3472->regulator.supply_name, + &int3472_gpio_regulator_ops); + + int3472->regulator.gpio = acpi_get_and_request_gpiod(path, agpio->pin_table[0], + "int3472,regulator"); + if (IS_ERR(int3472->regulator.gpio)) { + dev_err(int3472->dev, "Failed to get regulator GPIO line\n"); + return PTR_ERR(int3472->regulator.gpio); + } + + cfg.dev = &int3472->adev->dev; + cfg.init_data = &init_data; + cfg.ena_gpiod = int3472->regulator.gpio; + + int3472->regulator.rdev = regulator_register(&int3472->regulator.rdesc, + &cfg); + if (IS_ERR(int3472->regulator.rdev)) { + ret = PTR_ERR(int3472->regulator.rdev); + goto err_free_gpio; + } + + return 0; + +err_free_gpio: + gpiod_put(int3472->regulator.gpio); + + return ret; +} + +void skl_int3472_unregister_regulator(struct int3472_discrete_device *int3472) +{ + regulator_unregister(int3472->regulator.rdev); + gpiod_put(int3472->regulator.gpio); +} diff --git a/drivers/platform/x86/intel/int3472/intel_skl_int3472_common.c b/drivers/platform/x86/intel/int3472/intel_skl_int3472_common.c new file mode 100644 index 000000000000..497e74fba75f --- /dev/null +++ b/drivers/platform/x86/intel/int3472/intel_skl_int3472_common.c @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Author: Dan Scally */ + +#include +#include +#include +#include + +#include "intel_skl_int3472_common.h" + +union acpi_object *skl_int3472_get_acpi_buffer(struct acpi_device *adev, char *id) +{ + struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; + acpi_handle handle = adev->handle; + union acpi_object *obj; + acpi_status status; + + status = acpi_evaluate_object(handle, id, NULL, &buffer); + if (ACPI_FAILURE(status)) + return ERR_PTR(-ENODEV); + + obj = buffer.pointer; + if (!obj) + return ERR_PTR(-ENODEV); + + if (obj->type != ACPI_TYPE_BUFFER) { + acpi_handle_err(handle, "%s object is not an ACPI buffer\n", id); + kfree(obj); + return ERR_PTR(-EINVAL); + } + + return obj; +} + +int skl_int3472_fill_cldb(struct acpi_device *adev, struct int3472_cldb *cldb) +{ + union acpi_object *obj; + int ret; + + obj = skl_int3472_get_acpi_buffer(adev, "CLDB"); + if (IS_ERR(obj)) + return PTR_ERR(obj); + + if (obj->buffer.length > sizeof(*cldb)) { + acpi_handle_err(adev->handle, "The CLDB buffer is too large\n"); + ret = -EINVAL; + goto out_free_obj; + } + + memcpy(cldb, obj->buffer.pointer, obj->buffer.length); + ret = 0; + +out_free_obj: + kfree(obj); + return ret; +} + +static const struct acpi_device_id int3472_device_id[] = { + { "INT3472", 0 }, + { } +}; +MODULE_DEVICE_TABLE(acpi, int3472_device_id); + +static struct platform_driver int3472_discrete = { + .driver = { + .name = "int3472-discrete", + .acpi_match_table = int3472_device_id, + }, + .probe = skl_int3472_discrete_probe, + .remove = skl_int3472_discrete_remove, +}; + +static struct i2c_driver int3472_tps68470 = { + .driver = { + .name = "int3472-tps68470", + .acpi_match_table = int3472_device_id, + }, + .probe_new = skl_int3472_tps68470_probe, +}; + +static int skl_int3472_init(void) +{ + int ret; + + ret = platform_driver_register(&int3472_discrete); + if (ret) + return ret; + + ret = i2c_register_driver(THIS_MODULE, &int3472_tps68470); + if (ret) + platform_driver_unregister(&int3472_discrete); + + return ret; +} +module_init(skl_int3472_init); + +static void skl_int3472_exit(void) +{ + platform_driver_unregister(&int3472_discrete); + i2c_del_driver(&int3472_tps68470); +} +module_exit(skl_int3472_exit); + +MODULE_DESCRIPTION("Intel SkyLake INT3472 ACPI Device Driver"); +MODULE_AUTHOR("Daniel Scally "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/platform/x86/intel/int3472/intel_skl_int3472_common.h b/drivers/platform/x86/intel/int3472/intel_skl_int3472_common.h new file mode 100644 index 000000000000..714fde73b524 --- /dev/null +++ b/drivers/platform/x86/intel/int3472/intel_skl_int3472_common.h @@ -0,0 +1,122 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Author: Dan Scally */ + +#ifndef _INTEL_SKL_INT3472_H +#define _INTEL_SKL_INT3472_H + +#include +#include +#include +#include +#include + +/* FIXME drop this once the I2C_DEV_NAME_FORMAT macro has been added to include/linux/i2c.h */ +#ifndef I2C_DEV_NAME_FORMAT +#define I2C_DEV_NAME_FORMAT "i2c-%s" +#endif + +/* PMIC GPIO Types */ +#define INT3472_GPIO_TYPE_RESET 0x00 +#define INT3472_GPIO_TYPE_POWERDOWN 0x01 +#define INT3472_GPIO_TYPE_POWER_ENABLE 0x0b +#define INT3472_GPIO_TYPE_CLK_ENABLE 0x0c +#define INT3472_GPIO_TYPE_PRIVACY_LED 0x0d + +#define INT3472_PDEV_MAX_NAME_LEN 23 +#define INT3472_MAX_SENSOR_GPIOS 3 + +#define GPIO_REGULATOR_NAME_LENGTH 21 +#define GPIO_REGULATOR_SUPPLY_NAME_LENGTH 9 + +#define CIO2_SENSOR_SSDB_MCLKSPEED_OFFSET 86 + +#define INT3472_REGULATOR(_name, _supply, _ops) \ + (const struct regulator_desc) { \ + .name = _name, \ + .supply_name = _supply, \ + .type = REGULATOR_VOLTAGE, \ + .ops = _ops, \ + .owner = THIS_MODULE, \ + } + +#define to_int3472_clk(hw) \ + container_of(hw, struct int3472_gpio_clock, clk_hw) + +#define to_int3472_device(clk) \ + container_of(clk, struct int3472_discrete_device, clock) + +struct acpi_device; +struct i2c_client; +struct platform_device; + +struct int3472_cldb { + u8 version; + /* + * control logic type + * 0: UNKNOWN + * 1: DISCRETE(CRD-D) + * 2: PMIC TPS68470 + * 3: PMIC uP6641 + */ + u8 control_logic_type; + u8 control_logic_id; + u8 sensor_card_sku; + u8 reserved[28]; +}; + +struct int3472_gpio_function_remap { + const char *documented; + const char *actual; +}; + +struct int3472_sensor_config { + const char *sensor_module_name; + struct regulator_consumer_supply supply_map; + const struct int3472_gpio_function_remap *function_maps; +}; + +struct int3472_discrete_device { + struct acpi_device *adev; + struct device *dev; + struct acpi_device *sensor; + const char *sensor_name; + + const struct int3472_sensor_config *sensor_config; + + struct int3472_gpio_regulator { + char regulator_name[GPIO_REGULATOR_NAME_LENGTH]; + char supply_name[GPIO_REGULATOR_SUPPLY_NAME_LENGTH]; + struct gpio_desc *gpio; + struct regulator_dev *rdev; + struct regulator_desc rdesc; + } regulator; + + struct int3472_gpio_clock { + struct clk *clk; + struct clk_hw clk_hw; + struct clk_lookup *cl; + struct gpio_desc *ena_gpio; + struct gpio_desc *led_gpio; + u32 frequency; + } clock; + + unsigned int ngpios; /* how many GPIOs have we seen */ + unsigned int n_sensor_gpios; /* how many have we mapped to sensor */ + struct gpiod_lookup_table gpios; +}; + +int skl_int3472_discrete_probe(struct platform_device *pdev); +int skl_int3472_discrete_remove(struct platform_device *pdev); +int skl_int3472_tps68470_probe(struct i2c_client *client); +union acpi_object *skl_int3472_get_acpi_buffer(struct acpi_device *adev, + char *id); +int skl_int3472_fill_cldb(struct acpi_device *adev, struct int3472_cldb *cldb); + +int skl_int3472_register_clock(struct int3472_discrete_device *int3472); +void skl_int3472_unregister_clock(struct int3472_discrete_device *int3472); + +int skl_int3472_register_regulator(struct int3472_discrete_device *int3472, + struct acpi_resource_gpio *agpio); +void skl_int3472_unregister_regulator(struct int3472_discrete_device *int3472); + +#endif diff --git a/drivers/platform/x86/intel/int3472/intel_skl_int3472_discrete.c b/drivers/platform/x86/intel/int3472/intel_skl_int3472_discrete.c new file mode 100644 index 000000000000..17c6fe830765 --- /dev/null +++ b/drivers/platform/x86/intel/int3472/intel_skl_int3472_discrete.c @@ -0,0 +1,413 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Author: Dan Scally */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "intel_skl_int3472_common.h" + +/* + * 79234640-9e10-4fea-a5c1-b5aa8b19756f + * This _DSM GUID returns information about the GPIO lines mapped to a + * discrete INT3472 device. Function number 1 returns a count of the GPIO + * lines that are mapped. Subsequent functions return 32 bit ints encoding + * information about the GPIO line, including its purpose. + */ +static const guid_t int3472_gpio_guid = + GUID_INIT(0x79234640, 0x9e10, 0x4fea, + 0xa5, 0xc1, 0xb5, 0xaa, 0x8b, 0x19, 0x75, 0x6f); + +/* + * 822ace8f-2814-4174-a56b-5f029fe079ee + * This _DSM GUID returns a string from the sensor device, which acts as a + * module identifier. + */ +static const guid_t cio2_sensor_module_guid = + GUID_INIT(0x822ace8f, 0x2814, 0x4174, + 0xa5, 0x6b, 0x5f, 0x02, 0x9f, 0xe0, 0x79, 0xee); + +/* + * Here follows platform specific mapping information that we can pass to + * the functions mapping resources to the sensors. Where the sensors have + * a power enable pin defined in DSDT we need to provide a supply name so + * the sensor drivers can find the regulator. The device name will be derived + * from the sensor's ACPI device within the code. Optionally, we can provide a + * NULL terminated array of function name mappings to deal with any platform + * specific deviations from the documented behaviour of GPIOs. + * + * Map a GPIO function name to NULL to prevent the driver from mapping that + * GPIO at all. + */ + +static const struct int3472_gpio_function_remap ov2680_gpio_function_remaps[] = { + { "reset", NULL }, + { "powerdown", "reset" }, + { } +}; + +static const struct int3472_sensor_config int3472_sensor_configs[] = { + /* Lenovo Miix 510-12ISK - OV2680, Front */ + { "GNDF140809R", { 0 }, ov2680_gpio_function_remaps }, + /* Lenovo Miix 510-12ISK - OV5648, Rear */ + { "GEFF150023R", REGULATOR_SUPPLY("avdd", NULL), NULL }, + /* Surface Go 1&2 - OV5693, Front */ + { "YHCU", REGULATOR_SUPPLY("avdd", NULL), NULL }, +}; + +static const struct int3472_sensor_config * +skl_int3472_get_sensor_module_config(struct int3472_discrete_device *int3472) +{ + union acpi_object *obj; + unsigned int i; + + obj = acpi_evaluate_dsm_typed(int3472->sensor->handle, + &cio2_sensor_module_guid, 0x00, + 0x01, NULL, ACPI_TYPE_STRING); + + if (!obj) { + dev_err(int3472->dev, + "Failed to get sensor module string from _DSM\n"); + return ERR_PTR(-ENODEV); + } + + if (obj->string.type != ACPI_TYPE_STRING) { + dev_err(int3472->dev, + "Sensor _DSM returned a non-string value\n"); + + ACPI_FREE(obj); + return ERR_PTR(-EINVAL); + } + + for (i = 0; i < ARRAY_SIZE(int3472_sensor_configs); i++) { + if (!strcmp(int3472_sensor_configs[i].sensor_module_name, + obj->string.pointer)) + break; + } + + ACPI_FREE(obj); + + if (i >= ARRAY_SIZE(int3472_sensor_configs)) + return ERR_PTR(-EINVAL); + + return &int3472_sensor_configs[i]; +} + +static int skl_int3472_map_gpio_to_sensor(struct int3472_discrete_device *int3472, + struct acpi_resource_gpio *agpio, + const char *func, u32 polarity) +{ + const struct int3472_sensor_config *sensor_config; + char *path = agpio->resource_source.string_ptr; + struct gpiod_lookup *table_entry; + struct acpi_device *adev; + acpi_handle handle; + acpi_status status; + int ret; + + if (int3472->n_sensor_gpios >= INT3472_MAX_SENSOR_GPIOS) { + dev_warn(int3472->dev, "Too many GPIOs mapped\n"); + return -EINVAL; + } + + sensor_config = int3472->sensor_config; + if (!IS_ERR(sensor_config) && sensor_config->function_maps) { + const struct int3472_gpio_function_remap *remap; + + for (remap = sensor_config->function_maps; remap->documented; remap++) { + if (!strcmp(func, remap->documented)) { + func = remap->actual; + break; + } + } + } + + /* Functions mapped to NULL should not be mapped to the sensor */ + if (!func) + return 0; + + status = acpi_get_handle(NULL, path, &handle); + if (ACPI_FAILURE(status)) + return -EINVAL; + + ret = acpi_bus_get_device(handle, &adev); + if (ret) + return -ENODEV; + + table_entry = &int3472->gpios.table[int3472->n_sensor_gpios]; + table_entry->key = acpi_dev_name(adev); + table_entry->chip_hwnum = agpio->pin_table[0]; + table_entry->con_id = func; + table_entry->idx = 0; + table_entry->flags = polarity; + + int3472->n_sensor_gpios++; + + return 0; +} + +static int skl_int3472_map_gpio_to_clk(struct int3472_discrete_device *int3472, + struct acpi_resource_gpio *agpio, u8 type) +{ + char *path = agpio->resource_source.string_ptr; + u16 pin = agpio->pin_table[0]; + struct gpio_desc *gpio; + + switch (type) { + case INT3472_GPIO_TYPE_CLK_ENABLE: + gpio = acpi_get_and_request_gpiod(path, pin, "int3472,clk-enable"); + if (IS_ERR(gpio)) + return (PTR_ERR(gpio)); + + int3472->clock.ena_gpio = gpio; + break; + case INT3472_GPIO_TYPE_PRIVACY_LED: + gpio = acpi_get_and_request_gpiod(path, pin, "int3472,privacy-led"); + if (IS_ERR(gpio)) + return (PTR_ERR(gpio)); + + int3472->clock.led_gpio = gpio; + break; + default: + dev_err(int3472->dev, "Invalid GPIO type 0x%02x for clock\n", type); + break; + } + + return 0; +} + +/** + * skl_int3472_handle_gpio_resources: Map PMIC resources to consuming sensor + * @ares: A pointer to a &struct acpi_resource + * @data: A pointer to a &struct int3472_discrete_device + * + * This function handles GPIO resources that are against an INT3472 + * ACPI device, by checking the value of the corresponding _DSM entry. + * This will return a 32bit int, where the lowest byte represents the + * function of the GPIO pin: + * + * 0x00 Reset + * 0x01 Power down + * 0x0b Power enable + * 0x0c Clock enable + * 0x0d Privacy LED + * + * There are some known platform specific quirks where that does not quite + * hold up; for example where a pin with type 0x01 (Power down) is mapped to + * a sensor pin that performs a reset function or entries in _CRS and _DSM that + * do not actually correspond to a physical connection. These will be handled + * by the mapping sub-functions. + * + * GPIOs will either be mapped directly to the sensor device or else used + * to create clocks and regulators via the usual frameworks. + * + * Return: + * * 1 - To continue the loop + * * 0 - When all resources found are handled properly. + * * -EINVAL - If the resource is not a GPIO IO resource + * * -ENODEV - If the resource has no corresponding _DSM entry + * * -Other - Errors propagated from one of the sub-functions. + */ +static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares, + void *data) +{ + struct int3472_discrete_device *int3472 = data; + struct acpi_resource_gpio *agpio; + union acpi_object *obj; + const char *err_msg; + int ret; + u8 type; + + if (!acpi_gpio_get_io_resource(ares, &agpio)) + return 1; + + /* + * ngpios + 2 because the index of this _DSM function is 1-based and + * the first function is just a count. + */ + obj = acpi_evaluate_dsm_typed(int3472->adev->handle, + &int3472_gpio_guid, 0x00, + int3472->ngpios + 2, + NULL, ACPI_TYPE_INTEGER); + + if (!obj) { + dev_warn(int3472->dev, "No _DSM entry for GPIO pin %u\n", + agpio->pin_table[0]); + return 1; + } + + type = obj->integer.value & 0xff; + + switch (type) { + case INT3472_GPIO_TYPE_RESET: + ret = skl_int3472_map_gpio_to_sensor(int3472, agpio, "reset", + GPIO_ACTIVE_LOW); + if (ret) + err_msg = "Failed to map reset pin to sensor\n"; + + break; + case INT3472_GPIO_TYPE_POWERDOWN: + ret = skl_int3472_map_gpio_to_sensor(int3472, agpio, "powerdown", + GPIO_ACTIVE_LOW); + if (ret) + err_msg = "Failed to map powerdown pin to sensor\n"; + + break; + case INT3472_GPIO_TYPE_CLK_ENABLE: + case INT3472_GPIO_TYPE_PRIVACY_LED: + ret = skl_int3472_map_gpio_to_clk(int3472, agpio, type); + if (ret) + err_msg = "Failed to map GPIO to clock\n"; + + break; + case INT3472_GPIO_TYPE_POWER_ENABLE: + ret = skl_int3472_register_regulator(int3472, agpio); + if (ret) + err_msg = "Failed to map regulator to sensor\n"; + + break; + default: + dev_warn(int3472->dev, + "GPIO type 0x%02x unknown; the sensor may not work\n", + type); + ret = 1; + break; + } + + int3472->ngpios++; + ACPI_FREE(obj); + + if (ret) + return dev_err_probe(int3472->dev, ret, err_msg); + + return 0; +} + +static int skl_int3472_parse_crs(struct int3472_discrete_device *int3472) +{ + LIST_HEAD(resource_list); + int ret; + + /* + * No error check, because not having a sensor config is not necessarily + * a failure mode. + */ + int3472->sensor_config = skl_int3472_get_sensor_module_config(int3472); + + ret = acpi_dev_get_resources(int3472->adev, &resource_list, + skl_int3472_handle_gpio_resources, + int3472); + if (ret < 0) + return ret; + + acpi_dev_free_resource_list(&resource_list); + + /* + * If we find no clock enable GPIO pin then the privacy LED won't work. + * We've never seen that situation, but it's possible. Warn the user so + * it's clear what's happened. + */ + if (int3472->clock.ena_gpio) { + ret = skl_int3472_register_clock(int3472); + if (ret) + return ret; + } else { + if (int3472->clock.led_gpio) + dev_warn(int3472->dev, + "No clk GPIO. The privacy LED won't work\n"); + } + + int3472->gpios.dev_id = int3472->sensor_name; + gpiod_add_lookup_table(&int3472->gpios); + + return 0; +} + +int skl_int3472_discrete_probe(struct platform_device *pdev) +{ + struct acpi_device *adev = ACPI_COMPANION(&pdev->dev); + struct int3472_discrete_device *int3472; + struct int3472_cldb cldb; + int ret; + + ret = skl_int3472_fill_cldb(adev, &cldb); + if (ret) { + dev_err(&pdev->dev, "Couldn't fill CLDB structure\n"); + return ret; + } + + if (cldb.control_logic_type != 1) { + dev_err(&pdev->dev, "Unsupported control logic type %u\n", + cldb.control_logic_type); + return -EINVAL; + } + + /* Max num GPIOs we've seen plus a terminator */ + int3472 = devm_kzalloc(&pdev->dev, struct_size(int3472, gpios.table, + INT3472_MAX_SENSOR_GPIOS + 1), GFP_KERNEL); + if (!int3472) + return -ENOMEM; + + int3472->adev = adev; + int3472->dev = &pdev->dev; + platform_set_drvdata(pdev, int3472); + + int3472->sensor = acpi_dev_get_first_consumer_dev(adev); + if (!int3472->sensor) { + dev_err(&pdev->dev, "INT3472 seems to have no dependents.\n"); + return -ENODEV; + } + + int3472->sensor_name = devm_kasprintf(int3472->dev, GFP_KERNEL, + I2C_DEV_NAME_FORMAT, + acpi_dev_name(int3472->sensor)); + if (!int3472->sensor_name) { + ret = -ENOMEM; + goto err_put_sensor; + } + + /* + * Initialising this list means we can call gpiod_remove_lookup_table() + * in failure paths without issue. + */ + INIT_LIST_HEAD(&int3472->gpios.list); + + ret = skl_int3472_parse_crs(int3472); + if (ret) { + skl_int3472_discrete_remove(pdev); + return ret; + } + + return 0; + +err_put_sensor: + acpi_dev_put(int3472->sensor); + + return ret; +} + +int skl_int3472_discrete_remove(struct platform_device *pdev) +{ + struct int3472_discrete_device *int3472 = platform_get_drvdata(pdev); + + gpiod_remove_lookup_table(&int3472->gpios); + + if (int3472->clock.ena_gpio) + skl_int3472_unregister_clock(int3472); + + gpiod_put(int3472->clock.ena_gpio); + gpiod_put(int3472->clock.led_gpio); + + skl_int3472_unregister_regulator(int3472); + + return 0; +} diff --git a/drivers/platform/x86/intel/int3472/intel_skl_int3472_tps68470.c b/drivers/platform/x86/intel/int3472/intel_skl_int3472_tps68470.c new file mode 100644 index 000000000000..c05b4cf502fe --- /dev/null +++ b/drivers/platform/x86/intel/int3472/intel_skl_int3472_tps68470.c @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Author: Dan Scally */ + +#include +#include +#include +#include +#include + +#include "intel_skl_int3472_common.h" + +#define DESIGNED_FOR_CHROMEOS 1 +#define DESIGNED_FOR_WINDOWS 2 + +static const struct mfd_cell tps68470_cros[] = { + { .name = "tps68470-gpio" }, + { .name = "tps68470_pmic_opregion" }, +}; + +static const struct mfd_cell tps68470_win[] = { + { .name = "tps68470-gpio" }, + { .name = "tps68470-clk" }, + { .name = "tps68470-regulator" }, +}; + +static const struct regmap_config tps68470_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = TPS68470_REG_MAX, +}; + +static int tps68470_chip_init(struct device *dev, struct regmap *regmap) +{ + unsigned int version; + int ret; + + /* Force software reset */ + ret = regmap_write(regmap, TPS68470_REG_RESET, TPS68470_REG_RESET_MASK); + if (ret) + return ret; + + ret = regmap_read(regmap, TPS68470_REG_REVID, &version); + if (ret) { + dev_err(dev, "Failed to read revision register: %d\n", ret); + return ret; + } + + dev_info(dev, "TPS68470 REVID: 0x%02x\n", version); + + return 0; +} + +/** skl_int3472_tps68470_calc_type: Check what platform a device is designed for + * @adev: A pointer to a &struct acpi_device + * + * Check CLDB buffer against the PMIC's adev. If present, then we check + * the value of control_logic_type field and follow one of the + * following scenarios: + * + * 1. No CLDB - likely ACPI tables designed for ChromeOS. We + * create platform devices for the GPIOs and OpRegion drivers. + * + * 2. CLDB, with control_logic_type = 2 - probably ACPI tables + * made for Windows 2-in-1 platforms. Register pdevs for GPIO, + * Clock and Regulator drivers to bind to. + * + * 3. Any other value in control_logic_type, we should never have + * gotten to this point; fail probe and return. + * + * Return: + * * 1 Device intended for ChromeOS + * * 2 Device intended for Windows + * * -EINVAL Where @adev has an object named CLDB but it does not conform to + * our expectations + */ +static int skl_int3472_tps68470_calc_type(struct acpi_device *adev) +{ + struct int3472_cldb cldb = { 0 }; + int ret; + + /* + * A CLDB buffer that exists, but which does not match our expectations + * should trigger an error so we don't blindly continue. + */ + ret = skl_int3472_fill_cldb(adev, &cldb); + if (ret && ret != -ENODEV) + return ret; + + if (ret) + return DESIGNED_FOR_CHROMEOS; + + if (cldb.control_logic_type != 2) + return -EINVAL; + + return DESIGNED_FOR_WINDOWS; +} + +int skl_int3472_tps68470_probe(struct i2c_client *client) +{ + struct acpi_device *adev = ACPI_COMPANION(&client->dev); + struct regmap *regmap; + int device_type; + int ret; + + regmap = devm_regmap_init_i2c(client, &tps68470_regmap_config); + if (IS_ERR(regmap)) { + dev_err(&client->dev, "Failed to create regmap: %ld\n", PTR_ERR(regmap)); + return PTR_ERR(regmap); + } + + i2c_set_clientdata(client, regmap); + + ret = tps68470_chip_init(&client->dev, regmap); + if (ret < 0) { + dev_err(&client->dev, "TPS68470 init error %d\n", ret); + return ret; + } + + device_type = skl_int3472_tps68470_calc_type(adev); + switch (device_type) { + case DESIGNED_FOR_WINDOWS: + ret = devm_mfd_add_devices(&client->dev, PLATFORM_DEVID_NONE, + tps68470_win, ARRAY_SIZE(tps68470_win), + NULL, 0, NULL); + break; + case DESIGNED_FOR_CHROMEOS: + ret = devm_mfd_add_devices(&client->dev, PLATFORM_DEVID_NONE, + tps68470_cros, ARRAY_SIZE(tps68470_cros), + NULL, 0, NULL); + break; + default: + dev_err(&client->dev, "Failed to add MFD devices\n"); + return device_type; + } + + return ret; +} -- cgit v1.2.3 From 72fbcac2f40e690e1a5584358750e546a2678c2c Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 18 Jun 2021 15:55:16 +0300 Subject: platform/x86: intel_cht_int33fe: Move to its own subfolder Since we have started collecting Intel x86 specific drivers in their own folder, move intel_cht_int33fe to its own subfolder there. Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20210618125516.53510-8-andriy.shevchenko@linux.intel.com Signed-off-by: Hans de Goede --- drivers/platform/x86/Kconfig | 24 -- drivers/platform/x86/Makefile | 4 - drivers/platform/x86/intel/Kconfig | 1 + drivers/platform/x86/intel/Makefile | 1 + drivers/platform/x86/intel/int33fe/Kconfig | 24 ++ drivers/platform/x86/intel/int33fe/Makefile | 5 + .../x86/intel/int33fe/intel_cht_int33fe_common.c | 145 ++++++++ .../x86/intel/int33fe/intel_cht_int33fe_common.h | 41 +++ .../x86/intel/int33fe/intel_cht_int33fe_microb.c | 61 ++++ .../x86/intel/int33fe/intel_cht_int33fe_typec.c | 380 +++++++++++++++++++++ drivers/platform/x86/intel_cht_int33fe_common.c | 145 -------- drivers/platform/x86/intel_cht_int33fe_common.h | 41 --- drivers/platform/x86/intel_cht_int33fe_microb.c | 61 ---- drivers/platform/x86/intel_cht_int33fe_typec.c | 380 --------------------- 14 files changed, 658 insertions(+), 655 deletions(-) create mode 100644 drivers/platform/x86/intel/int33fe/Kconfig create mode 100644 drivers/platform/x86/intel/int33fe/Makefile create mode 100644 drivers/platform/x86/intel/int33fe/intel_cht_int33fe_common.c create mode 100644 drivers/platform/x86/intel/int33fe/intel_cht_int33fe_common.h create mode 100644 drivers/platform/x86/intel/int33fe/intel_cht_int33fe_microb.c create mode 100644 drivers/platform/x86/intel/int33fe/intel_cht_int33fe_typec.c delete mode 100644 drivers/platform/x86/intel_cht_int33fe_common.c delete mode 100644 drivers/platform/x86/intel_cht_int33fe_common.h delete mode 100644 drivers/platform/x86/intel_cht_int33fe_microb.c delete mode 100644 drivers/platform/x86/intel_cht_int33fe_typec.c diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 79d095c0ab61..7d385c3b2239 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -687,30 +687,6 @@ config INTEL_ATOMISP2_PM To compile this driver as a module, choose M here: the module will be called intel_atomisp2_pm. -config INTEL_CHT_INT33FE - tristate "Intel Cherry Trail ACPI INT33FE Driver" - depends on X86 && ACPI && I2C && REGULATOR - depends on CHARGER_BQ24190=y || (CHARGER_BQ24190=m && m) - depends on USB_ROLES_INTEL_XHCI=y || (USB_ROLES_INTEL_XHCI=m && m) - depends on TYPEC_MUX_PI3USB30532=y || (TYPEC_MUX_PI3USB30532=m && m) - help - This driver add support for the INT33FE ACPI device found on - some Intel Cherry Trail devices. - - There are two kinds of INT33FE ACPI device possible: for hardware - with USB Type-C and Micro-B connectors. This driver supports both. - - The INT33FE ACPI device has a CRS table with I2cSerialBusV2 - resources for Fuel Gauge Controller and (in the Type-C variant) - FUSB302 USB Type-C Controller and PI3USB30532 USB switch. - This driver instantiates i2c-clients for these, so that standard - i2c drivers for these chips can bind to the them. - - If you enable this driver it is advised to also select - CONFIG_BATTERY_BQ27XXX=m or CONFIG_BATTERY_BQ27XXX_I2C=m for Micro-B - device and CONFIG_TYPEC_FUSB302=m and CONFIG_BATTERY_MAX17042=m - for Type-C device. - config INTEL_HID_EVENT tristate "INTEL HID Event" depends on ACPI diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index e03b59ce3f9f..7ee369aab10d 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -70,10 +70,6 @@ obj-$(CONFIG_X86_PLATFORM_DRIVERS_INTEL) += intel/ obj-$(CONFIG_INTEL_ATOMISP2_LED) += intel_atomisp2_led.o obj-$(CONFIG_INTEL_ATOMISP2_PM) += intel_atomisp2_pm.o -obj-$(CONFIG_INTEL_CHT_INT33FE) += intel_cht_int33fe.o -intel_cht_int33fe-objs := intel_cht_int33fe_common.o \ - intel_cht_int33fe_typec.o \ - intel_cht_int33fe_microb.o obj-$(CONFIG_INTEL_HID_EVENT) += intel-hid.o obj-$(CONFIG_INTEL_INT0002_VGPIO) += intel_int0002_vgpio.o obj-$(CONFIG_INTEL_MENLOW) += intel_menlow.o diff --git a/drivers/platform/x86/intel/Kconfig b/drivers/platform/x86/intel/Kconfig index 33f2dab03d3d..f2eef337eb98 100644 --- a/drivers/platform/x86/intel/Kconfig +++ b/drivers/platform/x86/intel/Kconfig @@ -16,6 +16,7 @@ menuconfig X86_PLATFORM_DRIVERS_INTEL if X86_PLATFORM_DRIVERS_INTEL +source "drivers/platform/x86/intel/int33fe/Kconfig" source "drivers/platform/x86/intel/int3472/Kconfig" endif # X86_PLATFORM_DRIVERS_INTEL diff --git a/drivers/platform/x86/intel/Makefile b/drivers/platform/x86/intel/Makefile index 3ac795d810f1..0653055942d5 100644 --- a/drivers/platform/x86/intel/Makefile +++ b/drivers/platform/x86/intel/Makefile @@ -4,4 +4,5 @@ # Intel x86 Platform-Specific Drivers # +obj-$(CONFIG_INTEL_CHT_INT33FE) += int33fe/ obj-$(CONFIG_INTEL_SKL_INT3472) += int3472/ diff --git a/drivers/platform/x86/intel/int33fe/Kconfig b/drivers/platform/x86/intel/int33fe/Kconfig new file mode 100644 index 000000000000..2f7329a2e399 --- /dev/null +++ b/drivers/platform/x86/intel/int33fe/Kconfig @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: GPL-2.0-only +config INTEL_CHT_INT33FE + tristate "Intel Cherry Trail ACPI INT33FE Driver" + depends on X86 && ACPI && I2C && REGULATOR + depends on CHARGER_BQ24190=y || (CHARGER_BQ24190=m && m) + depends on USB_ROLES_INTEL_XHCI=y || (USB_ROLES_INTEL_XHCI=m && m) + depends on TYPEC_MUX_PI3USB30532=y || (TYPEC_MUX_PI3USB30532=m && m) + help + This driver add support for the INT33FE ACPI device found on + some Intel Cherry Trail devices. + + There are two kinds of INT33FE ACPI device possible: for hardware + with USB Type-C and Micro-B connectors. This driver supports both. + + The INT33FE ACPI device has a CRS table with I2cSerialBusV2 + resources for Fuel Gauge Controller and (in the Type-C variant) + FUSB302 USB Type-C Controller and PI3USB30532 USB switch. + This driver instantiates i2c-clients for these, so that standard + i2c drivers for these chips can bind to the them. + + If you enable this driver it is advised to also select + CONFIG_BATTERY_BQ27XXX=m or CONFIG_BATTERY_BQ27XXX_I2C=m for Micro-B + device and CONFIG_TYPEC_FUSB302=m and CONFIG_BATTERY_MAX17042=m + for Type-C device. diff --git a/drivers/platform/x86/intel/int33fe/Makefile b/drivers/platform/x86/intel/int33fe/Makefile new file mode 100644 index 000000000000..cc11183ce179 --- /dev/null +++ b/drivers/platform/x86/intel/int33fe/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_INTEL_CHT_INT33FE) += intel_cht_int33fe.o +intel_cht_int33fe-objs := intel_cht_int33fe_common.o \ + intel_cht_int33fe_typec.o \ + intel_cht_int33fe_microb.o diff --git a/drivers/platform/x86/intel/int33fe/intel_cht_int33fe_common.c b/drivers/platform/x86/intel/int33fe/intel_cht_int33fe_common.c new file mode 100644 index 000000000000..251ed9bac789 --- /dev/null +++ b/drivers/platform/x86/intel/int33fe/intel_cht_int33fe_common.c @@ -0,0 +1,145 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Common code for Intel Cherry Trail ACPI INT33FE pseudo device drivers + * (USB Micro-B and Type-C connector variants). + * + * Copyright (c) 2019 Yauhen Kharuzhy + */ + +#include +#include +#include +#include +#include + +#include "intel_cht_int33fe_common.h" + +#define EXPECTED_PTYPE 4 + +static int cht_int33fe_i2c_res_filter(struct acpi_resource *ares, void *data) +{ + struct acpi_resource_i2c_serialbus *sb; + int *count = data; + + if (i2c_acpi_get_i2c_resource(ares, &sb)) + (*count)++; + + return 1; +} + +static int cht_int33fe_count_i2c_clients(struct device *dev) +{ + struct acpi_device *adev = ACPI_COMPANION(dev); + LIST_HEAD(resource_list); + int count = 0; + int ret; + + ret = acpi_dev_get_resources(adev, &resource_list, + cht_int33fe_i2c_res_filter, &count); + acpi_dev_free_resource_list(&resource_list); + if (ret < 0) + return ret; + + return count; +} + +static int cht_int33fe_check_hw_type(struct device *dev) +{ + unsigned long long ptyp; + acpi_status status; + int ret; + + status = acpi_evaluate_integer(ACPI_HANDLE(dev), "PTYP", NULL, &ptyp); + if (ACPI_FAILURE(status)) { + dev_err(dev, "Error getting PTYPE\n"); + return -ENODEV; + } + + /* + * The same ACPI HID is used for different configurations check PTYP + * to ensure that we are dealing with the expected config. + */ + if (ptyp != EXPECTED_PTYPE) + return -ENODEV; + + /* Check presence of INT34D3 (hardware-rev 3) expected for ptype == 4 */ + if (!acpi_dev_present("INT34D3", "1", 3)) { + dev_err(dev, "Error PTYPE == %d, but no INT34D3 device\n", + EXPECTED_PTYPE); + return -ENODEV; + } + + ret = cht_int33fe_count_i2c_clients(dev); + if (ret < 0) + return ret; + + switch (ret) { + case 2: + return INT33FE_HW_MICROB; + case 4: + return INT33FE_HW_TYPEC; + default: + return -ENODEV; + } +} + +static int cht_int33fe_probe(struct platform_device *pdev) +{ + struct cht_int33fe_data *data; + struct device *dev = &pdev->dev; + int ret; + + ret = cht_int33fe_check_hw_type(dev); + if (ret < 0) + return ret; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->dev = dev; + + switch (ret) { + case INT33FE_HW_MICROB: + data->probe = cht_int33fe_microb_probe; + data->remove = cht_int33fe_microb_remove; + break; + + case INT33FE_HW_TYPEC: + data->probe = cht_int33fe_typec_probe; + data->remove = cht_int33fe_typec_remove; + break; + } + + platform_set_drvdata(pdev, data); + + return data->probe(data); +} + +static int cht_int33fe_remove(struct platform_device *pdev) +{ + struct cht_int33fe_data *data = platform_get_drvdata(pdev); + + return data->remove(data); +} + +static const struct acpi_device_id cht_int33fe_acpi_ids[] = { + { "INT33FE", }, + { } +}; +MODULE_DEVICE_TABLE(acpi, cht_int33fe_acpi_ids); + +static struct platform_driver cht_int33fe_driver = { + .driver = { + .name = "Intel Cherry Trail ACPI INT33FE driver", + .acpi_match_table = ACPI_PTR(cht_int33fe_acpi_ids), + }, + .probe = cht_int33fe_probe, + .remove = cht_int33fe_remove, +}; + +module_platform_driver(cht_int33fe_driver); + +MODULE_DESCRIPTION("Intel Cherry Trail ACPI INT33FE pseudo device driver"); +MODULE_AUTHOR("Yauhen Kharuzhy "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/platform/x86/intel/int33fe/intel_cht_int33fe_common.h b/drivers/platform/x86/intel/int33fe/intel_cht_int33fe_common.h new file mode 100644 index 000000000000..03cd45f4e8cb --- /dev/null +++ b/drivers/platform/x86/intel/int33fe/intel_cht_int33fe_common.h @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Common code for Intel Cherry Trail ACPI INT33FE pseudo device drivers + * (USB Micro-B and Type-C connector variants), header file + * + * Copyright (c) 2019 Yauhen Kharuzhy + */ + +#ifndef _INTEL_CHT_INT33FE_COMMON_H +#define _INTEL_CHT_INT33FE_COMMON_H + +#include +#include +#include + +enum int33fe_hw_type { + INT33FE_HW_MICROB, + INT33FE_HW_TYPEC, +}; + +struct cht_int33fe_data { + struct device *dev; + + int (*probe)(struct cht_int33fe_data *data); + int (*remove)(struct cht_int33fe_data *data); + + struct i2c_client *battery_fg; + + /* Type-C only */ + struct i2c_client *fusb302; + struct i2c_client *pi3usb30532; + + struct fwnode_handle *dp; +}; + +int cht_int33fe_microb_probe(struct cht_int33fe_data *data); +int cht_int33fe_microb_remove(struct cht_int33fe_data *data); +int cht_int33fe_typec_probe(struct cht_int33fe_data *data); +int cht_int33fe_typec_remove(struct cht_int33fe_data *data); + +#endif /* _INTEL_CHT_INT33FE_COMMON_H */ diff --git a/drivers/platform/x86/intel/int33fe/intel_cht_int33fe_microb.c b/drivers/platform/x86/intel/int33fe/intel_cht_int33fe_microb.c new file mode 100644 index 000000000000..673f41cd14b5 --- /dev/null +++ b/drivers/platform/x86/intel/int33fe/intel_cht_int33fe_microb.c @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Intel Cherry Trail ACPI INT33FE pseudo device driver for devices with + * USB Micro-B connector (e.g. without of FUSB302 USB Type-C controller) + * + * Copyright (C) 2019 Yauhen Kharuzhy + * + * At least one Intel Cherry Trail based device which ship with Windows 10 + * (Lenovo YogaBook YB1-X91L/F tablet), have this weird INT33FE ACPI device + * with a CRS table with 2 I2cSerialBusV2 resources, for 2 different chips + * attached to various i2c busses: + * 1. The Whiskey Cove PMIC, which is also described by the INT34D3 ACPI device + * 2. TI BQ27542 Fuel Gauge Controller + * + * So this driver is a stub / pseudo driver whose only purpose is to + * instantiate i2c-client for battery fuel gauge, so that standard i2c driver + * for these chip can bind to the it. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "intel_cht_int33fe_common.h" + +static const char * const bq27xxx_suppliers[] = { "bq25890-charger" }; + +static const struct property_entry bq27xxx_props[] = { + PROPERTY_ENTRY_STRING_ARRAY("supplied-from", bq27xxx_suppliers), + { } +}; + +static const struct software_node bq27xxx_node = { + .properties = bq27xxx_props, +}; + +int cht_int33fe_microb_probe(struct cht_int33fe_data *data) +{ + struct device *dev = data->dev; + struct i2c_board_info board_info; + + memset(&board_info, 0, sizeof(board_info)); + strscpy(board_info.type, "bq27542", ARRAY_SIZE(board_info.type)); + board_info.dev_name = "bq27542"; + board_info.swnode = &bq27xxx_node; + data->battery_fg = i2c_acpi_new_device(dev, 1, &board_info); + + return PTR_ERR_OR_ZERO(data->battery_fg); +} + +int cht_int33fe_microb_remove(struct cht_int33fe_data *data) +{ + i2c_unregister_device(data->battery_fg); + + return 0; +} diff --git a/drivers/platform/x86/intel/int33fe/intel_cht_int33fe_typec.c b/drivers/platform/x86/intel/int33fe/intel_cht_int33fe_typec.c new file mode 100644 index 000000000000..d59544167430 --- /dev/null +++ b/drivers/platform/x86/intel/int33fe/intel_cht_int33fe_typec.c @@ -0,0 +1,380 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Intel Cherry Trail ACPI INT33FE pseudo device driver + * + * Copyright (C) 2017 Hans de Goede + * + * Some Intel Cherry Trail based device which ship with Windows 10, have + * this weird INT33FE ACPI device with a CRS table with 4 I2cSerialBusV2 + * resources, for 4 different chips attached to various I²C buses: + * 1. The Whiskey Cove PMIC, which is also described by the INT34D3 ACPI device + * 2. Maxim MAX17047 Fuel Gauge Controller + * 3. FUSB302 USB Type-C Controller + * 4. PI3USB30532 USB switch + * + * So this driver is a stub / pseudo driver whose only purpose is to + * instantiate I²C clients for chips 2 - 4, so that standard I²C drivers + * for these chips can bind to the them. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "intel_cht_int33fe_common.h" + +/* + * Grrr, I severely dislike buggy BIOS-es. At least one BIOS enumerates + * the max17047 both through the INT33FE ACPI device (it is right there + * in the resources table) as well as through a separate MAX17047 device. + * + * These helpers are used to work around this by checking if an I²C client + * for the max17047 has already been registered. + */ +static int cht_int33fe_check_for_max17047(struct device *dev, void *data) +{ + struct i2c_client **max17047 = data; + struct acpi_device *adev; + + adev = ACPI_COMPANION(dev); + if (!adev) + return 0; + + /* The MAX17047 ACPI node doesn't have an UID, so we don't check that */ + if (!acpi_dev_hid_uid_match(adev, "MAX17047", NULL)) + return 0; + + *max17047 = to_i2c_client(dev); + return 1; +} + +static const char * const max17047_suppliers[] = { "bq24190-charger" }; + +static const struct property_entry max17047_properties[] = { + PROPERTY_ENTRY_STRING_ARRAY("supplied-from", max17047_suppliers), + { } +}; + +static const struct software_node max17047_node = { + .name = "max17047", + .properties = max17047_properties, +}; + +/* + * We are not using inline property here because those are constant, + * and we need to adjust this one at runtime to point to real + * software node. + */ +static struct software_node_ref_args fusb302_mux_refs[] = { + { .node = NULL }, +}; + +static const struct property_entry fusb302_properties[] = { + PROPERTY_ENTRY_STRING("linux,extcon-name", "cht_wcove_pwrsrc"), + PROPERTY_ENTRY_REF_ARRAY("usb-role-switch", fusb302_mux_refs), + { } +}; + +static const struct software_node fusb302_node = { + .name = "fusb302", + .properties = fusb302_properties, +}; + +#define PDO_FIXED_FLAGS \ + (PDO_FIXED_DUAL_ROLE | PDO_FIXED_DATA_SWAP | PDO_FIXED_USB_COMM) + +static const u32 src_pdo[] = { + PDO_FIXED(5000, 1500, PDO_FIXED_FLAGS), +}; + +static const u32 snk_pdo[] = { + PDO_FIXED(5000, 400, PDO_FIXED_FLAGS), + PDO_VAR(5000, 12000, 3000), +}; + +static const struct software_node pi3usb30532_node = { + .name = "pi3usb30532", +}; + +static const struct software_node displayport_node = { + .name = "displayport", +}; + +static const struct property_entry usb_connector_properties[] = { + PROPERTY_ENTRY_STRING("data-role", "dual"), + PROPERTY_ENTRY_STRING("power-role", "dual"), + PROPERTY_ENTRY_STRING("try-power-role", "sink"), + PROPERTY_ENTRY_U32_ARRAY("source-pdos", src_pdo), + PROPERTY_ENTRY_U32_ARRAY("sink-pdos", snk_pdo), + PROPERTY_ENTRY_U32("op-sink-microwatt", 2500000), + PROPERTY_ENTRY_REF("orientation-switch", &pi3usb30532_node), + PROPERTY_ENTRY_REF("mode-switch", &pi3usb30532_node), + PROPERTY_ENTRY_REF("displayport", &displayport_node), + { } +}; + +static const struct software_node usb_connector_node = { + .name = "connector", + .parent = &fusb302_node, + .properties = usb_connector_properties, +}; + +static const struct software_node altmodes_node = { + .name = "altmodes", + .parent = &usb_connector_node, +}; + +static const struct property_entry dp_altmode_properties[] = { + PROPERTY_ENTRY_U32("svid", 0xff01), + PROPERTY_ENTRY_U32("vdo", 0x0c0086), + { } +}; + +static const struct software_node dp_altmode_node = { + .name = "displayport-altmode", + .parent = &altmodes_node, + .properties = dp_altmode_properties, +}; + +static const struct software_node *node_group[] = { + &fusb302_node, + &max17047_node, + &pi3usb30532_node, + &displayport_node, + &usb_connector_node, + &altmodes_node, + &dp_altmode_node, + NULL +}; + +static int cht_int33fe_setup_dp(struct cht_int33fe_data *data) +{ + struct fwnode_handle *fwnode; + struct pci_dev *pdev; + + fwnode = software_node_fwnode(&displayport_node); + if (!fwnode) + return -ENODEV; + + /* First let's find the GPU PCI device */ + pdev = pci_get_class(PCI_CLASS_DISPLAY_VGA << 8, NULL); + if (!pdev || pdev->vendor != PCI_VENDOR_ID_INTEL) { + pci_dev_put(pdev); + return -ENODEV; + } + + /* Then the DP-2 child device node */ + data->dp = device_get_named_child_node(&pdev->dev, "DD04"); + pci_dev_put(pdev); + if (!data->dp) + return -ENODEV; + + fwnode->secondary = ERR_PTR(-ENODEV); + data->dp->secondary = fwnode; + + return 0; +} + +static void cht_int33fe_remove_nodes(struct cht_int33fe_data *data) +{ + software_node_unregister_node_group(node_group); + + if (fusb302_mux_refs[0].node) { + fwnode_handle_put(software_node_fwnode(fusb302_mux_refs[0].node)); + fusb302_mux_refs[0].node = NULL; + } + + if (data->dp) { + data->dp->secondary = NULL; + fwnode_handle_put(data->dp); + data->dp = NULL; + } +} + +static int cht_int33fe_add_nodes(struct cht_int33fe_data *data) +{ + const struct software_node *mux_ref_node; + int ret; + + /* + * There is no ACPI device node for the USB role mux, so we need to wait + * until the mux driver has created software node for the mux device. + * It means we depend on the mux driver. This function will return + * -EPROBE_DEFER until the mux device is registered. + */ + mux_ref_node = software_node_find_by_name(NULL, "intel-xhci-usb-sw"); + if (!mux_ref_node) + return -EPROBE_DEFER; + + /* + * Update node used in "usb-role-switch" property. Note that we + * rely on software_node_register_nodes() to use the original + * instance of properties instead of copying them. + */ + fusb302_mux_refs[0].node = mux_ref_node; + + ret = software_node_register_node_group(node_group); + if (ret) + return ret; + + /* The devices that are not created in this driver need extra steps. */ + + /* + * The DP connector does have ACPI device node. In this case we can just + * find that ACPI node and assign our node as the secondary node to it. + */ + ret = cht_int33fe_setup_dp(data); + if (ret) + goto err_remove_nodes; + + return 0; + +err_remove_nodes: + cht_int33fe_remove_nodes(data); + + return ret; +} + +static int +cht_int33fe_register_max17047(struct device *dev, struct cht_int33fe_data *data) +{ + struct i2c_client *max17047 = NULL; + struct i2c_board_info board_info; + struct fwnode_handle *fwnode; + int ret; + + fwnode = software_node_fwnode(&max17047_node); + if (!fwnode) + return -ENODEV; + + i2c_for_each_dev(&max17047, cht_int33fe_check_for_max17047); + if (max17047) { + /* Pre-existing I²C client for the max17047, add device properties */ + set_secondary_fwnode(&max17047->dev, fwnode); + /* And re-probe to get the new device properties applied */ + ret = device_reprobe(&max17047->dev); + if (ret) + dev_warn(dev, "Reprobing max17047 error: %d\n", ret); + return 0; + } + + memset(&board_info, 0, sizeof(board_info)); + strlcpy(board_info.type, "max17047", I2C_NAME_SIZE); + board_info.dev_name = "max17047"; + board_info.fwnode = fwnode; + data->battery_fg = i2c_acpi_new_device(dev, 1, &board_info); + + return PTR_ERR_OR_ZERO(data->battery_fg); +} + +int cht_int33fe_typec_probe(struct cht_int33fe_data *data) +{ + struct device *dev = data->dev; + struct i2c_board_info board_info; + struct fwnode_handle *fwnode; + struct regulator *regulator; + int fusb302_irq; + int ret; + + /* + * We expect the WC PMIC to be paired with a TI bq24292i charger-IC. + * We check for the bq24292i vbus regulator here, this has 2 purposes: + * 1) The bq24292i allows charging with up to 12V, setting the fusb302's + * max-snk voltage to 12V with another charger-IC is not good. + * 2) For the fusb302 driver to get the bq24292i vbus regulator, the + * regulator-map, which is part of the bq24292i regulator_init_data, + * must be registered before the fusb302 is instantiated, otherwise + * it will end up with a dummy-regulator. + * Note "cht_wc_usb_typec_vbus" comes from the regulator_init_data + * which is defined in i2c-cht-wc.c from where the bq24292i I²C client + * gets instantiated. We use regulator_get_optional here so that we + * don't end up getting a dummy-regulator ourselves. + */ + regulator = regulator_get_optional(dev, "cht_wc_usb_typec_vbus"); + if (IS_ERR(regulator)) { + ret = PTR_ERR(regulator); + return (ret == -ENODEV) ? -EPROBE_DEFER : ret; + } + regulator_put(regulator); + + /* The FUSB302 uses the IRQ at index 1 and is the only IRQ user */ + fusb302_irq = acpi_dev_gpio_irq_get(ACPI_COMPANION(dev), 1); + if (fusb302_irq < 0) { + if (fusb302_irq != -EPROBE_DEFER) + dev_err(dev, "Error getting FUSB302 irq\n"); + return fusb302_irq; + } + + ret = cht_int33fe_add_nodes(data); + if (ret) + return ret; + + /* Work around BIOS bug, see comment on cht_int33fe_check_for_max17047() */ + ret = cht_int33fe_register_max17047(dev, data); + if (ret) + goto out_remove_nodes; + + fwnode = software_node_fwnode(&fusb302_node); + if (!fwnode) { + ret = -ENODEV; + goto out_unregister_max17047; + } + + memset(&board_info, 0, sizeof(board_info)); + strlcpy(board_info.type, "typec_fusb302", I2C_NAME_SIZE); + board_info.dev_name = "fusb302"; + board_info.fwnode = fwnode; + board_info.irq = fusb302_irq; + + data->fusb302 = i2c_acpi_new_device(dev, 2, &board_info); + if (IS_ERR(data->fusb302)) { + ret = PTR_ERR(data->fusb302); + goto out_unregister_max17047; + } + + fwnode = software_node_fwnode(&pi3usb30532_node); + if (!fwnode) { + ret = -ENODEV; + goto out_unregister_fusb302; + } + + memset(&board_info, 0, sizeof(board_info)); + board_info.dev_name = "pi3usb30532"; + board_info.fwnode = fwnode; + strlcpy(board_info.type, "pi3usb30532", I2C_NAME_SIZE); + + data->pi3usb30532 = i2c_acpi_new_device(dev, 3, &board_info); + if (IS_ERR(data->pi3usb30532)) { + ret = PTR_ERR(data->pi3usb30532); + goto out_unregister_fusb302; + } + + return 0; + +out_unregister_fusb302: + i2c_unregister_device(data->fusb302); + +out_unregister_max17047: + i2c_unregister_device(data->battery_fg); + +out_remove_nodes: + cht_int33fe_remove_nodes(data); + + return ret; +} + +int cht_int33fe_typec_remove(struct cht_int33fe_data *data) +{ + i2c_unregister_device(data->pi3usb30532); + i2c_unregister_device(data->fusb302); + i2c_unregister_device(data->battery_fg); + + cht_int33fe_remove_nodes(data); + + return 0; +} diff --git a/drivers/platform/x86/intel_cht_int33fe_common.c b/drivers/platform/x86/intel_cht_int33fe_common.c deleted file mode 100644 index 251ed9bac789..000000000000 --- a/drivers/platform/x86/intel_cht_int33fe_common.c +++ /dev/null @@ -1,145 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Common code for Intel Cherry Trail ACPI INT33FE pseudo device drivers - * (USB Micro-B and Type-C connector variants). - * - * Copyright (c) 2019 Yauhen Kharuzhy - */ - -#include -#include -#include -#include -#include - -#include "intel_cht_int33fe_common.h" - -#define EXPECTED_PTYPE 4 - -static int cht_int33fe_i2c_res_filter(struct acpi_resource *ares, void *data) -{ - struct acpi_resource_i2c_serialbus *sb; - int *count = data; - - if (i2c_acpi_get_i2c_resource(ares, &sb)) - (*count)++; - - return 1; -} - -static int cht_int33fe_count_i2c_clients(struct device *dev) -{ - struct acpi_device *adev = ACPI_COMPANION(dev); - LIST_HEAD(resource_list); - int count = 0; - int ret; - - ret = acpi_dev_get_resources(adev, &resource_list, - cht_int33fe_i2c_res_filter, &count); - acpi_dev_free_resource_list(&resource_list); - if (ret < 0) - return ret; - - return count; -} - -static int cht_int33fe_check_hw_type(struct device *dev) -{ - unsigned long long ptyp; - acpi_status status; - int ret; - - status = acpi_evaluate_integer(ACPI_HANDLE(dev), "PTYP", NULL, &ptyp); - if (ACPI_FAILURE(status)) { - dev_err(dev, "Error getting PTYPE\n"); - return -ENODEV; - } - - /* - * The same ACPI HID is used for different configurations check PTYP - * to ensure that we are dealing with the expected config. - */ - if (ptyp != EXPECTED_PTYPE) - return -ENODEV; - - /* Check presence of INT34D3 (hardware-rev 3) expected for ptype == 4 */ - if (!acpi_dev_present("INT34D3", "1", 3)) { - dev_err(dev, "Error PTYPE == %d, but no INT34D3 device\n", - EXPECTED_PTYPE); - return -ENODEV; - } - - ret = cht_int33fe_count_i2c_clients(dev); - if (ret < 0) - return ret; - - switch (ret) { - case 2: - return INT33FE_HW_MICROB; - case 4: - return INT33FE_HW_TYPEC; - default: - return -ENODEV; - } -} - -static int cht_int33fe_probe(struct platform_device *pdev) -{ - struct cht_int33fe_data *data; - struct device *dev = &pdev->dev; - int ret; - - ret = cht_int33fe_check_hw_type(dev); - if (ret < 0) - return ret; - - data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); - if (!data) - return -ENOMEM; - - data->dev = dev; - - switch (ret) { - case INT33FE_HW_MICROB: - data->probe = cht_int33fe_microb_probe; - data->remove = cht_int33fe_microb_remove; - break; - - case INT33FE_HW_TYPEC: - data->probe = cht_int33fe_typec_probe; - data->remove = cht_int33fe_typec_remove; - break; - } - - platform_set_drvdata(pdev, data); - - return data->probe(data); -} - -static int cht_int33fe_remove(struct platform_device *pdev) -{ - struct cht_int33fe_data *data = platform_get_drvdata(pdev); - - return data->remove(data); -} - -static const struct acpi_device_id cht_int33fe_acpi_ids[] = { - { "INT33FE", }, - { } -}; -MODULE_DEVICE_TABLE(acpi, cht_int33fe_acpi_ids); - -static struct platform_driver cht_int33fe_driver = { - .driver = { - .name = "Intel Cherry Trail ACPI INT33FE driver", - .acpi_match_table = ACPI_PTR(cht_int33fe_acpi_ids), - }, - .probe = cht_int33fe_probe, - .remove = cht_int33fe_remove, -}; - -module_platform_driver(cht_int33fe_driver); - -MODULE_DESCRIPTION("Intel Cherry Trail ACPI INT33FE pseudo device driver"); -MODULE_AUTHOR("Yauhen Kharuzhy "); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/platform/x86/intel_cht_int33fe_common.h b/drivers/platform/x86/intel_cht_int33fe_common.h deleted file mode 100644 index 03cd45f4e8cb..000000000000 --- a/drivers/platform/x86/intel_cht_int33fe_common.h +++ /dev/null @@ -1,41 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* - * Common code for Intel Cherry Trail ACPI INT33FE pseudo device drivers - * (USB Micro-B and Type-C connector variants), header file - * - * Copyright (c) 2019 Yauhen Kharuzhy - */ - -#ifndef _INTEL_CHT_INT33FE_COMMON_H -#define _INTEL_CHT_INT33FE_COMMON_H - -#include -#include -#include - -enum int33fe_hw_type { - INT33FE_HW_MICROB, - INT33FE_HW_TYPEC, -}; - -struct cht_int33fe_data { - struct device *dev; - - int (*probe)(struct cht_int33fe_data *data); - int (*remove)(struct cht_int33fe_data *data); - - struct i2c_client *battery_fg; - - /* Type-C only */ - struct i2c_client *fusb302; - struct i2c_client *pi3usb30532; - - struct fwnode_handle *dp; -}; - -int cht_int33fe_microb_probe(struct cht_int33fe_data *data); -int cht_int33fe_microb_remove(struct cht_int33fe_data *data); -int cht_int33fe_typec_probe(struct cht_int33fe_data *data); -int cht_int33fe_typec_remove(struct cht_int33fe_data *data); - -#endif /* _INTEL_CHT_INT33FE_COMMON_H */ diff --git a/drivers/platform/x86/intel_cht_int33fe_microb.c b/drivers/platform/x86/intel_cht_int33fe_microb.c deleted file mode 100644 index 673f41cd14b5..000000000000 --- a/drivers/platform/x86/intel_cht_int33fe_microb.c +++ /dev/null @@ -1,61 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Intel Cherry Trail ACPI INT33FE pseudo device driver for devices with - * USB Micro-B connector (e.g. without of FUSB302 USB Type-C controller) - * - * Copyright (C) 2019 Yauhen Kharuzhy - * - * At least one Intel Cherry Trail based device which ship with Windows 10 - * (Lenovo YogaBook YB1-X91L/F tablet), have this weird INT33FE ACPI device - * with a CRS table with 2 I2cSerialBusV2 resources, for 2 different chips - * attached to various i2c busses: - * 1. The Whiskey Cove PMIC, which is also described by the INT34D3 ACPI device - * 2. TI BQ27542 Fuel Gauge Controller - * - * So this driver is a stub / pseudo driver whose only purpose is to - * instantiate i2c-client for battery fuel gauge, so that standard i2c driver - * for these chip can bind to the it. - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "intel_cht_int33fe_common.h" - -static const char * const bq27xxx_suppliers[] = { "bq25890-charger" }; - -static const struct property_entry bq27xxx_props[] = { - PROPERTY_ENTRY_STRING_ARRAY("supplied-from", bq27xxx_suppliers), - { } -}; - -static const struct software_node bq27xxx_node = { - .properties = bq27xxx_props, -}; - -int cht_int33fe_microb_probe(struct cht_int33fe_data *data) -{ - struct device *dev = data->dev; - struct i2c_board_info board_info; - - memset(&board_info, 0, sizeof(board_info)); - strscpy(board_info.type, "bq27542", ARRAY_SIZE(board_info.type)); - board_info.dev_name = "bq27542"; - board_info.swnode = &bq27xxx_node; - data->battery_fg = i2c_acpi_new_device(dev, 1, &board_info); - - return PTR_ERR_OR_ZERO(data->battery_fg); -} - -int cht_int33fe_microb_remove(struct cht_int33fe_data *data) -{ - i2c_unregister_device(data->battery_fg); - - return 0; -} diff --git a/drivers/platform/x86/intel_cht_int33fe_typec.c b/drivers/platform/x86/intel_cht_int33fe_typec.c deleted file mode 100644 index d59544167430..000000000000 --- a/drivers/platform/x86/intel_cht_int33fe_typec.c +++ /dev/null @@ -1,380 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Intel Cherry Trail ACPI INT33FE pseudo device driver - * - * Copyright (C) 2017 Hans de Goede - * - * Some Intel Cherry Trail based device which ship with Windows 10, have - * this weird INT33FE ACPI device with a CRS table with 4 I2cSerialBusV2 - * resources, for 4 different chips attached to various I²C buses: - * 1. The Whiskey Cove PMIC, which is also described by the INT34D3 ACPI device - * 2. Maxim MAX17047 Fuel Gauge Controller - * 3. FUSB302 USB Type-C Controller - * 4. PI3USB30532 USB switch - * - * So this driver is a stub / pseudo driver whose only purpose is to - * instantiate I²C clients for chips 2 - 4, so that standard I²C drivers - * for these chips can bind to the them. - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "intel_cht_int33fe_common.h" - -/* - * Grrr, I severely dislike buggy BIOS-es. At least one BIOS enumerates - * the max17047 both through the INT33FE ACPI device (it is right there - * in the resources table) as well as through a separate MAX17047 device. - * - * These helpers are used to work around this by checking if an I²C client - * for the max17047 has already been registered. - */ -static int cht_int33fe_check_for_max17047(struct device *dev, void *data) -{ - struct i2c_client **max17047 = data; - struct acpi_device *adev; - - adev = ACPI_COMPANION(dev); - if (!adev) - return 0; - - /* The MAX17047 ACPI node doesn't have an UID, so we don't check that */ - if (!acpi_dev_hid_uid_match(adev, "MAX17047", NULL)) - return 0; - - *max17047 = to_i2c_client(dev); - return 1; -} - -static const char * const max17047_suppliers[] = { "bq24190-charger" }; - -static const struct property_entry max17047_properties[] = { - PROPERTY_ENTRY_STRING_ARRAY("supplied-from", max17047_suppliers), - { } -}; - -static const struct software_node max17047_node = { - .name = "max17047", - .properties = max17047_properties, -}; - -/* - * We are not using inline property here because those are constant, - * and we need to adjust this one at runtime to point to real - * software node. - */ -static struct software_node_ref_args fusb302_mux_refs[] = { - { .node = NULL }, -}; - -static const struct property_entry fusb302_properties[] = { - PROPERTY_ENTRY_STRING("linux,extcon-name", "cht_wcove_pwrsrc"), - PROPERTY_ENTRY_REF_ARRAY("usb-role-switch", fusb302_mux_refs), - { } -}; - -static const struct software_node fusb302_node = { - .name = "fusb302", - .properties = fusb302_properties, -}; - -#define PDO_FIXED_FLAGS \ - (PDO_FIXED_DUAL_ROLE | PDO_FIXED_DATA_SWAP | PDO_FIXED_USB_COMM) - -static const u32 src_pdo[] = { - PDO_FIXED(5000, 1500, PDO_FIXED_FLAGS), -}; - -static const u32 snk_pdo[] = { - PDO_FIXED(5000, 400, PDO_FIXED_FLAGS), - PDO_VAR(5000, 12000, 3000), -}; - -static const struct software_node pi3usb30532_node = { - .name = "pi3usb30532", -}; - -static const struct software_node displayport_node = { - .name = "displayport", -}; - -static const struct property_entry usb_connector_properties[] = { - PROPERTY_ENTRY_STRING("data-role", "dual"), - PROPERTY_ENTRY_STRING("power-role", "dual"), - PROPERTY_ENTRY_STRING("try-power-role", "sink"), - PROPERTY_ENTRY_U32_ARRAY("source-pdos", src_pdo), - PROPERTY_ENTRY_U32_ARRAY("sink-pdos", snk_pdo), - PROPERTY_ENTRY_U32("op-sink-microwatt", 2500000), - PROPERTY_ENTRY_REF("orientation-switch", &pi3usb30532_node), - PROPERTY_ENTRY_REF("mode-switch", &pi3usb30532_node), - PROPERTY_ENTRY_REF("displayport", &displayport_node), - { } -}; - -static const struct software_node usb_connector_node = { - .name = "connector", - .parent = &fusb302_node, - .properties = usb_connector_properties, -}; - -static const struct software_node altmodes_node = { - .name = "altmodes", - .parent = &usb_connector_node, -}; - -static const struct property_entry dp_altmode_properties[] = { - PROPERTY_ENTRY_U32("svid", 0xff01), - PROPERTY_ENTRY_U32("vdo", 0x0c0086), - { } -}; - -static const struct software_node dp_altmode_node = { - .name = "displayport-altmode", - .parent = &altmodes_node, - .properties = dp_altmode_properties, -}; - -static const struct software_node *node_group[] = { - &fusb302_node, - &max17047_node, - &pi3usb30532_node, - &displayport_node, - &usb_connector_node, - &altmodes_node, - &dp_altmode_node, - NULL -}; - -static int cht_int33fe_setup_dp(struct cht_int33fe_data *data) -{ - struct fwnode_handle *fwnode; - struct pci_dev *pdev; - - fwnode = software_node_fwnode(&displayport_node); - if (!fwnode) - return -ENODEV; - - /* First let's find the GPU PCI device */ - pdev = pci_get_class(PCI_CLASS_DISPLAY_VGA << 8, NULL); - if (!pdev || pdev->vendor != PCI_VENDOR_ID_INTEL) { - pci_dev_put(pdev); - return -ENODEV; - } - - /* Then the DP-2 child device node */ - data->dp = device_get_named_child_node(&pdev->dev, "DD04"); - pci_dev_put(pdev); - if (!data->dp) - return -ENODEV; - - fwnode->secondary = ERR_PTR(-ENODEV); - data->dp->secondary = fwnode; - - return 0; -} - -static void cht_int33fe_remove_nodes(struct cht_int33fe_data *data) -{ - software_node_unregister_node_group(node_group); - - if (fusb302_mux_refs[0].node) { - fwnode_handle_put(software_node_fwnode(fusb302_mux_refs[0].node)); - fusb302_mux_refs[0].node = NULL; - } - - if (data->dp) { - data->dp->secondary = NULL; - fwnode_handle_put(data->dp); - data->dp = NULL; - } -} - -static int cht_int33fe_add_nodes(struct cht_int33fe_data *data) -{ - const struct software_node *mux_ref_node; - int ret; - - /* - * There is no ACPI device node for the USB role mux, so we need to wait - * until the mux driver has created software node for the mux device. - * It means we depend on the mux driver. This function will return - * -EPROBE_DEFER until the mux device is registered. - */ - mux_ref_node = software_node_find_by_name(NULL, "intel-xhci-usb-sw"); - if (!mux_ref_node) - return -EPROBE_DEFER; - - /* - * Update node used in "usb-role-switch" property. Note that we - * rely on software_node_register_nodes() to use the original - * instance of properties instead of copying them. - */ - fusb302_mux_refs[0].node = mux_ref_node; - - ret = software_node_register_node_group(node_group); - if (ret) - return ret; - - /* The devices that are not created in this driver need extra steps. */ - - /* - * The DP connector does have ACPI device node. In this case we can just - * find that ACPI node and assign our node as the secondary node to it. - */ - ret = cht_int33fe_setup_dp(data); - if (ret) - goto err_remove_nodes; - - return 0; - -err_remove_nodes: - cht_int33fe_remove_nodes(data); - - return ret; -} - -static int -cht_int33fe_register_max17047(struct device *dev, struct cht_int33fe_data *data) -{ - struct i2c_client *max17047 = NULL; - struct i2c_board_info board_info; - struct fwnode_handle *fwnode; - int ret; - - fwnode = software_node_fwnode(&max17047_node); - if (!fwnode) - return -ENODEV; - - i2c_for_each_dev(&max17047, cht_int33fe_check_for_max17047); - if (max17047) { - /* Pre-existing I²C client for the max17047, add device properties */ - set_secondary_fwnode(&max17047->dev, fwnode); - /* And re-probe to get the new device properties applied */ - ret = device_reprobe(&max17047->dev); - if (ret) - dev_warn(dev, "Reprobing max17047 error: %d\n", ret); - return 0; - } - - memset(&board_info, 0, sizeof(board_info)); - strlcpy(board_info.type, "max17047", I2C_NAME_SIZE); - board_info.dev_name = "max17047"; - board_info.fwnode = fwnode; - data->battery_fg = i2c_acpi_new_device(dev, 1, &board_info); - - return PTR_ERR_OR_ZERO(data->battery_fg); -} - -int cht_int33fe_typec_probe(struct cht_int33fe_data *data) -{ - struct device *dev = data->dev; - struct i2c_board_info board_info; - struct fwnode_handle *fwnode; - struct regulator *regulator; - int fusb302_irq; - int ret; - - /* - * We expect the WC PMIC to be paired with a TI bq24292i charger-IC. - * We check for the bq24292i vbus regulator here, this has 2 purposes: - * 1) The bq24292i allows charging with up to 12V, setting the fusb302's - * max-snk voltage to 12V with another charger-IC is not good. - * 2) For the fusb302 driver to get the bq24292i vbus regulator, the - * regulator-map, which is part of the bq24292i regulator_init_data, - * must be registered before the fusb302 is instantiated, otherwise - * it will end up with a dummy-regulator. - * Note "cht_wc_usb_typec_vbus" comes from the regulator_init_data - * which is defined in i2c-cht-wc.c from where the bq24292i I²C client - * gets instantiated. We use regulator_get_optional here so that we - * don't end up getting a dummy-regulator ourselves. - */ - regulator = regulator_get_optional(dev, "cht_wc_usb_typec_vbus"); - if (IS_ERR(regulator)) { - ret = PTR_ERR(regulator); - return (ret == -ENODEV) ? -EPROBE_DEFER : ret; - } - regulator_put(regulator); - - /* The FUSB302 uses the IRQ at index 1 and is the only IRQ user */ - fusb302_irq = acpi_dev_gpio_irq_get(ACPI_COMPANION(dev), 1); - if (fusb302_irq < 0) { - if (fusb302_irq != -EPROBE_DEFER) - dev_err(dev, "Error getting FUSB302 irq\n"); - return fusb302_irq; - } - - ret = cht_int33fe_add_nodes(data); - if (ret) - return ret; - - /* Work around BIOS bug, see comment on cht_int33fe_check_for_max17047() */ - ret = cht_int33fe_register_max17047(dev, data); - if (ret) - goto out_remove_nodes; - - fwnode = software_node_fwnode(&fusb302_node); - if (!fwnode) { - ret = -ENODEV; - goto out_unregister_max17047; - } - - memset(&board_info, 0, sizeof(board_info)); - strlcpy(board_info.type, "typec_fusb302", I2C_NAME_SIZE); - board_info.dev_name = "fusb302"; - board_info.fwnode = fwnode; - board_info.irq = fusb302_irq; - - data->fusb302 = i2c_acpi_new_device(dev, 2, &board_info); - if (IS_ERR(data->fusb302)) { - ret = PTR_ERR(data->fusb302); - goto out_unregister_max17047; - } - - fwnode = software_node_fwnode(&pi3usb30532_node); - if (!fwnode) { - ret = -ENODEV; - goto out_unregister_fusb302; - } - - memset(&board_info, 0, sizeof(board_info)); - board_info.dev_name = "pi3usb30532"; - board_info.fwnode = fwnode; - strlcpy(board_info.type, "pi3usb30532", I2C_NAME_SIZE); - - data->pi3usb30532 = i2c_acpi_new_device(dev, 3, &board_info); - if (IS_ERR(data->pi3usb30532)) { - ret = PTR_ERR(data->pi3usb30532); - goto out_unregister_fusb302; - } - - return 0; - -out_unregister_fusb302: - i2c_unregister_device(data->fusb302); - -out_unregister_max17047: - i2c_unregister_device(data->battery_fg); - -out_remove_nodes: - cht_int33fe_remove_nodes(data); - - return ret; -} - -int cht_int33fe_typec_remove(struct cht_int33fe_data *data) -{ - i2c_unregister_device(data->pi3usb30532); - i2c_unregister_device(data->fusb302); - i2c_unregister_device(data->battery_fg); - - cht_int33fe_remove_nodes(data); - - return 0; -} -- cgit v1.2.3 From 71d69e82f4168713afd89804d6e5f52d0e3848a2 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Mon, 21 Jun 2021 21:36:48 +0200 Subject: platform/x86: think-lmi: Return EINVAL when kbdlang gets set to a 0 length string Commit 0ddcf3a6b442 ("platform/x86: think-lmi: Avoid potential read before start of the buffer") moved the length == 0 up to before stripping the '\n' which typically gets added when users echo a value to a sysfs-attribute from the shell. This avoids a potential buffer-underrun, but it also causes a behavioral change, prior to this change "echo > kbdlang", iow writing just a single '\n' would result in an EINVAL error, but after the change this gets accepted setting kbdlang to an empty string. Fix this by replacing the manual '\n' check with using strchrnul() to get the length till '\n' or terminating 0 in one go; and then do the length != 0 check after this. Fixes: 0ddcf3a6b442 ("platform/x86: think-lmi: Avoid potential read before start of the buffer") Reported-by: Juha Leppänen Suggested-by: Andy Shevchenko Signed-off-by: Hans de Goede Link: https://lore.kernel.org/r/20210621193648.44138-1-hdegoede@redhat.com --- drivers/platform/x86/think-lmi.c | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/drivers/platform/x86/think-lmi.c b/drivers/platform/x86/think-lmi.c index c6c9fbb8a53e..b57061079288 100644 --- a/drivers/platform/x86/think-lmi.c +++ b/drivers/platform/x86/think-lmi.c @@ -442,14 +442,9 @@ static ssize_t kbdlang_store(struct kobject *kobj, struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj); int length; - length = strlen(buf); - if (!length) - return -EINVAL; - - if (buf[length-1] == '\n') - length--; - - if (length >= TLMI_LANG_MAXLEN) + /* Calculate length till '\n' or terminating 0 */ + length = strchrnul(buf, '\n') - buf; + if (!length || length >= TLMI_LANG_MAXLEN) return -EINVAL; memcpy(setting->kbdlang, buf, length); -- cgit v1.2.3 From 1bcad8e510b27ad843315ab2c27ccf459e3acded Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Tue, 22 Jun 2021 15:07:54 -0500 Subject: platform/x86: think-lmi: Fix issues with duplicate attributes On an AMD based Lenovo T14, I find that the module doesn't work at all, and instead has a traceback with messages like: ``` sysfs: cannot create duplicate filename '/devices/virtual/firmware-attributes/thinklmi/attributes/Reserved' ``` Duplicate and reserved values showing up appear to be a firmware bug, but they shouldn't make the driver explode. So catch them and skip them. Fixes: a40cd7ef22fb ("platform/x86: think-lmi: Add WMI interface support on Lenovo platforms") Signed-off-by: Mario Limonciello Link: https://lore.kernel.org/r/20210622200755.12379-2-mario.limonciello@amd.com [hdegoede@redhat.com: Add missing kfree(tlmi_priv.setting[i])] Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/think-lmi.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/drivers/platform/x86/think-lmi.c b/drivers/platform/x86/think-lmi.c index b57061079288..bbfcaf23d420 100644 --- a/drivers/platform/x86/think-lmi.c +++ b/drivers/platform/x86/think-lmi.c @@ -691,6 +691,17 @@ static int tlmi_sysfs_init(void) if (!tlmi_priv.setting[i]) continue; + /* check for duplicate or reserved values */ + if (kset_find_obj(tlmi_priv.attribute_kset, tlmi_priv.setting[i]->display_name) || + !strcmp(tlmi_priv.setting[i]->display_name, "Reserved")) { + pr_debug("duplicate or reserved attribute name found - %s\n", + tlmi_priv.setting[i]->display_name); + kfree(tlmi_priv.setting[i]->possible_values); + kfree(tlmi_priv.setting[i]); + tlmi_priv.setting[i] = NULL; + continue; + } + /* Build attribute */ tlmi_priv.setting[i]->kobj.kset = tlmi_priv.attribute_kset; ret = kobject_init_and_add(&tlmi_priv.setting[i]->kobj, &tlmi_attr_setting_ktype, -- cgit v1.2.3 From 0fdf10e5fc964c315cf131a2eaab9cc531a9f40f Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Tue, 22 Jun 2021 15:07:55 -0500 Subject: platform/x86: think-lmi: Split current_value to reflect only the value Currently attributes will show things like: `BootOrderLock,Disable` rather than just `Disable`. Of course this works, but the attribute is intended to be read by userspace tools and not require further processing. That is a userspace tool can display a drop down of `possible_values` and `current_value` is one of them from the list. This also aligns `think-lmi` with how `dell-wmi-sysman` works. Signed-off-by: Mario Limonciello Link: https://lore.kernel.org/r/20210622200755.12379-3-mario.limonciello@amd.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/think-lmi.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/platform/x86/think-lmi.c b/drivers/platform/x86/think-lmi.c index bbfcaf23d420..4cab341a3538 100644 --- a/drivers/platform/x86/think-lmi.c +++ b/drivers/platform/x86/think-lmi.c @@ -492,14 +492,19 @@ static ssize_t display_name_show(struct kobject *kobj, struct kobj_attribute *at static ssize_t current_value_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { struct tlmi_attr_setting *setting = to_tlmi_attr_setting(kobj); - char *item; + char *item, *value; int ret; ret = tlmi_setting(setting->index, &item, LENOVO_BIOS_SETTING_GUID); if (ret) return ret; - ret = sysfs_emit(buf, "%s\n", item); + /* validate and split from `item,value` -> `value` */ + value = strpbrk(item, ","); + if (!value || value == item || !strlen(value + 1)) + return -EINVAL; + + ret = sysfs_emit(buf, "%s\n", value + 1); kfree(item); return ret; } -- cgit v1.2.3 From 23dcd7497c227a16acdda5e44f141fdc1e660f94 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Mon, 28 Jun 2021 11:36:00 +0200 Subject: platform/x86: think-lmi: Move kfree(setting->possible_values) to tlmi_attr_setting_release() We must not free the possible_values string before we have called sysfs_remove_group(kobj, &tlmi_attr_group) otherwise there is a race where a sysfs read of possible_values could reference the free-ed memory. Move the kfree(setting->possible_values) together with the free of the actual tlmi_attr_setting struct to avoid this race. Signed-off-by: Hans de Goede --- drivers/platform/x86/think-lmi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/platform/x86/think-lmi.c b/drivers/platform/x86/think-lmi.c index 4cab341a3538..3671b5d20613 100644 --- a/drivers/platform/x86/think-lmi.c +++ b/drivers/platform/x86/think-lmi.c @@ -626,6 +626,7 @@ static void tlmi_attr_setting_release(struct kobject *kobj) { struct tlmi_attr_setting *setting = to_tlmi_attr_setting(kobj); + kfree(setting->possible_values); kfree(setting); } @@ -654,7 +655,6 @@ static void tlmi_release_attr(void) /* Attribute structures */ for (i = 0; i < TLMI_SETTINGS_COUNT; i++) { if (tlmi_priv.setting[i]) { - kfree(tlmi_priv.setting[i]->possible_values); sysfs_remove_group(&tlmi_priv.setting[i]->kobj, &tlmi_attr_group); kobject_put(&tlmi_priv.setting[i]->kobj); } -- cgit v1.2.3 From caf23895ce96e90d8667328144344263ff0e7f1f Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Fri, 25 Jun 2021 16:01:04 +0300 Subject: platform/x86: intel_skl_int3472: Uninitialized variable in skl_int3472_handle_gpio_resources() This function returns negative error codes, zero (to indicate that everything has been completed successfully) and one (to indicate that more resources need to be handled still). This code prints an uninitialized error message when the function returns one which potentially leads to an Oops. Fixes: 5de691bffe57 ("platform/x86: Add intel_skl_int3472 driver") Signed-off-by: Dan Carpenter Reviewed-by: Daniel Scally Link: https://lore.kernel.org/r/YNXTkLNtiTDlFlZa@mwanda Signed-off-by: Hans de Goede --- drivers/platform/x86/intel/int3472/intel_skl_int3472_discrete.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/platform/x86/intel/int3472/intel_skl_int3472_discrete.c b/drivers/platform/x86/intel/int3472/intel_skl_int3472_discrete.c index 17c6fe830765..9fe0a2527e1c 100644 --- a/drivers/platform/x86/intel/int3472/intel_skl_int3472_discrete.c +++ b/drivers/platform/x86/intel/int3472/intel_skl_int3472_discrete.c @@ -286,10 +286,10 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares, int3472->ngpios++; ACPI_FREE(obj); - if (ret) + if (ret < 0) return dev_err_probe(int3472->dev, ret, err_msg); - return 0; + return ret; } static int skl_int3472_parse_crs(struct int3472_discrete_device *int3472) -- cgit v1.2.3 From 0e695c3f7f66c66e0a1da90cc5378198a656d494 Mon Sep 17 00:00:00 2001 From: Prasanth KSR Date: Mon, 28 Jun 2021 14:19:06 +0530 Subject: platform/x86: dell-wmi-sysman: Change user experience when Admin/System Password is modified Whenever user has changed an Admin/System Password using the sysfs, then we are automatically copying the new password to existing password field. Co-developed-by: Divya Bharathi Signed-off-by: Divya Bharathi Signed-off-by: Prasanth KSR Link: https://lore.kernel.org/r/20210628084906.4233-1-prasanth.ksr@dell.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/dell/dell-wmi-sysman/passwordattr-interface.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/platform/x86/dell/dell-wmi-sysman/passwordattr-interface.c b/drivers/platform/x86/dell/dell-wmi-sysman/passwordattr-interface.c index 339a082d6c18..86ec962aace9 100644 --- a/drivers/platform/x86/dell/dell-wmi-sysman/passwordattr-interface.c +++ b/drivers/platform/x86/dell/dell-wmi-sysman/passwordattr-interface.c @@ -95,9 +95,9 @@ int set_new_password(const char *password_type, const char *new) print_hex_dump_bytes("set new password data: ", DUMP_PREFIX_NONE, buffer, buffer_size); ret = call_password_interface(wmi_priv.password_attr_wdev, buffer, buffer_size); - /* clear current_password here and use user input from wmi_priv.current_password */ + /* on success copy the new password to current password */ if (!ret) - memset(current_password, 0, MAX_BUFF); + strscpy(current_password, new, MAX_BUFF); /* explain to user the detailed failure reason */ else if (ret == -EOPNOTSUPP) dev_err(&wmi_priv.password_attr_wdev->dev, "admin password must be configured\n"); -- cgit v1.2.3