Compare commits

...

4 Commits

Author SHA1 Message Date
c0w-ar
06e7cbf7bd fix salina xhci reset on unbind (#20)
Co-authored-by: c0w <c0w@ps5.com>
2026-06-25 14:33:31 +02:00
c0w-ar
498b83a4a5 add xHCI Salina driver and btusb fix by bizkut (#19)
* add xHCI Salina driver and btusb fix by bizkut

* change abbreviation to 12 chars

---------

Co-authored-by: c0w <c0w@ps5.com>
2026-06-23 12:34:23 +02:00
Andy Nguyen
a2c666ba29 linux: mute audio and video on mode switch 2026-06-22 20:19:14 +02:00
Hasanuddin Abu Bakar
06bb2bb268 linux: fix HDMI audio on monitors with missing EDID audio modes (#18)
Some monitors do not advertise audio modes in their EDID CEA block,
causing audio_info.mode_count to be zero. When this happens, the
channel counting loop never executes and sceHdmiSetAudioConfig is
called with channels=0, which silently disables audio output on the
PS5 HDMI bridge.

Add a stereo (2ch) fallback when no audio modes are found so that
sceHdmiSetAudioConfig always receives a valid channel count.

Also fix a loop variable reuse bug where the inner audio channel
counting loop shared the iterator 'i' with the outer
for_each_oldnew_crtc_in_state loop. Rename the inner iterator to 'j'.
2026-06-22 17:54:33 +02:00

View File

@@ -1090,6 +1090,81 @@ index 000000000000..39cc76f39e06
+int salina_sata_phy_init(struct salina_sata_phy *p);
+
+#endif
diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c
index 830fefb342c6..f13155f049c4 100644
--- a/drivers/bluetooth/btusb.c
+++ b/drivers/bluetooth/btusb.c
@@ -67,6 +67,10 @@ static struct usb_driver btusb_driver;
#define BTUSB_INTEL_NO_WBS_SUPPORT BIT(26)
#define BTUSB_ACTIONS_SEMI BIT(27)
#define BTUSB_BARROT BIT(28)
+#define BTUSB_IFNUM_0 BIT(29)
+#define BTUSB_BROKEN_LE_DEFAULT_PHY BIT(30)
+#define BTUSB_BROKEN_READ_LOCAL_NAME BIT(31)
+#define BTUSB_NO_AUTOSUSPEND BIT(32)
static const struct usb_device_id btusb_table[] = {
/* Generic Bluetooth USB device */
@@ -172,6 +176,14 @@ static const struct usb_device_id btusb_table[] = {
{ USB_DEVICE(0x8087, 0x0a5a),
.driver_info = BTUSB_INTEL_BOOT | BTUSB_BROKEN_ISOC },
+ /* PS5 IW620 Bluetooth device */
+ { USB_DEVICE(0x1286, 0x2059),
+ .driver_info = BTUSB_MARVELL |
+ BTUSB_IFNUM_0 |
+ BTUSB_BROKEN_LE_DEFAULT_PHY |
+ BTUSB_BROKEN_READ_LOCAL_NAME |
+ BTUSB_NO_AUTOSUSPEND },
+
{ } /* Terminating entry */
};
@@ -472,6 +484,11 @@ static const struct usb_device_id quirks_table[] = {
{ USB_DEVICE(0x1286, 0x2044), .driver_info = BTUSB_MARVELL },
{ USB_DEVICE(0x1286, 0x2046), .driver_info = BTUSB_MARVELL },
{ USB_DEVICE(0x1286, 0x204e), .driver_info = BTUSB_MARVELL },
+ { USB_DEVICE(0x1286, 0x2059), .driver_info = BTUSB_MARVELL |
+ BTUSB_IFNUM_0 |
+ BTUSB_BROKEN_LE_DEFAULT_PHY |
+ BTUSB_BROKEN_READ_LOCAL_NAME |
+ BTUSB_NO_AUTOSUSPEND },
/* Intel Bluetooth devices */
{ USB_DEVICE(0x8087, 0x0025), .driver_info = BTUSB_INTEL_COMBINED },
@@ -4055,6 +4072,10 @@ static int btusb_probe(struct usb_interface *intf,
(intf->cur_altsetting->desc.bInterfaceNumber != 2))
return -ENODEV;
+ if ((id->driver_info & BTUSB_IFNUM_0) &&
+ intf->cur_altsetting->desc.bInterfaceNumber != 0)
+ return -ENODEV;
+
ifnum_base = intf->cur_altsetting->desc.bInterfaceNumber;
if (!id->driver_info) {
@@ -4344,6 +4365,12 @@ static int btusb_probe(struct usb_interface *intf,
if (id->driver_info & BTUSB_INVALID_LE_STATES)
hci_set_quirk(hdev, HCI_QUIRK_BROKEN_LE_STATES);
+ if (id->driver_info & BTUSB_BROKEN_LE_DEFAULT_PHY)
+ hci_set_quirk(hdev, HCI_QUIRK_BROKEN_SET_DEFAULT_PHY);
+
+ if (id->driver_info & BTUSB_BROKEN_READ_LOCAL_NAME)
+ hci_set_quirk(hdev, HCI_QUIRK_BROKEN_READ_LOCAL_NAME);
+
if (id->driver_info & BTUSB_DIGIANSWER) {
data->cmdreq_type = USB_TYPE_VENDOR;
hci_set_quirk(hdev, HCI_QUIRK_RESET_ON_CLOSE);
@@ -4400,7 +4427,7 @@ static int btusb_probe(struct usb_interface *intf,
data->diag = NULL;
}
- if (enable_autosuspend)
+ if (enable_autosuspend && !(id->driver_info & BTUSB_NO_AUTOSUSPEND))
usb_enable_autosuspend(data->udev);
data->poll_sync = enable_poll_sync;
diff --git a/drivers/firmware/dmi-id.c b/drivers/firmware/dmi-id.c
index 477a37e2ef80..e1af70946c89 100644
--- a/drivers/firmware/dmi-id.c
@@ -1505,7 +1580,7 @@ index 52f4e9e099cb..6e4ff7444536 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 f8c13bad4ac2..e2e6a2605f2a 100644
index f8c13bad4ac2..1a9cccc5fa5c 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 @@
@@ -1566,7 +1641,7 @@ index f8c13bad4ac2..e2e6a2605f2a 100644
if ((mode->flags & DRM_MODE_FLAG_INTERLACE) ||
(mode->flags & DRM_MODE_FLAG_DBLSCAN))
return result;
@@ -10957,12 +10978,58 @@ static void amdgpu_dm_atomic_commit_tail(struct drm_atomic_state *state)
@@ -10957,12 +10978,64 @@ 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);
@@ -1583,6 +1658,8 @@ index f8c13bad4ac2..e2e6a2605f2a 100644
+ (!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)) {
+ sceHdmiSetAudioMute(1);
+ sceHdmiDeviceSetVideoMute(1);
+ sceHdmiInitVideoConfig();
+ }
+ }
@@ -1606,13 +1683,17 @@ index f8c13bad4ac2..e2e6a2605f2a 100644
+ 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;
+ int channels = 0, j;
+
+ for (i = 0; i < stream->audio_info.mode_count; i++) {
+ if (stream->audio_info.modes[i].channel_count > channels)
+ channels = stream->audio_info.modes[i].channel_count;
+ for (j = 0; j < stream->audio_info.mode_count; j++) {
+ if (stream->audio_info.modes[j].channel_count > channels)
+ channels = stream->audio_info.modes[j].channel_count;
+ }
+
+ /* Fallback to stereo if EDID has no audio modes */
+ if (channels == 0)
+ channels = 2;
+
+ sceHdmiSetVideoConfig(&new_crtc_state->mode);
+ sceHdmiDeviceSetVideoMute(0);
+ sceHdmiSetAudioConfig(channels);
@@ -8950,6 +9031,739 @@ index 000000000000..f5348c0d7e82
+ pop %_ASM_BP
+ RET
+SYM_FUNC_END(svm_run_guest)
diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile
index a07e7ba9cd53..4c020957b7a7 100644
--- a/drivers/usb/host/Makefile
+++ b/drivers/usb/host/Makefile
@@ -71,6 +71,7 @@ obj-$(CONFIG_USB_UHCI_HCD) += uhci-hcd.o
obj-$(CONFIG_USB_FHCI_HCD) += fhci.o
obj-$(CONFIG_USB_XHCI_HCD) += xhci-hcd.o
obj-$(CONFIG_USB_XHCI_PCI) += xhci-pci.o
+obj-$(CONFIG_USB_XHCI_PCI) += xhci-ps5.o
obj-$(CONFIG_USB_XHCI_PCI_RENESAS) += xhci-pci-renesas.o
obj-$(CONFIG_USB_XHCI_PLATFORM) += xhci-plat-hcd.o
obj-$(CONFIG_USB_XHCI_HISTB) += xhci-histb.o
diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c
index 585b2f3117b0..16d5e2178ae0 100644
--- a/drivers/usb/host/xhci-pci.c
+++ b/drivers/usb/host/xhci-pci.c
@@ -18,6 +18,7 @@
#include "xhci.h"
#include "xhci-trace.h"
#include "xhci-pci.h"
+#include "xhci-ps5.h"
#define SSIC_PORT_NUM 2
#define SSIC_PORT_CFG2 0x880c
@@ -94,6 +95,9 @@
#define PCI_DEVICE_ID_ASMEDIA_3042_XHCI 0x3042
#define PCI_DEVICE_ID_ASMEDIA_3242_XHCI 0x3242
+#define PCI_VENDOR_ID_SONY 0x104d
+#define PCI_DEVICE_ID_PS5_SALINA_XHCI 0x9108
+
static const char hcd_name[] = "xhci_hcd";
static struct hc_driver __read_mostly xhci_pci_hc_driver;
@@ -511,6 +515,13 @@ static void xhci_pci_quirks(struct device *dev, struct xhci_hcd *xhci)
if (xhci->hci_version >= 0x120)
xhci->quirks |= XHCI_DEFAULT_PM_RUNTIME_ALLOW;
+ if (pdev->vendor == PCI_VENDOR_ID_SONY &&
+ pdev->device == PCI_DEVICE_ID_PS5_SALINA_XHCI) {
+ xhci_dbg_trace(xhci, trace_xhci_dbg_init, "%s detected Salina USB PHY setting quirk", __func__);
+ xhci->quirks |= XHCI_PS5_SALINA_PHY;
+ xhci->quirks |= XHCI_WRITE_64_HI_LO;
+ }
+
if (xhci->quirks & XHCI_RESET_ON_RESUME)
xhci_dbg_trace(xhci, trace_xhci_dbg_quirks,
"QUIRK: Resetting on resume");
@@ -575,6 +586,13 @@ static int xhci_pci_setup(struct usb_hcd *hcd)
/* imod_interval is the interrupt moderation value in nanoseconds. */
xhci->imod_interval = 40000;
+ if (pdev->vendor == PCI_VENDOR_ID_SONY &&
+ pdev->device == PCI_DEVICE_ID_PS5_SALINA_XHCI) {
+ retval = xhci_salina_init(pdev);
+ if (retval)
+ return retval;
+ }
+
retval = xhci_gen_setup(hcd, xhci_pci_quirks);
if (retval)
return retval;
@@ -713,6 +731,11 @@ void xhci_pci_remove(struct pci_dev *dev)
xhci = hcd_to_xhci(pci_get_drvdata(dev));
set_power_d3 = xhci->quirks & XHCI_SPURIOUS_WAKEUP;
+ if(xhci->quirks & XHCI_PS5_SALINA_PHY) {
+ salina_stop_xhci_poller();
+ xhci_salina_exit();
+ }
+
xhci->xhc_state |= XHCI_STATE_REMOVING;
if (pci_choose_state(dev, PMSG_SUSPEND) == PCI_D0)
@@ -945,6 +968,7 @@ static const struct pci_device_id pci_ids[] = {
/* handle any USB 3.0 xHCI controller */
{ PCI_DEVICE_CLASS(PCI_CLASS_SERIAL_USB_XHCI, ~0),
},
+ { PCI_DEVICE(PCI_VENDOR_ID_SONY, PCI_DEVICE_ID_PS5_SALINA_XHCI) },
{ /* end: all zeroes */ }
};
MODULE_DEVICE_TABLE(pci, pci_ids);
diff --git a/drivers/usb/host/xhci-ps5.c b/drivers/usb/host/xhci-ps5.c
new file mode 100644
index 000000000000..b1c2ff265c7e
--- /dev/null
+++ b/drivers/usb/host/xhci-ps5.c
@@ -0,0 +1,516 @@
+/*
+* Playstation 5 Salina XHCI driver helper
+* Author: https://github.com/c0w-ar
+*/
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/pci.h>
+#include <linux/types.h>
+#include <linux/io.h>
+#include <linux/usb.h>
+#include <linux/usb/hcd.h>
+#include <linux/workqueue.h>
+#include <asm/delay.h>
+#include "xhci-ps5.h"
+#include "xhci.h"
+#include "xhci-trace.h"
+#include "xhci-ring.h"
+
+/* External SPCIE driver functions */
+extern bool spcie_is_initialized(void);
+extern u32 spcie_get_chip_id(void);
+extern u32 spcie_bar2_180000_read(u32 reg);
+extern void spcie_bar2_180000_write(u32 reg, u32 val);
+extern u32 spcie_pervasive0_4000_read(u32 reg);
+
+#define SALINA_XHCI_BAR_SIZE 0x140000
+#define SALINA_POLL_INTERVAL_MS 20
+
+struct salina_xhci_dev {
+ void __iomem *spcie_bar2;
+ void __iomem *spcie_pervasive0;
+ void __iomem *xhci_bar;
+ u32 revision;
+};
+static struct salina_xhci_dev salina_xhci;
+
+struct salina_xhci_poller {
+ struct xhci_hcd *xhci;
+ struct delayed_work work;
+ bool running;
+};
+static struct salina_xhci_poller salina_poll;
+
+static int dev_ioremap_bar(struct pci_dev *dev, int bar_nr, resource_size_t min_size, void __iomem **bar) {
+ resource_size_t start = pci_resource_start(dev, bar_nr);
+ resource_size_t len = pci_resource_len(dev, bar_nr);
+
+ if (start == 0) {
+ return -EINVAL;
+ }
+
+ if (len < min_size) {
+ return -ENODEV;
+ }
+
+ *bar = ioremap(start, min_size);
+ if (!*bar) {
+ pr_err("Could not ioremap memory\n");
+ release_mem_region(start, min_size);
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+static void unmap_bar(void) {
+ if(salina_xhci.xhci_bar) {
+ iounmap(salina_xhci.xhci_bar);
+ salina_xhci.xhci_bar = NULL;
+ }
+}
+
+static int init_bar(struct pci_dev *own_pdev) {
+ int ret = dev_ioremap_bar(own_pdev, 0, SALINA_XHCI_BAR_SIZE, &salina_xhci.xhci_bar);
+ if (ret)
+ goto unmap;
+ return 0;
+
+unmap:
+ unmap_bar();
+ return ret;
+}
+
+static u32 phys_readl(u64 addr) {
+ void __iomem *ptr = ioremap(addr, 4);
+ if (!ptr) {
+ pr_err("Could not ioremap memory\n");
+ return 0;
+ }
+ u32 val = readl(ptr);
+ iounmap(ptr);
+ return val;
+}
+
+static u8 usb_get_length(u8 usb_n) {
+ return (u8)((phys_readl(0x9bf44) >> (usb_n*8))&0x1f);
+}
+
+inline void spcie_phy_bit_set(u32 reg, u32 bit) {
+ u32 val_old = spcie_bar2_180000_read(reg);
+ spcie_bar2_180000_write(reg, val_old | BIT(bit));
+}
+
+inline void spcie_phy_bit_unset(u32 reg, u32 bit) {
+ u32 val_old = spcie_bar2_180000_read(reg);
+ spcie_bar2_180000_write(reg, val_old & ~BIT(bit));
+}
+
+inline u32 spcie_config_read(u32 reg) {
+ return spcie_pervasive0_4000_read(reg);
+}
+
+inline u32 xhci_read(u64 offset, u32 reg) {
+ if (salina_xhci.xhci_bar)
+ return readl(salina_xhci.xhci_bar + offset + reg);
+ else
+ return -1;
+}
+
+inline void xhci_write(u64 offset, u32 reg, u32 val) {
+ if (salina_xhci.xhci_bar)
+ writel(val, salina_xhci.xhci_bar + offset + reg);
+}
+
+inline void xhci_or(u64 offset, u32 reg, u32 val) {
+ if (salina_xhci.xhci_bar) {
+ u32 val_old = readl(salina_xhci.xhci_bar + offset + reg);
+ writel(val_old | val, salina_xhci.xhci_bar + offset + reg);
+ }
+}
+
+inline void xhci_and(u64 offset, u32 reg, u32 val) {
+ if (salina_xhci.xhci_bar) {
+ u32 val_old = readl(salina_xhci.xhci_bar + offset + reg);
+ writel(val_old & val, salina_xhci.xhci_bar + offset + reg);
+ }
+}
+
+inline void xhci_bit_set(u64 offset, u32 reg, u32 bit) {
+ if (salina_xhci.xhci_bar) {
+ u32 val_old = readl(salina_xhci.xhci_bar + offset + reg);
+ writel(val_old | BIT(bit), salina_xhci.xhci_bar + offset + reg);
+ }
+}
+
+inline void xhci_bit_unset(u64 offset, u32 reg, u32 bit) {
+ if (salina_xhci.xhci_bar) {
+ u32 val_old = readl(salina_xhci.xhci_bar + offset + reg);
+ writel(val_old & ~BIT(bit), salina_xhci.xhci_bar + offset + reg);
+ }
+}
+
+inline void xhci_bit_toggle(u64 offset, u32 reg, u32 bit) {
+ xhci_bit_set(offset, reg, bit);
+ xhci_bit_unset(offset, reg, bit);
+}
+
+static inline u64 usb_phy_offset (u8 usb_n) {
+ switch (usb_n) {
+ case 0: return 0x120000;
+ case 1: return 0x124000;
+ case 2: return 0x128000;
+ default: return 0x120000;
+ }
+}
+
+static inline u32 usb_trace_trim(u8 usb_n, u32 usb_length) {
+ if (usb_length < 3)
+ return 8;
+ else if (usb_length < 6)
+ return 7;
+ else if (usb_length < 8)
+ return 6;
+ else if (usb_length < 10)
+ return 5;
+ else if (usb_length < 13)
+ return 4;
+ else {
+ switch (usb_n) {
+ case 0: return 4;
+ case 1: return 6;
+ case 2: return 7;
+ default: return 4;
+ }
+ }
+}
+
+static void salina_usb_phy_config_step_1(u8 usb_n) {
+
+ u64 offset = usb_phy_offset(usb_n);
+ u32 val32;
+ u32 usb_efuse;
+ u32 usb_length;
+
+ val32 = spcie_config_read(0x6c);
+ if ((val32 >> 0x19 & val32 >> 0x11 & 1) == 0) {
+ printk("[USB%d] eFuse not blown for usb\n", usb_n);
+ usb_efuse = 0x98;
+ }
+ else {
+ usb_efuse = (spcie_config_read(0x44) >> (usb_n*5+0x10)) & 0x1f;
+ }
+
+ xhci_write(offset, 0x800, 0x480a);
+
+ xhci_write(offset, 0x814, 0x10a01000);
+
+ xhci_bit_unset(offset, 0x818, 23);
+
+ xhci_bit_set(offset, 0x800, 5);
+
+ xhci_bit_unset(offset, 0x868, 18);
+
+ xhci_and(offset, 0x804, 0xff07ffff);
+ xhci_or(offset, 0x804, usb_efuse << 0x13);
+
+ usb_length = usb_trace_trim(usb_n, usb_get_length(usb_n));
+
+ xhci_and(offset, 0x818, 0xffffff0f);
+ xhci_or(offset, 0x818, 0xa0);
+
+ xhci_and(offset, 0x818, 0xfffffff0);
+ xhci_or(offset, 0x818, usb_length);
+}
+
+static int salina_usb_phy_config_step_2(u8 usb_n) {
+
+ u64 offset = usb_phy_offset(usb_n);
+
+ xhci_bit_set(offset, 0x814, 15);
+
+ udelay(2);
+
+ xhci_bit_set(offset, 0x110, 8);
+
+ xhci_and(offset, 0x100, 0xff000000);
+ xhci_or(offset, 0x100, 0x400);
+
+ xhci_bit_set(offset, 0x100, 24);
+
+ int attemps = 100;
+ u32 measure;
+ do {
+ measure = xhci_read(offset, 0x10c);
+ udelay(2);
+ if (measure!=0) break;
+ attemps--;
+ } while(attemps!=0);
+
+ xhci_bit_unset(offset, 0x100, 24);
+
+ xhci_bit_unset(offset, 0x110, 8);
+
+ if (!measure) {
+ printk("Something went wrong while waiting for USB init\n");
+ return -1;
+ }
+
+ u32 calibration = (u32)((0xb9000 / (u64)measure) / 1000);
+
+ xhci_and(offset, 0x814, 0xffff8fff);
+ xhci_or(offset, 0x814, calibration<<12);
+
+ xhci_bit_unset(offset, 0x814, 15);
+
+ return 0;
+}
+
+static void salina_usb_phy_config_step_3(u8 usb_n) {
+ u32 offset = usb_phy_offset(usb_n);
+ xhci_bit_set(offset, 0x800, 0);
+ xhci_bit_unset(offset, 0x808, 17);
+ xhci_bit_set(offset, 0x880, 0);
+}
+
+static void salina_usb_phy_config_step_4(void) {
+ xhci_and(0, 0xc100, 0xfffffff0);
+ xhci_or(0, 0xc100, 0xe);
+ xhci_write(0, 0xc12c, 0x7fc0c010);
+ xhci_write(0, 0xc200, 0x1548);
+ if (salina_xhci.revision == 0x110000) {
+ xhci_write(0, 0xc204, 0x1548);
+ xhci_write(0, 0xc208, 0x1548);
+ }
+}
+
+static int xhci_salina_phy_init(void) {
+
+ spcie_phy_bit_set(0x24, 1);
+ spcie_phy_bit_set(0x24, 0);
+ spcie_phy_bit_unset(0x24, 1);
+
+ salina_usb_phy_config_step_1(0);
+ if (salina_xhci.revision == 0x110000) {
+ salina_usb_phy_config_step_1(1);
+ salina_usb_phy_config_step_1(2);
+ }
+
+ spcie_phy_bit_unset(0x24, 0);
+
+ udelay(2000);
+
+ if(salina_usb_phy_config_step_2(0)) return -1;
+ if (salina_xhci.revision == 0x110000) {
+ if(salina_usb_phy_config_step_2(1)) return -1;
+ if(salina_usb_phy_config_step_2(2)) return -1;
+ }
+
+ salina_usb_phy_config_step_3(0);
+ if (salina_xhci.revision == 0x110000) {
+ salina_usb_phy_config_step_3(1);
+ salina_usb_phy_config_step_3(2);
+ }
+
+ xhci_bit_unset(0x100000, 0x4, 0);
+
+ salina_usb_phy_config_step_4();
+
+ return 0;
+}
+
+int xhci_salina_init(struct pci_dev *pdev) {
+
+ int ret;
+
+ if (!spcie_is_initialized()) {
+ dev_warn(&pdev->dev, "spcie not initialized, deferring\n");
+ return -EPROBE_DEFER;
+ }
+
+ salina_xhci.revision = spcie_get_chip_id();
+ if (salina_xhci.revision!=0x110000 && salina_xhci.revision!=0x120000) {
+ dev_warn(&pdev->dev, "PS5 Salina revision unknown %08x\n", salina_xhci.revision);
+ return ENODEV;
+ }
+
+ ret = init_bar(pdev);
+ if (ret) {
+ pr_err("PS5 Salina could not map BAR\n");
+ return ret;
+ }
+
+ xhci_salina_phy_init();
+
+ dev_info(&pdev->dev, "PS5 Salina xHCI PHY initialized\n");
+ return 0;
+}
+
+int xhci_salina_reset(struct xhci_hcd *xhci, u64 timeout_us) {
+
+ u32 state;
+ int ret;
+
+ if (salina_xhci.revision == 0x110000) {
+ xhci_bit_unset(0x100000, 0x0, 2);
+ }
+
+ state = readl(&xhci->op_regs->status);
+
+ if (state == ~(u32)0) {
+ if (!(xhci->xhc_state & XHCI_STATE_DYING))
+ xhci_warn(xhci, "Host not accessible, reset failed.\n");
+ return -ENODEV;
+ }
+
+ if ((state & STS_HALT) == 0) {
+ xhci_warn(xhci, "Host controller not halted, aborting reset.\n");
+ return 0;
+ }
+
+ xhci_info(xhci, "// Reset the HC on custom Salina Reset");
+ writel(CMD_RESET, &xhci->op_regs->command);
+
+ udelay(1000);
+
+ ret = xhci_handshake(&xhci->op_regs->command, CMD_RESET, 0, timeout_us);
+ if (ret)
+ return ret;
+
+ ret = xhci_handshake(&xhci->op_regs->status, STS_CNR, 0, timeout_us);
+ if (ret)
+ return ret;
+
+ if (salina_xhci.revision == 0x110000) {
+ xhci_bit_toggle(0x100000, 0x0, 1);
+ xhci_bit_toggle(0x100000, 0x0, 17);
+ xhci_bit_toggle(0x100000, 0x0, 25);
+ udelay(10000);
+ udelay(10000);
+ udelay(10000);
+ }
+
+ xhci_bit_unset(0x100000, 0x0, 1);
+ udelay(10000);
+ udelay(10000);
+
+ if (salina_xhci.revision == 0x110000) {
+ xhci_bit_set(0x100000, 0x0, 2);
+ udelay(10000);
+ udelay(10000);
+ }
+
+ salina_usb_phy_config_step_4();
+
+ // Reset software state
+ xhci->usb2_rhub.bus_state.port_c_suspend = 0;
+ xhci->usb2_rhub.bus_state.suspended_ports = 0;
+ xhci->usb2_rhub.bus_state.resuming_ports = 0;
+ xhci->usb3_rhub.bus_state.port_c_suspend = 0;
+ xhci->usb3_rhub.bus_state.suspended_ports = 0;
+ xhci->usb3_rhub.bus_state.resuming_ports = 0;
+
+ return 0;
+}
+
+void xhci_salina_exit(void)
+{
+ unmap_bar();
+}
+
+static bool ring_has_event_pending(struct xhci_ring *ring)
+{
+ struct xhci_segment *seg;
+ u32 idx;
+ u32 ccs;
+
+ if (!ring || !ring->deq_seg || !ring->dequeue)
+ return false;
+
+ seg = ring->deq_seg;
+ idx = ring->dequeue - ring->deq_seg->trbs;
+ ccs = ring->cycle_state;
+
+ if (idx >= TRBS_PER_SEGMENT)
+ return false;
+
+ u32 trb_cycle = le32_to_cpu(seg->trbs[idx].generic.field[3]) & TRB_CYCLE;
+ return trb_cycle == ccs;
+}
+
+static void salina_xhci_poll_fn(struct work_struct *w)
+{
+ struct salina_xhci_poller *p =
+ container_of(w, struct salina_xhci_poller, work.work);
+ struct xhci_hcd *xhci = p->xhci;
+ struct xhci_ring *er;
+ unsigned long flags;
+
+ if (!p->running || !xhci)
+ return;
+
+ er = xhci->interrupters[0]->event_ring;
+ if (!er)
+ goto resched;
+
+ if (!spin_trylock_irqsave(&xhci->lock, flags)) {
+ printk(KERN_INFO "Salina poller: lock busy, rescheduling\n");
+ goto resched;
+ }
+
+ u32 status = readl(&xhci->op_regs->status);
+ if (status == ~(u32)0) {
+ xhci_hc_died(xhci);
+ goto unlock;
+ }
+
+ if (status & STS_HCE) {
+ xhci_warn(xhci, "WARNING: Host Controller Error\n");
+ xhci_halt(xhci);
+ goto unlock;
+ }
+
+ if (status & STS_FATAL) {
+ xhci_warn(xhci, "WARNING: Host System Error\n");
+ xhci_halt(xhci);
+ goto unlock;
+ }
+
+ if (ring_has_event_pending(er)) {
+ xhci_handle_events(xhci, xhci->interrupters[0], false);
+ }
+
+unlock:
+ spin_unlock_irqrestore(&xhci->lock, flags);
+
+resched:
+ schedule_delayed_work(&p->work,
+ msecs_to_jiffies(SALINA_POLL_INTERVAL_MS));
+}
+
+void salina_start_xhci_poller(struct xhci_hcd *xhci)
+{
+ if (salina_poll.running)
+ return;
+
+ memset(&salina_poll, 0, sizeof(salina_poll));
+ salina_poll.xhci = xhci;
+ salina_poll.running = true;
+
+ INIT_DELAYED_WORK(&salina_poll.work, salina_xhci_poll_fn);
+
+ schedule_delayed_work(&salina_poll.work,
+ msecs_to_jiffies(SALINA_POLL_INTERVAL_MS));
+
+ xhci_info(xhci, "Salina: software IRQ poller enabled\n");
+}
+
+void salina_stop_xhci_poller(void)
+{
+ if (!salina_poll.running)
+ return;
+ cancel_delayed_work_sync(&salina_poll.work);
+ salina_poll.running = false;
+ salina_poll.xhci = NULL;
+}
diff --git a/drivers/usb/host/xhci-ps5.h b/drivers/usb/host/xhci-ps5.h
new file mode 100755
index 000000000000..90d41d3dc524
--- /dev/null
+++ b/drivers/usb/host/xhci-ps5.h
@@ -0,0 +1,9 @@
+#include <linux/pci.h>
+#include "xhci.h"
+
+int xhci_salina_reset(struct xhci_hcd *xhci, u64 timeout_us);
+int xhci_salina_init(struct pci_dev *pdev);
+void xhci_salina_exit(void);
+
+void salina_start_xhci_poller(struct xhci_hcd *xhci);
+void salina_stop_xhci_poller(void);
\ No newline at end of file
diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c
index e47e644b296e..774c4955b2bb 100644
--- a/drivers/usb/host/xhci-ring.c
+++ b/drivers/usb/host/xhci-ring.c
@@ -59,6 +59,8 @@
#include <linux/dma-mapping.h>
#include "xhci.h"
#include "xhci-trace.h"
+#include "xhci-ring.h"
+#include "xhci-ps5.h"
static int queue_command(struct xhci_hcd *xhci, struct xhci_command *cmd,
u32 field1, u32 field2,
@@ -3083,12 +3085,14 @@ static void xhci_clear_interrupt_pending(struct xhci_interrupter *ir)
* Handle all OS-owned events on an interrupter event ring. It may drop
* and reaquire xhci->lock between event processing.
*/
-static int xhci_handle_events(struct xhci_hcd *xhci, struct xhci_interrupter *ir,
+int xhci_handle_events(struct xhci_hcd *xhci, struct xhci_interrupter *ir,
bool skip_events)
{
int event_loop = 0;
int err = 0;
u64 temp;
+ int iters = 0;
+ const int max_iters = 32;
xhci_clear_interrupt_pending(ir);
@@ -3131,11 +3135,27 @@ static int xhci_handle_events(struct xhci_hcd *xhci, struct xhci_interrupter *ir
if (err)
break;
+
+ if (xhci->quirks & XHCI_PS5_SALINA_PHY) {
+ if (++iters > max_iters) {
+ xhci_warn(xhci,
+ "Salina: event ring appears stuck after %d TRBs\n",
+ iters);
+ goto salina_stuck;
+ }
+ }
}
xhci_update_erst_dequeue(xhci, ir, true);
return 0;
+
+salina_stuck:
+ /* mark host dying so we don't deadlock future URBs */
+ xhci->xhc_state |= XHCI_STATE_DYING;
+
+ xhci_update_erst_dequeue(xhci, ir, true);
+ return -EIO;
}
/*
diff --git a/drivers/usb/host/xhci-ring.h b/drivers/usb/host/xhci-ring.h
new file mode 100755
index 000000000000..193ac03a3074
--- /dev/null
+++ b/drivers/usb/host/xhci-ring.h
@@ -0,0 +1,4 @@
+#include "xhci.h"
+
+dma_addr_t xhci_trb_virt_to_dma(struct xhci_segment *seg, union xhci_trb *trb);
+int xhci_handle_events(struct xhci_hcd *xhci, struct xhci_interrupter *ir, bool skip_events);
diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c
index a54f5b57f205..0d99186ec967 100644
--- a/drivers/usb/host/xhci.c
+++ b/drivers/usb/host/xhci.c
@@ -26,6 +26,7 @@
#include "xhci-trace.h"
#include "xhci-debugfs.h"
#include "xhci-dbgcap.h"
+#include "xhci-ps5.h"
#define DRIVER_AUTHOR "Sarah Sharp"
#define DRIVER_DESC "'eXtensible' Host Controller (xHC) Driver"
@@ -191,6 +192,10 @@ int xhci_reset(struct xhci_hcd *xhci, u64 timeout_us)
u32 state;
int ret;
+ if (xhci->quirks & XHCI_PS5_SALINA_PHY) {
+ return xhci_salina_reset(xhci, 5000);
+ }
+
state = readl(&xhci->op_regs->status);
if (state == ~(u32)0) {
@@ -615,6 +620,10 @@ static int xhci_run_finished(struct xhci_hcd *xhci)
spin_unlock_irqrestore(&xhci->lock, flags);
+ if(xhci->quirks & XHCI_PS5_SALINA_PHY) {
+ salina_start_xhci_poller(xhci);
+ }
+
return 0;
}
diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h
index aeecd301f207..e202fbdd2f41 100644
--- a/drivers/usb/host/xhci.h
+++ b/drivers/usb/host/xhci.h
@@ -1645,6 +1645,7 @@ struct xhci_hcd {
#define XHCI_CDNS_SCTX_QUIRK BIT_ULL(48)
#define XHCI_ETRON_HOST BIT_ULL(49)
#define XHCI_LIMIT_ENDPOINT_INTERVAL_9 BIT_ULL(50)
+#define XHCI_PS5_SALINA_PHY BIT_ULL(51)
unsigned int num_active_eps;
unsigned int limit_active_eps;
diff --git a/include/linux/dmi.h b/include/linux/dmi.h
index c8700e6a694d..7d0afd378c4e 100644
--- a/include/linux/dmi.h
@@ -9065,6 +9879,60 @@ index 000000000000..24adcd953ab3
+u64 svm_execute_guest(void *guest_func, u64 a0, u64 a1, u64 a2, u64 a3);
+
+#endif /* _LINUX_PS5_H */
diff --git a/include/net/bluetooth/hci.h b/include/net/bluetooth/hci.h
index 572b1c620c5d..b776491a1241 100644
--- a/include/net/bluetooth/hci.h
+++ b/include/net/bluetooth/hci.h
@@ -309,6 +309,22 @@ enum {
*/
HCI_QUIRK_BROKEN_SET_RPA_TIMEOUT,
+ /* When this quirk is set, the HCI_OP_LE_SET_DEFAULT_PHY command is
+ * skipped during initialization. This is required for controllers
+ * which advertise the command but reject the default 1M PHY request.
+ *
+ * This quirk can be set before hci_register_dev is called.
+ */
+ HCI_QUIRK_BROKEN_SET_DEFAULT_PHY,
+
+ /* When this quirk is set, the HCI_OP_READ_LOCAL_NAME command is
+ * skipped during initialization. This is required for controllers
+ * which become unstable when queried for the local name.
+ *
+ * This quirk can be set before hci_register_dev is called.
+ */
+ HCI_QUIRK_BROKEN_READ_LOCAL_NAME,
+
/*
* When this quirk is set, the HCI_OP_LE_EXT_CREATE_CONN command is
* disabled. This is required for the Actions Semiconductor ATS2851
diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c
index df23245d6ccd..1248c3735900 100644
--- a/net/bluetooth/hci_sync.c
+++ b/net/bluetooth/hci_sync.c
@@ -3787,6 +3787,9 @@ static int hci_read_dev_class_sync(struct hci_dev *hdev)
/* Read Local Name */
static int hci_read_local_name_sync(struct hci_dev *hdev)
{
+ if (hci_test_quirk(hdev, HCI_QUIRK_BROKEN_READ_LOCAL_NAME))
+ return 0;
+
return __hci_cmd_sync_status(hdev, HCI_OP_READ_LOCAL_NAME,
0, NULL, HCI_CMD_TIMEOUT);
}
@@ -4874,6 +4877,12 @@ static int hci_le_set_default_phy_sync(struct hci_dev *hdev)
{
struct hci_cp_le_set_default_phy cp;
+ if (hci_test_quirk(hdev, HCI_QUIRK_BROKEN_SET_DEFAULT_PHY)) {
+ hdev->le_tx_def_phys = HCI_LE_SET_PHY_1M;
+ hdev->le_rx_def_phys = HCI_LE_SET_PHY_1M;
+ return 0;
+ }
+
if (!(hdev->commands[35] & 0x20)) {
/* If the command is not supported it means only 1M PHY is
* supported.
diff --git a/scripts/setlocalversion b/scripts/setlocalversion
index 28169d7e143b..44f516daa21d 100755
--- a/scripts/setlocalversion