summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--arch/riscv/boot/dts/starfive/evb-overlay/jh7110-evb-overlay-spi.dtso7
-rw-r--r--arch/riscv/boot/dts/starfive/jh7110-evb.dts1
-rw-r--r--arch/riscv/boot/dts/starfive/jh7110-evb.dtsi3
-rw-r--r--drivers/media/platform/starfive/v4l2_driver/stfcamss.c2
-rw-r--r--drivers/spi/spi-pl022.c271
-rw-r--r--drivers/usb/core/hcd.c4
-rw-r--r--drivers/usb/host/xhci-mem.c69
-rw-r--r--drivers/usb/host/xhci-plat.c9
-rw-r--r--drivers/usb/host/xhci-ring.c3
-rw-r--r--drivers/usb/host/xhci.c57
-rw-r--r--drivers/usb/host/xhci.h11
11 files changed, 431 insertions, 6 deletions
diff --git a/arch/riscv/boot/dts/starfive/evb-overlay/jh7110-evb-overlay-spi.dtso b/arch/riscv/boot/dts/starfive/evb-overlay/jh7110-evb-overlay-spi.dtso
index a49866ff93c3..638d13c606e0 100644
--- a/arch/riscv/boot/dts/starfive/evb-overlay/jh7110-evb-overlay-spi.dtso
+++ b/arch/riscv/boot/dts/starfive/evb-overlay/jh7110-evb-overlay-spi.dtso
@@ -9,6 +9,7 @@
fragment@0 {
target-path = "/soc/spi@10060000";
__overlay__ {
+ compatible = "starfive,jh7110-spi-pl022";
status = "okay";
};
};
@@ -17,6 +18,7 @@
fragment@1 {
target-path = "/soc/spi@10070000";
__overlay__ {
+ compatible = "starfive,jh7110-spi-pl022";
status = "okay";
};
};
@@ -25,6 +27,7 @@
fragment@2 {
target-path = "/soc/spi@10080000";
__overlay__ {
+ compatible = "starfive,jh7110-spi-pl022";
status = "okay";
};
};
@@ -33,6 +36,7 @@
fragment@3 {
target-path = "/soc/spi@12070000";
__overlay__ {
+ compatible = "starfive,jh7110-spi-pl022";
status = "okay";
};
};
@@ -41,6 +45,7 @@
fragment@4 {
target-path = "/soc/spi@12080000";
__overlay__ {
+ compatible = "starfive,jh7110-spi-pl022";
status = "okay";
};
};
@@ -49,6 +54,7 @@
fragment@5 {
target-path = "/soc/spi@12090000";
__overlay__ {
+ compatible = "starfive,jh7110-spi-pl022";
status = "okay";
};
};
@@ -57,6 +63,7 @@
fragment@6 {
target-path = "/soc/spi@120a0000";
__overlay__ {
+ compatible = "starfive,jh7110-spi-pl022";
status = "okay";
};
};
diff --git a/arch/riscv/boot/dts/starfive/jh7110-evb.dts b/arch/riscv/boot/dts/starfive/jh7110-evb.dts
index 3e9e1372f510..fb34043e2ba4 100644
--- a/arch/riscv/boot/dts/starfive/jh7110-evb.dts
+++ b/arch/riscv/boot/dts/starfive/jh7110-evb.dts
@@ -31,5 +31,6 @@
};
&usb0 {
+ xhci-lowmem-pool;
status = "okay";
};
diff --git a/arch/riscv/boot/dts/starfive/jh7110-evb.dtsi b/arch/riscv/boot/dts/starfive/jh7110-evb.dtsi
index 1c6ebe473353..8d1ad85f5a30 100644
--- a/arch/riscv/boot/dts/starfive/jh7110-evb.dtsi
+++ b/arch/riscv/boot/dts/starfive/jh7110-evb.dtsi
@@ -568,6 +568,9 @@
spl@0 {
reg = <0x0 0x40000>;
};
+ uboot-env@f0000 {
+ reg = <0xf0000 0x10000>;
+ };
uboot@100000 {
reg = <0x100000 0x300000>;
};
diff --git a/drivers/media/platform/starfive/v4l2_driver/stfcamss.c b/drivers/media/platform/starfive/v4l2_driver/stfcamss.c
index 431801e7a6ed..24bfe8da9c9a 100644
--- a/drivers/media/platform/starfive/v4l2_driver/stfcamss.c
+++ b/drivers/media/platform/starfive/v4l2_driver/stfcamss.c
@@ -1362,7 +1362,7 @@ static void __exit stfcamss_cleanup(void)
platform_driver_unregister(&stfcamss_driver);
}
-module_init(stfcamss_init);
+late_initcall(stfcamss_init);
//fs_initcall(stfcamss_init);
module_exit(stfcamss_cleanup);
diff --git a/drivers/spi/spi-pl022.c b/drivers/spi/spi-pl022.c
index 869f0095e6ac..1420c40fa815 100644
--- a/drivers/spi/spi-pl022.c
+++ b/drivers/spi/spi-pl022.c
@@ -34,6 +34,9 @@
#include <linux/of.h>
#include <linux/pinctrl/consumer.h>
#include <linux/reset.h>
+#include <linux/platform_device.h>
+#include <linux/clk/clk-conf.h>
+#include <linux/pm_domain.h>
/*
* This macro is used to define some register default values.
@@ -2088,7 +2091,10 @@ pl022_platform_data_dt_get(struct device *dev)
return NULL;
}
- pd = devm_kzalloc(dev, sizeof(struct pl022_ssp_controller), GFP_KERNEL);
+ if (strncmp(dev->bus->name, "platform", strlen("platform")))
+ pd = devm_kzalloc(dev, sizeof(struct pl022_ssp_controller), GFP_KERNEL);
+ else
+ pd = kzalloc(sizeof(struct pl022_ssp_controller), GFP_KERNEL);
if (!pd)
return NULL;
@@ -2100,6 +2106,172 @@ pl022_platform_data_dt_get(struct device *dev)
return pd;
}
+static int pl022_platform_probe(struct platform_device *pdev, const struct amba_id *id)
+{
+ struct device *dev = &pdev->dev;
+ struct spi_controller *host;
+ struct pl022_ssp_controller *platform_info;
+ struct amba_device *adev;
+ struct pl022 *pl022 = NULL;
+ struct resource *res;
+ int status = 0;
+ int irq;
+
+ dev_info(dev,
+ "ARM PL022 driver for StarFive SoC platform, device ID: 0x%08x\n",
+ id->id);
+
+ adev = devm_kzalloc(dev, sizeof(*adev), GFP_KERNEL);
+ adev->dev = pdev->dev;
+ platform_info = pl022_platform_data_dt_get(dev);
+ if (!platform_info) {
+ dev_err(dev, "probe: no platform data defined\n");
+ return -ENODEV;
+ }
+ /* Allocate host with space for data */
+ host = spi_alloc_host(dev, sizeof(struct pl022));
+ if (host == NULL) {
+ dev_err(dev, "probe - cannot alloc SPI host\n");
+ return -ENOMEM;
+ }
+
+ pl022 = spi_controller_get_devdata(host);
+ pl022->host = host;
+ pl022->host_info = platform_info;
+ pl022->adev = adev;
+ pl022->vendor = id->data;
+ pl022->host->dev.parent = &pdev->dev;
+ /*
+ * Bus Number Which has been Assigned to this SSP controller
+ * on this board
+ */
+ host->bus_num = platform_info->bus_id;
+ host->cleanup = pl022_cleanup;
+ host->setup = pl022_setup;
+ /* If open CONFIG_PM, auto_runtime_pm should be false when of-platform.*/
+ host->auto_runtime_pm = true;
+ host->transfer_one_message = pl022_transfer_one_message;
+ host->unprepare_transfer_hardware = pl022_unprepare_transfer_hardware;
+ host->rt = platform_info->rt;
+ host->dev.of_node = dev->of_node;
+ host->use_gpio_descriptors = true;
+
+ /*
+ * Supports mode 0-3, loopback, and active low CS. Transfers are
+ * always MS bit first on the original pl022.
+ */
+ host->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH | SPI_LOOP;
+ if (pl022->vendor->extended_cr)
+ host->mode_bits |= SPI_LSB_FIRST;
+
+ dev_dbg(dev, "BUSNO: %d\n", host->bus_num);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ pl022->phybase = res->start;
+ pl022->virtbase = devm_ioremap_resource(dev, res);
+ if (pl022->virtbase == NULL) {
+ status = -ENOMEM;
+ goto err_no_ioremap;
+ }
+ dev_info(dev, "mapped registers from %llx to %llx\n",
+ pdev->resource->start, pdev->resource->end);
+
+ pl022->clk = devm_clk_get(dev, NULL);
+ if (IS_ERR(pl022->clk)) {
+ status = PTR_ERR(pl022->clk);
+ dev_err(dev, "could not retrieve SSP/SPI bus clock\n");
+ goto err_no_clk;
+ }
+ status = clk_prepare_enable(pl022->clk);
+ if (status) {
+ dev_err(dev, "could not enable SSP/SPI bus clock\n");
+ goto err_no_clk_en;
+ }
+
+ pl022->rst = devm_reset_control_get_exclusive(dev, NULL);
+ if (!IS_ERR(pl022->rst)) {
+ status = reset_control_deassert(pl022->rst);
+ if (status) {
+ dev_err(dev, "could not deassert SSP/SPI bus reset\n");
+ goto err_no_rst_clr;
+ }
+ } else {
+ status = PTR_ERR(pl022->rst);
+ dev_err(dev, "could not retrieve SSP/SPI bus reset\n");
+ goto err_no_rst;
+ }
+
+ /* Initialize transfer pump */
+ tasklet_init(&pl022->pump_transfers, pump_transfers,
+ (unsigned long)pl022);
+
+ /* Disable SSP */
+ writew((readw(SSP_CR1(pl022->virtbase)) & (~SSP_CR1_MASK_SSE)),
+ SSP_CR1(pl022->virtbase));
+ load_ssp_default_config(pl022);
+
+ /* Obtain IRQ line. */
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0) {
+ status = -ENXIO;
+ goto err_no_irq;
+ }
+ status = devm_request_irq(dev, irq, pl022_interrupt_handler,
+ 0, "pl022", pl022);
+ if (status < 0) {
+ dev_err(dev, "probe - cannot get IRQ (%d)\n", status);
+ goto err_no_irq;
+ }
+
+ /* Get DMA channels, try autoconfiguration first */
+ status = pl022_dma_autoprobe(pl022);
+ if (status == -EPROBE_DEFER) {
+ dev_dbg(dev, "deferring probe to get DMA channel\n");
+ goto err_no_irq;
+ }
+
+ /* dma is not used unless configured in the device tree */
+ platform_info->enable_dma = 0;
+
+ /* If that failed, use channels from platform_info */
+ if (status == 0)
+ platform_info->enable_dma = 1;
+ else if (platform_info->enable_dma) {
+ status = pl022_dma_probe(pl022);
+ if (status != 0)
+ platform_info->enable_dma = 0;
+ }
+
+ /* Register with the SPI framework */
+ dev_set_drvdata(dev, pl022);
+
+ status = devm_spi_register_controller(dev, host);
+ if (status != 0) {
+ dev_err(dev,
+ "probe - problem registering spi host\n");
+ goto err_spi_register;
+ }
+ dev_dbg(dev, "probe succeeded\n");
+
+ clk_disable_unprepare(pl022->clk);
+
+ return 0;
+ err_spi_register:
+ if (platform_info->enable_dma)
+ pl022_dma_remove(pl022);
+ err_no_irq:
+ reset_control_assert(pl022->rst);
+ err_no_rst_clr:
+ err_no_rst:
+ clk_disable_unprepare(pl022->clk);
+ err_no_clk_en:
+ err_no_clk:
+ err_no_ioremap:
+ release_mem_region(pdev->resource->start, resource_size(pdev->resource));
+ spi_controller_put(host);
+ return status;
+}
+
static int pl022_probe(struct amba_device *adev, const struct amba_id *id)
{
struct device *dev = &adev->dev;
@@ -2230,6 +2402,7 @@ static int pl022_probe(struct amba_device *adev, const struct amba_id *id)
/* Register with the SPI framework */
amba_set_drvdata(adev, pl022);
+
status = devm_spi_register_controller(&adev->dev, host);
if (status != 0) {
dev_err_probe(&adev->dev, status,
@@ -2238,6 +2411,7 @@ static int pl022_probe(struct amba_device *adev, const struct amba_id *id)
}
dev_dbg(dev, "probe succeeded\n");
+ platform_info->autosuspend_delay = 100;
/* let runtime pm put suspend */
if (platform_info->autosuspend_delay > 0) {
dev_info(&adev->dev,
@@ -2247,6 +2421,7 @@ static int pl022_probe(struct amba_device *adev, const struct amba_id *id)
platform_info->autosuspend_delay);
pm_runtime_use_autosuspend(dev);
}
+
pm_runtime_put(dev);
return 0;
@@ -2255,6 +2430,7 @@ static int pl022_probe(struct amba_device *adev, const struct amba_id *id)
if (platform_info->enable_dma)
pl022_dma_remove(pl022);
err_no_irq:
+ reset_control_assert(pl022->rst);
err_no_rst_de:
err_no_rst:
clk_disable_unprepare(pl022->clk);
@@ -2264,6 +2440,7 @@ static int pl022_probe(struct amba_device *adev, const struct amba_id *id)
amba_release_regions(adev);
err_no_ioregion:
spi_controller_put(host);
+
return status;
}
@@ -2456,7 +2633,11 @@ static int __init pl022_init(void)
{
return amba_driver_register(&pl022_driver);
}
+#if !IS_MODULE(CONFIG_SPI_PL022)
subsys_initcall(pl022_init);
+#else
+module_init(pl022_init);
+#endif
static void __exit pl022_exit(void)
{
@@ -2464,6 +2645,94 @@ static void __exit pl022_exit(void)
}
module_exit(pl022_exit);
+/*
+ * Register PL022 in platform bus to accommodate overlay use.
+ * Because overlay only trigger response from the platform bus
+ * not amba bus.
+ */
+static int starfive_of_pl022_probe(struct platform_device *pdev)
+{
+ int ret;
+ const struct amba_id id = {
+ .id = 0x00041022,
+ .mask = 0x000fffff,
+ .data = &vendor_arm
+ };
+ struct device *dev = &pdev->dev;
+
+ ret = of_clk_set_defaults(dev->of_node, false);
+ if (ret < 0)
+ goto err_probe;
+
+ ret = dev_pm_domain_attach(dev, true);
+ if (ret)
+ goto err_probe;
+
+ ret = pl022_platform_probe(pdev, &id);
+
+ pm_runtime_enable(dev);
+ pm_runtime_set_autosuspend_delay(dev, 100);
+ pm_runtime_use_autosuspend(dev);
+
+ if (ret) {
+ pm_runtime_disable(dev);
+ pm_runtime_set_suspended(dev);
+ pm_runtime_put_noidle(dev);
+ dev_pm_domain_detach(dev, true);
+ }
+
+err_probe:
+ return ret;
+}
+
+static int starfive_of_pl022_remove(struct platform_device *pdev)
+{
+ struct pl022 *pl022 = dev_get_drvdata(&pdev->dev);
+
+ if (!pl022)
+ return 0;
+
+ pm_runtime_get_sync(&pdev->dev);
+ pm_runtime_get_noresume(&pdev->dev);
+
+ load_ssp_default_config(pl022);
+ if (pl022->host_info->enable_dma)
+ pl022_dma_remove(pl022);
+
+ clk_disable_unprepare(pl022->clk);
+ tasklet_disable(&pl022->pump_transfers);
+
+ pm_runtime_put_noidle(&pdev->dev);
+ pm_runtime_disable(&pdev->dev);
+ pm_runtime_set_suspended(&pdev->dev);
+ pm_runtime_put_noidle(&pdev->dev);
+ dev_pm_domain_detach(&pdev->dev, true);
+
+ return 0;
+}
+
+static const struct of_device_id starfive_of_pl022_match[] = {
+ { .compatible = "starfive,jh7110-spi-pl022" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, starfive_of_pl022_match);
+
+static struct platform_driver starfive_of_pl022_driver = {
+ .driver = {
+ .name = "starfive-spi-pl022",
+ .of_match_table = starfive_of_pl022_match,
+ .pm = &pl022_dev_pm_ops,
+ },
+ .probe = starfive_of_pl022_probe,
+ .remove = starfive_of_pl022_remove,
+};
+
+#if !IS_MODULE(CONFIG_SPI_PL022)
+module_platform_driver(starfive_of_pl022_driver);
+#endif
+/* platform register end */
+
+MODULE_AUTHOR("xingyu.wu <xingyu.wu@starfivetech.com>");
MODULE_AUTHOR("Linus Walleij <linus.walleij@stericsson.com>");
MODULE_DESCRIPTION("PL022 SSP Controller Driver");
MODULE_LICENSE("GPL");
diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c
index 12b6dfeaf658..226e9b248c23 100644
--- a/drivers/usb/core/hcd.c
+++ b/drivers/usb/core/hcd.c
@@ -1439,7 +1439,9 @@ int usb_hcd_map_urb_for_dma(struct usb_hcd *hcd, struct urb *urb,
if (ret == 0)
urb->transfer_flags |= URB_MAP_LOCAL;
} else if (hcd_uses_dma(hcd)) {
- if (urb->num_sgs) {
+ if (urb->transfer_flags & URB_MAP_LOCAL)
+ return ret;
+ else if (urb->num_sgs) {
int n;
/* We don't support sg for isoc transfers ! */
diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c
index 0a37f0d511cf..9b670e913eaa 100644
--- a/drivers/usb/host/xhci-mem.c
+++ b/drivers/usb/host/xhci-mem.c
@@ -14,6 +14,7 @@
#include <linux/slab.h>
#include <linux/dmapool.h>
#include <linux/dma-mapping.h>
+#include <linux/genalloc.h>
#include "xhci.h"
#include "xhci-trace.h"
@@ -1840,6 +1841,7 @@ xhci_free_interrupter(struct xhci_hcd *xhci, struct xhci_interrupter *ir)
void xhci_mem_cleanup(struct xhci_hcd *xhci)
{
struct device *dev = xhci_to_hcd(xhci)->self.sysdev;
+ struct xhci_lowmem_pool *pool;
int i, j, num_ports;
cancel_delayed_work_sync(&xhci->cmd_timer);
@@ -1885,6 +1887,14 @@ void xhci_mem_cleanup(struct xhci_hcd *xhci)
xhci_dbg_trace(xhci, trace_xhci_dbg_init,
"Freed medium stream array pool");
+ if (xhci->lowmem_pool.pool) {
+ pool = &xhci->lowmem_pool;
+ dma_free_noncoherent(dev, pool->size, (void *)pool->cached_base,
+ pool->dma_addr, DMA_BIDIRECTIONAL);
+ gen_pool_destroy(pool->pool);
+ pool->pool = NULL;
+ }
+
if (xhci->dcbaa)
dma_free_coherent(dev, sizeof(*xhci->dcbaa),
xhci->dcbaa, xhci->dcbaa->dma);
@@ -2295,6 +2305,55 @@ xhci_add_interrupter(struct xhci_hcd *xhci, struct xhci_interrupter *ir,
return 0;
}
+int xhci_setup_local_lowmem(struct xhci_hcd *xhci, size_t size)
+{
+ int err;
+ void *buffer;
+ dma_addr_t dma_addr;
+ struct usb_hcd *hcd = xhci_to_hcd(xhci);
+ struct xhci_lowmem_pool *pool = &xhci->lowmem_pool;
+
+ if (!pool->pool) {
+ /* minimal alloc one page */
+ pool->pool = gen_pool_create(PAGE_SHIFT, dev_to_node(hcd->self.sysdev));
+ if (!pool->pool)
+ return -ENOMEM;
+ }
+
+ buffer = dma_alloc_noncoherent(hcd->self.sysdev, size, &dma_addr,
+ DMA_BIDIRECTIONAL, GFP_ATOMIC);
+
+ if (!buffer) {
+ err = -ENOMEM;
+ goto destroy_pool;
+ }
+
+ /*
+ * Here we pass a dma_addr_t but the arg type is a phys_addr_t.
+ * It's not backed by system memory and thus there's no kernel mapping
+ * for it.
+ */
+ err = gen_pool_add_virt(pool->pool, (unsigned long)buffer,
+ dma_addr, size, dev_to_node(hcd->self.sysdev));
+ if (err < 0) {
+ dev_err(hcd->self.sysdev, "gen_pool_add_virt failed with %d\n",
+ err);
+ dma_free_noncoherent(hcd->self.sysdev, size, buffer, dma_addr, DMA_BIDIRECTIONAL);
+ goto destroy_pool;
+ }
+
+ pool->cached_base = (u64)buffer;
+ pool->dma_addr = dma_addr;
+
+ return 0;
+
+destroy_pool:
+ gen_pool_destroy(pool->pool);
+ pool->pool = NULL;
+ return err;
+}
+EXPORT_SYMBOL_GPL(xhci_setup_local_lowmem);
+
int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags)
{
dma_addr_t dma;
@@ -2302,7 +2361,7 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags)
unsigned int val, val2;
u64 val_64;
u32 page_size, temp;
- int i;
+ int i, ret;
INIT_LIST_HEAD(&xhci->cmd_list);
@@ -2431,6 +2490,14 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags)
xhci->isoc_bei_interval = AVOID_BEI_INTERVAL_MAX;
+ if (xhci->quirks & XHCI_LOCAL_BUFFER) {
+ ret = xhci_setup_local_lowmem(xhci, xhci->lowmem_pool.size);
+ if (ret) {
+ xhci->quirks &= ~XHCI_LOCAL_BUFFER;
+ xhci_warn(xhci, "WARN: Can't alloc lowmem pool\n");
+ }
+ }
+
/*
* XXX: Might need to set the Interrupter Moderation Register to
* something other than the default (~1ms minimum between interrupts).
diff --git a/drivers/usb/host/xhci-plat.c b/drivers/usb/host/xhci-plat.c
index d68e9abcdc69..c2a5a19ae716 100644
--- a/drivers/usb/host/xhci-plat.c
+++ b/drivers/usb/host/xhci-plat.c
@@ -253,6 +253,15 @@ int xhci_plat_probe(struct platform_device *pdev, struct device *sysdev, const s
if (device_property_read_bool(tmpdev, "xhci-sg-trb-cache-size-quirk"))
xhci->quirks |= XHCI_SG_TRB_CACHE_SIZE_QUIRK;
+ if (device_property_read_bool(tmpdev, "xhci-lowmem-pool")) {
+ xhci->quirks |= XHCI_LOCAL_BUFFER;
+ ret = device_property_read_u32(tmpdev, "lowmem-pool-size",
+ &xhci->lowmem_pool.size);
+ if (ret || xhci->lowmem_pool.size >= 4)
+ xhci->lowmem_pool.size = 4 << 20;
+ else
+ xhci->lowmem_pool.size <<= 20;
+ }
device_property_read_u32(tmpdev, "imod-interval-ns",
&xhci->imod_interval);
}
diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c
index c410a98ed63c..948e43b935d7 100644
--- a/drivers/usb/host/xhci-ring.c
+++ b/drivers/usb/host/xhci-ring.c
@@ -3612,7 +3612,8 @@ int xhci_queue_bulk_tx(struct xhci_hcd *xhci, gfp_t mem_flags,
full_len = urb->transfer_buffer_length;
/* If we have scatter/gather list, we use it. */
- if (urb->num_sgs && !(urb->transfer_flags & URB_DMA_MAP_SINGLE)) {
+ if (urb->num_sgs && !(urb->transfer_flags & URB_DMA_MAP_SINGLE)
+ && !(urb->transfer_flags & URB_MAP_LOCAL)) {
num_sgs = urb->num_mapped_sgs;
sg = urb->sg;
addr = (u64) sg_dma_address(sg);
diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c
index 132b76fa7ca6..ae9d66e75f43 100644
--- a/drivers/usb/host/xhci.c
+++ b/drivers/usb/host/xhci.c
@@ -18,6 +18,8 @@
#include <linux/slab.h>
#include <linux/dmi.h>
#include <linux/dma-mapping.h>
+#include <linux/dma-map-ops.h>
+#include <linux/genalloc.h>
#include "xhci.h"
#include "xhci-trace.h"
@@ -1273,6 +1275,55 @@ static void xhci_unmap_temp_buf(struct usb_hcd *hcd, struct urb *urb)
urb->transfer_buffer = NULL;
}
+static void xhci_map_urb_local(struct usb_hcd *hcd, struct urb *urb,
+ gfp_t mem_flags)
+{
+ void *buffer;
+ dma_addr_t dma_handle;
+ struct xhci_hcd *xhci = hcd_to_xhci(hcd);
+ struct xhci_lowmem_pool *lowmem_pool = &xhci->lowmem_pool;
+
+ if (lowmem_pool->pool
+ && (usb_endpoint_type(&urb->ep->desc) == USB_ENDPOINT_XFER_BULK)
+ && (urb->transfer_buffer_length > PAGE_SIZE)
+ && urb->num_sgs && urb->sg && (sg_phys(urb->sg) > 0xffffffff)) {
+ buffer = gen_pool_dma_alloc(lowmem_pool->pool,
+ urb->transfer_buffer_length, &dma_handle);
+ if (buffer) {
+ urb->transfer_dma = dma_handle;
+ urb->transfer_buffer = buffer;
+ urb->transfer_flags |= URB_MAP_LOCAL;
+ if (usb_urb_dir_out(urb))
+ sg_copy_to_buffer(urb->sg, urb->num_sgs,
+ (void *)buffer,
+ urb->transfer_buffer_length);
+ }
+ }
+
+}
+
+static void xhci_unmap_urb_local(struct usb_hcd *hcd, struct urb *urb)
+{
+ dma_addr_t dma_handle;
+ u64 cached_buffer;
+ struct xhci_hcd *xhci = hcd_to_xhci(hcd);
+ struct xhci_lowmem_pool *lowmem_pool = &xhci->lowmem_pool;
+
+ if (urb->transfer_flags & URB_MAP_LOCAL) {
+ dma_handle = urb->transfer_dma;
+ cached_buffer = lowmem_pool->cached_base +
+ ((u32)urb->transfer_dma & (lowmem_pool->size - 1));
+ if (usb_urb_dir_in(urb))
+ sg_copy_from_buffer(urb->sg, urb->num_sgs,
+ (void *)cached_buffer, urb->transfer_buffer_length);
+ gen_pool_free(lowmem_pool->pool, (unsigned long)urb->transfer_buffer,
+ urb->transfer_buffer_length);
+ urb->transfer_flags &= ~URB_MAP_LOCAL;
+ urb->transfer_buffer = NULL;
+ }
+}
+
+
/*
* Bypass the DMA mapping if URB is suitable for Immediate Transfer (IDT),
* we'll copy the actual data into the TRB address register. This is limited to
@@ -1293,9 +1344,11 @@ static int xhci_map_urb_for_dma(struct usb_hcd *hcd, struct urb *urb,
if (xhci_urb_temp_buffer_required(hcd, urb))
return xhci_map_temp_buffer(hcd, urb);
}
+ xhci_map_urb_local(hcd, urb, mem_flags);
return usb_hcd_map_urb_for_dma(hcd, urb, mem_flags);
}
+
static void xhci_unmap_urb_for_dma(struct usb_hcd *hcd, struct urb *urb)
{
struct xhci_hcd *xhci;
@@ -1308,8 +1361,10 @@ static void xhci_unmap_urb_for_dma(struct usb_hcd *hcd, struct urb *urb)
if ((xhci->quirks & XHCI_SG_TRB_CACHE_SIZE_QUIRK) && unmap_temp_buf)
xhci_unmap_temp_buf(hcd, urb);
- else
+ else {
+ xhci_unmap_urb_local(hcd, urb);
usb_hcd_unmap_urb_for_dma(hcd, urb);
+ }
}
/**
diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h
index 31088602c070..48e2f817247b 100644
--- a/drivers/usb/host/xhci.h
+++ b/drivers/usb/host/xhci.h
@@ -1757,6 +1757,13 @@ struct xhci_hub {
u8 min_rev;
};
+struct xhci_lowmem_pool {
+ struct gen_pool *pool;
+ u64 cached_base;
+ dma_addr_t dma_addr;
+ unsigned int size;
+};
+
/* There is one xhci_hcd structure per controller */
struct xhci_hcd {
struct usb_hcd *main_hcd;
@@ -1908,6 +1915,8 @@ struct xhci_hcd {
#define XHCI_ZHAOXIN_TRB_FETCH BIT_ULL(45)
#define XHCI_ZHAOXIN_HOST BIT_ULL(46)
+#define XHCI_LOCAL_BUFFER BIT_ULL(63)
+
unsigned int num_active_eps;
unsigned int limit_active_eps;
struct xhci_port *hw_ports;
@@ -1937,6 +1946,8 @@ struct xhci_hcd {
struct list_head regset_list;
void *dbc;
+ struct xhci_lowmem_pool lowmem_pool;
+
/* platform-specific data -- must come last */
unsigned long priv[] __aligned(sizeof(s64));
};