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

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
...@@ -4,7 +4,15 @@ ...@@ -4,7 +4,15 @@
# #
ccflags-y := -Iinclude/drm -Werror 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: # temporary:
omapdrm-y += omap_gem_helpers.o omapdrm-y += omap_gem_helpers.o
......
...@@ -22,6 +22,7 @@ TODO ...@@ -22,6 +22,7 @@ TODO
. Review DSS vs KMS mismatches. The omap_dss_device is sort of part encoder, . 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 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. encoder to connector. Possibly this could be done a bit better.
. Add debugfs information for DMM/TILER
Userspace: Userspace:
. git://github.com/robclark/xf86-video-omap.git . git://github.com/robclark/xf86-video-omap.git
......
/*
*
* 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
/*
* 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;
}
/*
*
* 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_TILER_H
#define OMAP_DMM_TILER_H
#include "omap_drv.h"
#include "tcm.h"
enum tiler_fmt {
TILFMT_8BIT = 0,
TILFMT_16BIT,
TILFMT_32BIT,
TILFMT_PAGE,
TILFMT_NFORMATS
};
struct pat_area {
u32 x0:8;
u32 y0:8;
u32 x1:8;
u32 y1:8;
};
struct tiler_block {
struct list_head alloc_node; /* node for global block list */
struct tcm_area area; /* area */
enum tiler_fmt fmt; /* format */
};
/* bits representing the same slot in DMM-TILER hw-block */
#define SLOT_WIDTH_BITS 6
#define SLOT_HEIGHT_BITS 6
/* bits reserved to describe coordinates in DMM-TILER hw-block */
#define CONT_WIDTH_BITS 14
#define CONT_HEIGHT_BITS 13
/* calculated constants */
#define TILER_PAGE (1 << (SLOT_WIDTH_BITS + SLOT_HEIGHT_BITS))
#define TILER_WIDTH (1 << (CONT_WIDTH_BITS - SLOT_WIDTH_BITS))
#define TILER_HEIGHT (1 << (CONT_HEIGHT_BITS - SLOT_HEIGHT_BITS))
/* tiler space addressing bitfields */
#define MASK_XY_FLIP (1 << 31)
#define MASK_Y_INVERT (1 << 30)
#define MASK_X_INVERT (1 << 29)
#define SHIFT_ACC_MODE 27
#define MASK_ACC_MODE 3
#define MASK(bits) ((1 << (bits)) - 1)
#define TILVIEW_8BIT 0x60000000u
#define TILVIEW_16BIT (TILVIEW_8BIT + VIEW_SIZE)
#define TILVIEW_32BIT (TILVIEW_16BIT + VIEW_SIZE)
#define TILVIEW_PAGE (TILVIEW_32BIT + VIEW_SIZE)
#define TILVIEW_END (TILVIEW_PAGE + VIEW_SIZE)
/* create tsptr by adding view orientation and access mode */
#define TIL_ADDR(x, orient, a)\
((u32) (x) | (orient) | ((a) << SHIFT_ACC_MODE))
/* externally accessible functions */
int omap_dmm_init(struct drm_device *dev);
int omap_dmm_remove(void);
/* pin/unpin */
int tiler_pin(struct tiler_block *block, struct page **pages, bool wait);
int tiler_unpin(struct tiler_block *block);
/* reserve/release */
struct tiler_block *tiler_reserve_2d(enum tiler_fmt fmt, uint16_t w, uint16_t h,
uint16_t align);
struct tiler_block *tiler_reserve_1d(size_t size);
int tiler_release(struct tiler_block *block);
/* utilities */
dma_addr_t tiler_ssptr(struct tiler_block *block);
uint32_t tiler_stride(enum tiler_fmt fmt);
size_t tiler_size(enum tiler_fmt fmt, uint16_t w, uint16_t h);
size_t tiler_vsize(enum tiler_fmt fmt, uint16_t w, uint16_t h);
void tiler_align(enum tiler_fmt fmt, uint16_t *w, uint16_t *h);
/* GEM bo flags -> tiler fmt */
static inline enum tiler_fmt gem2fmt(uint32_t flags)
{
switch (flags & OMAP_BO_TILED) {
case OMAP_BO_TILED_8:
return TILFMT_8BIT;
case OMAP_BO_TILED_16:
return TILFMT_16BIT;
case OMAP_BO_TILED_32:
return TILFMT_32BIT;
default:
return TILFMT_PAGE;
}
}
static inline bool validfmt(enum tiler_fmt fmt)
{
switch (fmt) {
case TILFMT_8BIT:
case TILFMT_16BIT:
case TILFMT_32BIT:
case TILFMT_PAGE:
return true;
default:
return false;
}
}
struct omap_dmm_platform_data {
void __iomem *base;
int irq;
};
#endif
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
#ifndef __OMAP_DRM_H__ #ifndef __OMAP_DRM_H__
#define __OMAP_DRM_H__ #define __OMAP_DRM_H__
#include "drm.h" #include <drm/drm.h>
/* Please note that modifications to all structs defined here are /* Please note that modifications to all structs defined here are
* subject to backwards-compatibility constraints. * subject to backwards-compatibility constraints.
......
...@@ -290,6 +290,7 @@ static unsigned int detect_connectors(struct drm_device *dev) ...@@ -290,6 +290,7 @@ static unsigned int detect_connectors(struct drm_device *dev)
static int omap_modeset_init(struct drm_device *dev) static int omap_modeset_init(struct drm_device *dev)
{ {
const struct omap_drm_platform_data *pdata = dev->dev->platform_data; const struct omap_drm_platform_data *pdata = dev->dev->platform_data;
struct omap_kms_platform_data *kms_pdata = NULL;
struct omap_drm_private *priv = dev->dev_private; struct omap_drm_private *priv = dev->dev_private;
struct omap_dss_device *dssdev = NULL; struct omap_dss_device *dssdev = NULL;
int i, j; int i, j;
...@@ -297,23 +298,27 @@ static int omap_modeset_init(struct drm_device *dev) ...@@ -297,23 +298,27 @@ static int omap_modeset_init(struct drm_device *dev)
drm_mode_config_init(dev); drm_mode_config_init(dev);
if (pdata) { if (pdata && pdata->kms_pdata) {
kms_pdata = pdata->kms_pdata;
/* if platform data is provided by the board file, use it to /* if platform data is provided by the board file, use it to
* control which overlays, managers, and devices we own. * control which overlays, managers, and devices we own.
*/ */
for (i = 0; i < pdata->mgr_cnt; i++) { for (i = 0; i < kms_pdata->mgr_cnt; i++) {
struct omap_overlay_manager *mgr = struct omap_overlay_manager *mgr =
omap_dss_get_overlay_manager(pdata->mgr_ids[i]); omap_dss_get_overlay_manager(
kms_pdata->mgr_ids[i]);
create_encoder(dev, mgr); create_encoder(dev, mgr);
} }
for (i = 0; i < pdata->dev_cnt; i++) { for (i = 0; i < kms_pdata->dev_cnt; i++) {
struct omap_dss_device *dssdev = struct omap_dss_device *dssdev =
omap_dss_find_device( omap_dss_find_device(
(void *)pdata->dev_names[i], match_dev_name); (void *)kms_pdata->dev_names[i],
match_dev_name);
if (!dssdev) { if (!dssdev) {
dev_warn(dev->dev, "no such dssdev: %s\n", dev_warn(dev->dev, "no such dssdev: %s\n",
pdata->dev_names[i]); kms_pdata->dev_names[i]);
continue; continue;
} }
create_connector(dev, dssdev); create_connector(dev, dssdev);
...@@ -322,9 +327,9 @@ static int omap_modeset_init(struct drm_device *dev) ...@@ -322,9 +327,9 @@ static int omap_modeset_init(struct drm_device *dev)
connected_connectors = detect_connectors(dev); connected_connectors = detect_connectors(dev);
j = 0; j = 0;
for (i = 0; i < pdata->ovl_cnt; i++) { for (i = 0; i < kms_pdata->ovl_cnt; i++) {
struct omap_overlay *ovl = struct omap_overlay *ovl =
omap_dss_get_overlay(pdata->ovl_ids[i]); omap_dss_get_overlay(kms_pdata->ovl_ids[i]);
create_crtc(dev, ovl, &j, connected_connectors); create_crtc(dev, ovl, &j, connected_connectors);
} }
} else { } else {
......
...@@ -30,7 +30,7 @@ ...@@ -30,7 +30,7 @@
* detected devices. This should be a good default behavior for most cases, * detected devices. This should be a good default behavior for most cases,
* but yet there still might be times when you wish to do something different. * but yet there still might be times when you wish to do something different.
*/ */
struct omap_drm_platform_data { struct omap_kms_platform_data {
int ovl_cnt; int ovl_cnt;
const int *ovl_ids; const int *ovl_ids;
int mgr_cnt; int mgr_cnt;
...@@ -39,4 +39,9 @@ struct omap_drm_platform_data { ...@@ -39,4 +39,9 @@ struct omap_drm_platform_data {
const char **dev_names; const char **dev_names;
}; };
struct omap_drm_platform_data {
struct omap_kms_platform_data *kms_pdata;
struct omap_dmm_platform_data *dmm_pdata;
};
#endif /* __OMAP_DRM_H__ */ #endif /* __OMAP_DRM_H__ */
/*
* tcm-sita.c
*
* SImple Tiler Allocator (SiTA): 2D and 1D allocation(reservation) algorithm
*
* Authors: Ravi Ramachandra <r.ramachandra@ti.com>,
* Lajos Molnar <molnar@ti.com>
*
* Copyright (C) 2009-2010 Texas Instruments, Inc.
*
* This package is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
* WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*
*/
#include <linux/slab.h>
#include <linux/spinlock.h>
#include "tcm-sita.h"
#define ALIGN_DOWN(value, align) ((value) & ~((align) - 1))
/* Individual selection criteria for different scan areas */
static s32 CR_L2R_T2B = CR_BIAS_HORIZONTAL;
static s32 CR_R2L_T2B = CR_DIAGONAL_BALANCE;
/*********************************************
* TCM API - Sita Implementation
*********************************************/
static s32 sita_reserve_2d(struct tcm *tcm, u16 h, u16 w, u8 align,
struct tcm_area *area);
static s32 sita_reserve_1d(struct tcm *tcm, u32 slots, struct tcm_area *area);
static s32 sita_free(struct tcm *tcm, struct tcm_area *area);
static void sita_deinit(struct tcm *tcm);
/*********************************************
* Main Scanner functions
*********************************************/
static s32 scan_areas_and_find_fit(struct tcm *tcm, u16 w, u16 h, u16 align,
struct tcm_area *area);
static s32 scan_l2r_t2b(struct tcm *tcm, u16 w, u16 h, u16 align,
struct tcm_area *field, struct tcm_area *area);
static s32 scan_r2l_t2b(struct tcm *tcm, u16 w, u16 h, u16 align,
struct tcm_area *field, struct tcm_area *area);
static s32 scan_r2l_b2t_one_dim(struct tcm *tcm, u32 num_slots,
struct tcm_area *field, struct tcm_area *area);
/*********************************************
* Support Infrastructure Methods
*********************************************/
static s32 is_area_free(struct tcm_area ***map, u16 x0, u16 y0, u16 w, u16 h);
static s32 update_candidate(struct tcm *tcm, u16 x0, u16 y0, u16 w, u16 h,
struct tcm_area *field, s32 criteria,
struct score *best);
static void get_nearness_factor(struct tcm_area *field,
struct tcm_area *candidate,
struct nearness_factor *nf);
static void get_neighbor_stats(struct tcm *tcm, struct tcm_area *area,
struct neighbor_stats *stat);
static void fill_area(struct tcm *tcm,
struct tcm_area *area, struct tcm_area *parent);
/*********************************************/
/*********************************************
* Utility Methods
*********************************************/
struct tcm *sita_init(u16 width, u16 height, struct tcm_pt *attr)
{
struct tcm *tcm;
struct sita_pvt *pvt;
struct tcm_area area = {0};
s32 i;
if (width == 0 || height == 0)
return NULL;
tcm = kmalloc(sizeof(*tcm), GFP_KERNEL);
pvt = kmalloc(sizeof(*pvt), GFP_KERNEL);
if (!tcm || !pvt)
goto error;
memset(tcm, 0, sizeof(*tcm));
memset(pvt, 0, sizeof(*pvt));
/* Updating the pointers to SiTA implementation APIs */
tcm->height = height;
tcm->width = width;
tcm->reserve_2d = sita_reserve_2d;
tcm->reserve_1d = sita_reserve_1d;
tcm->free = sita_free;
tcm->deinit = sita_deinit;
tcm->pvt = (void *)pvt;
spin_lock_init(&(pvt->lock));
/* Creating tam map */
pvt->map = kmalloc(sizeof(*pvt->map) * tcm->width, GFP_KERNEL);
if (!pvt->map)
goto error;
for (i = 0; i < tcm->width; i++) {
pvt->map[i] =
kmalloc(sizeof(**pvt->map) * tcm->height,
GFP_KERNEL);
if (pvt->map[i] == NULL) {
while (i--)
kfree(pvt->map[i]);
kfree(pvt->map);
goto error;
}
}
if (attr && attr->x <= tcm->width && attr->y <= tcm->height) {
pvt->div_pt.x = attr->x;
pvt->div_pt.y = attr->y;
} else {
/* Defaulting to 3:1 ratio on width for 2D area split */
/* Defaulting to 3:1 ratio on height for 2D and 1D split */
pvt->div_pt.x = (tcm->width * 3) / 4;
pvt->div_pt.y = (tcm->height * 3) / 4;
}
spin_lock(&(pvt->lock));
assign(&area, 0, 0, width - 1, height - 1);
fill_area(tcm, &area, NULL);
spin_unlock(&(pvt->lock));
return tcm;
error:
kfree(tcm);
kfree(pvt);
return NULL;
}
static void sita_deinit(struct tcm *tcm)
{
struct sita_pvt *pvt = (struct sita_pvt *)tcm->pvt;
struct tcm_area area = {0};
s32 i;
area.p1.x = tcm->width - 1;
area.p1.y = tcm->height - 1;
spin_lock(&(pvt->lock));
fill_area(tcm, &area, NULL);
spin_unlock(&(pvt->lock));
for (i = 0; i < tcm->height; i++)
kfree(pvt->map[i]);
kfree(pvt->map);
kfree(pvt);
}
/**
* Reserve a 1D area in the container
*
* @param num_slots size of 1D area
* @param area pointer to the area that will be populated with the
* reserved area
*
* @return 0 on success, non-0 error value on failure.
*/
static s32 sita_reserve_1d(struct tcm *tcm, u32 num_slots,
struct tcm_area *area)
{
s32 ret;
struct tcm_area field = {0};
struct sita_pvt *pvt = (struct sita_pvt *)tcm->pvt;
spin_lock(&(pvt->lock));
/* Scanning entire container */
assign(&field, tcm->width - 1, tcm->height - 1, 0, 0);
ret = scan_r2l_b2t_one_dim(tcm, num_slots, &field, area);
if (!ret)
/* update map */
fill_area(tcm, area, area);
spin_unlock(&(pvt->lock));
return ret;
}
/**
* Reserve a 2D area in the container
*
* @param w width
* @param h height
* @param area pointer to the area that will be populated with the reesrved
* area
*
* @return 0 on success, non-0 error value on failure.
*/
static s32 sita_reserve_2d(struct tcm *tcm, u16 h, u16 w, u8 align,
struct tcm_area *area)
{
s32 ret;
struct sita_pvt *pvt = (struct sita_pvt *)tcm->pvt;
/* not supporting more than 64 as alignment */
if (align > 64)
return -EINVAL;
/* we prefer 1, 32 and 64 as alignment */
align = align <= 1 ? 1 : align <= 32 ? 32 : 64;
spin_lock(&(pvt->lock));
ret = scan_areas_and_find_fit(tcm, w, h, align, area);
if (!ret)
/* update map */
fill_area(tcm, area, area);
spin_unlock(&(pvt->lock));
return ret;
}
/**
* Unreserve a previously allocated 2D or 1D area
* @param area area to be freed
* @return 0 - success
*/
static s32 sita_free(struct tcm *tcm, struct tcm_area *area)
{
struct sita_pvt *pvt = (struct sita_pvt *)tcm->pvt;
spin_lock(&(pvt->lock));
/* check that this is in fact an existing area */
WARN_ON(pvt->map[area->p0.x][area->p0.y] != area ||
pvt->map[area->p1.x][area->p1.y] != area);
/* Clear the contents of the associated tiles in the map */
fill_area(tcm, area, NULL);
spin_unlock(&(pvt->lock));
return 0;
}
/**
* Note: In general the cordinates in the scan field area relevant to the can
* sweep directions. The scan origin (e.g. top-left corner) will always be
* the p0 member of the field. Therfore, for a scan from top-left p0.x <= p1.x
* and p0.y <= p1.y; whereas, for a scan from bottom-right p1.x <= p0.x and p1.y
* <= p0.y
*/
/**
* Raster scan horizontally right to left from top to bottom to find a place for
* a 2D area of given size inside a scan field.
*
* @param w width of desired area
* @param h height of desired area
* @param align desired area alignment
* @param area pointer to the area that will be set to the best position
* @param field area to scan (inclusive)
*
* @return 0 on success, non-0 error value on failure.
*/
static s32 scan_r2l_t2b(struct tcm *tcm, u16 w, u16 h, u16 align,
struct tcm_area *field, struct tcm_area *area)
{
s32 x, y;
s16 start_x, end_x, start_y, end_y, found_x = -1;
struct tcm_area ***map = ((struct sita_pvt *)tcm->pvt)->map;
struct score best = {{0}, {0}, {0}, 0};
start_x = field->p0.x;
end_x = field->p1.x;
start_y = field->p0.y;
end_y = field->p1.y;
/* check scan area co-ordinates */
if (field->p0.x < field->p1.x ||
field->p1.y < field->p0.y)
return -EINVAL;
/* check if allocation would fit in scan area */
if (w > LEN(start_x, end_x) || h > LEN(end_y, start_y))
return -ENOSPC;
/* adjust start_x and end_y, as allocation would not fit beyond */
start_x = ALIGN_DOWN(start_x - w + 1, align); /* - 1 to be inclusive */
end_y = end_y - h + 1;
/* check if allocation would still fit in scan area */
if (start_x < end_x)
return -ENOSPC;
/* scan field top-to-bottom, right-to-left */
for (y = start_y; y <= end_y; y++) {
for (x = start_x; x >= end_x; x -= align) {
if (is_area_free(map, x, y, w, h)) {
found_x = x;
/* update best candidate */
if (update_candidate(tcm, x, y, w, h, field,
CR_R2L_T2B, &best))
goto done;
/* change upper x bound */
end_x = x + 1;
break;
} else if (map[x][y] && map[x][y]->is2d) {
/* step over 2D areas */
x = ALIGN(map[x][y]->p0.x - w + 1, align);
}
}
/* break if you find a free area shouldering the scan field */
if (found_x == start_x)
break;
}
if (!best.a.tcm)
return -ENOSPC;
done:
assign(area, best.a.p0.x, best.a.p0.y, best.a.p1.x, best.a.p1.y);
return 0;
}
/**
* Raster scan horizontally left to right from top to bottom to find a place for
* a 2D area of given size inside a scan field.
*
* @param w width of desired area
* @param h height of desired area
* @param align desired area alignment
* @param area pointer to the area that will be set to the best position
* @param field area to scan (inclusive)
*
* @return 0 on success, non-0 error value on failure.
*/
static s32 scan_l2r_t2b(struct tcm *tcm, u16 w, u16 h, u16 align,
struct tcm_area *field, struct tcm_area *area)
{
s32 x, y;
s16 start_x, end_x, start_y, end_y, found_x = -1;
struct tcm_area ***map = ((struct sita_pvt *)tcm->pvt)->map;
struct score best = {{0}, {0}, {0}, 0};
start_x = field->p0.x;
end_x = field->p1.x;
start_y = field->p0.y;
end_y = field->p1.y;
/* check scan area co-ordinates */
if (field->p1.x < field->p0.x ||
field->p1.y < field->p0.y)
return -EINVAL;
/* check if allocation would fit in scan area */
if (w > LEN(end_x, start_x) || h > LEN(end_y, start_y))
return -ENOSPC;
start_x = ALIGN(start_x, align);
/* check if allocation would still fit in scan area */
if (w > LEN(end_x, start_x))
return -ENOSPC;
/* adjust end_x and end_y, as allocation would not fit beyond */
end_x = end_x - w + 1; /* + 1 to be inclusive */
end_y = end_y - h + 1;
/* scan field top-to-bottom, left-to-right */
for (y = start_y; y <= end_y; y++) {
for (x = start_x; x <= end_x; x += align) {
if (is_area_free(map, x, y, w, h)) {
found_x = x;
/* update best candidate */
if (update_candidate(tcm, x, y, w, h, field,
CR_L2R_T2B, &best))
goto done;
/* change upper x bound */
end_x = x - 1;
break;
} else if (map[x][y] && map[x][y]->is2d) {
/* step over 2D areas */
x = ALIGN_DOWN(map[x][y]->p1.x, align);
}
}
/* break if you find a free area shouldering the scan field */
if (found_x == start_x)
break;
}
if (!best.a.tcm)
return -ENOSPC;
done:
assign(area, best.a.p0.x, best.a.p0.y, best.a.p1.x, best.a.p1.y);
return 0;
}
/**
* Raster scan horizontally right to left from bottom to top to find a place
* for a 1D area of given size inside a scan field.
*
* @param num_slots size of desired area
* @param align desired area alignment
* @param area pointer to the area that will be set to the best
* position
* @param field area to scan (inclusive)
*
* @return 0 on success, non-0 error value on failure.
*/
static s32 scan_r2l_b2t_one_dim(struct tcm *tcm, u32 num_slots,
struct tcm_area *field, struct tcm_area *area)
{
s32 found = 0;
s16 x, y;
struct sita_pvt *pvt = (struct sita_pvt *)tcm->pvt;
struct tcm_area *p;
/* check scan area co-ordinates */
if (field->p0.y < field->p1.y)
return -EINVAL;
/**
* Currently we only support full width 1D scan field, which makes sense
* since 1D slot-ordering spans the full container width.
*/
if (tcm->width != field->p0.x - field->p1.x + 1)
return -EINVAL;
/* check if allocation would fit in scan area */
if (num_slots > tcm->width * LEN(field->p0.y, field->p1.y))
return -ENOSPC;
x = field->p0.x;
y = field->p0.y;
/* find num_slots consecutive free slots to the left */
while (found < num_slots) {
if (y < 0)
return -ENOSPC;
/* remember bottom-right corner */
if (found == 0) {
area->p1.x = x;
area->p1.y = y;
}
/* skip busy regions */
p = pvt->map[x][y];
if (p) {
/* move to left of 2D areas, top left of 1D */
x = p->p0.x;
if (!p->is2d)
y = p->p0.y;
/* start over */
found = 0;
} else {
/* count consecutive free slots */
found++;
if (found == num_slots)
break;
}
/* move to the left */
if (x == 0)
y--;
x = (x ? : tcm->width) - 1;
}
/* set top-left corner */
area->p0.x = x;
area->p0.y = y;
return 0;
}
/**
* Find a place for a 2D area of given size inside a scan field based on its
* alignment needs.
*
* @param w width of desired area
* @param h height of desired area
* @param align desired area alignment
* @param area pointer to the area that will be set to the best position
*
* @return 0 on success, non-0 error value on failure.
*/
static s32 scan_areas_and_find_fit(struct tcm *tcm, u16 w, u16 h, u16 align,
struct tcm_area *area)
{
s32 ret = 0;
struct tcm_area field = {0};
u16 boundary_x, boundary_y;
struct sita_pvt *pvt = (struct sita_pvt *)tcm->pvt;
if (align > 1) {
/* prefer top-left corner */
boundary_x = pvt->div_pt.x - 1;
boundary_y = pvt->div_pt.y - 1;
/* expand width and height if needed */
if (w > pvt->div_pt.x)
boundary_x = tcm->width - 1;
if (h > pvt->div_pt.y)
boundary_y = tcm->height - 1;
assign(&field, 0, 0, boundary_x, boundary_y);
ret = scan_l2r_t2b(tcm, w, h, align, &field, area);
/* scan whole container if failed, but do not scan 2x */
if (ret != 0 && (boundary_x != tcm->width - 1 ||
boundary_y != tcm->height - 1)) {
/* scan the entire container if nothing found */
assign(&field, 0, 0, tcm->width - 1, tcm->height - 1);
ret = scan_l2r_t2b(tcm, w, h, align, &field, area);
}
} else if (align == 1) {
/* prefer top-right corner */
boundary_x = pvt->div_pt.x;
boundary_y = pvt->div_pt.y - 1;
/* expand width and height if needed */
if (w > (tcm->width - pvt->div_pt.x))
boundary_x = 0;
if (h > pvt->div_pt.y)
boundary_y = tcm->height - 1;
assign(&field, tcm->width - 1, 0, boundary_x, boundary_y);
ret = scan_r2l_t2b(tcm, w, h, align, &field, area);
/* scan whole container if failed, but do not scan 2x */
if (ret != 0 && (boundary_x != 0 ||
boundary_y != tcm->height - 1)) {
/* scan the entire container if nothing found */
assign(&field, tcm->width - 1, 0, 0, tcm->height - 1);
ret = scan_r2l_t2b(tcm, w, h, align, &field,
area);
}
}
return ret;
}
/* check if an entire area is free */
static s32 is_area_free(struct tcm_area ***map, u16 x0, u16 y0, u16 w, u16 h)
{
u16 x = 0, y = 0;
for (y = y0; y < y0 + h; y++) {
for (x = x0; x < x0 + w; x++) {
if (map[x][y])
return false;
}
}
return true;
}
/* fills an area with a parent tcm_area */
static void fill_area(struct tcm *tcm, struct tcm_area *area,
struct tcm_area *parent)
{
s32 x, y;
struct sita_pvt *pvt = (struct sita_pvt *)tcm->pvt;
struct tcm_area a, a_;
/* set area's tcm; otherwise, enumerator considers it invalid */
area->tcm = tcm;
tcm_for_each_slice(a, *area, a_) {
for (x = a.p0.x; x <= a.p1.x; ++x)
for (y = a.p0.y; y <= a.p1.y; ++y)
pvt->map[x][y] = parent;
}
}
/**
* Compares a candidate area to the current best area, and if it is a better
* fit, it updates the best to this one.
*
* @param x0, y0, w, h top, left, width, height of candidate area
* @param field scan field
* @param criteria scan criteria
* @param best best candidate and its scores
*
* @return 1 (true) if the candidate area is known to be the final best, so no
* more searching should be performed
*/
static s32 update_candidate(struct tcm *tcm, u16 x0, u16 y0, u16 w, u16 h,
struct tcm_area *field, s32 criteria,
struct score *best)
{
struct score me; /* score for area */
/*
* NOTE: For horizontal bias we always give the first found, because our
* scan is horizontal-raster-based and the first candidate will always
* have the horizontal bias.
*/
bool first = criteria & CR_BIAS_HORIZONTAL;
assign(&me.a, x0, y0, x0 + w - 1, y0 + h - 1);
/* calculate score for current candidate */
if (!first) {
get_neighbor_stats(tcm, &me.a, &me.n);
me.neighs = me.n.edge + me.n.busy;
get_nearness_factor(field, &me.a, &me.f);
}
/* the 1st candidate is always the best */
if (!best->a.tcm)
goto better;
BUG_ON(first);
/* diagonal balance check */
if ((criteria & CR_DIAGONAL_BALANCE) &&
best->neighs <= me.neighs &&
(best->neighs < me.neighs ||
/* this implies that neighs and occupied match */
best->n.busy < me.n.busy ||
(best->n.busy == me.n.busy &&
/* check the nearness factor */
best->f.x + best->f.y > me.f.x + me.f.y)))
goto better;
/* not better, keep going */
return 0;
better:
/* save current area as best */
memcpy(best, &me, sizeof(me));
best->a.tcm = tcm;
return first;
}
/**
* Calculate the nearness factor of an area in a search field. The nearness
* factor is smaller if the area is closer to the search origin.
*/
static void get_nearness_factor(struct tcm_area *field, struct tcm_area *area,
struct nearness_factor *nf)
{
/**
* Using signed math as field coordinates may be reversed if
* search direction is right-to-left or bottom-to-top.
*/
nf->x = (s32)(area->p0.x - field->p0.x) * 1000 /
(field->p1.x - field->p0.x);
nf->y = (s32)(area->p0.y - field->p0.y) * 1000 /
(field->p1.y - field->p0.y);
}
/* get neighbor statistics */
static void get_neighbor_stats(struct tcm *tcm, struct tcm_area *area,
struct neighbor_stats *stat)
{
s16 x = 0, y = 0;
struct sita_pvt *pvt = (struct sita_pvt *)tcm->pvt;
/* Clearing any exisiting values */
memset(stat, 0, sizeof(*stat));
/* process top & bottom edges */
for (x = area->p0.x; x <= area->p1.x; x++) {
if (area->p0.y == 0)
stat->edge++;
else if (pvt->map[x][area->p0.y - 1])
stat->busy++;
if (area->p1.y == tcm->height - 1)
stat->edge++;
else if (pvt->map[x][area->p1.y + 1])
stat->busy++;
}
/* process left & right edges */
for (y = area->p0.y; y <= area->p1.y; ++y) {
if (area->p0.x == 0)
stat->edge++;
else if (pvt->map[area->p0.x - 1][y])
stat->busy++;
if (area->p1.x == tcm->width - 1)
stat->edge++;
else if (pvt->map[area->p1.x + 1][y])
stat->busy++;
}
}
/*
* tcm_sita.h
*
* SImple Tiler Allocator (SiTA) private structures.
*
* Author: Ravi Ramachandra <r.ramachandra@ti.com>
*
* Copyright (C) 2009-2011 Texas Instruments, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of Texas Instruments Incorporated nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef _TCM_SITA_H
#define _TCM_SITA_H
#include "tcm.h"
/* length between two coordinates */
#define LEN(a, b) ((a) > (b) ? (a) - (b) + 1 : (b) - (a) + 1)
enum criteria {
CR_MAX_NEIGHS = 0x01,
CR_FIRST_FOUND = 0x10,
CR_BIAS_HORIZONTAL = 0x20,
CR_BIAS_VERTICAL = 0x40,
CR_DIAGONAL_BALANCE = 0x80
};
/* nearness to the beginning of the search field from 0 to 1000 */
struct nearness_factor {
s32 x;
s32 y;
};
/*
* Statistics on immediately neighboring slots. Edge is the number of
* border segments that are also border segments of the scan field. Busy
* refers to the number of neighbors that are occupied.
*/
struct neighbor_stats {
u16 edge;
u16 busy;
};
/* structure to keep the score of a potential allocation */
struct score {
struct nearness_factor f;
struct neighbor_stats n;
struct tcm_area a;
u16 neighs; /* number of busy neighbors */
};
struct sita_pvt {
spinlock_t lock; /* spinlock to protect access */
struct tcm_pt div_pt; /* divider point splitting container */
struct tcm_area ***map; /* pointers to the parent area for each slot */
};
/* assign coordinates to area */
static inline
void assign(struct tcm_area *a, u16 x0, u16 y0, u16 x1, u16 y1)
{
a->p0.x = x0;
a->p0.y = y0;
a->p1.x = x1;
a->p1.y = y1;
}
#endif
/*
* tcm.h
*
* TILER container manager specification and support functions for TI
* TILER driver.
*
* Author: Lajos Molnar <molnar@ti.com>
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of Texas Instruments Incorporated nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef TCM_H
#define TCM_H
struct tcm;
/* point */
struct tcm_pt {
u16 x;
u16 y;
};
/* 1d or 2d area */
struct tcm_area {
bool is2d; /* whether area is 1d or 2d */
struct tcm *tcm; /* parent */
struct tcm_pt p0;
struct tcm_pt p1;
};
struct tcm {
u16 width, height; /* container dimensions */
int lut_id; /* Lookup table identifier */
/* 'pvt' structure shall contain any tcm details (attr) along with
linked list of allocated areas and mutex for mutually exclusive access
to the list. It may also contain copies of width and height to notice
any changes to the publicly available width and height fields. */
void *pvt;
/* function table */
s32 (*reserve_2d)(struct tcm *tcm, u16 height, u16 width, u8 align,
struct tcm_area *area);
s32 (*reserve_1d)(struct tcm *tcm, u32 slots, struct tcm_area *area);
s32 (*free) (struct tcm *tcm, struct tcm_area *area);
void (*deinit) (struct tcm *tcm);
};
/*=============================================================================
BASIC TILER CONTAINER MANAGER INTERFACE
=============================================================================*/
/*
* NOTE:
*
* Since some basic parameter checking is done outside the TCM algorithms,
* TCM implementation do NOT have to check the following:
*
* area pointer is NULL
* width and height fits within container
* number of pages is more than the size of the container
*
*/
struct tcm *sita_init(u16 width, u16 height, struct tcm_pt *attr);
/**
* Deinitialize tiler container manager.
*
* @param tcm Pointer to container manager.
*
* @return 0 on success, non-0 error value on error. The call
* should free as much memory as possible and meaningful
* even on failure. Some error codes: -ENODEV: invalid
* manager.
*/
static inline void tcm_deinit(struct tcm *tcm)
{
if (tcm)
tcm->deinit(tcm);
}
/**
* Reserves a 2D area in the container.
*
* @param tcm Pointer to container manager.
* @param height Height(in pages) of area to be reserved.
* @param width Width(in pages) of area to be reserved.
* @param align Alignment requirement for top-left corner of area. Not
* all values may be supported by the container manager,
* but it must support 0 (1), 32 and 64.
* 0 value is equivalent to 1.
* @param area Pointer to where the reserved area should be stored.
*
* @return 0 on success. Non-0 error code on failure. Also,
* the tcm field of the area will be set to NULL on
* failure. Some error codes: -ENODEV: invalid manager,
* -EINVAL: invalid area, -ENOMEM: not enough space for
* allocation.
*/
static inline s32 tcm_reserve_2d(struct tcm *tcm, u16 width, u16 height,
u16 align, struct tcm_area *area)
{
/* perform rudimentary error checking */
s32 res = tcm == NULL ? -ENODEV :
(area == NULL || width == 0 || height == 0 ||
/* align must be a 2 power */
(align & (align - 1))) ? -EINVAL :
(height > tcm->height || width > tcm->width) ? -ENOMEM : 0;
if (!res) {
area->is2d = true;
res = tcm->reserve_2d(tcm, height, width, align, area);
area->tcm = res ? NULL : tcm;
}
return res;
}
/**
* Reserves a 1D area in the container.
*
* @param tcm Pointer to container manager.
* @param slots Number of (contiguous) slots to reserve.
* @param area Pointer to where the reserved area should be stored.
*
* @return 0 on success. Non-0 error code on failure. Also,
* the tcm field of the area will be set to NULL on
* failure. Some error codes: -ENODEV: invalid manager,
* -EINVAL: invalid area, -ENOMEM: not enough space for
* allocation.
*/
static inline s32 tcm_reserve_1d(struct tcm *tcm, u32 slots,
struct tcm_area *area)
{
/* perform rudimentary error checking */
s32 res = tcm == NULL ? -ENODEV :
(area == NULL || slots == 0) ? -EINVAL :
slots > (tcm->width * (u32) tcm->height) ? -ENOMEM : 0;
if (!res) {
area->is2d = false;
res = tcm->reserve_1d(tcm, slots, area);
area->tcm = res ? NULL : tcm;
}
return res;
}
/**
* Free a previously reserved area from the container.
*
* @param area Pointer to area reserved by a prior call to
* tcm_reserve_1d or tcm_reserve_2d call, whether
* it was successful or not. (Note: all fields of
* the structure must match.)
*
* @return 0 on success. Non-0 error code on failure. Also, the tcm
* field of the area is set to NULL on success to avoid subsequent
* freeing. This call will succeed even if supplying
* the area from a failed reserved call.
*/
static inline s32 tcm_free(struct tcm_area *area)
{
s32 res = 0; /* free succeeds by default */
if (area && area->tcm) {
res = area->tcm->free(area->tcm, area);
if (res == 0)
area->tcm = NULL;
}
return res;
}
/*=============================================================================
HELPER FUNCTION FOR ANY TILER CONTAINER MANAGER
=============================================================================*/
/**
* This method slices off the topmost 2D slice from the parent area, and stores
* it in the 'slice' parameter. The 'parent' parameter will get modified to
* contain the remaining portion of the area. If the whole parent area can
* fit in a 2D slice, its tcm pointer is set to NULL to mark that it is no
* longer a valid area.
*
* @param parent Pointer to a VALID parent area that will get modified
* @param slice Pointer to the slice area that will get modified
*/
static inline void tcm_slice(struct tcm_area *parent, struct tcm_area *slice)
{
*slice = *parent;
/* check if we need to slice */
if (slice->tcm && !slice->is2d &&
slice->p0.y != slice->p1.y &&
(slice->p0.x || (slice->p1.x != slice->tcm->width - 1))) {
/* set end point of slice (start always remains) */
slice->p1.x = slice->tcm->width - 1;
slice->p1.y = (slice->p0.x) ? slice->p0.y : slice->p1.y - 1;
/* adjust remaining area */
parent->p0.x = 0;
parent->p0.y = slice->p1.y + 1;
} else {
/* mark this as the last slice */
parent->tcm = NULL;
}
}
/* Verify if a tcm area is logically valid */
static inline bool tcm_area_is_valid(struct tcm_area *area)
{
return area && area->tcm &&
/* coordinate bounds */
area->p1.x < area->tcm->width &&
area->p1.y < area->tcm->height &&
area->p0.y <= area->p1.y &&
/* 1D coordinate relationship + p0.x check */
((!area->is2d &&
area->p0.x < area->tcm->width &&
area->p0.x + area->p0.y * area->tcm->width <=
area->p1.x + area->p1.y * area->tcm->width) ||
/* 2D coordinate relationship */
(area->is2d &&
area->p0.x <= area->p1.x));
}
/* see if a coordinate is within an area */
static inline bool __tcm_is_in(struct tcm_pt *p, struct tcm_area *a)
{
u16 i;
if (a->is2d) {
return p->x >= a->p0.x && p->x <= a->p1.x &&
p->y >= a->p0.y && p->y <= a->p1.y;
} else {
i = p->x + p->y * a->tcm->width;
return i >= a->p0.x + a->p0.y * a->tcm->width &&
i <= a->p1.x + a->p1.y * a->tcm->width;
}
}
/* calculate area width */
static inline u16 __tcm_area_width(struct tcm_area *area)
{
return area->p1.x - area->p0.x + 1;
}
/* calculate area height */
static inline u16 __tcm_area_height(struct tcm_area *area)
{
return area->p1.y - area->p0.y + 1;
}
/* calculate number of slots in an area */
static inline u16 __tcm_sizeof(struct tcm_area *area)
{
return area->is2d ?
__tcm_area_width(area) * __tcm_area_height(area) :
(area->p1.x - area->p0.x + 1) + (area->p1.y - area->p0.y) *
area->tcm->width;
}
#define tcm_sizeof(area) __tcm_sizeof(&(area))
#define tcm_awidth(area) __tcm_area_width(&(area))
#define tcm_aheight(area) __tcm_area_height(&(area))
#define tcm_is_in(pt, area) __tcm_is_in(&(pt), &(area))
/* limit a 1D area to the first N pages */
static inline s32 tcm_1d_limit(struct tcm_area *a, u32 num_pg)
{
if (__tcm_sizeof(a) < num_pg)
return -ENOMEM;
if (!num_pg)
return -EINVAL;
a->p1.x = (a->p0.x + num_pg - 1) % a->tcm->width;
a->p1.y = a->p0.y + ((a->p0.x + num_pg - 1) / a->tcm->width);
return 0;
}
/**
* Iterate through 2D slices of a valid area. Behaves
* syntactically as a for(;;) statement.
*
* @param var Name of a local variable of type 'struct
* tcm_area *' that will get modified to
* contain each slice.
* @param area Pointer to the VALID parent area. This
* structure will not get modified
* throughout the loop.
*
*/
#define tcm_for_each_slice(var, area, safe) \
for (safe = area, \
tcm_slice(&safe, &var); \
var.tcm; tcm_slice(&safe, &var))
#endif
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment