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...@renesas.com>
> > > Signed-off-by: Christian Lamparter <chunk...@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 <chunk...@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 <chunk...@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



--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to