Re: [PATCH] usb: xhci: handle uPD720201 and uPD720202 w/o ROM

Christian Lamparter
2016-06-23T18:45:23Z

Hello,

On Tuesday, June 21, 2016 05:56:58 AM Yoshihiro Shimoda wrote:
> > From: Christian Lamparter
> > Sent: Tuesday, June 21, 2016 12:32 AM
> >
> > On Wednesday, June 08, 2016 12:14:57 AM Christian Lamparter wrote:
> > > This patch adds a firmware check for the uPD720201K8-711-BAC-A
> > > and uPD720202K8-711-BAA-A variant. Both of these chips are listed
> > > in Renesas' R19UH0078EJ0500 Rev.5.00 "User's Manual: Hardware" as
> > > devices which need a firmware in order to work as they do not have
> > > support to load the firmware from an external ROM.
> > >
> > > Currently, the xhci-pci driver is unable to initialize the hcd in
> > > this case. Instead it will wait for 30 seconds and cause a timeout
> > > in xhci_handshake() and fails.
> > >
> > > [ 5.116990] xhci_hcd 0000:45:00.0: new USB bus registered ...
> > > [ 32.335215] xhci_hcd 0000:45:00.0: can't setup: -110
> > > [ 32.340179] xhci_hcd 0000:45:00.0: USB bus 2 deregistered
> > > [ 32.345587] xhci_hcd 0000:45:00.0: init 0000:45:00.0 fail, -110
> > > [ 32.351496] xhci_hcd: probe of 0000:45:00.0 failed with error -110
> > >
> > > Cc: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com>
> > > Signed-off-by: Christian Lamparter <chunkeey@gmail.com>
> > Hello?
> >
> > Are there any news on this? Or is there anything else that I'm missing
> > which blocks this patch? If it's because the device won't be working
> > either with this patch, then please let me know.
>
> Thank you for the patch with CC me.
> However, I'm afraid but I don't know the detail of the Renesas xHCI PCI controller.
> (My job is for R-Car environment for now.)
Oh. I see. Still, I would like to thank you for your input. I looked at
the rcar loader and firmware: The firmware header (aa55 + firmware version
pointer at 0x14) are carbon-copies of the pci parts (or vice versa?). I guess
I shouldn't be surprised, I bet the rcar and uPD both have a similar SuperH.
The firmware upload mechanism itself seems to be a bit different though, the
spec for the pci devices lists much more stuff to check and verify. Also, the
pci variant uploades 2 x 32bit packages at a time (via data0 and data1 in the
pci config space). However, the procedure is still somewhat identical.

Anyway, if you want to take a look: I went ahead and wrote a firmware loader
for the uPD720201 and uPD720202: I have attached in the patch below. The device
now works for this APM82181 (PowerPC 464 - Big Endian).

xhci_hcd 0000:45:00.0: xHCI Host Controller
xhci_hcd 0000:45:00.0: new USB bus registered, assigned bus number 2
xhci_hcd 0000:45:00.0: hcc params 0x014051cf hci version 0x100 quirks 0x01000090
xhci_hcd 0000:45:00.0: xHCI Host Controller
xhci_hcd 0000:45:00.0: new USB bus registered, assigned bus number 3
usb usb3: We don't know the algorithms for LPM for this host, disabling LPM.
hub 3-0:1.0: USB hub found
hub 3-0:1.0: 2 ports detected
usb 3-1: new SuperSpeed USB device number 2 using xhci_hcd
usb-storage 3-1:1.0: USB Mass Storage device detected
scsi host1: usb-storage 3-1:1.0
scsi 1:0:0:0: Direct-Access Intenso Slim Line PMAP PQ: 0 ANSI: 6
sd 1:0:0:0: [sdb] 15466496 512-byte logical blocks: (7.92 GB/7.38 GiB)
...

> By the way, the issue seems similar with R-Car environment though :)
> http://thread.gmane.org/gmane.linux.kernel.stable/175457/focus=140699
>
> and fix patch for it:
> http://thread.gmane.org/gmane.linux.kernel.stable/177524
Yes :). Same thing here. Load the firmware and the device is happy.
I know this is a big ask: Do you know or can you give me a lead
to whom would be willing to help/support the uPD720201/2? It would
be great if the firmware could be added to linux-firmware.git.

