@@ -155,10 +155,12 @@ struct refill_engine {
struct dmm_platform_data {
u32 cpu_cache_flags;
+ bool errata_i878_wa;
};
struct dmm {
struct device *dev;
+ dma_addr_t phys_base;
void __iomem *base;
int irq;
@@ -189,6 +191,12 @@ struct dmm {
struct list_head alloc_head;
const struct dmm_platform_data *plat_data;
+
+ bool dmm_workaround;
+ struct mutex wa_lock;
+ u32 *wa_dma_data;
+ dma_addr_t wa_dma_handle;
+ struct dma_chan *wa_dma_chan;
};
#endif
@@ -18,12 +18,14 @@
#include <linux/completion.h>
#include <linux/delay.h>
#include <linux/dma-mapping.h>
+#include <linux/dmaengine.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/list.h>
#include <linux/mm.h>
#include <linux/module.h>
+#include <linux/mutex.h>
#include <linux/platform_device.h> /* platform_device() */
#include <linux/sched.h>
#include <linux/seq_file.h>
@@ -70,6 +72,7 @@ static const struct {
[TILFMT_PAGE] = GEOM(SLOT_WIDTH_BITS, SLOT_HEIGHT_BITS, 1),
};
+#define DMM_REG_SIZE 4
/* lookup table for registers w/ per-engine instances */
static const u32 reg[][4] = {
@@ -79,14 +82,135 @@ static const u32 reg[][4] = {
DMM_PAT_DESCR__2, DMM_PAT_DESCR__3},
};
+static int dmm_dma_copy(struct dmm *dmm, dma_addr_t src, dma_addr_t dst)
+{
+ struct dma_async_tx_descriptor *tx;
+ enum dma_status status;
+ dma_cookie_t cookie;
+
+ tx = dmaengine_prep_dma_memcpy(dmm->wa_dma_chan, dst, src, 4, 0);
+ if (!tx) {
+ dev_err(dmm->dev, "Failed to prepare DMA memcpy\n");
+ return -EIO;
+ }
+
+ cookie = tx->tx_submit(tx);
+ if (dma_submit_error(cookie)) {
+ dev_err(dmm->dev, "Failed to do DMA tx_submit\n");
+ return -EIO;
+ }
+
+ status = dma_sync_wait(dmm->wa_dma_chan, cookie);
+ if (status != DMA_COMPLETE)
+ dev_err(dmm->dev, "i878 wa DMA copy failure\n");
+
+ dmaengine_terminate_all(dmm->wa_dma_chan);
+ return 0;
+}
+
+static u32 dmm_read_wa(struct dmm *dmm, u32 reg)
+{
+ dma_addr_t src, dst;
+ int r;
+
+ src = dmm->phys_base + reg;
+ dst = dmm->wa_dma_handle;
+
+ r = dmm_dma_copy(dmm, src, dst);
+ if (r) {
+ dev_err(dmm->dev, "sDMA read transfer timeout\n");
+ return readl(dmm->base + reg);
+ }
+
+ /*
+ * As per i878 workaround, the DMA is used to access the DMM registers.
+ * Make sure that the readl is not moved by the compiler or the CPU
+ * earlier than the DMA finished writing the value to memory.
+ */
+ rmb();
+ return readl(dmm->wa_dma_data);
+}
+
+static void dmm_write_wa(struct dmm *dmm, u32 val, u32 reg)
+{
+ dma_addr_t src, dst;
+ int r;
+
+ writel(val, dmm->wa_dma_data);
+ /*
+ * As per i878 workaround, the DMA is used to access the DMM registers.
+ * Make sure that the writel is not moved by the compiler or the CPU, so
+ * the data will be in place before we start the DMA to do the actual
+ * register write.
+ */
+ wmb();
+
+ src = dmm->wa_dma_handle;
+ dst = dmm->phys_base + reg;
+
+ r = dmm_dma_copy(dmm, src, dst);
+ if (r) {
+ dev_err(dmm->dev, "sDMA write transfer timeout\n");
+ writel(val, dmm->base + reg);
+ }
+}
+
static u32 dmm_read(struct dmm *dmm, u32 reg)
{
- return readl(dmm->base + reg);
+ if (dmm->dmm_workaround) {
+ u32 v;
+
+ mutex_lock(&dmm->wa_lock);
+ v = dmm_read_wa(dmm, reg);
+ mutex_unlock(&dmm->wa_lock);
+
+ return v;
+ } else {
+ return readl(dmm->base + reg);
+ }
}
static void dmm_write(struct dmm *dmm, u32 val, u32 reg)
{
- writel(val, dmm->base + reg);
+ if (dmm->dmm_workaround) {
+ mutex_lock(&dmm->wa_lock);
+ dmm_write_wa(dmm, val, reg);
+ mutex_unlock(&dmm->wa_lock);
+ } else {
+ writel(val, dmm->base + reg);
+ }
+}
+
+static int dmm_workaround_init(struct dmm *dmm)
+{
+ dma_cap_mask_t mask;
+
+ mutex_init(&dmm->wa_lock);
+
+ dmm->wa_dma_data = dma_alloc_coherent(dmm->dev, DMM_REG_SIZE,
+ &dmm->wa_dma_handle, GFP_KERNEL);
+ if (!dmm->wa_dma_data)
+ return -ENOMEM;
+
+ dma_cap_zero(mask);
+ dma_cap_set(DMA_MEMCPY, mask);
+
+ dmm->wa_dma_chan = dma_request_channel(mask, NULL, NULL);
+ if (!dmm->wa_dma_chan) {
+ dma_free_coherent(dmm->dev, DMM_REG_SIZE, dmm->wa_dma_data,
+ dmm->wa_dma_handle);
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+static void dmm_workaround_uninit(struct dmm *dmm)
+{
+ dma_release_channel(dmm->wa_dma_chan);
+
+ dma_free_coherent(dmm->dev, DMM_REG_SIZE, dmm->wa_dma_data,
+ dmm->wa_dma_handle);
}
/* simple allocator to grab next 16 byte aligned memory from txn */
@@ -634,6 +758,9 @@ static int omap_dmm_remove(struct platform_device *dev)
free_irq(omap_dmm->irq, omap_dmm);
+ if (omap_dmm->dmm_workaround)
+ dmm_workaround_uninit(omap_dmm);
+
iounmap(omap_dmm->base);
kfree(omap_dmm);
omap_dmm = NULL;
@@ -679,6 +806,7 @@ static int omap_dmm_probe(struct platform_device *dev)
goto fail;
}
+ omap_dmm->phys_base = mem->start;
omap_dmm->base = ioremap(mem->start, SZ_2K);
if (!omap_dmm->base) {
@@ -694,6 +822,17 @@ static int omap_dmm_probe(struct platform_device *dev)
omap_dmm->dev = &dev->dev;
+ if (omap_dmm->plat_data->errata_i878_wa) {
+ if (!dmm_workaround_init(omap_dmm)) {
+ omap_dmm->dmm_workaround = true;
+ dev_info(&dev->dev,
+ "workaround for errata i878 in use\n");
+ } else {
+ dev_warn(&dev->dev,
+ "failed to initialize work-around for i878\n");
+ }
+ }
+
hwinfo = dmm_read(omap_dmm, DMM_PAT_HWINFO);
omap_dmm->num_engines = (hwinfo >> 24) & 0x1F;
omap_dmm->num_lut = (hwinfo >> 16) & 0x1F;
@@ -720,8 +859,13 @@ static int omap_dmm_probe(struct platform_device *dev)
dmm_write(omap_dmm, 0x88888888, DMM_TILER_OR__0);
dmm_write(omap_dmm, 0x88888888, DMM_TILER_OR__1);
- ret = request_irq(omap_dmm->irq, omap_dmm_irq_handler, IRQF_SHARED,
- "omap_dmm_irq_handler", omap_dmm);
+ if (omap_dmm->dmm_workaround)
+ ret = request_threaded_irq(omap_dmm->irq, NULL,
+ omap_dmm_irq_handler, IRQF_ONESHOT,
+ "omap_dmm_irq_handler", omap_dmm);
+ else
+ ret = request_irq(omap_dmm->irq, omap_dmm_irq_handler,
+ IRQF_SHARED, "omap_dmm_irq_handler", omap_dmm);
if (ret) {
dev_err(&dev->dev, "couldn't register IRQ %d, error %d\n",
@@ -1057,6 +1201,11 @@ static const struct dmm_platform_data dmm_omap5_platform_data = {
.cpu_cache_flags = OMAP_BO_UNCACHED,
};
+static const struct dmm_platform_data dmm_dra7_platform_data = {
+ .cpu_cache_flags = OMAP_BO_UNCACHED,
+ .errata_i878_wa = true,
+};
+
static const struct of_device_id dmm_of_match[] = {
{
.compatible = "ti,omap4-dmm",
@@ -1066,6 +1215,10 @@ static const struct of_device_id dmm_of_match[] = {
.compatible = "ti,omap5-dmm",
.data = &dmm_omap5_platform_data,
},
+ {
+ .compatible = "ti,dra7-dmm",
+ .data = &dmm_dra7_platform_data,
+ },
{},
};
#endif