Skip to content

Commit b0363a0

Browse files
committed
xhci-pci: asmedia: Add a firmware loader for ASM2214a chips
Apple ships ASM2214a ICs in some Apple Silicon hardware (notably, the 2021 iMac and the 2022 Mac Studio) without a flash ROM, and relies on the OS to load firmware on startup. Add support for this to the generic xhci-pci driver. The loader code first checks the firmware version, and only attempts to load firmware if the version isn't the known ROM version. Since this arrangement only exists on Apple machines so far, and Apple are the only source of the (non-redistributable) firmware intended for use on these machines, the firmware is named asmedia/asm2214a-apple.bin. If this style of firmware loading ever becomes necessary on non-Apple machines, we should add a generic firmware name at the time (if it can be part of linux-firmware) or another vendor-specific firmware name. Signed-off-by: Hector Martin <[email protected]>
1 parent 20e07dd commit b0363a0

6 files changed

Lines changed: 360 additions & 0 deletions

File tree

drivers/usb/host/Kconfig

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,15 @@ config USB_XHCI_PCI_RENESAS
5151
installed on your system for this device to work.
5252
If unsure, say 'N'.
5353

54+
config USB_XHCI_PCI_ASMEDIA
55+
bool "Support for ASMedia xHCI controller with firmware"
56+
default ARCH_APPLE
57+
depends on USB_XHCI_PCI
58+
help
59+
Say 'Y' to enable support for ASMedia xHCI controllers with
60+
host-supplied firmware. These are usually present on Apple devices.
61+
If unsure, say 'N'.
62+
5463
config USB_XHCI_PLATFORM
5564
tristate "Generic xHCI driver for a platform device"
5665
help

drivers/usb/host/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ obj-$(CONFIG_USB_XHCI_HCD) += xhci-hcd.o
7070
obj-$(CONFIG_USB_XHCI_PCI) += xhci-pci.o
7171
xhci-pci-y += xhci-pci-core.o
7272
xhci-pci-$(CONFIG_USB_XHCI_PCI_RENESAS) += xhci-pci-renesas.o
73+
xhci-pci-$(CONFIG_USB_XHCI_PCI_ASMEDIA) += xhci-pci-asmedia.o
7374
obj-$(CONFIG_USB_XHCI_PLATFORM) += xhci-plat-hcd.o
7475
obj-$(CONFIG_USB_XHCI_HISTB) += xhci-histb.o
7576
obj-$(CONFIG_USB_XHCI_RCAR) += xhci-rcar-hcd.o
Lines changed: 312 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,312 @@
1+
// SPDX-License-Identifier: GPL-2.0 OR MIT
2+
/*
3+
* ASMedia xHCI firmware loader
4+
* Copyright (C) The Asahi Linux Contributors
5+
*/
6+
7+
#include <linux/acpi.h>
8+
#include <linux/firmware.h>
9+
#include <linux/pci.h>
10+
#include <linux/iopoll.h>
11+
#include <linux/slab.h>
12+
#include <asm/unaligned.h>
13+
14+
#include "xhci.h"
15+
#include "xhci-trace.h"
16+
#include "xhci-pci.h"
17+
18+
/* Configuration space registers */
19+
#define ASMT_CFG_CONTROL 0xe0
20+
#define ASMT_CFG_CONTROL_WRITE BIT(1)
21+
#define ASMT_CFG_CONTROL_READ BIT(0)
22+
23+
#define ASMT_CFG_SRAM_ADDR 0xe2
24+
25+
#define ASMT_CFG_SRAM_ACCESS 0xef
26+
#define ASMT_CFG_SRAM_ACCESS_READ BIT(6)
27+
#define ASMT_CFG_SRAM_ACCESS_ENABLE BIT(7)
28+
29+
#define ASMT_CFG_DATA_READ0 0xf0
30+
#define ASMT_CFG_DATA_READ1 0xf4
31+
32+
#define ASMT_CFG_DATA_WRITE0 0xf8
33+
#define ASMT_CFG_DATA_WRITE1 0xfc
34+
35+
#define ASMT_CMD_GET_FWVER 0x8000060840
36+
#define ASMT_FWVER_ROM 0x010250090816
37+
38+
/* BAR0 registers */
39+
#define ASMT_REG_ADDR 0x3000
40+
41+
#define ASMT_REG_DATA 0x3004
42+
43+
#define ASMT_REG_STATUS 0x3009
44+
#define ASMT_REG_STATUS_BUSY BIT(7)
45+
46+
#define ASMT_REG_WDATA 0x3010
47+
#define ASMT_REG_RDATA 0x3018
48+
49+
#define TIMEOUT_USEC 10000
50+
#define RESET_TIMEOUT_USEC 500000
51+
52+
static int asmedia_mbox_tx(struct pci_dev *pdev, u64 data)
53+
{
54+
u8 op;
55+
int i;
56+
57+
for (i = 0; i < TIMEOUT_USEC; i++) {
58+
pci_read_config_byte(pdev, ASMT_CFG_CONTROL, &op);
59+
if (!(op & ASMT_CFG_CONTROL_WRITE))
60+
break;
61+
udelay(1);
62+
}
63+
64+
if (op & ASMT_CFG_CONTROL_WRITE) {
65+
dev_err(&pdev->dev,
66+
"Timed out on mailbox tx: 0x%llx\n",
67+
data);
68+
return -ETIMEDOUT;
69+
}
70+
71+
pci_write_config_dword(pdev, ASMT_CFG_DATA_WRITE0, data);
72+
pci_write_config_dword(pdev, ASMT_CFG_DATA_WRITE1, data >> 32);
73+
pci_write_config_byte(pdev, ASMT_CFG_CONTROL,
74+
ASMT_CFG_CONTROL_WRITE);
75+
76+
return 0;
77+
}
78+
79+
static int asmedia_mbox_rx(struct pci_dev *pdev, u64 *data)
80+
{
81+
u8 op;
82+
u32 low, high;
83+
int i;
84+
85+
for (i = 0; i < TIMEOUT_USEC; i++) {
86+
pci_read_config_byte(pdev, ASMT_CFG_CONTROL, &op);
87+
if (op & ASMT_CFG_CONTROL_READ)
88+
break;
89+
udelay(1);
90+
}
91+
92+
if (!(op & ASMT_CFG_CONTROL_READ)) {
93+
dev_err(&pdev->dev, "Timed out on mailbox rx\n");
94+
return -ETIMEDOUT;
95+
}
96+
97+
pci_read_config_dword(pdev, ASMT_CFG_DATA_READ0, &low);
98+
pci_read_config_dword(pdev, ASMT_CFG_DATA_READ1, &high);
99+
pci_write_config_byte(pdev, ASMT_CFG_CONTROL,
100+
ASMT_CFG_CONTROL_READ);
101+
102+
*data = ((u64)high << 32) | low;
103+
return 0;
104+
}
105+
106+
static int asmedia_get_fw_version(struct pci_dev *pdev, u64 *version)
107+
{
108+
int err = 0;
109+
u64 cmd;
110+
111+
err = asmedia_mbox_tx(pdev, ASMT_CMD_GET_FWVER);
112+
if (err)
113+
return err;
114+
err = asmedia_mbox_tx(pdev, 0);
115+
if (err)
116+
return err;
117+
118+
err = asmedia_mbox_rx(pdev, &cmd);
119+
if (err)
120+
return err;
121+
err = asmedia_mbox_rx(pdev, version);
122+
if (err)
123+
return err;
124+
125+
if (cmd != ASMT_CMD_GET_FWVER) {
126+
dev_err(&pdev->dev, "Unexpected reply command 0x%llx\n", cmd);
127+
return -EIO;
128+
}
129+
130+
return 0;
131+
}
132+
133+
static bool asmedia_check_firmware(struct pci_dev *pdev)
134+
{
135+
u64 fwver;
136+
int ret;
137+
138+
ret = asmedia_get_fw_version(pdev, &fwver);
139+
if (ret)
140+
return ret;
141+
142+
dev_info(&pdev->dev, "Firmware version: 0x%llx\n", fwver);
143+
144+
return fwver != ASMT_FWVER_ROM;
145+
}
146+
147+
static int asmedia_wait_reset(struct pci_dev *pdev)
148+
{
149+
struct usb_hcd *hcd = dev_get_drvdata(&pdev->dev);
150+
struct xhci_cap_regs __iomem *cap = hcd->regs;
151+
struct xhci_op_regs __iomem *op;
152+
u32 val;
153+
int ret;
154+
155+
op = hcd->regs + HC_LENGTH(readl(&cap->hc_capbase));
156+
157+
ret = readl_poll_timeout(&op->command,
158+
val, !(val & CMD_RESET),
159+
1000, RESET_TIMEOUT_USEC);
160+
161+
if (!ret)
162+
return 0;
163+
164+
dev_err(hcd->self.controller, "Reset timed out, trying to kick it\n");
165+
166+
pci_write_config_byte(pdev, ASMT_CFG_SRAM_ACCESS,
167+
ASMT_CFG_SRAM_ACCESS_ENABLE);
168+
169+
pci_write_config_byte(pdev, ASMT_CFG_SRAM_ACCESS, 0);
170+
171+
ret = readl_poll_timeout(&op->command,
172+
val, !(val & CMD_RESET),
173+
1000, RESET_TIMEOUT_USEC);
174+
175+
if (ret)
176+
dev_err(hcd->self.controller, "Reset timed out, giving up\n");
177+
178+
return ret;
179+
}
180+
181+
static void asmedia_write_reg(struct usb_hcd *hcd, u16 addr, u8 data) {
182+
void __iomem *regs = hcd->regs;
183+
u8 status;
184+
int ret;
185+
186+
writew_relaxed(addr, regs + ASMT_REG_ADDR);
187+
188+
ret = readb_poll_timeout(regs + ASMT_REG_STATUS,
189+
status, !(status & ASMT_REG_STATUS_BUSY),
190+
1000, TIMEOUT_USEC);
191+
192+
if (ret)
193+
dev_err(hcd->self.controller,
194+
"Write addr timed out ([%04x] = %02x)\n",
195+
addr, data);
196+
197+
writeb_relaxed(data, regs + ASMT_REG_DATA);
198+
199+
ret = readb_poll_timeout(regs + ASMT_REG_STATUS,
200+
status, !(status & ASMT_REG_STATUS_BUSY),
201+
1000, TIMEOUT_USEC);
202+
203+
if (ret)
204+
dev_err(hcd->self.controller,
205+
"Write data timed out ([%04x] = %02x)\n",
206+
addr, data);
207+
}
208+
209+
static int asmedia_load_fw(struct pci_dev *pdev, const struct firmware *fw)
210+
{
211+
struct usb_hcd *hcd;
212+
void __iomem *regs;
213+
const u16 *fw_data = (const u16 *)fw->data;
214+
u32 data;
215+
size_t index = 0, addr = 0;
216+
size_t words = fw->size >> 1;
217+
int ret;
218+
219+
hcd = dev_get_drvdata(&pdev->dev);
220+
regs = hcd->regs;
221+
222+
asmedia_write_reg(hcd, 0x5040, 2);
223+
asmedia_write_reg(hcd, 0x5042, 1);
224+
225+
ret = asmedia_wait_reset(pdev);
226+
if (ret) {
227+
dev_err(hcd->self.controller, "Failed pre-upload reset\n");
228+
return ret;
229+
}
230+
231+
asmedia_write_reg(hcd, 0x500e, 1);
232+
233+
pci_write_config_byte(pdev, ASMT_CFG_SRAM_ACCESS,
234+
ASMT_CFG_SRAM_ACCESS_ENABLE);
235+
236+
/* The firmware upload is interleaved in 0x4000 word blocks */
237+
addr = index = 0;
238+
while (index < words) {
239+
data = fw_data[index];
240+
if ((index | 0x4000) < words)
241+
data |= fw_data[index | 0x4000] << 16;
242+
243+
pci_write_config_word(pdev, ASMT_CFG_SRAM_ADDR,
244+
addr);
245+
246+
writel_relaxed(data, regs + ASMT_REG_WDATA);
247+
248+
if (++index & 0x4000)
249+
index += 0x4000;
250+
addr += 2;
251+
}
252+
253+
asmedia_write_reg(hcd, 0x5040, 3);
254+
255+
pci_write_config_byte(pdev, ASMT_CFG_SRAM_ACCESS, 0);
256+
257+
asmedia_write_reg(hcd, 0x500e, 0);
258+
259+
ret = asmedia_wait_reset(pdev);
260+
if (ret) {
261+
dev_err(hcd->self.controller, "Failed post-upload reset\n");
262+
return ret;
263+
}
264+
265+
return 0;
266+
}
267+
268+
int asmedia_xhci_check_request_fw(struct pci_dev *pdev,
269+
const struct pci_device_id *id)
270+
{
271+
struct xhci_driver_data *driver_data =
272+
(struct xhci_driver_data *)id->driver_data;
273+
const char *fw_name = driver_data->firmware;
274+
const struct firmware *fw;
275+
int ret;
276+
277+
/* Check if device has firmware, if so skip everything */
278+
ret = asmedia_check_firmware(pdev);
279+
if (ret < 0)
280+
return ret;
281+
else if (ret == 1)
282+
return 0;
283+
284+
pci_dev_get(pdev);
285+
ret = request_firmware(&fw, fw_name, &pdev->dev);
286+
pci_dev_put(pdev);
287+
if (ret) {
288+
dev_err(&pdev->dev, "Could not load firmware %s: %d\n",
289+
fw_name, ret);
290+
return ret;
291+
}
292+
293+
ret = asmedia_load_fw(pdev, fw);
294+
if (ret) {
295+
dev_err(&pdev->dev, "Firmware upload failed: %d\n", ret);
296+
goto err;
297+
}
298+
299+
ret = asmedia_check_firmware(pdev);
300+
if (ret < 0) {
301+
goto err;
302+
} else if (ret != 1) {
303+
dev_err(&pdev->dev, "Firmware version is too old after upload\n");
304+
ret = -EIO;
305+
} else {
306+
ret = 0;
307+
}
308+
309+
err:
310+
release_firmware(fw);
311+
return ret;
312+
}

drivers/usb/host/xhci-pci-core.c

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,18 @@ static int xhci_pci_setup(struct usb_hcd *hcd)
395395
struct xhci_hcd *xhci;
396396
struct pci_dev *pdev = to_pci_dev(hcd->self.controller);
397397
int retval;
398+
struct xhci_driver_data *driver_data;
399+
const struct pci_device_id *id;
400+
401+
id = pci_match_id(to_pci_driver(pdev->dev.driver)->id_table, pdev);
402+
if (id && id->driver_data && usb_hcd_is_primary_hcd(hcd)) {
403+
driver_data = (struct xhci_driver_data *)id->driver_data;
404+
if (driver_data->quirks & XHCI_ASMEDIA_FW_QUIRK) {
405+
retval = asmedia_xhci_check_request_fw(pdev, id);
406+
if (retval < 0)
407+
return retval;
408+
}
409+
}
398410

399411
xhci = hcd_to_xhci(hcd);
400412
if (!xhci->sbrn)
@@ -738,6 +750,11 @@ static const struct xhci_driver_data reneses_data = {
738750
.firmware = "renesas_usb_fw.mem",
739751
};
740752

753+
static const struct xhci_driver_data asmedia_data = {
754+
.quirks = XHCI_ASMEDIA_FW_QUIRK,
755+
.firmware = "asmedia/asm2214a-apple.bin",
756+
};
757+
741758
/* PCI driver selection metadata; PCI hotplugging uses this */
742759
static const struct pci_device_id pci_ids[] = {
743760
{ PCI_DEVICE(0x1912, 0x0014),
@@ -746,6 +763,9 @@ static const struct pci_device_id pci_ids[] = {
746763
{ PCI_DEVICE(0x1912, 0x0015),
747764
.driver_data = (unsigned long)&reneses_data,
748765
},
766+
{ PCI_DEVICE(0x1b21, 0x2142),
767+
.driver_data = (unsigned long)&asmedia_data,
768+
},
749769
/* handle any USB 3.0 xHCI controller */
750770
{ PCI_DEVICE_CLASS(PCI_CLASS_SERIAL_USB_XHCI, ~0),
751771
},
@@ -761,6 +781,10 @@ MODULE_DEVICE_TABLE(pci, pci_ids);
761781
MODULE_FIRMWARE("renesas_usb_fw.mem");
762782
#endif
763783

784+
#if IS_ENABLED(CONFIG_USB_XHCI_PCI_ASMEDIA)
785+
MODULE_FIRMWARE("asmedia/asm2214a-apple.bin");
786+
#endif
787+
764788
/* pci driver glue; this is a "new style" PCI driver module */
765789
static struct pci_driver xhci_pci_driver = {
766790
.name = hcd_name,

0 commit comments

Comments
 (0)