Note: This is one way to do it. But there are many more... I think adding
a pci_enable quirk to drivers/usb/host/pci-quirk.c is the least invasive
way... unless of course, someone knows a even better method... So please:
let me know!

Regards,
Christian
---
>From a0dc613140bab907a3d5787a7ae7b0638bf674d0 Mon Sep 17 00:00:00 2001
From: Christian Lamparter <chunkeey@gmail.com>
Date: Thu, 23 Jun 2016 20:28:20 +0200
Subject: [PATCH] usb: xhci: add firmware loader quirk for uPD720201 and
uPD720202

This patch adds a firmware loader for the uPD720201K8-711-BAC-A
and uPD720202K8-711-BAA-A variant. Both of these chips are listed
in Renesas' R19UH0078EJ0500 Rev.5.00 "User's Manual: Hardware" as
devices which need a firmware in order to work as they do not have
support to load the firmware from an external ROM.

Currently, the xhci-pci driver is unable to initialize the hcd in
this case. Instead it will wait for 30 seconds and cause a timeout
in xhci_handshake() and fail.

[ 5.116990] xhci_hcd 0000:45:00.0: new USB bus registered ...
[ 32.335215] xhci_hcd 0000:45:00.0: can't setup: -110
[ 32.340179] xhci_hcd 0000:45:00.0: USB bus 2 deregistered
[ 32.345587] xhci_hcd 0000:45:00.0: init 0000:45:00.0 fail, -110
[ 32.351496] xhci_hcd: probe of 0000:45:00.0 failed with error -110

With the firmware loader quirk and the correct firmware in place,
the device will now initialize successfully:

[ 5.384776] xhci_hcd 0000:45:00.0: xHCI Host Controller
[ 5.390028] xhci_hcd 0000:45:00.0: new USB bus registered ...
[ 5.403017] xhci_hcd 0000:45:00.0: hcc params 0x014051cf ...
[ 5.412217] hub 2-0:1.0: USB hub found
[ 5.416123] hub 2-0:1.0: 2 ports detected
[ 5.420494] xhci_hcd 0000:45:00.0: xHCI Host Controller
[ 5.425730] xhci_hcd 0000:45:00.0: new USB bus registered...
[ 5.441771] hub 3-0:1.0: USB hub found
[ 5.445692] hub 3-0:1.0: 2 ports detected
...

The firmware image can be extracted from the Windows driver.
A viable source is the "PP2U-E" (USB3.0 Host to PCIe Adapter)'s
"Firmware download (ver 2.0.1.3) Jun 15, 2012" file. It contains
the K2013080.mem file which needs to be placed in /lib/firmware.

Signed-off-by: Christian Lamparter <chunkeey@gmail.com>
---
drivers/usb/host/pci-quirks.c | 362 ++++++++++++++++++++++++++++++++++++++++++
1 file changed, 362 insertions(+)

diff --git a/drivers/usb/host/pci-quirks.c b/drivers/usb/host/pci-quirks.c
index 35af362..4d96142 100644
--- a/drivers/usb/host/pci-quirks.c
+++ b/drivers/usb/host/pci-quirks.c
@@ -16,6 +16,10 @@
#include <linux/export.h>
#include <linux/acpi.h>
#include <linux/dmi.h>
+#include <linux/firmware.h>
+
+#include <asm/unaligned.h>
+
#include "pci-quirks.h"
#include "xhci-ext-caps.h"

@@ -1089,3 +1093,361 @@ static void quirk_usb_early_handoff(struct pci_dev *pdev)
}
DECLARE_PCI_FIXUP_CLASS_FINAL(PCI_ANY_ID, PCI_ANY_ID,
PCI_CLASS_SERIAL_USB, 8, quirk_usb_early_handoff);
+
+
+/* Renesas uPD720201/uPD720202 firmware loader */
+
+static const struct renesas_fw_entry {
+ const char *firmware_name;
+ u16 device;
+ u8 revision;
+ u16 expected_version;
+} renesas_fw_table[] = {
+ /*
+ * Only the uPD720201K8-711-BAC-A or uPD720202K8-711-BAA-A
+ * are listed in R19UH0078EJ0500 Rev.5.00 as devices which
+ * need the software loader.
+ *
+ * PP2U/ReleaseNote_USB3-201-202-FW.txt:
+ *
+ * Note: This firmware is for the following devices.
+ * - uPD720201 ES 2.0 sample whose revision ID is 2.
+ * - uPD720201 ES 2.1 sample & CS sample & Mass product, ID is 3.
+ * - uPD720202 ES 2.0 sample & CS sample & Mass product, ID is 2.
+ */
+ { "K2013080.mem", 0x0014, 0x02, 0x2013 },
+ { "K2013080.mem", 0x0014, 0x03, 0x2013 },
+ { "K2013080.mem", 0x0015, 0x02, 0x2013 },
+};
+
+static const struct renesas_fw_entry *renesas_needs_fw_dl(struct pci_dev *dev)
+{
+ const struct renesas_fw_entry *entry;
+ size_t i;
+
+ /* This loader will only work with a RENESAS device. */
+ if (!(dev->vendor == PCI_VENDOR_ID_RENESAS))
+ return NULL;
+
+ for (i = 0; i < ARRAY_SIZE(renesas_fw_table); i++) {
+ entry = &renesas_fw_table[i];
+ if (entry->device == dev->device &&
+ entry->revision == dev->revision)
+ return entry;
+ }
+
+ return NULL;
+}
+
+static int renesas_fw_download_image(struct pci_dev *dev,
+ const u32 *fw,
+ size_t step)
+{
+ size_t i;
+ int err;
+ u8 fw_status;
+ bool data0_or_data1;
+
+ /*
+ * The hardware does alternate between two 32-bit pages.
+ * (This is because each row of the firmware is 8 bytes).
+ *
+ * for even steps we use DATA0, for odd steps DATA1.
+ */
+ data0_or_data1 = (step & 1) == 1;
+
+ /* step+1. Read "Set DATAX" and confirm it is cleared. */
+ for (i = 0; i < 10000; i++) {
+ err = pci_read_config_byte(dev, 0xF5, &fw_status);
+ if (err)
+ return pcibios_err_to_errno(err);
+ if (!(fw_status & BIT(data0_or_data1)))
+ break;
+
+ udelay(1);
+ }
+ if (i == 10000)
+ return -ETIMEDOUT;
+
+ /*
+ * step+2. Write FW data to "DATAX".
+ * "LSB is left" => force little endian
+ */
+ err = pci_write_config_dword(dev, data0_or_data1 ? 0xFC : 0xF8,
+ (__force u32) cpu_to_le32(fw[step]));
+ if (err)
+ return pcibios_err_to_errno(err);
+
+ /* step+3. Set "Set DATAX". */
+ err = pci_write_config_byte(dev, 0xF5, BIT(data0_or_data1));
+ if (err)
+ return pcibios_err_to_errno(err);
+
+ return 0;
+}
+
+static int renesas_fw_verify(struct pci_dev *dev,
+ const void *fw_data,
+ size_t length)
+{
+ const struct renesas_fw_entry *entry = renesas_needs_fw_dl(dev);
+ u16 fw_version_pointer;
+ u16 fw_version;
+
+ if (!entry)
+ return -EINVAL;
+
+ /*
+ * The Firmware's Data Format is describe in
+ * "6.3 Data Format" R19UH0078EJ0500 Rev.5.00 page 124
+ */
+
+ /* "Each row is 8 bytes". => firmware size must be a multiple of 8. */
+ if (length % 8 != 0) {
+ dev_err(&dev->dev, "firmware size is not a multipe of 8.");
+ return -EINVAL;
+ }
+
+ /*
+ * The bootrom chips of the big brother have sizes up to 64k.
+ * This is as big as the firmware can get.
+ */
+ if (length < 0x1000 || length >= 0x10000) {
+ dev_err(&dev->dev, "firmware is size %zd is not between 4k and 64k.",
+ length);
+ return -EINVAL;
+ }
+
+ /* The First 2 bytes are fixed value (55aa). "LSB on Left" */
+ if (get_unaligned_le16(fw_data) != 0x55aa) {
+ dev_err(&dev->dev, "no valid firmware header found.");
+ return -EINVAL;
+ }
+
+ /* verify the firmware version position and print it. */
+ fw_version_pointer = get_unaligned_le16(fw_data + 4);
+ if (fw_version_pointer + 2 >= length) {
+ dev_err(&dev->dev, "firmware version pointer is outside of the firmware image.");
+ return -EINVAL;
+ }
+
+ fw_version = get_unaligned_le16(fw_data + fw_version_pointer);
+ dev_dbg(&dev->dev, "got firmware version: %02x.", fw_version);
+
+ if (fw_version != entry->expected_version) {
+ dev_err(&dev->dev, "firmware version mismatch, expected version: %02x.",
+ entry->expected_version);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int renesas_fw_check_running(struct pci_dev *pdev)
+{
+ int err;
+ u8 fw_state;
+
+ /*
+ * Test if the device is actually needing the firmware. As most
+ * BIOSes will initialize the device for us. If the device is
+ * initialized.
+ */
+ err = pci_read_config_byte(pdev, 0xF4, &fw_state);
+ if (err)
+ return pcibios_err_to_errno(err);
+
+ /*
+ * Check if "FW Download Lock" is locked. If it is and the FW is
+ * ready we can simply continue. If the FW is not ready, we have
+ * to give up.
+ */
+ if (fw_state & BIT(1)) {
+ dev_dbg(&pdev->dev, "FW Download Lock is engaged.");
+
+ if (fw_state & BIT(4))
+ return 0;
+
+ dev_err(&pdev->dev, "FW Download Lock is set and FW is not ready. Giving Up.");
+ return -EIO;
+ }
+
+ /*
+ * Check if "FW Download Enable" is set. If someone (us?) tampered
+ * with it and it can't be resetted, we have to give up too... and
+ * ask for a forgiveness and a reboot.
+ */
+ if (fw_state & BIT(0)) {
+ dev_err(&pdev->dev, "FW Download Enable is stale. Giving Up (poweroff/reboot needed).");
+ return -EIO;
+ }
+
+ /* Otherwise, Check the "Result Code" Bits (6:4) and act accordingly */
+ switch ((fw_state & 0x70)) {
+ case 0: /* No result yet */
+ dev_dbg(&pdev->dev, "FW is not ready/loaded yet.");
+
+ /* tell the caller, that this device needs the firmware. */
+ return 1;
+
+ case BIT(4): /* Success, device should be working. */
+ dev_dbg(&pdev->dev, "FW is ready.");
+ return 0;
+
+ case BIT(5): /* Error State */
+ dev_err(&pdev->dev, "hardware is in an error state. Giving up (poweroff/reboot needed).");
+ return -ENODEV;
+
+ default: /* All other states are marked as "Reserved states" */
+ dev_err(&pdev->dev, "hardware is in an invalid state %x. Giving up (poweroff/reboot needed).",
+ (fw_state & 0x70) >> 4);
+ return -EINVAL;
+ }
+}
+
+static int renesas_fw_download(struct pci_dev *pdev,
+ const struct firmware *fw, unsigned int retry_counter)
+{
+ const u32 *fw_data = (const u32 *) fw->data;
+ size_t i;
+ int err;
+ u8 fw_status;
+
+ /*
+ * For more information and the big picture: please look at the
+ * "Firmware Download Sequence" in "7.1 FW Download Interface"
+ * of R19UH0078EJ0500 Rev.5.00 page 131
+ *
+ * 0. Set "FW Download Enable" bit in the
+ * "FW Download Control & Status Register" at 0xF4
+ */
+ err = pci_write_config_byte(pdev, 0xF4, BIT(0));
+ if (err)
+ return pcibios_err_to_errno(err);
+
+ /* 1 - 10 follow one step after the other. */
+ for (i = 0; i < fw->size / 4; i++) {
+ err = renesas_fw_download_image(pdev, fw_data, i);
+ if (err) {
+ dev_err(&pdev->dev, "Firmware Download Step %zd failed at position %zd bytes with (%d).",
+ i, i * 4, err);
+ return err;
+ }
+ }
+
+ /*
+ * This sequence continues until the last data is written to
+ * "DATA0" or "DATA1". Naturally, we wait until "SET DATA0/1"
+ * is cleared by the hardware beforehand.
+ */
+ for (i = 0; i < 10000; i++) {
+ err = pci_read_config_byte(pdev, 0xF5, &fw_status);
+ if (err)
+ return pcibios_err_to_errno(err);
+ if (!(fw_status & (BIT(0) | BIT(1))))
+ break;
+
+ udelay(1);
+ }
+ if (i == 10000)
+ dev_warn(&pdev->dev, "Final Firmware Download step timed out.");
+
+ /*
+ * 11. After finishing writing the last data of FW, the
+ * System Software must clear "FW Download Enable"
+ */
+ err = pci_write_config_byte(pdev, 0xF4, 0);
+ if (err)
+ return pcibios_err_to_errno(err);
+
+ /* 12. Read "Result Code" and confirm it is good. */
+ for (i = 0; i < 10000; i++) {
+ err = pci_read_config_byte(pdev, 0xF4, &fw_status);
+ if (err)
+ return pcibios_err_to_errno(err);
+ if (fw_status & BIT(4))
+ break;
+
+ udelay(1);
+ }
+ if (i == 10000) {
+ /* Timed out / Error - let's see if we can fix this */
+ err = renesas_fw_check_running(pdev);
+ switch (err) {
+ case 0: /*
+ * we shouldn't end up here.
+ * maybe it took a little bit longer.
+ * But all should be well?
+ */
+ break;
+
+ case 1: /* (No result yet? - we can try to retry) */
+ if (retry_counter < 10) {
+ retry_counter++;
+ dev_warn(&pdev->dev, "Retry Firmware download: %d try.",
+ retry_counter);
+ return renesas_fw_download(pdev, fw,
+ retry_counter);
+ }
+ return -ETIMEDOUT;
+
+ default:
+ return err;
+ }
+ }
+ /*
+ * Optional last step: Engage Firmware Lock
+ *
+ * err = pci_write_config_byte(pdev, 0xF4, BIT(2));
+ * if (err)
+ * return pcibios_err_to_errno(err);
+ */
+
+ return 0;
+}
+
+static void renesas_fw_download_to_hw(struct pci_dev *pdev)
+{
+ const struct firmware *fw = NULL;
+ const struct renesas_fw_entry *entry;
+ int err;
+
+ /* check if we have a eligible RENESAS' uPD720201/2 w/o FW. */
+ entry = renesas_needs_fw_dl(pdev);
+ if (!entry)
+ return;
+
+ err = renesas_fw_check_running(pdev);
+ /* Continue ahead, if the firmware is already running. */
+ if (err == 0)
+ return;
+
+ if (err != 1) {
+ dev_err(&pdev->dev, "firmware running check failed (%d).",
+ err);
+ return;
+ }
+
+ err = request_firmware(&fw, entry->firmware_name, &pdev->dev);
+ if (err) {
+ dev_err(&pdev->dev, "firmware request failed (%d).", err);
+ return;
+ }
+
+ err = renesas_fw_verify(pdev, fw->data, fw->size);
+ if (err)
+ goto free_fw;
+
+ err = renesas_fw_download(pdev, fw, 0);
+ if (err) {
+ dev_err(&pdev->dev, "firmware failed to download (%d).", err);
+ goto free_fw;
+ }
+
+ free_fw:
+ release_firmware(fw);
+ return;
+}
+
+DECLARE_PCI_FIXUP_ENABLE(PCI_VENDOR_ID_RENESAS, 0x0014, renesas_fw_download_to_hw);
+DECLARE_PCI_FIXUP_ENABLE(PCI_VENDOR_ID_RENESAS, 0x0015, renesas_fw_download_to_hw);
--
2.8.1