Compare commits

...

7 Commits

Author SHA1 Message Date
MassZero0
e00c8a7d38 linux: mts: fix NAPI poll storm that pins a CPU when the NIC MSI never fires (#14)
The PS5 GbE NIC has no INTx pin and its MSI sometimes never fires after a
cold boot. The driver already works around that with a 1 Hz link-poll
watchdog that kicks NAPI by hand.

But gbe_poll() re-checks GBE_REG_ISR after completing NAPI and reschedules
itself whenever a GBE_IER_MASK cause is still latched, without ever
acknowledging (W1C) the ISR -- only the hardware handler gbe_isr() does.
When the MSI is dead gbe_isr() never runs, so the latched cause (e.g.
RX_DONE) is never cleared, the re-check stays true forever, and NAPI
reschedules itself ~30k times/s, pinning ksoftirqd on that CPU at 100%.

Acknowledge the pending causes before re-kicking so a stale, undelivered
cause can no longer spin NAPI.

While the MSI is dead also poll the rings every GBE_POLL_FALLBACK_MS (10 ms)
instead of once per second, so interactive RX latency drops from ~1 s to a
few ms; fall back to HZ as soon as real IRQs start arriving.
2026-06-01 21:54:28 +02:00
Andy Nguyen
45c3a2ec19 linux: improve hdmi sequence in amdgpu dm 2026-05-31 21:47:54 +02:00
rmux
61eb7c0f71 Add buzzer driver for PS5 chassis piezo (#12)
thanks to cow for testing. legend as usual.
2026-05-30 23:11:28 +02:00
Andy Nguyen
5b0dc56c1b linux: fix cu unlock patches 2026-05-30 21:47:59 +02:00
Andy Nguyen
bcf718f3d5 linux: improve icc code 2026-05-30 21:40:20 +02:00
Andy Nguyen
52b956d985 config: enable NFT_QUEUE and NFT_TUNNEL 2026-05-29 22:49:47 +02:00
Andy Nguyen
e9424efb51 linux: add icc_device_power_* functions and fix black screen 2026-05-29 22:43:06 +02:00
2 changed files with 495 additions and 108 deletions

10
.config
View File

@@ -1408,8 +1408,8 @@ CONFIG_NFT_LIMIT=m
CONFIG_NFT_MASQ=m
CONFIG_NFT_REDIR=m
CONFIG_NFT_NAT=m
# CONFIG_NFT_TUNNEL is not set
# CONFIG_NFT_QUEUE is not set
CONFIG_NFT_TUNNEL=m
CONFIG_NFT_QUEUE=m
# CONFIG_NFT_QUOTA is not set
CONFIG_NFT_REJECT=m
CONFIG_NFT_REJECT_INET=m
@@ -2015,6 +2015,7 @@ CONFIG_PNPACPI=y
CONFIG_BLK_DEV=y
# CONFIG_BLK_DEV_NULL_BLK is not set
# CONFIG_BLK_DEV_FD is not set
CONFIG_CDROM=y
# CONFIG_BLK_DEV_PCIESSD_MTIP32XX is not set
CONFIG_ZRAM=m
CONFIG_ZRAM_BACKEND_LZ4=y
@@ -2132,7 +2133,6 @@ CONFIG_SCSI_PROC_FS=y
#
CONFIG_BLK_DEV_SD=y
# CONFIG_CHR_DEV_ST is not set
CONFIG_CDROM=y
CONFIG_BLK_DEV_SR=y
CONFIG_CHR_DEV_SG=y
CONFIG_BLK_DEV_BSG=y
@@ -2171,7 +2171,7 @@ CONFIG_SATA_PMP=y
# CONFIG_SATA_AHCI is not set
# CONFIG_SATA_AHCI_PLATFORM is not set
# CONFIG_AHCI_DWC is not set
CONFIG_AHCI_SALINA=y
CONFIG_AHCI_SALINA=m
# CONFIG_SATA_INIC162X is not set
# CONFIG_SATA_ACARD_AHCI is not set
# CONFIG_SATA_SIL24 is not set
@@ -3121,6 +3121,7 @@ CONFIG_SENSORS_K10TEMP=m
# CONFIG_SENSORS_CHIPCAP2 is not set
# CONFIG_SENSORS_CORSAIR_CPRO is not set
# CONFIG_SENSORS_CORSAIR_PSU is not set
# CONFIG_SENSORS_DRIVETEMP is not set
# CONFIG_SENSORS_DS620 is not set
# CONFIG_SENSORS_DS1621 is not set
# CONFIG_SENSORS_DELL_SMM is not set
@@ -4272,6 +4273,7 @@ CONFIG_LEDS_CLASS_MULTICOLOR=m
CONFIG_LEDS_TRIGGERS=y
# CONFIG_LEDS_TRIGGER_TIMER is not set
# CONFIG_LEDS_TRIGGER_ONESHOT is not set
# CONFIG_LEDS_TRIGGER_DISK is not set
# CONFIG_LEDS_TRIGGER_HEARTBEAT is not set
# CONFIG_LEDS_TRIGGER_BACKLIGHT is not set
# CONFIG_LEDS_TRIGGER_CPU is not set

View File

@@ -506,10 +506,10 @@ index 20e6645ab737..bc98a92325b7 100644
obj-$(CONFIG_PDC_ADMA) += pdc_adma.o
diff --git a/drivers/ata/ahci_salina.c b/drivers/ata/ahci_salina.c
new file mode 100644
index 000000000000..1ba1ba77f86d
index 000000000000..c020194eb44a
--- /dev/null
+++ b/drivers/ata/ahci_salina.c
@@ -0,0 +1,357 @@
@@ -0,0 +1,340 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * PlayStation 5 Salina SATA / BD-ROM AHCI host driver
@@ -530,30 +530,10 @@ index 000000000000..1ba1ba77f86d
+#include "ahci.h"
+#include "ahci_salina_phy.h"
+
+#define SALINA_ICC_POWER_SVC 0x05
+#define SALINA_ICC_POWER_SET 0x00
+#define PCI_DEVICE_ID_SPCIE 0x9107
+
+#define SALINA_ICC_DEV_BD 0x01
+
+static void salina_bd_icc_power_on(struct device *dev)
+{
+ u8 q[ICC_MSG_MAX_SIZE] = {0};
+ u8 r[ICC_MSG_MAX_SIZE] = {0};
+ struct icc_msg *m = (struct icc_msg *)q;
+ int rc;
+
+ m->service_id = SALINA_ICC_POWER_SVC;
+ m->msg_type = SALINA_ICC_POWER_SET;
+ m->length = 0x20;
+ m->data[0] = SALINA_ICC_DEV_BD;
+ m->data[1] = 0x01;
+
+ rc = icc_query(q, r);
+ if (rc == -ETIMEDOUT)
+ dev_info(dev, "BD drive already powered on\n");
+ else if (rc)
+ dev_warn(dev, "ICC BD power-on returned %d\n", rc);
+}
+
+struct salina_ahci {
+ struct salina_sata_phy phy;
+ struct pci_dev *glue;
@@ -563,7 +543,7 @@ index 000000000000..1ba1ba77f86d
+{
+ struct pci_dev *glue;
+
+ glue = pci_get_device(SALINA_VENDOR_ID, SALINA_GLUE_ID, NULL);
+ glue = pci_get_device(PCI_VENDOR_ID_SONY, PCI_DEVICE_ID_SPCIE, NULL);
+ if (!glue)
+ return -ENODEV;
+
@@ -612,7 +592,7 @@ index 000000000000..1ba1ba77f86d
+static int salina_pick_bar(u16 devid, u32 chip_id, unsigned int *abar,
+ u32 *port_off)
+{
+ bool is_9106 = (devid == SALINA_SATA_ID_B);
+ bool is_9106 = (devid == PCI_DEVICE_ID_AHCI_B);
+
+ if (!is_9106 && chip_id == SALINA_CHIP_SALINA2)
+ return -ENODEV;
@@ -646,6 +626,7 @@ index 000000000000..1ba1ba77f86d
+ struct salina_ahci *sa;
+ unsigned int abar, n_ports;
+ u32 chip_id;
+ u8 state;
+ int rc;
+
+ if (!spcie_is_initialized()) {
@@ -699,7 +680,9 @@ index 000000000000..1ba1ba77f86d
+ sa->phy.rx_tracelen = 0xff;
+ sa->phy.tx_tracelen = 0xff;
+
+ salina_bd_icc_power_on(dev);
+ icc_device_power_get(SALINA_ICC_DEV_BD, &state);
+ if (state != 1)
+ icc_device_power_control(SALINA_ICC_DEV_BD, 1);
+
+ rc = salina_sata_phy_init(&sa->phy);
+ if (rc) {
@@ -845,8 +828,8 @@ index 000000000000..1ba1ba77f86d
+static SIMPLE_DEV_PM_OPS(salina_ahci_pm, salina_ahci_suspend, salina_ahci_resume);
+
+static const struct pci_device_id salina_ahci_tbl[] = {
+ { PCI_DEVICE(SALINA_VENDOR_ID, SALINA_SATA_ID_A) },
+ { PCI_DEVICE(SALINA_VENDOR_ID, SALINA_SATA_ID_B) },
+ { PCI_DEVICE(PCI_VENDOR_ID_SONY, PCI_DEVICE_ID_AHCI_A) },
+ { PCI_DEVICE(PCI_VENDOR_ID_SONY, PCI_DEVICE_ID_AHCI_B) },
+ { }
+};
+MODULE_DEVICE_TABLE(pci, salina_ahci_tbl);
@@ -1058,10 +1041,10 @@ index 000000000000..c5e85ae102ea
+}
diff --git a/drivers/ata/ahci_salina_phy.h b/drivers/ata/ahci_salina_phy.h
new file mode 100644
index 000000000000..71a33382d920
index 000000000000..39cc76f39e06
--- /dev/null
+++ b/drivers/ata/ahci_salina_phy.h
@@ -0,0 +1,47 @@
@@ -0,0 +1,45 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef _SALINA_SATA_PHY_H
+#define _SALINA_SATA_PHY_H
@@ -1069,10 +1052,8 @@ index 000000000000..71a33382d920
+#include <linux/types.h>
+#include <linux/io.h>
+
+#define SALINA_VENDOR_ID 0x104d
+#define SALINA_SATA_ID_A 0x9105
+#define SALINA_SATA_ID_B 0x9106
+#define SALINA_GLUE_ID 0x9107
+#define PCI_DEVICE_ID_AHCI_A 0x9105
+#define PCI_DEVICE_ID_AHCI_B 0x9106
+
+#define SALINA_DEVID_A 0x9105104d
+#define SALINA_DEVID_B 0x9106104d
@@ -1347,7 +1328,7 @@ index 321310ba2c08..764da52902ac 100644
header = (const struct common_firmware_header *)
adev->sdma.instance[instance].fw->data;
diff --git a/drivers/gpu/drm/amd/amdgpu/gfx_v10_0.c b/drivers/gpu/drm/amd/amdgpu/gfx_v10_0.c
index 8b60299b73ef..db803401a3be 100644
index 8b60299b73ef..7ecf928cae8e 100644
--- a/drivers/gpu/drm/amd/amdgpu/gfx_v10_0.c
+++ b/drivers/gpu/drm/amd/amdgpu/gfx_v10_0.c
@@ -4202,6 +4202,14 @@ static void gfx_v10_0_check_gfxoff_flag(struct amdgpu_device *adev)
@@ -1425,7 +1406,7 @@ index 8b60299b73ef..db803401a3be 100644
amdgpu_gfx_cp_init_microcode(adev, AMDGPU_UCODE_ID_CP_MEC2);
amdgpu_gfx_cp_init_microcode(adev, AMDGPU_UCODE_ID_CP_MEC2_JT);
} else {
@@ -10104,6 +10131,24 @@ static u32 gfx_v10_0_get_cu_active_bitmap_per_sh(struct amdgpu_device *adev)
@@ -10104,6 +10130,21 @@ static u32 gfx_v10_0_get_cu_active_bitmap_per_sh(struct amdgpu_device *adev)
return cu_active_bitmap;
}
@@ -1439,27 +1420,22 @@ index 8b60299b73ef..db803401a3be 100644
+ * amdgpu.ps5_cu_unlock=0 (disable)
+ * amdgpu.ps5_cu_unlock=1 (probe)
+ */
+static int ps5_cu_unlock = 3;
+static int ps5_cu_unlock = 0;
+module_param(ps5_cu_unlock, int, 0444);
+MODULE_PARM_DESC(ps5_cu_unlock,
+ "PS5 harvested-CU unlock (0=off, 1=probe, 2=SE0/SH0, 3=all (default), 4=probe-all)");
+
+// PCI device ID of the silicon this unlock applies to.
+#define PS5_PCI_DEVID_RETAIL 0x13FB
+ "PS5 harvested-CU unlock (0=off (default), 1=probe, 2=SE0/SH0, 3=all, 4=probe-all)");
+
static int gfx_v10_0_get_cu_info(struct amdgpu_device *adev,
struct amdgpu_cu_info *cu_info)
{
@@ -10117,6 +10162,45 @@ static int gfx_v10_0_get_cu_info(struct amdgpu_device *adev,
@@ -10117,6 +10158,43 @@ static int gfx_v10_0_get_cu_info(struct amdgpu_device *adev,
amdgpu_gfx_parse_disable_cu(adev, disable_masks, 4, 2);
mutex_lock(&adev->grbm_idx_mutex);
+ /* PS5: unlock harvested CUs. CC alone updates enumeration but SPI
+ * still dispatches only to enabled WGPs; both writes are required
+ * (verified empirically on BC-250). */
+ if (ps5_cu_unlock > 0 &&
+ adev->pdev->vendor == PCI_VENDOR_ID_ATI &&
+ adev->pdev->device == PS5_PCI_DEVID_RETAIL) {
+ if (ps5_cu_unlock > 0) {
+ int ps5_se, ps5_sh;
+ for (ps5_se = 0; ps5_se < adev->gfx.config.max_shader_engines; ps5_se++) {
+ for (ps5_sh = 0; ps5_sh < adev->gfx.config.max_sh_per_se; ps5_sh++) {
@@ -1529,7 +1505,7 @@ index e3a035c9fece..6eb67dd7b52c 100644
if (r)
return r;
diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
index 0aee65503642..1ca2bcd7755d 100644
index 0aee65503642..3ae712111a93 100644
--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
+++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c
@@ -80,6 +80,10 @@
@@ -1590,33 +1566,44 @@ index 0aee65503642..1ca2bcd7755d 100644
if ((mode->flags & DRM_MODE_FLAG_INTERLACE) ||
(mode->flags & DRM_MODE_FLAG_DBLSCAN))
return result;
@@ -10458,6 +10479,9 @@ static void amdgpu_dm_commit_streams(struct drm_atomic_state *state,
new_crtc_state, i) {
struct amdgpu_crtc *acrtc = to_amdgpu_crtc(crtc);
@@ -10889,12 +10910,58 @@ static void amdgpu_dm_atomic_commit_tail(struct drm_atomic_state *state)
drm_atomic_helper_update_legacy_modeset_state(dev, state);
drm_dp_mst_atomic_wait_for_dependencies(state);
+#ifdef CONFIG_X86_PS5
+ for_each_oldnew_crtc_in_state(state, crtc, old_crtc_state,
+ new_crtc_state, i) {
+ dm_new_crtc_state = to_dm_crtc_state(new_crtc_state);
+#endif
dm_old_crtc_state = to_dm_crtc_state(old_crtc_state);
if (old_crtc_state->active &&
@@ -10465,6 +10489,11 @@ static void amdgpu_dm_commit_streams(struct drm_atomic_state *state,
drm_atomic_crtc_needs_modeset(new_crtc_state))) {
manage_dm_interrupts(adev, acrtc, NULL);
dc_stream_release(dm_old_crtc_state->stream);
+#ifdef CONFIG_X86_PS5
+ dm_old_crtc_state = to_dm_crtc_state(old_crtc_state);
+
+ if ((new_crtc_state->active &&
+ (!old_crtc_state->active ||
+ drm_atomic_crtc_needs_modeset(new_crtc_state))) ||
+ (old_crtc_state->active &&
+ (!new_crtc_state->active ||
+ drm_atomic_crtc_needs_modeset(new_crtc_state)))) {
+ if (dm_new_crtc_state->stream && dc_is_dp_signal(dm_new_crtc_state->stream->signal)) {
+ sceHdmiInitVideoConfig();
+ }
+ }
+ }
+#endif
}
+
dm_state = dm_atomic_get_new_state(state);
if (dm_state && dm_state->context) {
dc_state = dm_state->context;
amdgpu_dm_commit_streams(state, dc_state);
}
@@ -11035,6 +11064,22 @@ static void amdgpu_dm_atomic_commit_tail(struct drm_atomic_state *state)
dc_stream_retain(dm_new_crtc_state->stream);
acrtc->dm_irq_params.stream = dm_new_crtc_state->stream;
manage_dm_interrupts(adev, acrtc, dm_new_crtc_state);
+#ifdef CONFIG_X86_PS5
+ for_each_oldnew_crtc_in_state(state, crtc, old_crtc_state,
+ new_crtc_state, i) {
+ dm_new_crtc_state = to_dm_crtc_state(new_crtc_state);
+ dm_old_crtc_state = to_dm_crtc_state(old_crtc_state);
+
+ if (new_crtc_state->active &&
+ (!old_crtc_state->active ||
+ drm_atomic_crtc_needs_modeset(new_crtc_state))) {
+ if (dm_new_crtc_state->stream && dc_is_dp_signal(dm_new_crtc_state->stream->signal)) {
+ struct dc_stream_state *stream = dm_new_crtc_state->stream;
+ int channels = 0;
@@ -1631,10 +1618,13 @@ index 0aee65503642..1ca2bcd7755d 100644
+ sceHdmiSetAudioConfig(channels);
+ sceHdmiSetAudioMute(0);
+ }
+ }
+ }
+#endif
}
/* Handle vrr on->off / off->on transitions */
amdgpu_dm_handle_vrr_transition(dm_old_crtc_state, dm_new_crtc_state);
+
amdgpu_dm_update_hdcp(state);
/* Handle connector state changes */
diff --git a/drivers/gpu/drm/amd/display/dc/core/dc_resource.c b/drivers/gpu/drm/amd/display/dc/core/dc_resource.c
index 896f4719cf7b..0eb818421a50 100644
--- a/drivers/gpu/drm/amd/display/dc/core/dc_resource.c
@@ -1877,7 +1867,7 @@ new file mode 100644
index 000000000000..e846b6b0edbc
--- /dev/null
+++ b/drivers/net/phy/mts/mts.h
@@ -0,0 +1,181 @@
@@ -0,0 +1,186 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * PlayStation 5 Gigabit Ethernet driver
@@ -1949,6 +1939,11 @@ index 000000000000..e846b6b0edbc
+
+#define GBE_COAL_VAL 0x10001388
+
+/* this chip has no intx pin and its msi sometimes never fires (cold boot). when
+ * we detect that, the link_poll work becomes the only way to service the rings,
+ * so it polls this often (ms) instead of once a second to stay responsive. */
+#define GBE_POLL_FALLBACK_MS 10
+
+/* excludes bit2 (LINK) - polling link state avoids IRQ storm from autoneg pulses */
+#define GBE_IER_MASK 0x5014fa
+
@@ -2064,7 +2059,7 @@ new file mode 100644
index 000000000000..4250a25d7260
--- /dev/null
+++ b/drivers/net/phy/mts/mts_main.c
@@ -0,0 +1,810 @@
@@ -0,0 +1,823 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * PlayStation 5 Gigabit Ethernet driver
@@ -2539,26 +2534,32 @@ index 000000000000..4250a25d7260
+{
+ struct gbe_priv *p = container_of(to_delayed_work(work),
+ struct gbe_priv, link_poll);
+ unsigned long delay = HZ;
+
+ gbe_link_change(p);
+
+ /*
+ * on a cold boot the chip sometimes just never fires its first irq and
+ * napi sits there doing nothing. if the irq count hasnt moved since last tick,
+ * we kick it by hand and re-arm the mask.
+ * napi sits there doing nothing. if the irq count hasnt moved since last
+ * tick the msi is dead -- kick napi by hand and re-arm the mask. msi is the
+ * only irq source (no intx pin), so while it stays dead we keep polling fast
+ * (GBE_POLL_FALLBACK_MS) instead of once a second, or interactive rx lags by
+ * up to a second. as soon as real irqs start arriving we drop back to HZ.
+ */
+ if (netif_running(p->netdev) && p->cnt_irq == p->irq_watchdog) {
+ napi_schedule(&p->napi);
+ gbe_wr(p, GBE_REG_IER, GBE_IER_MASK);
+ delay = max_t(unsigned long, 1, msecs_to_jiffies(GBE_POLL_FALLBACK_MS));
+ }
+ p->irq_watchdog = p->cnt_irq;
+
+ if (++p->poll_tick >= 10) {
+ /* dump stats roughly every 10s regardless of the current poll cadence */
+ if (++p->poll_tick >= 10 * HZ / delay) {
+ gbe_dump_stats(p);
+ p->poll_tick = 0;
+ }
+
+ schedule_delayed_work(&p->link_poll, HZ);
+ schedule_delayed_work(&p->link_poll, delay);
+}
+
+/*
@@ -2606,11 +2607,18 @@ index 000000000000..4250a25d7260
+ rx_done = gbe_rx_poll(p, budget);
+
+ if (rx_done < budget) {
+ u32 isr;
+
+ napi_complete_done(napi, rx_done);
+ gbe_wr(p, GBE_REG_IER, GBE_IER_MASK);
+ /* chip wont send a fresh msi for stuff that landed while ier was
+ off, so re-check isr and re-kick or interrupts just freeze */
+ if (gbe_rd(p, GBE_REG_ISR) & GBE_IER_MASK) {
+ off, so re-check isr and re-kick or interrupts just freeze.
+ ACK the causes (W1C) first: otherwise a latched/undelivered
+ cause (the msi-silent cold-boot case) keeps this condition
+ permanently true and spins napi at ~30k polls/s, pinning a cpu. */
+ isr = gbe_rd(p, GBE_REG_ISR) & GBE_IER_MASK;
+ if (isr) {
+ gbe_wr(p, GBE_REG_ISR, isr);
+ if (napi_schedule_prep(&p->napi)) {
+ gbe_wr(p, GBE_REG_IER, 0);
+ __napi_schedule(&p->napi);
@@ -3306,11 +3314,11 @@ index b4707640e102..7b854e5bb8ff 100644
diff --git a/drivers/ps5/Makefile b/drivers/ps5/Makefile
new file mode 100644
index 000000000000..7acb030a4976
index 000000000000..ff1bbbbf219c
--- /dev/null
+++ b/drivers/ps5/Makefile
@@ -0,0 +1 @@
+obj-y += spcie.o tpcie.o hdmi.o mp1.o
+obj-y += spcie.o tpcie.o hdmi.o mp1.o buzzer.o
diff --git a/drivers/ps5/autoservo_param.h b/drivers/ps5/autoservo_param.h
new file mode 100644
index 000000000000..d2c25e461bf7
@@ -5219,12 +5227,309 @@ index 000000000000..d2c25e461bf7
+};
+
+#endif /* _AUTOSERVO_PARAM_H */
diff --git a/drivers/ps5/buzzer.c b/drivers/ps5/buzzer.c
new file mode 100644
index 000000000000..f48ad6f92534
--- /dev/null
+++ b/drivers/ps5/buzzer.c
@@ -0,0 +1,291 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/miscdevice.h>
+#include <linux/fs.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <linux/input.h>
+#include <linux/delay.h>
+#include <linux/kthread.h>
+#include <linux/mutex.h>
+#include <linux/device.h>
+#include <linux/ps5.h>
+
+#define BUZZER_IOC_MAGIC 'B'
+
+#define PS5_BUZZ_SILENT 0
+#define PS5_BUZZ_SHORT 1
+#define PS5_BUZZ_ERROR 2
+#define PS5_BUZZ_LONG 3
+
+struct ps5_buzzer_step {
+ __u8 value;
+ __u16 hold_ms;
+} __attribute__((packed));
+
+struct ps5_buzzer_pattern {
+ __u32 nsteps;
+ struct ps5_buzzer_step steps[];
+};
+
+#define PS5_BUZZER_BEEP _IOW(BUZZER_IOC_MAGIC, 1, __u8)
+#define PS5_BUZZER_PLAY _IOW(BUZZER_IOC_MAGIC, 2, struct ps5_buzzer_pattern)
+#define PS5_BUZZER_STOP _IO(BUZZER_IOC_MAGIC, 3)
+
+#define MAX_PATTERN_STEPS 1024
+
+static struct input_dev *snd_idev;
+static struct task_struct *play_thread;
+static DEFINE_MUTEX(play_lock);
+
+static int ps5_buzz_raw(u8 level)
+{
+ u8 buf[ICC_MSG_MAX_SIZE] = {};
+ struct icc_msg *msg = (struct icc_msg *)buf;
+
+ if (level > PS5_BUZZ_LONG)
+ return -EINVAL;
+
+ msg->service_id = ICC_SERVICE_ID_INDICATOR;
+ msg->msg_type = 0x00;
+ msg->length = 0x20;
+ msg->data[0] = level;
+
+ return icc_query(buf, buf);
+}
+
+struct play_ctx {
+ struct ps5_buzzer_step *steps;
+ u32 nsteps;
+};
+
+static int play_fn(void *data)
+{
+ struct play_ctx *ctx = data;
+ u32 i;
+
+ for (i = 0; i < ctx->nsteps && !kthread_should_stop(); i++) {
+ ps5_buzz_raw(ctx->steps[i].value);
+ if (ctx->steps[i].hold_ms)
+ msleep_interruptible(ctx->steps[i].hold_ms);
+ }
+
+ kfree(ctx->steps);
+ kfree(ctx);
+
+ mutex_lock(&play_lock);
+ play_thread = NULL;
+ mutex_unlock(&play_lock);
+ return 0;
+}
+
+static int play_start(struct ps5_buzzer_step *steps, u32 nsteps)
+{
+ struct play_ctx *ctx;
+ struct task_struct *t;
+
+ mutex_lock(&play_lock);
+ if (play_thread) {
+ mutex_unlock(&play_lock);
+ return -EBUSY;
+ }
+
+ ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
+ if (!ctx) {
+ mutex_unlock(&play_lock);
+ return -ENOMEM;
+ }
+ ctx->steps = steps;
+ ctx->nsteps = nsteps;
+
+ t = kthread_run(play_fn, ctx, "ps5_buzz_play");
+ if (IS_ERR(t)) {
+ kfree(ctx);
+ mutex_unlock(&play_lock);
+ return PTR_ERR(t);
+ }
+ play_thread = t;
+ mutex_unlock(&play_lock);
+ return 0;
+}
+
+static int play_stop(void)
+{
+ mutex_lock(&play_lock);
+ if (play_thread) {
+ kthread_stop(play_thread);
+ play_thread = NULL;
+ }
+ mutex_unlock(&play_lock);
+ return 0;
+}
+
+static ssize_t buzz_write(struct file *f, const char __user *ubuf,
+ size_t n, loff_t *pos)
+{
+ char c;
+ int ret;
+
+ if (n < 1)
+ return -EINVAL;
+ if (get_user(c, ubuf))
+ return -EFAULT;
+ if (c < '0' || c > '3')
+ return -EINVAL;
+
+ ret = ps5_buzz_raw(c - '0');
+ return ret ? ret : n;
+}
+
+static long buzz_ioctl(struct file *f, unsigned int cmd, unsigned long arg)
+{
+ switch (cmd) {
+ case PS5_BUZZER_BEEP: {
+ u8 level;
+ if (get_user(level, (u8 __user *)arg))
+ return -EFAULT;
+ return ps5_buzz_raw(level);
+ }
+ case PS5_BUZZER_PLAY: {
+ struct ps5_buzzer_pattern hdr;
+ struct ps5_buzzer_step *steps;
+ size_t bytes;
+ int ret;
+
+ if (copy_from_user(&hdr, (void __user *)arg, sizeof(hdr)))
+ return -EFAULT;
+ if (!hdr.nsteps || hdr.nsteps > MAX_PATTERN_STEPS)
+ return -EINVAL;
+
+ bytes = hdr.nsteps * sizeof(struct ps5_buzzer_step);
+ steps = kmalloc(bytes, GFP_KERNEL);
+ if (!steps)
+ return -ENOMEM;
+ if (copy_from_user(steps,
+ (void __user *)(arg + sizeof(hdr)), bytes)) {
+ kfree(steps);
+ return -EFAULT;
+ }
+ ret = play_start(steps, hdr.nsteps);
+ if (ret)
+ kfree(steps);
+ return ret;
+ }
+ case PS5_BUZZER_STOP:
+ return play_stop();
+ }
+ return -ENOTTY;
+}
+
+static const struct file_operations buzz_fops = {
+ .owner = THIS_MODULE,
+ .write = buzz_write,
+ .unlocked_ioctl = buzz_ioctl,
+ .llseek = noop_llseek,
+};
+
+static struct miscdevice buzz_misc = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = "ps5_buzzer",
+ .fops = &buzz_fops,
+ .mode = 0660,
+};
+
+static int snd_event(struct input_dev *dev, unsigned int type,
+ unsigned int code, int value)
+{
+ if (type == EV_SND && code == SND_BELL)
+ ps5_buzz_raw(value ? PS5_BUZZ_SHORT : PS5_BUZZ_SILENT);
+ return 0;
+}
+
+static ssize_t beep_store(struct device *d, struct device_attribute *a,
+ const char *buf, size_t n)
+{
+ u8 level;
+ int ret;
+
+ if (kstrtou8(buf, 0, &level))
+ return -EINVAL;
+ ret = ps5_buzz_raw(level);
+ return ret ? ret : n;
+}
+static DEVICE_ATTR_WO(beep);
+
+static ssize_t stop_store(struct device *d, struct device_attribute *a,
+ const char *buf, size_t n)
+{
+ play_stop();
+ return n;
+}
+static DEVICE_ATTR_WO(stop);
+
+static struct attribute *buzz_attrs[] = {
+ &dev_attr_beep.attr,
+ &dev_attr_stop.attr,
+ NULL,
+};
+
+static const struct attribute_group buzz_attr_group = {
+ .attrs = buzz_attrs,
+};
+
+static int __init ps5_buzzer_init(void)
+{
+ int ret;
+
+ ret = misc_register(&buzz_misc);
+ if (ret)
+ return ret;
+
+ ret = sysfs_create_group(&buzz_misc.this_device->kobj, &buzz_attr_group);
+ if (ret)
+ goto err_misc;
+
+ snd_idev = input_allocate_device();
+ if (!snd_idev) {
+ ret = -ENOMEM;
+ goto err_sysfs;
+ }
+ snd_idev->name = "PS5 Chassis Buzzer";
+ snd_idev->phys = "ps5_buzzer/input0";
+ snd_idev->id.bustype = BUS_VIRTUAL;
+ snd_idev->event = snd_event;
+ input_set_capability(snd_idev, EV_SND, SND_BELL);
+
+ ret = input_register_device(snd_idev);
+ if (ret)
+ goto err_input;
+
+ return 0;
+
+err_input:
+ input_free_device(snd_idev);
+ snd_idev = NULL;
+err_sysfs:
+ sysfs_remove_group(&buzz_misc.this_device->kobj, &buzz_attr_group);
+err_misc:
+ misc_deregister(&buzz_misc);
+ return ret;
+}
+
+static void __exit ps5_buzzer_exit(void)
+{
+ play_stop();
+ if (snd_idev)
+ input_unregister_device(snd_idev);
+ sysfs_remove_group(&buzz_misc.this_device->kobj, &buzz_attr_group);
+ misc_deregister(&buzz_misc);
+}
+
+module_init(ps5_buzzer_init);
+module_exit(ps5_buzzer_exit);
+
+MODULE_AUTHOR("Armandas Kvietkus");
+MODULE_DESCRIPTION("PlayStation 5 chassis piezo buzzer");
+MODULE_LICENSE("GPL");
diff --git a/drivers/ps5/hdmi.c b/drivers/ps5/hdmi.c
new file mode 100644
index 000000000000..1193d840f5d6
index 000000000000..8f094490d677
--- /dev/null
+++ b/drivers/ps5/hdmi.c
@@ -0,0 +1,1226 @@
@@ -0,0 +1,1237 @@
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
@@ -5747,8 +6052,8 @@ index 000000000000..1193d840f5d6
+
+void hdmiSystemResume(void)
+{
+ sceSetBackToUnpluggedSequence();
+ sceSetBackToWaitResolutionSequence();
+ // sceSetBackToUnpluggedSequence();
+ // sceSetBackToWaitResolutionSequence();
+ if (hdmi_ic_type == HDMI_IC_TYPE_FLAVA3) {
+ i2c_cmd_4_2();
+ // sceSetHdcpSequence1st();
@@ -6374,6 +6679,16 @@ index 000000000000..1193d840f5d6
+}
+EXPORT_SYMBOL(sceHdmiSetAudioMute);
+
+void sceHdmiOutputMode(void)
+{
+ i2c_init(4);
+ if (hdmi_ic_type == HDMI_IC_TYPE_FLAVA3) {
+ i2c_write(0x7005, 0x80);
+ }
+ i2c_exec();
+}
+EXPORT_SYMBOL(sceHdmiOutputMode);
+
+int getHdmiConfiguration(void)
+{
+ u8 buf[ICC_MSG_MAX_SIZE] = {};
@@ -6448,6 +6763,7 @@ index 000000000000..1193d840f5d6
+ fix_edid(&msg->data[4]);
+ real_edid = drm_edid_alloc(&msg->data[4], *(u16 *)&msg->data[2]);
+ pr_info("got real edid\n");
+ sceHdmiOutputMode();
+ }
+}
+EXPORT_SYMBOL(hdmi_notification_handler);
@@ -6947,10 +7263,10 @@ index 000000000000..4af890c842a7
+MODULE_LICENSE("GPL");
diff --git a/drivers/ps5/spcie.c b/drivers/ps5/spcie.c
new file mode 100644
index 000000000000..8a073f2f0f0a
index 000000000000..e02c074ee0b1
--- /dev/null
+++ b/drivers/ps5/spcie.c
@@ -0,0 +1,1084 @@
@@ -0,0 +1,1149 @@
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
@@ -6960,6 +7276,7 @@ index 000000000000..8a073f2f0f0a
+#include <linux/ps5.h>
+#include <linux/dmi.h>
+#include <linux/delay.h>
+#include <linux/kfifo.h>
+#include "autoservo_param.h"
+
+#define PCI_DEVICE_ID_SPCIE 0x9107
@@ -7035,6 +7352,16 @@ index 000000000000..8a073f2f0f0a
+ void __iomem *pervasive0;
+};
+
+struct icc_notification {
+ u8 service_id;
+ u8 notification[ICC_MSG_MAX_SIZE];
+};
+
+#define ICC_NOTIFICATION_FIFO_SIZE 32
+static DEFINE_KFIFO(icc_notification_fifo, struct icc_notification, ICC_NOTIFICATION_FIFO_SIZE);
+static DEFINE_SPINLOCK(icc_notification_lock);
+static struct work_struct icc_notification_work;
+
+static struct spcie_dev *sdev;
+static struct spcie_icc_dev *icc_dev;
+
@@ -7118,16 +7445,12 @@ index 000000000000..8a073f2f0f0a
+
+ icc_send(query);
+
+ ret = wait_event_interruptible_timeout(icc_dev->wq, icc_dev->reply_ready,
+ ret = wait_event_timeout(icc_dev->wq, icc_dev->reply_ready,
+ msecs_to_jiffies(ICC_TIMEOUT_MSECS));
+ if (ret == 0) {
+ dev_err(&icc_dev->pdev->dev, "timeout\n");
+ mutex_unlock(&icc_dev->lock);
+ return -ETIMEDOUT;
+ } else if (ret < 0) {
+ dev_err(&icc_dev->pdev->dev, "interrupted\n");
+ mutex_unlock(&icc_dev->lock);
+ return -ETIMEDOUT;
+ }
+
+ memcpy(reply, icc_dev->reply, icc_dev->reply_length);
@@ -7360,6 +7683,42 @@ index 000000000000..8a073f2f0f0a
+}
+EXPORT_SYMBOL(icc_thermal_enable_notification);
+
+int icc_device_power_control(u8 device, u8 state)
+{
+ u8 buf[ICC_MSG_MAX_SIZE] = {};
+ struct icc_msg *msg = (struct icc_msg *)buf;
+
+ msg->service_id = ICC_SERVICE_ID_DEVICE;
+ msg->msg_type = 0;
+ msg->length = 0x20;
+ msg->data[0] = device;
+ msg->data[1] = state;
+
+ return icc_query(buf, buf);
+}
+EXPORT_SYMBOL(icc_device_power_control);
+
+int icc_device_power_get(u8 device, u8 *state)
+{
+ u8 buf[ICC_MSG_MAX_SIZE] = {};
+ struct icc_msg *msg = (struct icc_msg *)buf;
+ int ret;
+
+ msg->service_id = ICC_SERVICE_ID_DEVICE;
+ msg->msg_type = 1;
+ msg->length = 0x20;
+ msg->data[0] = device;
+
+ ret = icc_query(buf, buf);
+ if (ret)
+ return ret;
+
+ *state = msg->data[2];
+
+ return 0;
+}
+EXPORT_SYMBOL(icc_device_power_get);
+
+int icc_indicator_set_led(const u8 setting[], size_t setting_size)
+{
+ u8 buf[ICC_MSG_MAX_SIZE] = {};
@@ -7558,18 +7917,38 @@ index 000000000000..8a073f2f0f0a
+ }
+}
+
+static int icc_notification_handler(u8 service_id, u8 *buf)
+static void icc_notification_work_func(struct work_struct *work)
+{
+ struct icc_msg *msg = (struct icc_msg *)buf;
+ struct icc_msg *msg;
+ struct icc_notification entry;
+
+ if (service_id == ICC_SERVICE_ID_HDMI) {
+ hdmi_notification_handler(msg);
+ } else if (service_id == ICC_SERVICE_ID_BUTTON) {
+ button_notification_handler(msg);
+ while (kfifo_out_spinlocked(&icc_notification_fifo, &entry, 1, &icc_notification_lock)) {
+ msg = (struct icc_msg *)entry.notification;
+ if (entry.service_id == ICC_SERVICE_ID_HDMI) {
+ hdmi_notification_handler(msg);
+ } else if (entry.service_id == ICC_SERVICE_ID_BUTTON) {
+ button_notification_handler(msg);
+ } else {
+ pr_info("service id: %x\n", entry.service_id);
+ print_hex_dump(KERN_INFO, "event: ", DUMP_PREFIX_OFFSET, 16, 1,
+ msg->data, msg->length - sizeof(*msg), true);
+ }
+
+ }
+}
+
+static int icc_notification_handler(u8 service_id, u8 *notification)
+{
+ struct icc_msg *msg = (struct icc_msg *)notification;
+ struct icc_notification entry;
+
+ entry.service_id = service_id;
+ memcpy(entry.notification, msg, msg->length);
+
+ if (kfifo_in_spinlocked(&icc_notification_fifo, &entry, 1, &icc_notification_lock)) {
+ schedule_work(&icc_notification_work);
+ } else {
+ pr_info("service id: %x\n", service_id);
+ print_hex_dump(KERN_INFO, "event: ", DUMP_PREFIX_OFFSET, 16, 1,
+ msg->data, msg->length - sizeof(*msg), true);
+ pr_warn_ratelimited("icc notification buffer full\n");
+ }
+
+ return 0;
@@ -7612,7 +7991,7 @@ index 000000000000..8a073f2f0f0a
+ writel(ICC_ACK, icc_dev->icc_doorbell_base + ICC_REG_DOORBELL);
+
+ icc_dev->reply_ready = true;
+ wake_up_interruptible(&icc_dev->wq);
+ wake_up(&icc_dev->wq);
+ } else {
+ dev_err(&icc_dev->pdev->dev, "unknown query\n");
+ }
@@ -7682,6 +8061,8 @@ index 000000000000..8a073f2f0f0a
+
+ sdev->icc_dev = icc_dev;
+
+ INIT_WORK(&icc_notification_work, icc_notification_work_func);
+
+ mutex_init(&icc_dev->lock);
+ init_waitqueue_head(&icc_dev->wq);
+
@@ -8360,10 +8741,10 @@ index 2eedf44e6801..2785a47665be 100644
extern void dmi_setup(void);
diff --git a/include/linux/ps5.h b/include/linux/ps5.h
new file mode 100644
index 000000000000..7ed7adc99719
index 000000000000..4020a0b0c61c
--- /dev/null
+++ b/include/linux/ps5.h
@@ -0,0 +1,89 @@
@@ -0,0 +1,93 @@
+#ifndef _LINUX_PS5_H
+#define _LINUX_PS5_H
+
@@ -8390,18 +8771,18 @@ index 000000000000..7ed7adc99719
+ ICC_SERVICE_ID_NVS = 0x03,
+ ICC_SERVICE_ID_POWER = 0x04,
+ ICC_SERVICE_ID_DEVICE = 0x05,
+ ICC_SERVICE_ID_UNKNOWN1 = 0x07,
+ ICC_SERVICE_ID_UNKNOWN_07 = 0x07,
+ ICC_SERVICE_ID_BUTTON = 0x08,
+ ICC_SERVICE_ID_INDICATOR = 0x09,
+ ICC_SERVICE_ID_FAN = 0x0a,
+ ICC_SERVICE_ID_THERMAL = 0x0b,
+ ICC_SERVICE_ID_HDMI = 0x10,
+ ICC_SERVICE_ID_USBC = 0x12,
+ ICC_SERVICE_ID_UNKNOWN3 = 0x13,
+ ICC_SERVICE_ID_UNKNOWN_13 = 0x13,
+ ICC_SERVICE_ID_CRASH_REPORT = 0x14,
+ ICC_SERVICE_ID_BDDRIVE = 0x15,
+ ICC_SERVICE_ID_UNKNOWN4 = 0x8c,
+ ICC_SERVICE_ID_UNKNOWN5 = 0x8d,
+ ICC_SERVICE_ID_UNKNOWN_8C = 0x8c,
+ ICC_SERVICE_ID_UNKNOWN_8D = 0x8d,
+ ICC_SERVICE_ID_SC_CONFIG = 0x8e,
+ ICC_SERVICE_ID_FLOYD = 0x9a,
+};
@@ -8422,6 +8803,7 @@ index 000000000000..7ed7adc99719
+void sceHdmiSetAudioConfig(int channels);
+void sceHdmiDeviceSetVideoMute(int mute);
+void sceHdmiSetAudioMute(int mute);
+void sceHdmiOutputMode(void);
+int getHdmiConfiguration(void);
+bool isHdmiModeValid(const struct drm_display_mode *mode, int force_1080p);
+
@@ -8440,6 +8822,9 @@ index 000000000000..7ed7adc99719
+int icc_button_enable_all_notifications(u8 enable);
+int icc_thermal_enable_notification(u8 enable);
+
+int icc_device_power_control(u8 device, u8 state);
+int icc_device_power_get(u8 device, u8 *state);
+
+int icc_indicator_set_led(const u8 setting[], size_t setting_size);
+int icc_indicator_set_led_white(u8 level);
+