diff options
-rw-r--r-- | drivers/staging/omapdrm/Makefile | 10 | ||||
-rw-r--r-- | drivers/staging/omapdrm/TODO | 1 | ||||
-rw-r--r-- | drivers/staging/omapdrm/omap_dmm_priv.h | 187 | ||||
-rw-r--r-- | drivers/staging/omapdrm/omap_dmm_tiler.c | 672 | ||||
-rw-r--r-- | drivers/staging/omapdrm/omap_dmm_tiler.h | 130 | ||||
-rw-r--r-- | drivers/staging/omapdrm/omap_drm.h | 2 | ||||
-rw-r--r-- | drivers/staging/omapdrm/omap_drv.c | 21 | ||||
-rw-r--r-- | drivers/staging/omapdrm/omap_priv.h | 7 | ||||
-rw-r--r-- | drivers/staging/omapdrm/tcm-sita.c | 703 | ||||
-rw-r--r-- | drivers/staging/omapdrm/tcm-sita.h | 95 | ||||
-rw-r--r-- | drivers/staging/omapdrm/tcm.h | 326 |
11 files changed, 2143 insertions, 11 deletions
diff --git a/drivers/staging/omapdrm/Makefile b/drivers/staging/omapdrm/Makefile index 4aa9a2f34d4a..275054a3451e 100644 --- a/drivers/staging/omapdrm/Makefile +++ b/drivers/staging/omapdrm/Makefile @@ -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 diff --git a/drivers/staging/omapdrm/TODO b/drivers/staging/omapdrm/TODO index 17781c954a83..18677e777368 100644 --- a/drivers/staging/omapdrm/TODO +++ b/drivers/staging/omapdrm/TODO @@ -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 diff --git a/drivers/staging/omapdrm/omap_dmm_priv.h b/drivers/staging/omapdrm/omap_dmm_priv.h new file mode 100644 index 000000000000..65b990c8e16a --- /dev/null +++ b/drivers/staging/omapdrm/omap_dmm_priv.h @@ -0,0 +1,187 @@ +/* + * + * 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 diff --git a/drivers/staging/omapdrm/omap_dmm_tiler.c b/drivers/staging/omapdrm/omap_dmm_tiler.c new file mode 100644 index 000000000000..9ed5215c65ac --- /dev/null +++ b/drivers/staging/omapdrm/omap_dmm_tiler.c @@ -0,0 +1,672 @@ +/* + * 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; +} diff --git a/drivers/staging/omapdrm/omap_dmm_tiler.h b/drivers/staging/omapdrm/omap_dmm_tiler.h new file mode 100644 index 000000000000..7e63b6be29f7 --- /dev/null +++ b/drivers/staging/omapdrm/omap_dmm_tiler.h @@ -0,0 +1,130 @@ +/* + * + * 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 diff --git a/drivers/staging/omapdrm/omap_drm.h b/drivers/staging/omapdrm/omap_drm.h index 40167dd97589..f0ac34a8973e 100644 --- a/drivers/staging/omapdrm/omap_drm.h +++ b/drivers/staging/omapdrm/omap_drm.h @@ -20,7 +20,7 @@ #ifndef __OMAP_DRM_H__ #define __OMAP_DRM_H__ -#include "drm.h" +#include <drm/drm.h> /* Please note that modifications to all structs defined here are * subject to backwards-compatibility constraints. diff --git a/drivers/staging/omapdrm/omap_drv.c b/drivers/staging/omapdrm/omap_drv.c index cee005043dbd..71de7cf34d89 100644 --- a/drivers/staging/omapdrm/omap_drv.c +++ b/drivers/staging/omapdrm/omap_drv.c @@ -290,6 +290,7 @@ static unsigned int detect_connectors(struct drm_device *dev) static int omap_modeset_init(struct drm_device *dev) { 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_dss_device *dssdev = NULL; int i, j; @@ -297,23 +298,27 @@ static int omap_modeset_init(struct drm_device *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 * 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 = - omap_dss_get_overlay_manager(pdata->mgr_ids[i]); + omap_dss_get_overlay_manager( + kms_pdata->mgr_ids[i]); 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 = omap_dss_find_device( - (void *)pdata->dev_names[i], match_dev_name); + (void *)kms_pdata->dev_names[i], + match_dev_name); if (!dssdev) { dev_warn(dev->dev, "no such dssdev: %s\n", - pdata->dev_names[i]); + kms_pdata->dev_names[i]); continue; } create_connector(dev, dssdev); @@ -322,9 +327,9 @@ static int omap_modeset_init(struct drm_device *dev) connected_connectors = detect_connectors(dev); j = 0; - for (i = 0; i < pdata->ovl_cnt; i++) { + for (i = 0; i < kms_pdata->ovl_cnt; i++) { 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); } } else { diff --git a/drivers/staging/omapdrm/omap_priv.h b/drivers/staging/omapdrm/omap_priv.h index f482d1edb060..c324709aa9a1 100644 --- a/drivers/staging/omapdrm/omap_priv.h +++ b/drivers/staging/omapdrm/omap_priv.h @@ -30,7 +30,7 @@ * 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. */ -struct omap_drm_platform_data { +struct omap_kms_platform_data { int ovl_cnt; const int *ovl_ids; int mgr_cnt; @@ -39,4 +39,9 @@ struct omap_drm_platform_data { 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__ */ diff --git a/drivers/staging/omapdrm/tcm-sita.c b/drivers/staging/omapdrm/tcm-sita.c new file mode 100644 index 000000000000..10d5ac3dae4b --- /dev/null +++ b/drivers/staging/omapdrm/tcm-sita.c @@ -0,0 +1,703 @@ +/* + * 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++; + } +} diff --git a/drivers/staging/omapdrm/tcm-sita.h b/drivers/staging/omapdrm/tcm-sita.h new file mode 100644 index 000000000000..0444f868671c --- /dev/null +++ b/drivers/staging/omapdrm/tcm-sita.h @@ -0,0 +1,95 @@ +/* + * 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 diff --git a/drivers/staging/omapdrm/tcm.h b/drivers/staging/omapdrm/tcm.h new file mode 100644 index 000000000000..d273e3ee0b4c --- /dev/null +++ b/drivers/staging/omapdrm/tcm.h @@ -0,0 +1,326 @@ +/* + * 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 |