Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 71e8831f authored by Andy Gross's avatar Andy Gross Committed by Greg Kroah-Hartman
Browse files

drm/omap: DMM/TILER support for OMAP4+ platform



Dynamic Memory Manager (DMM) is a hardware block in the OMAP4+
processor that contains at least one TILER instance.  TILER, or
Tiling and Isometric Lightweight Engine for Rotation, provides
IOMMU capabilities through the use of a physical address translation
table.  The TILER also provides zero cost rotation and mirroring.

The TILER provides both 1D and 2D access by providing different views
or address ranges that can be used to access the physical memory that
has been mapped in through the PAT.  Access to the 1D view results in
linear access to the underlying memory.  Access to the 2D views result
in tiled access to the underlying memory resulted in increased
efficiency.

The TILER address space is managed by a tiler container manager (TCM)
and allocates the address space through the use of the Simple Tiler
Allocation algorithm (SiTA).  The purpose of the algorithm is to keep
fragmentation of the address space as low as possible.

Signed-off-by: default avatarAndy Gross <andy.gross@ti.com>
Signed-off-by: default avatarRob Clark <rob@ti.com>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@suse.de>
parent e0134715
Loading
Loading
Loading
Loading
+9 −1
Original line number Diff line number Diff line
@@ -4,7 +4,15 @@
#

ccflags-y := -Iinclude/drm -Werror
omapdrm-y := omap_drv.o omap_crtc.o omap_encoder.o omap_connector.o omap_fb.o omap_fbdev.o omap_gem.o
omapdrm-y := omap_drv.o \
	omap_crtc.o \
	omap_encoder.o \
	omap_connector.o \
	omap_fb.o \
	omap_fbdev.o \
	omap_gem.o \
	omap_dmm_tiler.o \
	tcm-sita.o

# temporary:
omapdrm-y += omap_gem_helpers.o
+1 −0
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ TODO
. Review DSS vs KMS mismatches.  The omap_dss_device is sort of part encoder,
  part connector.  Which results in a bit of duct tape to fwd calls from
  encoder to connector.  Possibly this could be done a bit better.
. Add debugfs information for DMM/TILER

Userspace:
. git://github.com/robclark/xf86-video-omap.git
+187 −0
Original line number Diff line number Diff line
/*
 *
 * Copyright (C) 2011 Texas Instruments Incorporated - http://www.ti.com/
 * Author: Rob Clark <rob@ti.com>
 *         Andy Gross <andy.gross@ti.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation version 2.
 *
 * This program is distributed "as is" WITHOUT ANY WARRANTY of any
 * kind, whether express or implied; without even the implied warranty
 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */
#ifndef OMAP_DMM_PRIV_H
#define OMAP_DMM_PRIV_H

#define DMM_REVISION          0x000
#define DMM_HWINFO            0x004
#define DMM_LISA_HWINFO       0x008
#define DMM_DMM_SYSCONFIG     0x010
#define DMM_LISA_LOCK         0x01C
#define DMM_LISA_MAP__0       0x040
#define DMM_LISA_MAP__1       0x044
#define DMM_TILER_HWINFO      0x208
#define DMM_TILER_OR__0       0x220
#define DMM_TILER_OR__1       0x224
#define DMM_PAT_HWINFO        0x408
#define DMM_PAT_GEOMETRY      0x40C
#define DMM_PAT_CONFIG        0x410
#define DMM_PAT_VIEW__0       0x420
#define DMM_PAT_VIEW__1       0x424
#define DMM_PAT_VIEW_MAP__0   0x440
#define DMM_PAT_VIEW_MAP_BASE 0x460
#define DMM_PAT_IRQ_EOI       0x478
#define DMM_PAT_IRQSTATUS_RAW 0x480
#define DMM_PAT_IRQSTATUS     0x490
#define DMM_PAT_IRQENABLE_SET 0x4A0
#define DMM_PAT_IRQENABLE_CLR 0x4B0
#define DMM_PAT_STATUS__0     0x4C0
#define DMM_PAT_STATUS__1     0x4C4
#define DMM_PAT_STATUS__2     0x4C8
#define DMM_PAT_STATUS__3     0x4CC
#define DMM_PAT_DESCR__0      0x500
#define DMM_PAT_DESCR__1      0x510
#define DMM_PAT_DESCR__2      0x520
#define DMM_PAT_DESCR__3      0x530
#define DMM_PEG_HWINFO        0x608
#define DMM_PEG_PRIO          0x620
#define DMM_PEG_PRIO_PAT      0x640

#define DMM_IRQSTAT_DST			(1<<0)
#define DMM_IRQSTAT_LST			(1<<1)
#define DMM_IRQSTAT_ERR_INV_DSC		(1<<2)
#define DMM_IRQSTAT_ERR_INV_DATA	(1<<3)
#define DMM_IRQSTAT_ERR_UPD_AREA	(1<<4)
#define DMM_IRQSTAT_ERR_UPD_CTRL	(1<<5)
#define DMM_IRQSTAT_ERR_UPD_DATA	(1<<6)
#define DMM_IRQSTAT_ERR_LUT_MISS	(1<<7)

#define DMM_IRQSTAT_ERR_MASK	(DMM_IRQ_STAT_ERR_INV_DSC | \
				DMM_IRQ_STAT_ERR_INV_DATA | \
				DMM_IRQ_STAT_ERR_UPD_AREA | \
				DMM_IRQ_STAT_ERR_UPD_CTRL | \
				DMM_IRQ_STAT_ERR_UPD_DATA | \
				DMM_IRQ_STAT_ERR_LUT_MISS)

#define DMM_PATSTATUS_READY		(1<<0)
#define DMM_PATSTATUS_VALID		(1<<1)
#define DMM_PATSTATUS_RUN		(1<<2)
#define DMM_PATSTATUS_DONE		(1<<3)
#define DMM_PATSTATUS_LINKED		(1<<4)
#define DMM_PATSTATUS_BYPASSED		(1<<7)
#define DMM_PATSTATUS_ERR_INV_DESCR	(1<<10)
#define DMM_PATSTATUS_ERR_INV_DATA	(1<<11)
#define DMM_PATSTATUS_ERR_UPD_AREA	(1<<12)
#define DMM_PATSTATUS_ERR_UPD_CTRL	(1<<13)
#define DMM_PATSTATUS_ERR_UPD_DATA	(1<<14)
#define DMM_PATSTATUS_ERR_ACCESS	(1<<15)

#define DMM_PATSTATUS_ERR	(DMM_PATSTATUS_ERR_INV_DESCR | \
				DMM_PATSTATUS_ERR_INV_DATA | \
				DMM_PATSTATUS_ERR_UPD_AREA | \
				DMM_PATSTATUS_ERR_UPD_CTRL | \
				DMM_PATSTATUS_ERR_UPD_DATA | \
				DMM_PATSTATUS_ERR_ACCESS)



enum {
	PAT_STATUS,
	PAT_DESCR
};

struct pat_ctrl {
	u32 start:4;
	u32 dir:4;
	u32 lut_id:8;
	u32 sync:12;
	u32 ini:4;
};

struct pat {
	uint32_t next_pa;
	struct pat_area area;
	struct pat_ctrl ctrl;
	uint32_t data_pa;
};

#define DMM_FIXED_RETRY_COUNT 1000

/* create refill buffer big enough to refill all slots, plus 3 descriptors..
 * 3 descriptors is probably the worst-case for # of 2d-slices in a 1d area,
 * but I guess you don't hit that worst case at the same time as full area
 * refill
 */
#define DESCR_SIZE 128
#define REFILL_BUFFER_SIZE ((4 * 128 * 256) + (3 * DESCR_SIZE))

struct dmm;

struct dmm_txn {
	void *engine_handle;
	struct tcm *tcm;

	uint8_t *current_va;
	dma_addr_t current_pa;

	struct pat *last_pat;
};

struct refill_engine {
	int id;
	struct dmm *dmm;
	struct tcm *tcm;

	uint8_t *refill_va;
	dma_addr_t refill_pa;

	/* only one trans per engine for now */
	struct dmm_txn txn;

	/* offset to lut associated with container */
	u32 *lut_offset;

	wait_queue_head_t wait_for_refill;

	struct list_head idle_node;
};

struct dmm {
	struct device *dev;
	void __iomem *base;
	int irq;

	struct page *dummy_page;
	dma_addr_t dummy_pa;

	void *refill_va;
	dma_addr_t refill_pa;

	/* refill engines */
	struct semaphore engine_sem;
	struct list_head idle_head;
	struct refill_engine *engines;
	int num_engines;

	/* container information */
	int container_width;
	int container_height;
	int lut_width;
	int lut_height;
	int num_lut;

	/* array of LUT - TCM containers */
	struct tcm **tcm;

	/* LUT table storage */
	u32 *lut;

	/* allocation list and lock */
	struct list_head alloc_head;
	spinlock_t list_lock;
};

#endif
+672 −0
Original line number Diff line number Diff line
/*
 * DMM IOMMU driver support functions for TI OMAP processors.
 *
 * Author: Rob Clark <rob@ti.com>
 *         Andy Gross <andy.gross@ti.com>
 *
 * Copyright (C) 2011 Texas Instruments Incorporated - http://www.ti.com/
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation version 2.
 *
 * This program is distributed "as is" WITHOUT ANY WARRANTY of any
 * kind, whether express or implied; without even the implied warranty
 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h> /* platform_device() */
#include <linux/errno.h>
#include <linux/sched.h>
#include <linux/wait.h>
#include <linux/interrupt.h>
#include <linux/dma-mapping.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/delay.h>
#include <linux/mm.h>
#include <linux/time.h>
#include <linux/list.h>
#include <linux/semaphore.h>

#include "omap_dmm_tiler.h"
#include "omap_dmm_priv.h"

/* mappings for associating views to luts */
static struct tcm *containers[TILFMT_NFORMATS];
static struct dmm *omap_dmm;

/* Geometry table */
#define GEOM(xshift, yshift, bytes_per_pixel) { \
		.x_shft = (xshift), \
		.y_shft = (yshift), \
		.cpp    = (bytes_per_pixel), \
		.slot_w = 1 << (SLOT_WIDTH_BITS - (xshift)), \
		.slot_h = 1 << (SLOT_HEIGHT_BITS - (yshift)), \
	}

static const struct {
	uint32_t x_shft;	/* unused X-bits (as part of bpp) */
	uint32_t y_shft;	/* unused Y-bits (as part of bpp) */
	uint32_t cpp;		/* bytes/chars per pixel */
	uint32_t slot_w;	/* width of each slot (in pixels) */
	uint32_t slot_h;	/* height of each slot (in pixels) */
} geom[TILFMT_NFORMATS] = {
		[TILFMT_8BIT]  = GEOM(0, 0, 1),
		[TILFMT_16BIT] = GEOM(0, 1, 2),
		[TILFMT_32BIT] = GEOM(1, 1, 4),
		[TILFMT_PAGE]  = GEOM(SLOT_WIDTH_BITS, SLOT_HEIGHT_BITS, 1),
};


/* lookup table for registers w/ per-engine instances */
static const uint32_t reg[][4] = {
		[PAT_STATUS] = {DMM_PAT_STATUS__0, DMM_PAT_STATUS__1,
				DMM_PAT_STATUS__2, DMM_PAT_STATUS__3},
		[PAT_DESCR]  = {DMM_PAT_DESCR__0, DMM_PAT_DESCR__1,
				DMM_PAT_DESCR__2, DMM_PAT_DESCR__3},
};

/* simple allocator to grab next 16 byte aligned memory from txn */
static void *alloc_dma(struct dmm_txn *txn, size_t sz, dma_addr_t *pa)
{
	void *ptr;
	struct refill_engine *engine = txn->engine_handle;

	/* dmm programming requires 16 byte aligned addresses */
	txn->current_pa = round_up(txn->current_pa, 16);
	txn->current_va = (void *)round_up((long)txn->current_va, 16);

	ptr = txn->current_va;
	*pa = txn->current_pa;

	txn->current_pa += sz;
	txn->current_va += sz;

	BUG_ON((txn->current_va - engine->refill_va) > REFILL_BUFFER_SIZE);

	return ptr;
}

/* check status and spin until wait_mask comes true */
static int wait_status(struct refill_engine *engine, uint32_t wait_mask)
{
	struct dmm *dmm = engine->dmm;
	uint32_t r = 0, err, i;

	i = DMM_FIXED_RETRY_COUNT;
	while (true) {
		r = readl(dmm->base + reg[PAT_STATUS][engine->id]);
		err = r & DMM_PATSTATUS_ERR;
		if (err)
			return -EFAULT;

		if ((r & wait_mask) == wait_mask)
			break;

		if (--i == 0)
			return -ETIMEDOUT;

		udelay(1);
	}

	return 0;
}

irqreturn_t omap_dmm_irq_handler(int irq, void *arg)
{
	struct dmm *dmm = arg;
	uint32_t status = readl(dmm->base + DMM_PAT_IRQSTATUS);
	int i;

	/* ack IRQ */
	writel(status, dmm->base + DMM_PAT_IRQSTATUS);

	for (i = 0; i < dmm->num_engines; i++) {
		if (status & DMM_IRQSTAT_LST)
			wake_up_interruptible(&dmm->engines[i].wait_for_refill);

		status >>= 8;
	}

	return IRQ_HANDLED;
}

/**
 * Get a handle for a DMM transaction
 */
static struct dmm_txn *dmm_txn_init(struct dmm *dmm, struct tcm *tcm)
{
	struct dmm_txn *txn = NULL;
	struct refill_engine *engine = NULL;

	down(&dmm->engine_sem);

	/* grab an idle engine */
	spin_lock(&dmm->list_lock);
	if (!list_empty(&dmm->idle_head)) {
		engine = list_entry(dmm->idle_head.next, struct refill_engine,
					idle_node);
		list_del(&engine->idle_node);
	}
	spin_unlock(&dmm->list_lock);

	BUG_ON(!engine);

	txn = &engine->txn;
	engine->tcm = tcm;
	txn->engine_handle = engine;
	txn->last_pat = NULL;
	txn->current_va = engine->refill_va;
	txn->current_pa = engine->refill_pa;

	return txn;
}

/**
 * Add region to DMM transaction.  If pages or pages[i] is NULL, then the
 * corresponding slot is cleared (ie. dummy_pa is programmed)
 */
static int dmm_txn_append(struct dmm_txn *txn, struct pat_area *area,
			struct page **pages)
{
	dma_addr_t pat_pa = 0;
	uint32_t *data;
	struct pat *pat;
	struct refill_engine *engine = txn->engine_handle;
	int columns = (1 + area->x1 - area->x0);
	int rows = (1 + area->y1 - area->y0);
	int i = columns*rows;
	u32 *lut = omap_dmm->lut + (engine->tcm->lut_id * omap_dmm->lut_width *
			omap_dmm->lut_height) +
			(area->y0 * omap_dmm->lut_width) + area->x0;

	pat = alloc_dma(txn, sizeof(struct pat), &pat_pa);

	if (txn->last_pat)
		txn->last_pat->next_pa = (uint32_t)pat_pa;

	pat->area = *area;
	pat->ctrl = (struct pat_ctrl){
			.start = 1,
			.lut_id = engine->tcm->lut_id,
		};

	data = alloc_dma(txn, 4*i, &pat->data_pa);

	while (i--) {
		data[i] = (pages && pages[i]) ?
		page_to_phys(pages[i]) : engine->dmm->dummy_pa;
	}

	/* fill in lut with new addresses */
	for (i = 0; i < rows; i++, lut += omap_dmm->lut_width)
		memcpy(lut, &data[i*columns], columns * sizeof(u32));

	txn->last_pat = pat;

	return 0;
}

/**
 * Commit the DMM transaction.
 */
static int dmm_txn_commit(struct dmm_txn *txn, bool wait)
{
	int ret = 0;
	struct refill_engine *engine = txn->engine_handle;
	struct dmm *dmm = engine->dmm;

	if (!txn->last_pat) {
		dev_err(engine->dmm->dev, "need at least one txn\n");
		ret = -EINVAL;
		goto cleanup;
	}

	txn->last_pat->next_pa = 0;

	/* write to PAT_DESCR to clear out any pending transaction */
	writel(0x0, dmm->base + reg[PAT_DESCR][engine->id]);

	/* wait for engine ready: */
	ret = wait_status(engine, DMM_PATSTATUS_READY);
	if (ret) {
		ret = -EFAULT;
		goto cleanup;
	}

	/* kick reload */
	writel(engine->refill_pa,
		dmm->base + reg[PAT_DESCR][engine->id]);

	if (wait) {
		if (wait_event_interruptible_timeout(engine->wait_for_refill,
				wait_status(engine, DMM_PATSTATUS_READY) == 0,
				msecs_to_jiffies(1)) <= 0) {
			dev_err(dmm->dev, "timed out waiting for done\n");
			ret = -ETIMEDOUT;
		}
	}

cleanup:
	spin_lock(&dmm->list_lock);
	list_add(&engine->idle_node, &dmm->idle_head);
	spin_unlock(&dmm->list_lock);

	up(&omap_dmm->engine_sem);
	return ret;
}

/*
 * DMM programming
 */
static int fill(struct tcm_area *area, struct page **pages, bool wait)
{
	int ret = 0;
	struct tcm_area slice, area_s;
	struct dmm_txn *txn;

	txn = dmm_txn_init(omap_dmm, area->tcm);
	if (IS_ERR_OR_NULL(txn))
		return PTR_ERR(txn);

	tcm_for_each_slice(slice, *area, area_s) {
		struct pat_area p_area = {
				.x0 = slice.p0.x,  .y0 = slice.p0.y,
				.x1 = slice.p1.x,  .y1 = slice.p1.y,
		};

		ret = dmm_txn_append(txn, &p_area, pages);
		if (ret)
			goto fail;

		if (pages)
			pages += tcm_sizeof(slice);
	}

	ret = dmm_txn_commit(txn, wait);

fail:
	return ret;
}

/*
 * Pin/unpin
 */

/* note: slots for which pages[i] == NULL are filled w/ dummy page
 */
int tiler_pin(struct tiler_block *block, struct page **pages, bool wait)
{
	int ret;

	ret = fill(&block->area, pages, wait);

	if (ret)
		tiler_unpin(block);

	return ret;
}

int tiler_unpin(struct tiler_block *block)
{
	return fill(&block->area, NULL, false);
}

/*
 * Reserve/release
 */
struct tiler_block *tiler_reserve_2d(enum tiler_fmt fmt, uint16_t w,
		uint16_t h, uint16_t align)
{
	struct tiler_block *block = kzalloc(sizeof(*block), GFP_KERNEL);
	u32 min_align = 128;
	int ret;

	BUG_ON(!validfmt(fmt));

	/* convert width/height to slots */
	w = DIV_ROUND_UP(w, geom[fmt].slot_w);
	h = DIV_ROUND_UP(h, geom[fmt].slot_h);

	/* convert alignment to slots */
	min_align = max(min_align, (geom[fmt].slot_w * geom[fmt].cpp));
	align = ALIGN(align, min_align);
	align /= geom[fmt].slot_w * geom[fmt].cpp;

	block->fmt = fmt;

	ret = tcm_reserve_2d(containers[fmt], w, h, align, &block->area);
	if (ret) {
		kfree(block);
		return 0;
	}

	/* add to allocation list */
	spin_lock(&omap_dmm->list_lock);
	list_add(&block->alloc_node, &omap_dmm->alloc_head);
	spin_unlock(&omap_dmm->list_lock);

	return block;
}

struct tiler_block *tiler_reserve_1d(size_t size)
{
	struct tiler_block *block = kzalloc(sizeof(*block), GFP_KERNEL);
	int num_pages = (size + PAGE_SIZE - 1) >> PAGE_SHIFT;

	if (!block)
		return 0;

	block->fmt = TILFMT_PAGE;

	if (tcm_reserve_1d(containers[TILFMT_PAGE], num_pages,
				&block->area)) {
		kfree(block);
		return 0;
	}

	spin_lock(&omap_dmm->list_lock);
	list_add(&block->alloc_node, &omap_dmm->alloc_head);
	spin_unlock(&omap_dmm->list_lock);

	return block;
}

/* note: if you have pin'd pages, you should have already unpin'd first! */
int tiler_release(struct tiler_block *block)
{
	int ret = tcm_free(&block->area);

	if (block->area.tcm)
		dev_err(omap_dmm->dev, "failed to release block\n");

	spin_lock(&omap_dmm->list_lock);
	list_del(&block->alloc_node);
	spin_unlock(&omap_dmm->list_lock);

	kfree(block);
	return ret;
}

/*
 * Utils
 */

/* calculate the tiler space address of a pixel in a view orientation */
static u32 tiler_get_address(u32 orient, enum tiler_fmt fmt, u32 x, u32 y)
{
	u32 x_bits, y_bits, tmp, x_mask, y_mask, alignment;

	x_bits = CONT_WIDTH_BITS - geom[fmt].x_shft;
	y_bits = CONT_HEIGHT_BITS - geom[fmt].y_shft;
	alignment = geom[fmt].x_shft + geom[fmt].y_shft;

	/* validate coordinate */
	x_mask = MASK(x_bits);
	y_mask = MASK(y_bits);

	if (x < 0 || x > x_mask || y < 0 || y > y_mask)
		return 0;

	/* account for mirroring */
	if (orient & MASK_X_INVERT)
		x ^= x_mask;
	if (orient & MASK_Y_INVERT)
		y ^= y_mask;

	/* get coordinate address */
	if (orient & MASK_XY_FLIP)
		tmp = ((x << y_bits) + y);
	else
		tmp = ((y << x_bits) + x);

	return TIL_ADDR((tmp << alignment), orient, fmt);
}

dma_addr_t tiler_ssptr(struct tiler_block *block)
{
	BUG_ON(!validfmt(block->fmt));

	return TILVIEW_8BIT + tiler_get_address(0, block->fmt,
			block->area.p0.x * geom[block->fmt].slot_w,
			block->area.p0.y * geom[block->fmt].slot_h);
}

void tiler_align(enum tiler_fmt fmt, uint16_t *w, uint16_t *h)
{
	BUG_ON(!validfmt(fmt));
	*w = round_up(*w, geom[fmt].slot_w);
	*h = round_up(*h, geom[fmt].slot_h);
}

uint32_t tiler_stride(enum tiler_fmt fmt)
{
	BUG_ON(!validfmt(fmt));

	return 1 << (CONT_WIDTH_BITS + geom[fmt].y_shft);
}

size_t tiler_size(enum tiler_fmt fmt, uint16_t w, uint16_t h)
{
	tiler_align(fmt, &w, &h);
	return geom[fmt].cpp * w * h;
}

size_t tiler_vsize(enum tiler_fmt fmt, uint16_t w, uint16_t h)
{
	BUG_ON(!validfmt(fmt));
	return round_up(geom[fmt].cpp * w, PAGE_SIZE) * h;
}

int omap_dmm_remove(void)
{
	struct tiler_block *block, *_block;
	int i;

	if (omap_dmm) {
		/* free all area regions */
		spin_lock(&omap_dmm->list_lock);
		list_for_each_entry_safe(block, _block, &omap_dmm->alloc_head,
					alloc_node) {
			list_del(&block->alloc_node);
			kfree(block);
		}
		spin_unlock(&omap_dmm->list_lock);

		for (i = 0; i < omap_dmm->num_lut; i++)
			if (omap_dmm->tcm && omap_dmm->tcm[i])
				omap_dmm->tcm[i]->deinit(omap_dmm->tcm[i]);
		kfree(omap_dmm->tcm);

		kfree(omap_dmm->engines);
		if (omap_dmm->refill_va)
			dma_free_coherent(omap_dmm->dev,
				REFILL_BUFFER_SIZE * omap_dmm->num_engines,
				omap_dmm->refill_va,
				omap_dmm->refill_pa);
		if (omap_dmm->dummy_page)
			__free_page(omap_dmm->dummy_page);

		vfree(omap_dmm->lut);

		if (omap_dmm->irq != -1)
			free_irq(omap_dmm->irq, omap_dmm);

		kfree(omap_dmm);
	}

	return 0;
}

int omap_dmm_init(struct drm_device *dev)
{
	int ret = -EFAULT, i;
	struct tcm_area area = {0};
	u32 hwinfo, pat_geom, lut_table_size;
	struct omap_drm_platform_data *pdata = dev->dev->platform_data;

	if (!pdata || !pdata->dmm_pdata) {
		dev_err(dev->dev, "dmm platform data not present, skipping\n");
		return ret;
	}

	omap_dmm = kzalloc(sizeof(*omap_dmm), GFP_KERNEL);
	if (!omap_dmm) {
		dev_err(dev->dev, "failed to allocate driver data section\n");
		goto fail;
	}

	/* lookup hwmod data - base address and irq */
	omap_dmm->base = pdata->dmm_pdata->base;
	omap_dmm->irq = pdata->dmm_pdata->irq;
	omap_dmm->dev = dev->dev;

	if (!omap_dmm->base) {
		dev_err(dev->dev, "failed to get dmm base address\n");
		goto fail;
	}

	hwinfo = readl(omap_dmm->base + DMM_PAT_HWINFO);
	omap_dmm->num_engines = (hwinfo >> 24) & 0x1F;
	omap_dmm->num_lut = (hwinfo >> 16) & 0x1F;
	omap_dmm->container_width = 256;
	omap_dmm->container_height = 128;

	/* read out actual LUT width and height */
	pat_geom = readl(omap_dmm->base + DMM_PAT_GEOMETRY);
	omap_dmm->lut_width = ((pat_geom >> 16) & 0xF) << 5;
	omap_dmm->lut_height = ((pat_geom >> 24) & 0xF) << 5;

	/* initialize DMM registers */
	writel(0x88888888, omap_dmm->base + DMM_PAT_VIEW__0);
	writel(0x88888888, omap_dmm->base + DMM_PAT_VIEW__1);
	writel(0x80808080, omap_dmm->base + DMM_PAT_VIEW_MAP__0);
	writel(0x80000000, omap_dmm->base + DMM_PAT_VIEW_MAP_BASE);
	writel(0x88888888, omap_dmm->base + DMM_TILER_OR__0);
	writel(0x88888888, omap_dmm->base + DMM_TILER_OR__1);

	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",
			omap_dmm->irq, ret);
		omap_dmm->irq = -1;
		goto fail;
	}

	/* enable some interrupts! */
	writel(0xfefefefe, omap_dmm->base + DMM_PAT_IRQENABLE_SET);

	lut_table_size = omap_dmm->lut_width * omap_dmm->lut_height *
			omap_dmm->num_lut;

	omap_dmm->lut = vmalloc(lut_table_size * sizeof(*omap_dmm->lut));
	if (!omap_dmm->lut) {
		dev_err(dev->dev, "could not allocate lut table\n");
		ret = -ENOMEM;
		goto fail;
	}

	omap_dmm->dummy_page = alloc_page(GFP_KERNEL | __GFP_DMA32);
	if (!omap_dmm->dummy_page) {
		dev_err(dev->dev, "could not allocate dummy page\n");
		ret = -ENOMEM;
		goto fail;
	}
	omap_dmm->dummy_pa = page_to_phys(omap_dmm->dummy_page);

	/* alloc refill memory */
	omap_dmm->refill_va = dma_alloc_coherent(dev->dev,
				REFILL_BUFFER_SIZE * omap_dmm->num_engines,
				&omap_dmm->refill_pa, GFP_KERNEL);
	if (!omap_dmm->refill_va) {
		dev_err(dev->dev, "could not allocate refill memory\n");
		goto fail;
	}

	/* alloc engines */
	omap_dmm->engines = kzalloc(
			omap_dmm->num_engines * sizeof(struct refill_engine),
			GFP_KERNEL);
	if (!omap_dmm->engines) {
		dev_err(dev->dev, "could not allocate engines\n");
		ret = -ENOMEM;
		goto fail;
	}

	sema_init(&omap_dmm->engine_sem, omap_dmm->num_engines);
	INIT_LIST_HEAD(&omap_dmm->idle_head);
	for (i = 0; i < omap_dmm->num_engines; i++) {
		omap_dmm->engines[i].id = i;
		omap_dmm->engines[i].dmm = omap_dmm;
		omap_dmm->engines[i].refill_va = omap_dmm->refill_va +
						(REFILL_BUFFER_SIZE * i);
		omap_dmm->engines[i].refill_pa = omap_dmm->refill_pa +
						(REFILL_BUFFER_SIZE * i);
		init_waitqueue_head(&omap_dmm->engines[i].wait_for_refill);

		list_add(&omap_dmm->engines[i].idle_node, &omap_dmm->idle_head);
	}

	omap_dmm->tcm = kzalloc(omap_dmm->num_lut * sizeof(*omap_dmm->tcm),
				GFP_KERNEL);
	if (!omap_dmm->tcm) {
		dev_err(dev->dev, "failed to allocate lut ptrs\n");
		ret = -ENOMEM;
		goto fail;
	}

	/* init containers */
	for (i = 0; i < omap_dmm->num_lut; i++) {
		omap_dmm->tcm[i] = sita_init(omap_dmm->container_width,
						omap_dmm->container_height,
						NULL);

		if (!omap_dmm->tcm[i]) {
			dev_err(dev->dev, "failed to allocate container\n");
			ret = -ENOMEM;
			goto fail;
		}

		omap_dmm->tcm[i]->lut_id = i;
	}

	/* assign access mode containers to applicable tcm container */
	/* OMAP 4 has 1 container for all 4 views */
	containers[TILFMT_8BIT] = omap_dmm->tcm[0];
	containers[TILFMT_16BIT] = omap_dmm->tcm[0];
	containers[TILFMT_32BIT] = omap_dmm->tcm[0];
	containers[TILFMT_PAGE] = omap_dmm->tcm[0];

	INIT_LIST_HEAD(&omap_dmm->alloc_head);
	spin_lock_init(&omap_dmm->list_lock);

	area = (struct tcm_area) {
		.is2d = true,
		.tcm = NULL,
		.p1.x = omap_dmm->container_width - 1,
		.p1.y = omap_dmm->container_height - 1,
	};

	for (i = 0; i < lut_table_size; i++)
		omap_dmm->lut[i] = omap_dmm->dummy_pa;

	/* initialize all LUTs to dummy page entries */
	for (i = 0; i < omap_dmm->num_lut; i++) {
		area.tcm = omap_dmm->tcm[i];
		if (fill(&area, NULL, true))
			dev_err(omap_dmm->dev, "refill failed");
	}

	dev_info(omap_dmm->dev, "initialized all PAT entries\n");

	return 0;

fail:
	omap_dmm_remove();
	return ret;
}
+130 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading