Skip to content

Instantly share code, notes, and snippets.

@khalilst
Created February 15, 2026 13:13
Show Gist options
  • Select an option

  • Save khalilst/0454f6ef2086f6d79d095653fb90313f to your computer and use it in GitHub Desktop.

Select an option

Save khalilst/0454f6ef2086f6d79d095653fb90313f to your computer and use it in GitHub Desktop.
Fix: CS35L56 second amplifier not working on Intel Lunar Lake SPI
--- a/drivers/platform/x86/b/drivers/platform/x86/serial-multi-instantiate.c 2026-01-25 21:11:47.545238297 +0100
+++ b/drivers/platform/x86/serial-multi-instantiate.c 2026-02-15 01:30:16.053732768 +0100
@@ -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);
--- a/drivers/spi/b/drivers/spi/spi-pxa2xx.c 2026-01-23 22:39:27.224517135 +0100
+++ b/drivers/spi/spi-pxa2xx.c 2026-02-15 02:02:37.756595001 +0100
@@ -419,20 +419,43 @@
{
struct driver_data *drv_data =
spi_controller_get_devdata(spi->controller);
+ const struct lpss_config *config;
if (drv_data->ssp_type == CE4100_SSP) {
pxa2xx_spi_write(drv_data, SSSR, spi_get_chipselect(spi, 0));
return;
}
- if (is_lpss_ssp(drv_data))
- lpss_ssp_cs_control(spi, true);
+ if (is_lpss_ssp(drv_data)) {
+ config = lpss_get_config(drv_data);
+
+ if (spi_is_csgpiod(spi)) {
+ /*
+ * GPIO handles the actual chip select to the device.
+ * On LPSS controllers with dynamic clock gating, the
+ * SPI clock won't run unless the native CS state says
+ * "asserted" in the CS control register. Assert native
+ * CS in the register to enable the clock, and force
+ * the clock gate on.
+ */
+ lpss_ssp_cs_control(spi, true);
+ if (config->cs_clk_stays_gated) {
+ __lpss_ssp_update_priv(drv_data,
+ LPSS_PRIV_CLOCK_GATE,
+ LPSS_PRIV_CLOCK_GATE_CLK_CTL_MASK,
+ LPSS_PRIV_CLOCK_GATE_CLK_CTL_FORCE_ON);
+ }
+ } else {
+ lpss_ssp_cs_control(spi, true);
+ }
+ }
}
static void cs_deassert(struct spi_device *spi)
{
struct driver_data *drv_data =
spi_controller_get_devdata(spi->controller);
+ const struct lpss_config *config;
unsigned long timeout;
if (drv_data->ssp_type == CE4100_SSP)
@@ -444,8 +467,22 @@
!time_after(jiffies, timeout))
cpu_relax();
- if (is_lpss_ssp(drv_data))
- lpss_ssp_cs_control(spi, false);
+ if (is_lpss_ssp(drv_data)) {
+ config = lpss_get_config(drv_data);
+
+ if (spi_is_csgpiod(spi)) {
+ /* Deassert native CS and restore clock gating */
+ lpss_ssp_cs_control(spi, false);
+ if (config->cs_clk_stays_gated) {
+ __lpss_ssp_update_priv(drv_data,
+ LPSS_PRIV_CLOCK_GATE,
+ LPSS_PRIV_CLOCK_GATE_CLK_CTL_MASK,
+ LPSS_PRIV_CLOCK_GATE_CLK_CTL_FORCE_OFF);
+ }
+ } else {
+ lpss_ssp_cs_control(spi, false);
+ }
+ }
}
static void pxa2xx_spi_set_cs(struct spi_device *spi, bool level)

Fix: CS35L56 second amplifier not working on Intel Lunar Lake SPI

Problem

On HP laptops (and potentially ASUS) with Intel Lunar Lake processors using dual Cirrus Logic CS35L56 amplifiers over SPI, only the first amplifier (CS0) works. The second amplifier (CS1) fails with HALO_STATE=0x0 (firmware boot timeout). Speakers work correctly in Windows.

Affected bugs:

Root Cause

The ACPI DSDT has two bugs that combine to prevent the second amplifier from working:

Bug 1: Incomplete cs-gpios property on SPI controller

The SPI controller's _DSD declares:

"cs-gpios", Package (0x01) { Zero }

This only declares CS0 (native). The entry for CS1 is missing entirely. The ACPI GSPK device does define a GpioIo resource for the CS1 GPIO pin, and both SpiSerialBusV2 resources correctly specify DeviceSelection 0 and 1, but the SPI controller doesn't know about the GPIO for CS1.

Bug 2: SPI framework overwrites device GPIO CS from controller array

Even when serial-multi-instantiate correctly acquires the GPIO and calls spi_set_csgpiod() on the SPI device, the kernel's __spi_add_device() function unconditionally overwrites spi->cs_gpiod[] from ctlr->cs_gpiods[cs]:

if (ctlr->cs_gpiods) {
    for (idx = 0; idx < SPI_CS_CNT_MAX; idx++) {
        cs = spi_get_chipselect(spi, idx);
        if (is_valid_cs(cs))
            spi_set_csgpiod(spi, idx, ctlr->cs_gpiods[cs]);
    }
}

Since ctlr->cs_gpiods was allocated with only 1 entry (from the broken cs-gpios), accessing ctlr->cs_gpiods[1] reads NULL, wiping out the GPIO descriptor. The SPI framework then never toggles the GPIO chip select, and on Intel LPSS controllers with dynamic clock gating (cs_clk_stays_gated=true), the SPI clock doesn't run because no native CS is asserted.

Fix

Two patches are required:

Patch 1: serial-multi-instantiate - Set GPIO CS on controller array

Instead of setting the GPIO on the SPI device (which gets overwritten), set it on the controller's cs_gpiods array. This requires reallocating the array to accommodate the additional chip select. The SPI_CONTROLLER_GPIO_SS flag is set so the framework calls both the GPIO toggle and the controller's set_cs callback.

File: drivers/platform/x86/serial-multi-instantiate.c

Patch 2: spi-pxa2xx - Handle clock gating for GPIO CS devices

On Intel LPSS SPI controllers (Cannon Lake and later) with cs_clk_stays_gated, the SPI clock is gated when no native CS is asserted. When using GPIO chip select, the native CS register must still be programmed to "asserted" state, and the clock gate must be forced on. On deassert, restore both.

File: drivers/spi/spi-pxa2xx.c

Tested On

  • HP EliteBook 8 G1i (HP 8E2C)
  • Intel Core Ultra 7 258V (Lunar Lake-M)
  • PCI SPI Controller: 8086:a827 (LPSS_CNL_SSP via tgl_spi_info)
  • 2x Cirrus Logic CS35L56 Rev B0 OTP3 fw:3.4.4
  • ACPI HID: CSC3556
  • Kernel: 6.17.0-14-generic (Ubuntu)

Both amplifiers now probe successfully, load calibration data, and produce audio.

@KathyMorocco
Copy link

@khalilst

Thank you for your hard work. I look forward to this patch flowing into the kernel distribution so my Elitebook 8 G1i will have sound from the speakers.

@Fireye04
Copy link

Fireye04 commented Mar 4, 2026

Issue occurring on Lenovo Yoga 9 as well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment