[RFC PATCH 1/2] platform/x86: serial-multi-instantiate: Add GPIO chip select support for SPI devices
From: Khalil
Date: Sun Feb 15 2026 - 08:57:11 EST
To: broonie@xxxxxxxxxx, hdegoede@xxxxxxxxxx, ilpo.jarvinen@xxxxxxxxxxxxxxx
Cc: rf@xxxxxxxxxxxxxxxxxxxxx, linux-spi@xxxxxxxxxxxxxxx,
platform-driver-x86@xxxxxxxxxxxxxxx, linux-kernel@xxxxxxxxxxxxxxx
Some ACPI platforms (e.g., HP/ASUS laptops with Intel Lunar Lake and
dual CS35L56 amplifiers) have a broken cs-gpios property in the SPI
controller's _DSD that only declares the first chip select. The second
chip select GPIO is defined as a GpioIo resource in the peripheral's
ACPI node but is not referenced by the controller.
This causes the SPI framework to have no GPIO descriptor for CS > 0,
so the chip select line is never toggled for the second device.
Fix this by acquiring the chip select GPIO from the ACPI GpioIo resource
in serial-multi-instantiate and installing it on the controller's
cs_gpiods array. The GPIO must be set on the controller (not the device)
because __spi_add_device() unconditionally overwrites spi->cs_gpiod[]
from ctlr->cs_gpiods[cs].
The controller's cs_gpiods array is reallocated to accommodate the
additional chip select, and SPI_CONTROLLER_GPIO_SS is set so the
framework calls both the GPIO toggle and the controller's set_cs
callback (needed for clock gate management on Intel LPSS controllers).
Link: https://bugzilla.kernel.org/show_bug.cgi?id=221064
Link: https://bugs.launchpad.net/ubuntu/+source/alsa-driver/+bug/2131138
Link: https://github.com/thesofproject/linux/issues/5621
Signed-off-by: Khalil Laleh <khalilst@xxxxxxxxx>
---
drivers/platform/x86/serial-multi-instantiate.c | 83 ++++++++++++++++++
1 file changed, 83 insertions(+)
--- a/drivers/platform/x86/serial-multi-instantiate.c
+++ b/drivers/platform/x86/serial-multi-instantiate.c
@@ -8,6 +8,9 @@
#include <linux/acpi.h>
#include <linux/bits.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/gpio/machine.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
@@ -17,6 +20,17 @@
#include <linux/spi/spi.h>
#include <linux/types.h>
+/*
+ * ACPI GPIO mapping for the chip select GpioIo resource.
+ * Maps "cs-gpios" to the first GpioIo resource in the ACPI _CRS.
+ */
+static const struct acpi_gpio_params smi_cs_gpio_params = { 0, 0, false };
+
+static const struct acpi_gpio_mapping smi_cs_gpio_mapping[] = {
+ { "cs-gpios", &smi_cs_gpio_params, 1 },
+ { }
+};
+
#define IRQ_RESOURCE_TYPE GENMASK(1, 0)
#define IRQ_RESOURCE_NONE 0
#define IRQ_RESOURCE_GPIO 1
@@ -114,8 +128,11 @@
struct acpi_device *adev = ACPI_COMPANION(dev);
struct spi_controller *ctlr;
struct spi_device *spi_dev;
+ struct gpio_desc *cs_gpio = NULL;
char name[50];
int i, ret, count;
+ bool gpio_mapped = false;
+ u16 cs;
ret = acpi_spi_count_resources(adev);
if (ret < 0)
@@ -139,6 +156,72 @@
}
ctlr = spi_dev->controller;
+ cs = spi_get_chipselect(spi_dev, 0);
+
+ if (cs >= ctlr->num_chipselect) {
+ dev_info(dev, "Increasing num_chipselect from %u to %u for CS%u\n",
+ ctlr->num_chipselect, cs + 1, cs);
+ ctlr->num_chipselect = cs + 1;
+ }
+
+ /*
+ * For devices with CS > 0, use GPIO chip select from ACPI
+ * GpioIo resource. The GPIO must be set on the controller's
+ * cs_gpiods array (not the device's cs_gpiod) because
+ * __spi_add_device() overwrites spi->cs_gpiod from
+ * ctlr->cs_gpiods[cs]. Also reallocate the array since
+ * a broken ACPI cs-gpios property may have undersized it.
+ *
+ * SPI_CONTROLLER_GPIO_SS ensures the framework calls both
+ * the GPIO toggle and controller->set_cs (for clock gate
+ * management on Intel LPSS controllers).
+ */
+ if (cs > 0 && !gpio_mapped) {
+ ret = devm_acpi_dev_add_driver_gpios(dev, smi_cs_gpio_mapping);
+ if (ret)
+ dev_warn(dev, "Failed to add GPIO mapping: %d\n", ret);
+ else
+ gpio_mapped = true;
+ }
+
+ if (cs > 0 && gpio_mapped && !cs_gpio) {
+ cs_gpio = devm_gpiod_get(dev, "cs", GPIOD_OUT_HIGH);
+ if (IS_ERR(cs_gpio)) {
+ dev_warn(dev, "Failed to get CS GPIO: %ld\n",
+ PTR_ERR(cs_gpio));
+ cs_gpio = NULL;
+ } else {
+ dev_info(dev, "Got CS GPIO for amp CS%u\n", cs);
+ }
+ }
+
+ if (cs > 0 && cs_gpio) {
+ /*
+ * Set GPIO on the controller's cs_gpiods array so
+ * __spi_add_device() will propagate it to the device.
+ * Reallocate the array to fit the new num_chipselect.
+ */
+ struct gpio_desc **new_gpiods;
+
+ new_gpiods = devm_kcalloc(&ctlr->dev,
+ ctlr->num_chipselect,
+ sizeof(*new_gpiods),
+ GFP_KERNEL);
+ if (new_gpiods) {
+ if (ctlr->cs_gpiods)
+ new_gpiods[0] = ctlr->cs_gpiods[0];
+ new_gpiods[cs] = cs_gpio;
+ ctlr->cs_gpiods = new_gpiods;
+ ctlr->flags |= SPI_CONTROLLER_GPIO_SS;
+ dev_info(dev, "Set GPIO CS on controller for CS%u\n", cs);
+ } else {
+ dev_err(dev, "Failed to alloc cs_gpiods\n");
+ }
+ }
+
+ dev_info(dev, "Device %d: CS=%u, num_chipselect=%u, gpio_cs=%s\n",
+ i, cs, ctlr->num_chipselect,
+ (cs > 0 && cs_gpio) ? "yes" : "no");
strscpy(spi_dev->modalias, inst_array[i].type);