xhci: tegra: support loading xusb firmware from file
JC Kuo [Mon, 24 Jun 2013 12:19:31 +0000 (20:19 +0800)]
This commit add the capability of loading Tegra xusb firmware from
a firmware file in file system.

Two kernel module parameters provides the firmware loading flexibility.
1. "use_bootloader_firmware=Y" driver loads firmware from the
   bootloader carveout region.
2. "use_bootloader_firmware=N" driver loads firmware from the file
   specified by "firmware_file" parameter.

This example shows how to load firmware from /etc/firmware/xusb_sil_prod_fw

   insmod /system/lib/modules/xhci-hcd.ko use_bootloader_firmware=N firmware_file=xusb_sil_prod_fw

bug 1301430

Change-Id: I7ff4a86ab56b2724d3a4d17f28fe048e6303b067
Signed-off-by: JC Kuo <jckuo@nvidia.com>
Reviewed-on: http://git-master/r/241457
Reviewed-by: Harshada Kale <hkale@nvidia.com>
Tested-by: Harshada Kale <hkale@nvidia.com>

drivers/usb/host/xhci-tegra.c

index 6206fa4..67906a3 100644 (file)
@@ -31,6 +31,7 @@
 #include <linux/vmalloc.h>
 #include <linux/debugfs.h>
 #include <linux/kthread.h>
+#include <linux/firmware.h>
 #include <mach/powergate.h>
 #include <mach/clk.h>
 #include <mach/tegra_usb_pad_ctrl.h>
@@ -296,6 +297,20 @@ struct tegra_xhci_hcd {
 
 static struct tegra_usb_pmc_data pmc_data;
 
+static bool use_bootloader_firmware = true;
+module_param(use_bootloader_firmware, bool, S_IRUGO);
+MODULE_PARM_DESC(use_bootloader_firmware, "take bootloader initialized firmware");
+
+#define FIRMWARE_FILE "xusb_sil_rel_fw"
+static char *firmware_file = FIRMWARE_FILE;
+#define FIRMWARE_FILE_HELP     \
+       "used to specify firmware file of Tegra XHCI host controller. "\
+       "This takes effect only if \"use_bootloader_firmware\" is \"N\". " \
+       "Default value is \"" FIRMWARE_FILE "\"."
+
+module_param(firmware_file, charp, S_IRUGO);
+MODULE_PARM_DESC(firmware_file, FIRMWARE_FILE_HELP);
+
 /* functions */
 static inline struct tegra_xhci_hcd *hcd_to_tegra_xhci(struct usb_hcd *hcd)
 {
@@ -3131,14 +3146,78 @@ static void deinit_bootloader_firmware(struct tegra_xhci_hcd *tegra)
        memset(&tegra->firmware, 0, sizeof(tegra->firmware));
 }
 
+static int init_filesystem_firmware(struct tegra_xhci_hcd *tegra)
+{
+       struct platform_device *pdev = tegra->pdev;
+       const struct firmware *fw;
+       struct cfgtbl *fw_cfgtbl;
+       size_t fw_size;
+       void *fw_data;
+       dma_addr_t fw_dma;
+       int ret;
+
+       ret = request_firmware(&fw, firmware_file, &pdev->dev);
+       if (ret < 0) {
+               dev_err(&pdev->dev, "request_firmware failed %d\n", ret);
+               return ret;
+       }
+
+       fw_cfgtbl = (struct cfgtbl *) fw->data;
+       fw_size = fw_cfgtbl->fwimg_len;
+       dev_info(&pdev->dev, "Firmware File: %s (%d Bytes)\n",
+                       firmware_file, fw_size);
+
+       fw_data = dma_alloc_coherent(&pdev->dev, fw_size,
+                       &fw_dma, GFP_KERNEL);
+       if (!fw_data) {
+               dev_err(&pdev->dev, "%s: dma_alloc_coherent failed\n",
+                               __func__);
+               ret = -ENOMEM;
+               goto error_release_firmware;
+       }
+
+       memcpy(fw_data, fw->data, fw_size);
+       dev_info(&pdev->dev, "Firmware DMA Memory: dma 0x%p mapped 0x%p (%d Bytes)\n",
+                       (void *) fw_dma, fw_data, fw_size);
+
+       release_firmware(fw);
+
+       /* all set and ready to go */
+       tegra->firmware.data = fw_data;
+       tegra->firmware.dma = fw_dma;
+       tegra->firmware.size = fw_size;
+       return 0;
+
+error_release_firmware:
+       release_firmware(fw);
+       return ret;
+}
+
+static void deinit_filesystem_firmware(struct tegra_xhci_hcd *tegra)
+{
+       struct platform_device *pdev = tegra->pdev;
+
+       if (tegra->firmware.data) {
+               dma_free_coherent(&pdev->dev, tegra->firmware.size,
+                       tegra->firmware.data, tegra->firmware.dma);
+       }
+
+       memset(&tegra->firmware, 0, sizeof(tegra->firmware));
+}
 static int init_firmware(struct tegra_xhci_hcd *tegra)
 {
-       return init_bootloader_firmware(tegra);
+       if (use_bootloader_firmware)
+               return init_bootloader_firmware(tegra);
+       else
+               return init_filesystem_firmware(tegra);
 }
 
 static void deinit_firmware(struct tegra_xhci_hcd *tegra)
 {
-       deinit_bootloader_firmware(tegra);
+       if (use_bootloader_firmware)
+               return deinit_bootloader_firmware(tegra);
+       else
+               return deinit_filesystem_firmware(tegra);
 }
 
 /* TODO: we have to refine error handling in tegra_xhci_probe() */
@@ -3257,7 +3336,7 @@ static int tegra_xhci_probe(struct platform_device *pdev)
        if (ret < 0) {
                dev_err(&pdev->dev, "failed to init firmware\n");
                ret = -ENODEV;
-               goto err_deinit_usb2_clocks;
+               goto err_deinit_firmware_log;
        }
 
        ret = load_firmware(tegra, true /* do reset ARU */);
@@ -3405,6 +3484,8 @@ err_put_usb2_hcd:
        usb_put_hcd(hcd);
 err_deinit_firmware:
        deinit_firmware(tegra);
+err_deinit_firmware_log:
+       fw_log_deinit(tegra);
 err_deinit_usb2_clocks:
        tegra_usb2_clocks_deinit(tegra);
 err_deinit_tegra_xusb_regulator: