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.

@tleveque69

Copy link
Copy Markdown

hey @khalilst Thank for the great research job you did here ๐Ÿ™ .
Thank to you i managed to fixed the AMP discovery issue, but i didn't managed to get the speaker to work :/

i have the following dmesg logs:

[   14.556190] cs35l56-hda spi0-CSC3554:00-cs35l54-hda.0: Cirrus Logic CS35L54 Rev B0 OTP4 fw:3.4.4 (patched=0)
[   14.585357] cs35l56-hda spi0-CSC3554:00-cs35l54-hda.0: DSP system name: '103C8D8F', amp name: 'AMP1'
[   14.604019] cs35l56-hda spi0-CSC3554:00-cs35l54-hda.1: Reset GPIO busy, assume shared reset
[   14.613357] cs35l56-hda spi0-CSC3554:00-cs35l54-hda.1: Cirrus Logic CS35L54 Rev B0 OTP4 fw:3.4.4 (patched=0)
[   14.651128] cs35l56-hda spi0-CSC3554:00-cs35l54-hda.1: DSP system name: '103C8D8F', amp name: 'AMP2'
[   15.340878] sof-audio-pci-intel-lnl 0000:00:1f.3: Digital mics found on Skylake+ platform, using SOF driver
[   15.340909] sof-audio-pci-intel-lnl 0000:00:1f.3: enabling device (0000 -> 0002)
[   15.341009] sof-audio-pci-intel-lnl 0000:00:1f.3: DSP detected with PCI class/subclass/prog-if 0x040100
[   16.117651] sof-audio-pci-intel-lnl 0000:00:1f.3: bound 0000:00:02.0 (ops intel_audio_component_bind_ops [xe])
[   16.125728] sof-audio-pci-intel-lnl 0000:00:1f.3: use msi interrupt mode
[   16.167650] sof-audio-pci-intel-lnl 0000:00:1f.3: hda codecs found, mask 5
[   16.167656] sof-audio-pci-intel-lnl 0000:00:1f.3: using HDA machine driver skl_hda_dsp_generic now
[   16.167659] sof-audio-pci-intel-lnl 0000:00:1f.3: NHLT device BT(0) detected, ssp_mask 0x4
[   16.167661] sof-audio-pci-intel-lnl 0000:00:1f.3: BT link detected in NHLT tables: 0x4
[   16.167663] sof-audio-pci-intel-lnl 0000:00:1f.3: DMICs detected in NHLT tables: 2
[   16.177267] sof-audio-pci-intel-lnl 0000:00:1f.3: Firmware paths/files for ipc type 1:
[   16.177284] sof-audio-pci-intel-lnl 0000:00:1f.3:  Firmware file:     intel/sof-ipc4/lnl/sof-lnl.ri
[   16.177291] sof-audio-pci-intel-lnl 0000:00:1f.3:  Firmware lib path: intel/sof-ipc4-lib/lnl
[   16.177297] sof-audio-pci-intel-lnl 0000:00:1f.3:  Topology file:     intel/sof-ipc4-tplg/sof-hda-generic-2ch.tplg
[   16.178033] sof-audio-pci-intel-lnl 0000:00:1f.3: Loaded firmware library: ADSPFW, version: 2.14.1.1
[   16.461638] sof-audio-pci-intel-lnl 0000:00:1f.3: Booted firmware version: 2.14.1.1
[   16.487495] sof-audio-pci-intel-lnl 0000:00:1f.3: Topology: ABI 3:29:1 Kernel ABI 3:23:1
[   16.487846] skl_hda_dsp_generic skl_hda_dsp_generic: ASoC: Parent card not yet available, widget card binding deferred
[   16.490822] sof-audio-pci-intel-lnl 0000:00:1f.3: Loaded firmware library: ADSPFW, version: 2.14.1.1
[   16.542708] snd_hda_codec_generic ehdaudio0D0: autoconfig for Generic: line_outs=1 (0x17/0x0/0x0/0x0/0x0) type:speaker
[   16.542718] snd_hda_codec_generic ehdaudio0D0:    speaker_outs=0 (0x0/0x0/0x0/0x0/0x0)
[   16.542721] snd_hda_codec_generic ehdaudio0D0:    hp_outs=1 (0x21/0x0/0x0/0x0/0x0)
[   16.542723] snd_hda_codec_generic ehdaudio0D0:    mono: mono_out=0x0
[   16.542724] snd_hda_codec_generic ehdaudio0D0:    inputs:
[   16.542726] snd_hda_codec_generic ehdaudio0D0:      Mic=0x19
[   16.547197] skl_hda_dsp_generic skl_hda_dsp_generic: hda_dsp_hdmi_build_controls: no PCM in topology for HDMI converter 3
[   16.563568] input: sof-hda-dsp Mic as /devices/pci0000:00/0000:00:1f.3/skl_hda_dsp_generic/sound/card0/input13
[   16.563643] input: sof-hda-dsp Headphone as /devices/pci0000:00/0000:00:1f.3/skl_hda_dsp_generic/sound/card0/input14
[   16.563753] input: sof-hda-dsp HDMI/DP,pcm=3 as /devices/pci0000:00/0000:00:1f.3/skl_hda_dsp_generic/sound/card0/input15
[   16.563805] input: sof-hda-dsp HDMI/DP,pcm=4 as /devices/pci0000:00/0000:00:1f.3/skl_hda_dsp_generic/sound/card0/input16
[   16.563871] input: sof-hda-dsp HDMI/DP,pcm=5 as /devices/pci0000:00/0000:00:1f.3/skl_hda_dsp_generic/sound/card0/input17

It seems like it is loading a random secondary driver for the speaker is it because i missed something in what you did ? or do i need additional steps ?

I'd be really gratefull if you help me ๐Ÿ™

@coosh

coosh commented Jun 23, 2026

Copy link
Copy Markdown

For NixOS users, add the following to your configuration.nix or relevant module.
Note: this will trigger a full kernel recompile which takes up to 1 hour on Lunar Lake hardware.

    boot.kernelPatches = [
      {
        name = "patch-01-serial-multi-instantiate-gpio-cs.patch";
        patch = builtins.fetchurl {
          url = "https://gist.githubusercontent.com/khalilst/0454f6ef2086f6d79d095653fb90313f/raw/14c5a932c7bba25f7d75bb44ab1e4d6d61e6a91b/patch-01-serial-multi-instantiate-gpio-cs.patch";
          sha256 = "173qafv65rf2xxbcndszk7lms952bv7xzrww4m9bx7ww3hfphip1";
        };
      }
      {
        name = "patch-02-spi-pxa2xx-gpio-cs-clock-gate.patch";
        patch = builtins.fetchurl {
          url = "https://gist.githubusercontent.com/khalilst/0454f6ef2086f6d79d095653fb90313f/raw/14c5a932c7bba25f7d75bb44ab1e4d6d61e6a91b/patch-02-spi-pxa2xx-gpio-cs-clock-gate.patch";
          sha256 = "03f90czz18qjafdfs6ln38lmay60vqxcig8n6xlaa8dq0jy9ixnr";
        };
      }
    ];

Also worth mentioning:

  • The sha256 hashes are pinned to a specific gist commit, so if the gist ever changes the hashes will need updating
  • Once this patch lands upstream you can remove it
  • If it does not work on a later kernel try adding boot.kernelPackages = pkgs.linuxPackages_6_18;

Tested On

  • HP EliteBook 8 G1i 14 inch Notebook Next Gen AI PC
  • Intel Core Ultra 5 228V (Lunar Lake-M)
  • PCI SPI Controller: 8086:a827 (Intel Lunar Lake-M Serial IO SPI Controller #0, intel-lpss)
  • 2x Cirrus Logic CS35L54 Rev B0 OTP4 fw:3.4.4
  • ACPI HID: CSC3554
  • NixOS 26.05, kernel 6.18.35
  • Patches applied via boot.kernelPatches in NixOS configuration

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