diff options
Diffstat (limited to 'drivers/media/video/tiler')
-rw-r--r-- | drivers/media/video/tiler/Kconfig | 6 | ||||
-rw-r--r-- | drivers/media/video/tiler/Makefile | 4 | ||||
-rw-r--r-- | drivers/media/video/tiler/tcm/Makefile | 2 | ||||
-rw-r--r-- | drivers/media/video/tiler/tcm/_tcm_sita.h | 91 | ||||
-rw-r--r-- | drivers/media/video/tiler/tcm/tcm.h | 352 | ||||
-rw-r--r-- | drivers/media/video/tiler/tcm/tcm_sita.c | 1358 | ||||
-rw-r--r-- | drivers/media/video/tiler/tcm/tcm_sita.h | 39 | ||||
-rw-r--r-- | drivers/media/video/tiler/tcm/tcm_utils.h | 59 | ||||
-rw-r--r-- | drivers/media/video/tiler/tiler.c | 1583 | ||||
-rw-r--r-- | drivers/media/video/tiler/tiler_def.h | 158 | ||||
-rw-r--r-- | drivers/media/video/tiler/tiler_pack.c | 269 | ||||
-rw-r--r-- | drivers/media/video/tiler/tiler_rot.c | 239 |
12 files changed, 4160 insertions, 0 deletions
diff --git a/drivers/media/video/tiler/Kconfig b/drivers/media/video/tiler/Kconfig new file mode 100644 index 000000000000..fabbb59a6c8d --- /dev/null +++ b/drivers/media/video/tiler/Kconfig @@ -0,0 +1,6 @@ +config TILER_OMAP + tristate "OMAP TILER support" + default y + help + TILER driver for OMAP based boards. + diff --git a/drivers/media/video/tiler/Makefile b/drivers/media/video/tiler/Makefile new file mode 100644 index 000000000000..e6dbe24ce9d3 --- /dev/null +++ b/drivers/media/video/tiler/Makefile @@ -0,0 +1,4 @@ +obj-$(CONFIG_TILER_OMAP) += tcm/ +obj-$(CONFIG_TILER_OMAP) += tiler_omap.o +tiler_omap-objs = tiler.o tiler_pack.o tiler_rot.o + diff --git a/drivers/media/video/tiler/tcm/Makefile b/drivers/media/video/tiler/tcm/Makefile new file mode 100644 index 000000000000..f03f3b7c862f --- /dev/null +++ b/drivers/media/video/tiler/tcm/Makefile @@ -0,0 +1,2 @@ +obj-$(CONFIG_TILER_OMAP) += tcm_sita.o + diff --git a/drivers/media/video/tiler/tcm/_tcm_sita.h b/drivers/media/video/tiler/tcm/_tcm_sita.h new file mode 100644 index 000000000000..75de584c747d --- /dev/null +++ b/drivers/media/video/tiler/tcm/_tcm_sita.h @@ -0,0 +1,91 @@ +/* + * _tcm_sita.h + * + * SImple Tiler Allocator (SiTA) private structures. + * + * Author: Ravi Ramachandra <r.ramachandra@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. + */ + +#ifndef _TCM_SITA_H_ +#define _TCM_SITA_H_ + +#include "tcm.h" + +#define TL_CORNER 0 +#define TR_CORNER 1 +#define BL_CORNER 3 +#define BR_CORNER 4 + +/*Provide inclusive length between co-ordinates */ +#define INCL_LEN(high, low) ((high) - (low) + 1) +#define INCL_LEN_MOD(start, end) ((start) > (end) ? (start) - (end) + 1 : \ + (end) - (start) + 1) + +#define BOUNDARY(stat) ((stat)->top_boundary + (stat)->bottom_boundary + \ + (stat)->left_boundary + (stat)->right_boundary) +#define OCCUPIED(stat) ((stat)->top_occupied + (stat)->bottom_occupied + \ + (stat)->left_occupied + (stat)->right_occupied) + +enum Criteria { + CR_MAX_NEIGHS = 0x01, + CR_FIRST_FOUND = 0x10, + CR_BIAS_HORIZONTAL = 0x20, + CR_BIAS_VERTICAL = 0x40, + CR_DIAGONAL_BALANCE = 0x80 +}; + +struct nearness_factor { + s32 x; + s32 y; +}; + +/* + * Area info kept + */ +struct area_spec { + struct tcm_area area; + struct list_head list; +}; + +/* + * Everything is a rectangle with four sides and on + * each side you could have a boundary or another Tile. + * The tile could be Occupied or Not. These info is stored + */ +struct neighbour_stats { + u16 left_boundary; + u16 left_occupied; + u16 top_boundary; + u16 top_occupied; + u16 right_boundary; + u16 right_occupied; + u16 bottom_boundary; + u16 bottom_occupied; +}; + +struct slot { + u8 busy; /* is slot occupied */ + struct tcm_area parent; /* parent area */ + u32 reserved; +}; + +struct sita_pvt { + u16 width; + u16 height; + struct list_head res; /* all allocations */ + struct mutex mtx; + struct tcm_pt div_pt; /* divider point splitting container */ + struct slot **map; /* container slots */ +}; + +#endif /* _TCM_SITA_H_ */ diff --git a/drivers/media/video/tiler/tcm/tcm.h b/drivers/media/video/tiler/tcm/tcm.h new file mode 100644 index 000000000000..d205dad32a46 --- /dev/null +++ b/drivers/media/video/tiler/tcm/tcm.h @@ -0,0 +1,352 @@ +/* + * tcm.h + * + * TILER container manager specification and support functions for TI + * processors. + * + * 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. + */ + +#ifndef _TCM_H_ +#define _TCM_H_ + +#include <linux/init.h> +#include <linux/module.h> + +struct tcm; + +struct tcm_pt { + u16 x; + u16 y; +}; + +struct tcm_area { + bool is2d; /* whether are is 1d or 2d */ + struct tcm *tcm; /* parent */ + struct tcm_pt p0; + struct tcm_pt p1; +}; + +struct tcm { + u16 width, height; /* container dimensions */ + + /* '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); + s32 (*get_parent)(struct tcm *tcm, struct tcm_pt *pt, + 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 + * + */ + +/** + * Template for <ALGO_NAME>_tcm_init method. Define as: + * TCM_INIT(<ALGO_NAME>_tcm_init) + * + * Allocates and initializes a tiler container manager. + * + * @param width Width of container + * @param height Height of container + * @param attr Container manager specific configuration + * arguments. Please describe these in + * your header file. + * + * @return Pointer to the allocated and initialized container + * manager. NULL on failure. DO NOT leak any memory on + * failure! + */ +#define TCM_INIT(name, attr_t) \ +struct tcm *name(u16 width, u16 height, typeof(attr_t) *attr); + +/** + * Deinitialize tiler container manager. + * + * @author Ravi Ramachandra (3/1/2010) + * + * @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. + * + * @author Ravi Ramachandra (3/1/2010) + * + * @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 ? -EINVAL : + (height > tcm->height || width > tcm->width) ? -ENOMEM : + tcm->reserve_2d(tcm, height, width, align, area)); + + if (area) + area->tcm = res ? NULL : tcm; + + return res; +} + +/** + * Reserves a 1D area in the container. + * + * @author Ravi Ramachandra (3/1/2010) + * + * @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 ? -EINVAL : + slots > (tcm->width * (u32) tcm->height) ? -ENOMEM : + tcm->reserve_1d(tcm, slots, area)); + + if (area) + area->tcm = res ? NULL : tcm; + + return res; +} + +/** + * Free a previously reserved area from the container. + * + * @author Ravi Ramachandra (3/1/2010) + * + * @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; +} + + +/** + * Retrieves the parent area (1D or 2D) for a given co-ordinate in the + * container. + * + * @author Ravi Ramachandra (3/1/2010) + * + * @param tcm Pointer to container manager. + * @param pt Pointer to the coordinates of a slot in the container. + * @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, -ENOENT: coordinate is not part of any + * active area. + */ +static inline s32 tcm_get_parent(struct tcm *tcm, struct tcm_pt *pt, + struct tcm_area *area) +{ + s32 res = (tcm == NULL ? -ENODEV : + area == NULL ? -EINVAL : + (pt->x >= tcm->width || pt->y >= tcm->height) ? -ENOENT : + tcm->get_parent(tcm, pt, area)); + + if (area) + area->tcm = res ? NULL : tcm; + + 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. + * + * @author Lajos Molnar (3/17/2010) + * + * @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; + } +} + +/** + * Verifies if a tcm area is logically valid. + * + * @param area Pointer to tcm area + * + * @return TRUE if area is logically valid, FALSE otherwise. + */ +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)) + +/** + * 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 /* _TCM_H_ */ diff --git a/drivers/media/video/tiler/tcm/tcm_sita.c b/drivers/media/video/tiler/tcm/tcm_sita.c new file mode 100644 index 000000000000..0aea8f12be21 --- /dev/null +++ b/drivers/media/video/tiler/tcm/tcm_sita.c @@ -0,0 +1,1358 @@ +/* + * tcm_sita.c + * + * Author: Ravi Ramachandra <r.ramachandra@ti.com> + * + * SImple Tiler Allocator (SiTA): 2D and 1D allocation(reservation) algorithm + * + * 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 "_tcm_sita.h" +#include "tcm_sita.h" + +#define TCM_ALG_NAME "tcm_sita" +#include "tcm_utils.h" + +#define X_SCAN_LIMITER 1 +#define Y_SCAN_LIMITER 1 + +#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; +#ifdef SCAN_BOTTOM_UP +static s32 CR_R2L_B2T = CR_FIRST_FOUND; +static s32 CR_L2R_B2T = CR_DIAGONAL_BALANCE; +#endif + +/********************************************* + * 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 *to_be_removed_area); +static s32 sita_get_parent(struct tcm *tcm, struct tcm_pt *pt, + 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 stride, + struct tcm_area *area); + +static s32 scan_l2r_t2b(struct tcm *tcm, u16 w, u16 h, u16 stride, + struct tcm_area *field, struct tcm_area *area); + +static s32 scan_r2l_t2b(struct tcm *tcm, u16 w, u16 h, u16 stride, + struct tcm_area *field, struct tcm_area *area); + +#ifdef SCAN_BOTTOM_UP +static s32 scan_l2r_b2t(struct tcm *tcm, u16 w, u16 h, u16 stride, + struct tcm_area *field, struct tcm_area *area); + +static s32 scan_r2l_b2t(struct tcm *tcm, u16 w, u16 h, u16 stride, + struct tcm_area *field, struct tcm_area *area); +#endif +static s32 scan_r2l_b2t_one_dim(struct tcm *tcm, u32 num_pages, + struct tcm_area *field, struct tcm_area *area); + +/********************************************* + * Support Infrastructure Methods + *********************************************/ +static s32 check_fit_r_and_b(struct tcm *tcm, u16 w, u16 h, u16 left_x, + u16 top_y); + +static s32 check_fit_r_one_dim(struct tcm *tcm, u16 x, u16 y, u32 num_pages, + u16 *busy_x, u16 *busy_y); + +static void select_candidate(struct tcm *tcm, u16 w, u16 h, + struct list_head *maybes, struct tcm_area *field, + s32 criteria, struct tcm_area *area); + +static void get_nearness_factor(struct tcm_area *field, + struct tcm_area *candidate, struct nearness_factor *nf); + +static s32 get_busy_neigh_stats(struct tcm *tcm, u16 width, u16 height, + struct tcm_area *top_left_corner, + struct neighbour_stats *neighbour_stat); + +static void fill_1d_area(struct tcm *tcm, + struct tcm_area *area, struct slot slot); + +static void fill_2d_area(struct tcm *tcm, + struct tcm_area *area, struct slot slot); + +static s32 move_left(struct tcm *tcm, u16 x, u16 y, u32 num_pages, + u16 *xx, u16 *yy); +static s32 move_right(struct tcm *tcm, u16 x, u16 y, u32 num_pages, + u16 *xx, u16 *yy); +/*********************************************/ + +/********************************************* + * Utility Methods + *********************************************/ + +/* TODO: check if element allocation succeeded */ + +/* insert a given area at the end of a given list */ +static +struct area_spec *insert_element(struct list_head *head, struct tcm_area *area) +{ + struct area_spec *elem; + + elem = kmalloc(sizeof(*elem), GFP_KERNEL); + if (elem) { + elem->area = *area; + list_add_tail(&elem->list, head); + } + return elem; +} + +static +s32 rem_element_with_match(struct list_head *head, + struct tcm_area *area, u16 *is2d) +{ + struct area_spec *elem = NULL; + + /*If the area to be removed matchs the list head itself, + we need to put the next one as list head */ + list_for_each_entry(elem, head, list) { + if (elem->area.p0.x == area->p0.x + && elem->area.p0.y == area->p0.y + && elem->area.p1.x == area->p1.x + && elem->area.p1.y == area->p1.y) { + + *is2d = elem->area.is2d; + list_del(&elem->list); + + kfree(elem); + return 0; + } + } + return -ENOENT; +} + +static +void clean_list(struct list_head *head) +{ + struct area_spec *elem = NULL, *elem_ = NULL; + + list_for_each_entry_safe(elem, elem_, head, list) { + list_del(&elem->list); + kfree(elem); + } +} + +#if 0 +static +void dump_list_entries(struct list_head *head) +{ + struct area_spec *elem = NULL; + + P1("Printing List Entries:\n"); + + list_for_each_entry(elem, head, list) { + printk(KERN_NOTICE "%dD:" AREA_FMT "\n", elem->area.type, + AREA(elem->area)); + } + + P1("List Finished\n"); +} + +static +s32 dump_neigh_stats(struct neighbour_stats *neighbour) +{ + P1("Top Occ:Boundary %d:%d\n", neighbour->top_occupied, + neighbour->top_boundary); + P1("Bot Occ:Boundary %d:%d\n", neighbour->bottom_occupied, + neighbour->bottom_boundary); + P1("Left Occ:Boundary %d:%d\n", neighbour->left_occupied, + neighbour->left_boundary); + P1("Rigt Occ:Boundary %d:%d\n", neighbour->right_occupied, + neighbour->right_boundary); + return 0; +} +#endif + +struct tcm *sita_init(u16 width, u16 height, struct tcm_pt *attr) +{ + struct tcm *tcm = NULL; + struct sita_pvt *pvt = NULL; + struct slot init_tile = {0}; + struct tcm_area area = {0}; + s32 i = 0; + + if (width == 0 || height == 0) + goto error; + + 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->get_parent = sita_get_parent; + tcm->free = sita_free; + tcm->deinit = sita_deinit; + tcm->pvt = (void *)pvt; + + INIT_LIST_HEAD(&pvt->res); + pvt->height = height; + pvt->width = width; + + mutex_init(&(pvt->mtx)); + + /* Creating tam map */ + pvt->map = kmalloc(sizeof(*pvt->map) * pvt->width, GFP_KERNEL); + + if (!pvt->map) + goto error; + + for (i = 0; i < pvt->width; i++) { + pvt->map[i] = + kmalloc(sizeof(**pvt->map) * pvt->height, + GFP_KERNEL); + if (pvt->map[i] == NULL) { + while (i--) + kfree(pvt->map[i]); + kfree(pvt->map); + goto error; + } + } + + if (attr && attr->x <= pvt->width && attr->y <= pvt->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 = (pvt->width * 3) / 4; + pvt->div_pt.y = (pvt->height * 3) / 4; + } + + area.p1.x = width - 1; + area.p1.y = height - 1; + + mutex_lock(&(pvt->mtx)); + fill_2d_area(tcm, &area, init_tile); + mutex_unlock(&(pvt->mtx)); + return tcm; + +error: + kfree(tcm); + kfree(pvt); + return NULL; +} + +static void sita_deinit(struct tcm *tcm) +{ + struct slot init_tile = {0}; + struct sita_pvt *pvt = NULL; + struct tcm_area area = {0}; + s32 i = 0; + + pvt = (struct sita_pvt *)tcm->pvt; + if (pvt) { + area.p1.x = pvt->width - 1; + area.p1.y = pvt->height - 1; + + mutex_lock(&(pvt->mtx)); + fill_2d_area(tcm, &area, init_tile); + mutex_unlock(&(pvt->mtx)); + + mutex_destroy(&(pvt->mtx)); + + for (i = 0; i < pvt->height; i++) { + kfree(pvt->map[i]); + pvt->map[i] = NULL; + } + kfree(pvt->map); + pvt->map = NULL; + kfree(pvt); + } +} + +/** + * @description: Allocate 1d pages if the required number of pages are + * available in the container + * + * @input:num_pages to be allocated + * + * @return 0 on success, non-0 error value on failure. On success + * area contain co-ordinates of start and end Tiles(inclusive) + */ +static s32 sita_reserve_1d(struct tcm *tcm, u32 num_pages, + struct tcm_area *area) +{ + s32 ret = 0; + struct tcm_area field = {0}; + struct slot slot = {0}; + struct sita_pvt *pvt = (struct sita_pvt *)tcm->pvt; + + area->is2d = false; + + mutex_lock(&(pvt->mtx)); +#ifdef RESTRICT_1D + /* scan within predefined 1D boundary */ + assign(&field, pvt->width - 1, pvt->height - 1, 0, pvt->div_pt.y); +#else + /* Scanning entire container */ + assign(&field, pvt->width - 1, pvt->height - 1, 0, 0); +#endif + ret = scan_r2l_b2t_one_dim(tcm, num_pages, + &field, area); + /* There is not much to select, we pretty much give the first one + which accomodates */ + if (!ret) { + slot.busy = true; + slot.parent = *area; + /* inserting into tiler container */ + fill_1d_area(tcm, area, slot); + /* updating the list of allocations */ + insert_element(&pvt->res, area); + } + mutex_unlock(&(pvt->mtx)); + return ret; +} + +/** + * @description: Allocate 2d area on availability in the container + * + * @input:'w'idth and 'h'eight of the 2d area, 'align'ment specification + * + * @return 0 on success, non-0 error value on failure. On success + * area contain co-ordinates of TL corner Tile and BR corner Tile of + * the rectangle (inclusive) + */ +static s32 sita_reserve_2d(struct tcm *tcm, u16 h, u16 w, u8 align, + struct tcm_area *area) +{ + s32 ret = 0; + struct sita_pvt *pvt = (struct sita_pvt *)tcm->pvt; + /* we only support 1, 32 and 64 as alignment */ + u16 stride = align <= 1 ? 1 : align <= 32 ? 32 : 64; + struct slot slot = {0}; + + area->is2d = true; + + /* align must be 2 power */ + if (align & (align - 1) || align > 64) + return -EINVAL; + + mutex_lock(&(pvt->mtx)); + ret = scan_areas_and_find_fit(tcm, w, h, stride, area); + if (!ret) { + slot.busy = true; + slot.parent = *area; + + fill_2d_area(tcm, area, slot); + insert_element(&(pvt->res), area); + } + mutex_unlock(&(pvt->mtx)); + return ret; +} + +/** + * @description: unreserve 2d or 1D allocations if previously allocated + * + * @input:'area' specification: for 2D this should contain + * TL Corner and BR Corner of the 2D area, or for 1D allocation this should + * contain the start and end Tiles + * + * @return 0 on success, non-0 error value on failure. On success + * the to_be_removed_area is removed from g_allocation_list and the + * corresponding tiles are marked 'NOT_OCCUPIED' + * + */ +static s32 sita_free(struct tcm *tcm, struct tcm_area *area) +{ + s32 ret = 0; + struct slot slot = {0}; + struct sita_pvt *pvt = (struct sita_pvt *)tcm->pvt; + u16 is2d; + + slot.busy = false; + mutex_lock(&(pvt->mtx)); + /*First we check if the given Area is aleast valid in our list*/ + ret = rem_element_with_match(&(pvt->res), area, &is2d); + + /* If we found a positive match & removed the area details from list + * then we clear the contents of the associated tiles in the global + * container*/ + if (!ret) { + if (is2d) + fill_2d_area(tcm, area, slot); + else + fill_1d_area(tcm, area, slot); + } + mutex_unlock(&(pvt->mtx)); + return ret; +} + +/** + * @description: raster scan right to left from top to bottom; find if there is + * a free area to fit a given w x h inside the 'scan area'. If there is a free + * area, then adds to maybes candidates, which later is sent for selection + * as per pre-defined criteria. + * + * @input:'w x h' width and height of the allocation area. + * 'stride' - 64/32/None for start address alignment + * 'field' - area in which the scan operation should take place + * + * @return 0 on success, non-0 error value on failure. On success + * the 'area' area contains TL and BR corners of the allocated area + * + */ +static s32 scan_r2l_t2b(struct tcm *tcm, u16 w, u16 h, u16 stride, + struct tcm_area *field, struct tcm_area *area) +{ + s32 xx = 0, yy = 0; + s16 start_x = -1, end_x = -1, start_y = -1, end_y = -1; + s16 found_x = -1, found_y = -1; + LIST_HEAD(maybes); + struct tcm_area candidate = {0}; + struct sita_pvt *pvt = (struct sita_pvt *)tcm->pvt; + + PA(2, "scan_r2l_t2b:", field); + + 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 > INCL_LEN(start_x, end_x) || h > INCL_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, stride); /* - 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; + + P2("ali=%d x=%d..%d y=%d..%d", stride, start_x, end_x, start_y, end_y); + + /* + * Start scanning: These scans are always inclusive ones so if we are + * given a start x = 0 is a valid value so if we have a end_x = 255, + * 255th element is also checked + */ + for (yy = start_y; yy <= end_y; yy++) { + for (xx = start_x; xx >= end_x; xx -= stride) { + if (!pvt->map[xx][yy].busy) { + if (check_fit_r_and_b(tcm, w, h, xx, yy)) { + P3("found shoulder: %d,%d", xx, yy); + found_x = xx; + found_y = yy; + /* Insert this candidate, it is just a + co-ordinate, reusing Area */ + assign(&candidate, xx, yy, 0, 0); + insert_element(&maybes, &candidate); +#ifdef X_SCAN_LIMITER + /* change upper x bound */ + end_x = xx + 1; +#endif + break; + } + } else { + /* Optimization required only for Non Aligned, + Aligned anyways skip by 32/64 tiles at a time */ + if (stride == 1 && + pvt->map[xx][yy].parent.is2d) { + xx = pvt->map[xx][yy].parent.p0.x; + P3("moving to: %d,%d", xx, yy); + } + } + } + + /* if you find a free area shouldering the given scan area on + then we can break */ +#ifdef Y_SCAN_LIMITER + if (found_x == start_x) + break; +#endif + } + + if (list_empty(&maybes)) + return -ENOSPC; + + select_candidate(tcm, w, h, &maybes, field, CR_R2L_T2B, area); + /* dump_list_entries(maybes); */ + clean_list(&maybes); + return 0; +} + +#ifdef SCAN_BOTTOM_UP +/** + * @description: raster scan right to left from bottom to top; find if there is + * a free area to fit a given w x h inside the 'scan area'. If there is a free + * area, then adds to maybes candidates, which later is sent for selection + * as per pre-defined criteria. + * + * @input:'w x h' width and height of the allocation area. + * 'stride' - 64/32/None for start address alignment + * 'field' - area in which the scan operation should take place + * + * @return 0 on success, non-0 error value on failure. On success + * the 'area' area contains TL and BR corners of the allocated area + * + */ +static s32 scan_r2l_b2t(struct tcm *tcm, u16 w, u16 h, u16 stride, + struct tcm_area *field, struct tcm_area *area) +{ + /* TODO: Should I check scan area? + * Might have to take it as input during initialization + */ + s32 xx = 0, yy = 0; + s16 start_x = -1, end_x = -1, start_y = -1, end_y = -1; + s16 found_x = -1, found_y = -1; + LIST_HEAD(maybes); + struct tcm_area candidate = {0}; + struct sita_pvt *pvt = (struct sita_pvt *)tcm->pvt; + + PA(2, "scan_r2l_b2t:", field); + + 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 > INCL_LEN(start_x, end_x) || h > INCL_LEN(start_y, end_y)) + return -ENOSPC; + + /* adjust start_x and start_y, as allocation would not fit beyond */ + start_x = ALIGN_DOWN(start_x - w + 1, stride); /* + 1 to be inclusive */ + start_y = start_y - h + 1; + + /* check if allocation would still fit in scan area */ + if (start_x < end_x) + return -ENOSPC; + + P2("ali=%d x=%d..%d y=%d..%d", stride, start_x, end_x, start_y, end_y); + + /* + * Start scanning: These scans are always inclusive ones so if we are + * given a start x = 0 is a valid value so if we have a end_x = 255, + * 255th element is also checked + */ + for (yy = start_y; yy >= end_y; yy--) { + for (xx = start_x; xx >= end_x; xx -= stride) { + if (!pvt->map[xx][yy].busy) { + if (check_fit_r_and_b(tcm, w, h, xx, yy)) { + P3("found shoulder: %d,%d", xx, yy); + found_x = xx; + found_y = yy; + /* Insert this candidate, it is just a + co-ordinate, reusing Area */ + assign(&candidate, xx, yy, 0, 0); + insert_element(&maybes, &candidate); +#ifdef X_SCAN_LIMITER + /* change upper x bound */ + end_x = xx + 1; +#endif + break; + } + } else { + /* Optimization required only for Non Aligned, + Aligned anyways skip by 32/64 tiles at a time */ + if (stride == 1 && + pvt->map[xx][yy].parent.is2d) { + xx = pvt->map[xx][yy].parent.p0.x; + P3("moving to: %d,%d", xx, yy); + } + } + + } + + /* if you find a free area shouldering the given scan area on + then we can break */ +#ifdef Y_SCAN_LIMITER + if (found_x == start_x) + break; +#endif + } + + if (list_empty(&maybes)) + return -ENOSPC; + + select_candidate(tcm, w, h, &maybes, field, CR_R2L_B2T, area); + /* dump_list_entries(maybes); */ + clean_list(&maybes); + return 0; +} +#endif + +/** + * @description: raster scan left to right from top to bottom; find if there is + * a free area to fit a given w x h inside the 'scan area'. If there is a free + * area, then adds to maybes candidates, which later is sent for selection + * as per pre-defined criteria. + * + * @input:'w x h' width and height of the allocation area. + * 'stride' - 64/32/None for start address alignment + * 'field' - area in which the scan operation should take place + * + * @return 0 on success, non-0 error value on failure. On success + * the 'area' area contains TL and BR corners of the allocated area + * + */ +s32 scan_l2r_t2b(struct tcm *tcm, u16 w, u16 h, u16 stride, + struct tcm_area *field, struct tcm_area *area) +{ + s32 xx = 0, yy = 0; + s16 start_x = -1, end_x = -1, start_y = -1, end_y = -1; + s16 found_x = -1, found_y = -1; + LIST_HEAD(maybes); + struct tcm_area candidate = {0}; + struct sita_pvt *pvt = (struct sita_pvt *)tcm->pvt; + + PA(2, "scan_l2r_t2b:", field); + + 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 > INCL_LEN(end_x, start_x) || h > INCL_LEN(end_y, start_y)) + return -ENOSPC; + + start_x = ALIGN(start_x, stride); + + /* check if allocation would still fit in scan area */ + if (w > INCL_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; + + P2("ali=%d x=%d..%d y=%d..%d", stride, start_x, end_x, start_y, end_y); + + /* + * Start scanning: These scans are always inclusive ones so if we are + * given a start x = 0 is a valid value so if we have a end_x = 255, + * 255th element is also checked + */ + for (yy = start_y; yy <= end_y; yy++) { + for (xx = start_x; xx <= end_x; xx += stride) { + /* if NOT occupied */ + if (!pvt->map[xx][yy].busy) { + if (check_fit_r_and_b(tcm, w, h, xx, yy)) { + P3("found shoulder: %d,%d", xx, yy); + found_x = xx; + found_y = yy; + /* Insert this candidate, it is just a + co-ordinate, reusing Area */ + assign(&candidate, xx, yy, 0, 0); + insert_element(&maybes, &candidate); +#ifdef X_SCAN_LIMITER + /* change upper x bound */ + end_x = xx - 1; +#endif + break; + } + } else { + /* Optimization required only for Non Aligned, + Aligned anyways skip by 32/64 tiles at a time */ + if (stride == 1 && + pvt->map[xx][yy].parent.is2d) { + xx = pvt->map[xx][yy].parent.p1.x; + P3("moving to: %d,%d", xx, yy); + } + } + } + /* if you find a free area shouldering the given scan area on + then we can break */ +#ifdef Y_SCAN_LIMITER + if (found_x == start_x) + break; +#endif + } + + if (list_empty(&maybes)) + return -ENOSPC; + + select_candidate(tcm, w, h, &maybes, field, CR_L2R_T2B, area); + /* dump_list_entries(maybes); */ + clean_list(&maybes); + return 0; +} + +#ifdef SCAN_BOTTOM_UP +/** + * @description: raster scan left to right from bottom to top; find if there is + * a free area to fit a given w x h inside the 'scan area'. If there is a free + * area, then adds to maybes candidates, which later is sent for selection + * as per pre-defined criteria. + * + * @input:'w x h' width and height of the allocation area. + * 'stride' - 64/32/None for start address alignment + * 'field' - area in which the scan operation should take place + * + * @return 0 on success, non-0 error value on failure. On success + * the 'area' area contains TL and BR corners of the allocated area + * + */ +static s32 scan_l2r_b2t(struct tcm *tcm, u16 w, u16 h, u16 stride, + struct tcm_area *field, struct tcm_area *area) +{ + s32 xx = 0, yy = 0; + s16 start_x = -1, end_x = -1, start_y = -1, end_y = -1; + s16 found_x = -1, found_y = -1; + LIST_HEAD(maybes); + struct tcm_area candidate = {0}; + struct sita_pvt *pvt = (struct sita_pvt *)tcm->pvt; + + PA(2, "scan_l2r_b2t:", field); + + 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->p0.y < field->p1.y) + return -EINVAL; + + /* check if allocation would fit in scan area */ + if (w > INCL_LEN(end_x, start_x) || h > INCL_LEN(start_y, end_y)) + return -ENOSPC; + + start_x = ALIGN(start_x, stride); + + /* check if allocation would still fit in scan area */ + if (w > INCL_LEN(end_x, start_x)) + return -ENOSPC; + + /* adjust end_x and start_y, as allocation would not fit beyond */ + end_x = end_x - w + 1; /* + 1 to be inclusive */ + start_y = start_y - h + 1; + + P2("ali=%d x=%d..%d y=%d..%d", stride, start_x, end_x, start_y, end_y); + + /* + * Start scanning: These scans are always inclusive ones so if we are + * given a start x = 0 is a valid value so if we have a end_x = 255, + * 255th element is also checked + */ + for (yy = start_y; yy >= end_y; yy--) { + for (xx = start_x; xx <= end_x; xx += stride) { + /* if NOT occupied */ + if (!pvt->map[xx][yy].busy) { + if (check_fit_r_and_b(tcm, w, h, xx, yy)) { + P3("found shoulder: %d,%d", xx, yy); + found_x = xx; + found_y = yy; + /* Insert this candidate, it is just a + co-ordinate, reusing Area */ + assign(&candidate, xx, yy, 0, 0); + insert_element(&maybes, &candidate); +#ifdef X_SCAN_LIMITER + /* change upper x bound */ + end_x = xx - 1; +#endif + break; + } + } else { + /* Optimization required only for Non Aligned, + Aligned anyways skip by 32/64 tiles at a time */ + if (stride == 1 && + pvt->map[xx][yy].parent.is2d) { + xx = pvt->map[xx][yy].parent.p1.x; + P3("moving to: %d,%d", xx, yy); + } + } + } + + /* if you find a free area shouldering the given scan area on + then we can break */ +#ifdef Y_SCAN_LIMITER + if (found_x == start_x) + break; +#endif + } + + if (list_empty(&maybes)) + return -ENOSPC; + + select_candidate(tcm, w, h, &maybes, field, CR_L2R_B2T, area); + /* dump_list_entries(maybes); */ + clean_list(&maybes); + return 0; +} +#endif +/* +Note: In General the cordinates specified in the scan area area relevant to the +scan sweep directions. i.e A scan Area from Top Left Corner will have +p0.x <= p1.x and p0.y <= p1.y. Where as A scan Area from bottom Right Corner +will have p1.x <= p0.x and p1.y <= p0.y +*/ + +/** + * @description: raster scan right to left from bottom to top; find if there are + * continuous free pages(one slot is one page, continuity always from left to + * right) inside the 'scan area'. If there are enough continous free pages, + * then it returns the start and end Tile/page co-ordinates inside 'area' + * + * @input:'num_pages' required, + * 'field' - area in which the scan operation should take place + * + * @return 0 on success, non-0 error value on failure. On success + * the 'area' area contains start and end slot (inclusive). + * + */ +static s32 scan_r2l_b2t_one_dim(struct tcm *tcm, u32 num_pages, + struct tcm_area *field, struct tcm_area *area) +{ + s32 fit = false; + u16 x, y; + u16 left_x, left_y, busy_x, busy_y; + s32 ret = 0; + struct sita_pvt *pvt = (struct sita_pvt *)tcm->pvt; + + /* check scan area co-ordinates */ + if (field->p0.y < field->p1.y) + return -EINVAL; + + PA(2, "scan_r2l_b2t_one_dim:", field); + + /* Note: Checking sanctity of scan area + * The reason for checking this that 1D allocations assume that the X + ranges the entire TilerSpace X ie ALL Columns + * The scan area can limit only the Y ie, Num of Rows for 1D allocation. + We also expect we could have only 1 row for 1D allocation + * i.e our field p0.y and p1.y may have a same value. + */ + + /* only support full width 1d scan area */ + if (pvt->width != field->p0.x - field->p1.x + 1) + return -EINVAL; + + /* check if allocation would fit in scan area */ + if (num_pages > pvt->width * INCL_LEN(field->p0.y, field->p1.y)) + return -ENOSPC; + + left_x = field->p0.x; + left_y = field->p0.y; + while (!ret) { + x = left_x; + y = left_y; + + if (!pvt->map[x][y].busy) { + ret = move_left(tcm, x, y, num_pages - 1, + &left_x, &left_y); + if (ret) + break; /* out of space */ + + P3("moved left %d slots: %d,%d", num_pages - 1, + left_x, left_y); + fit = check_fit_r_one_dim(tcm, left_x, left_y, + num_pages, &busy_x, &busy_y); + if (fit) { + assign(area, left_x, left_y, + busy_x, busy_y); + break; + } else { + /* no fit, continue at the busy slot */ + x = busy_x; + y = busy_y; + } + } + + /* now the tile is occupied, skip busy region */ + if (pvt->map[x][y].parent.is2d) { + busy_x = pvt->map[x][y].parent.p0.x; + busy_y = y; + } else { + busy_x = pvt->map[x][y].parent.p0.x; + busy_y = pvt->map[x][y].parent.p0.y; + } + x = busy_x; + y = busy_y; + + P3("moving left from: %d,%d", x, y); + ret = move_left(tcm, x, y, 1, &left_x, &left_y); + } + + return fit ? 0 : -ENOSPC; +} + +/** + * @description: + * + * + * + * + * @input: + * + * + * @return 0 on success, non-0 error value on failure. On success + */ +static s32 scan_areas_and_find_fit(struct tcm *tcm, u16 w, u16 h, u16 stride, + struct tcm_area *area) +{ + s32 ret = 0; + struct tcm_area field = {0}; + u16 boundary_x = 0, boundary_y = 0; + struct sita_pvt *pvt = (struct sita_pvt *)tcm->pvt; + s32 need_scan = 2; + + if (stride > 1) { + boundary_x = pvt->div_pt.x - 1; + boundary_y = pvt->div_pt.y - 1; + + /* more intelligence here */ + if (w > pvt->div_pt.x) { + boundary_x = pvt->width - 1; + need_scan--; + } + if (h > pvt->div_pt.y) { + boundary_y = pvt->height - 1; + need_scan--; + } + + assign(&field, 0, 0, boundary_x, boundary_y); + ret = scan_l2r_t2b(tcm, w, h, stride, &field, area); + if (ret != 0 && need_scan) { + /* scan the entire container if nothing found */ + assign(&field, 0, 0, pvt->width - 1, pvt->height - 1); + ret = scan_l2r_t2b(tcm, w, h, stride, &field, area); + } + } else if (stride == 1) { + boundary_x = pvt->div_pt.x; + boundary_y = pvt->div_pt.y - 1; + + /* more intelligence here */ + if (w > (pvt->width - pvt->div_pt.x)) { + boundary_x = 0; + need_scan--; + } + if (h > pvt->div_pt.y) { + boundary_y = pvt->height - 1; + need_scan--; + } + + assign(&field, pvt->width - 1, 0, boundary_x, boundary_y); + ret = scan_r2l_t2b(tcm, w, h, stride, &field, area); + + if (ret != 0 && need_scan) { + /* scan the entire container if nothing found */ + assign(&field, pvt->width - 1, 0, 0, + pvt->height - 1); + ret = scan_r2l_t2b(tcm, w, h, stride, &field, + area); + } + } + + /* 3/30/2010: moved aligned to left, and unaligned to right side. */ +#if 0 + else if (stride == 1) { + /* use 64-align area so we don't grow down and shrink 1D area */ + if (h > pvt->div_pt.y) { + need_scan -= 2; + assign(&field, 0, 0, pvt->width - 1, pvt->height - 1); + ret = scan_l2r_t2b(tcm, w, h, stride, &field, area); + } else { + assign(&field, 0, pvt->div_pt.y - 1, pvt->width - 1, 0); + /* scan up in 64 and 32 areas accross whole width */ + ret = scan_l2r_b2t(tcm, w, h, stride, &field, area); + } + + if (ret != 0 && need_scan) { + assign(&field, 0, 0, pvt->width - 1, pvt->height - 1); + ret = scan_l2r_t2b(tcm, w, h, stride, &field, area); + } + } +#endif + return ret; +} + +static s32 check_fit_r_and_b(struct tcm *tcm, u16 w, u16 h, u16 left_x, + u16 top_y) +{ + u16 xx = 0, yy = 0; + struct sita_pvt *pvt = (struct sita_pvt *)tcm->pvt; + + for (yy = top_y; yy < top_y + h; yy++) { + for (xx = left_x; xx < left_x + w; xx++) { + if (pvt->map[xx][yy].busy) + return false; + } + } + return true; +} + +static s32 check_fit_r_one_dim(struct tcm *tcm, u16 x, u16 y, u32 num_pages, + u16 *busy_x, u16 *busy_y) +{ + s32 ret = 0; + struct sita_pvt *pvt = (struct sita_pvt *)tcm->pvt; + s32 i = 0; + *busy_x = x; + *busy_y = y; + + P2("checking fit for %d pages from %d,%d", num_pages, x, y); + while (i < num_pages) { + if (pvt->map[x][y].busy) { + /* go to the start of the blocking allocation + to avoid unecessary checking */ + if (pvt->map[x][y].parent.is2d) { + *busy_x = pvt->map[x][y].parent.p0.x; + *busy_y = y; + } else { + *busy_x = pvt->map[x][y].parent.p0.x; + *busy_y = pvt->map[x][y].parent.p0.y; + } + /* TODO: Could also move left in case of 2D */ + P2("after busy slot at: %d,%d", *busy_x, *busy_y); + return false; + } + + i++; + + /* break here so busy_x, busy_y will be correct */ + if (i == num_pages) + break; + + ret = move_right(tcm, x, y, 1, busy_x, busy_y); + if (ret) + return false; + + x = *busy_x; + y = *busy_y; + } + + return true; +} + +static void fill_2d_area(struct tcm *tcm, struct tcm_area *area, + struct slot slot) +{ + s32 x, y; + struct sita_pvt *pvt = (struct sita_pvt *)tcm->pvt; + + PA(2, "fill 2d area", area); + for (x = area->p0.x; x <= area->p1.x; ++x) + for (y = area->p0.y; y <= area->p1.y; ++y) + pvt->map[x][y] = slot; +} + +/* area should be a valid area */ +static void fill_1d_area(struct tcm *tcm, struct tcm_area *area, + struct slot slot) +{ + u16 x = 0, y = 0; + struct sita_pvt *pvt = (struct sita_pvt *)tcm->pvt; + + PA(2, "fill 1d area", area); + x = area->p0.x; + y = area->p0.y; + + while (!(x == area->p1.x && y == area->p1.y)) { + pvt->map[x++][y] = slot; + if (x == pvt->width) { + x = 0; + y++; + } + } + /* set the last slot */ + pvt->map[x][y] = slot; +} + +static void select_candidate(struct tcm *tcm, u16 w, u16 h, + struct list_head *maybes, + struct tcm_area *field, s32 criteria, + struct tcm_area *area) +{ + /* bookkeeping the best match and the one evaluated */ + struct area_spec *best = NULL; + struct nearness_factor best_factor = {0}; + struct neighbour_stats best_stats = {0}; + u16 win_neighs = 0; + + /* bookkeeping the current one being evaluated */ + struct area_spec *elem = NULL; + struct nearness_factor factor = {0}; + struct neighbour_stats stats = {0}; + u16 neighs = 0; + + bool better; /* whether current is better */ + + /* we default to the 1st candidate */ + best = list_first_entry(maybes, struct area_spec, list); + + /*i f there is only one candidate then that is the selection*/ + + /* If first found is enabled then we just provide bluntly the first + found candidate + * NOTE: For Horizontal bias we just give the first found, because our + * scan is Horizontal raster based and the first candidate will always + * be the same as if selecting the Horizontal one. + */ + if (list_is_singular(maybes) || + criteria & CR_FIRST_FOUND || criteria & CR_BIAS_HORIZONTAL) + /* Note: Sure we could have done this in the previous function, + but just wanted this to be cleaner so having + * one place where the selection is made. Here I am returning + the first one + */ + goto done; + + /* lets calculate for the first candidate and assign him the best and + replace with the one who has better credentials w/ to the criteria */ + + get_busy_neigh_stats(tcm, w, h, &best->area, &best_stats); + win_neighs = BOUNDARY(&best_stats) + + OCCUPIED(&best_stats); + get_nearness_factor(field, &best->area, &best_factor); + + list_for_each_entry(elem, maybes->next, list) { + better = false; + + /* calculate required statistics */ + get_busy_neigh_stats(tcm, w, h, &elem->area, &stats); + get_nearness_factor(field, &elem->area, &factor); + neighs = BOUNDARY(&stats) + OCCUPIED(&stats); + + /* see if this are is better than the best so far */ + + /* neighbor check */ + if ((criteria & CR_MAX_NEIGHS) && + neighs > win_neighs) + better = true; + + /* vertical bias check */ + if ((criteria & CR_BIAS_VERTICAL) && + /* + * NOTE: not checking if lengths are same, because that does not + * find new shoulders on the same row after a fit + */ + INCL_LEN_MOD(elem->area.p0.y, field->p0.y) > + INCL_LEN_MOD(best->area.p0.y, field->p0.y)) + better = true; + + /* diagonal balance check */ + if ((criteria & CR_DIAGONAL_BALANCE) && + win_neighs <= neighs && + (win_neighs < neighs || + /* this implies that neighs and occupied match */ + OCCUPIED(&best_stats) < OCCUPIED(&stats) || + (OCCUPIED(&best_stats) == OCCUPIED(&stats) && + /* check the nearness factor */ + best_factor.x + best_factor.y > factor.x + factor.y))) + better = true; + + if (better) { + best = elem; + best_factor = factor; + best_stats = stats; + win_neighs = neighs; + } + } + +done: + assign(area, best->area.p0.x, best->area.p0.y, + best->area.p0.x + w - 1, best->area.p0.y + h - 1); +} + +/* get the nearness factor of an area in a search field */ +static void get_nearness_factor(struct tcm_area *field, + struct tcm_area *area, struct nearness_factor *nf) +{ + /* For the following calculation we need worry of +/- sign, the + relative distances take of this. Multiplied by 1000, there + is no floating point arithmetic used in kernel */ + + 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); +} + +/* Neighbours + * + * |<-----T------>| + * _ _______________ _ + * L | Ar | R + * _ |______________|_ + * |<-----B------>| + */ +static s32 get_busy_neigh_stats(struct tcm *tcm, u16 width, u16 height, + struct tcm_area *top_left_corner, + struct neighbour_stats *neighbour_stat) +{ + s16 xx = 0, yy = 0; + struct tcm_area left_edge; + struct tcm_area right_edge; + struct tcm_area top_edge; + struct tcm_area bottom_edge; + struct sita_pvt *pvt = (struct sita_pvt *)tcm->pvt; + + if (neighbour_stat == NULL) + return -EINVAL; + + if (width == 0 || height == 0) + return -EINVAL; + + /* Clearing any exisiting values */ + memset(neighbour_stat, 0, sizeof(*neighbour_stat)); + + /* Finding Top Edge */ + assign(&top_edge, top_left_corner->p0.x, top_left_corner->p0.y, + top_left_corner->p0.x + width - 1, top_left_corner->p0.y); + + /* Finding Bottom Edge */ + assign(&bottom_edge, top_left_corner->p0.x, + top_left_corner->p0.y+height - 1, + top_left_corner->p0.x + width - 1, + top_left_corner->p0.y + height - 1); + + /* Finding Left Edge */ + assign(&left_edge, top_left_corner->p0.x, top_left_corner->p0.y, + top_left_corner->p0.x, top_left_corner->p0.y + height - 1); + + /* Finding Right Edge */ + assign(&right_edge, top_left_corner->p0.x + width - 1, + top_left_corner->p0.y, + top_left_corner->p0.x + width - 1, + top_left_corner->p0.y + height - 1); + + /* dump_area(&top_edge); + dump_area(&right_edge); + dump_area(&bottom_edge); + dump_area(&left_edge); + */ + + /* Parsing through top & bottom edge */ + for (xx = top_edge.p0.x; xx <= top_edge.p1.x; xx++) { + if (top_edge.p0.y - 1 < 0) + neighbour_stat->top_boundary++; + else if (pvt->map[xx][top_edge.p0.y - 1].busy) + neighbour_stat->top_occupied++; + + if (bottom_edge.p0.y + 1 > pvt->height - 1) + neighbour_stat->bottom_boundary++; + else if (pvt->map[xx][bottom_edge.p0.y+1].busy) + neighbour_stat->bottom_occupied++; + } + + /* Parsing throught left and right edge */ + for (yy = left_edge.p0.y; yy <= left_edge.p1.y; ++yy) { + if (left_edge.p0.x - 1 < 0) + neighbour_stat->left_boundary++; + else if (pvt->map[left_edge.p0.x - 1][yy].busy) + neighbour_stat->left_occupied++; + + if (right_edge.p0.x + 1 > pvt->width - 1) + neighbour_stat->right_boundary++; + else if (pvt->map[right_edge.p0.x + 1][yy].busy) + neighbour_stat->right_occupied++; + + } + + return 0; +} + +/** + @description: Retrieves the parent area of the page at p0.x, p0.y if + occupied + @input:co-ordinates of the page (p0.x, p0.y) whoes parent area + is required + @return 0 on success, non-0 error value on failure. On success + + parent will contain co-ordinates (TL & BR corner) of the parent + area +*/ +static s32 sita_get_parent(struct tcm *tcm, struct tcm_pt *pt, + struct tcm_area *parent) +{ + struct sita_pvt *pvt = (struct sita_pvt *)tcm->pvt; + s32 res = 0; + + mutex_lock(&(pvt->mtx)); + + if (pvt->map[pt->x][pt->y].busy) { + *parent = pvt->map[pt->x][pt->y].parent; + } else { + memset(parent, 0, sizeof(*parent)); + res = -ENOENT; + } + + mutex_unlock(&(pvt->mtx)); + + return res; +} + +static s32 move_left(struct tcm *tcm, u16 x, u16 y, u32 num_pages, + u16 *xx, u16 *yy) +{ + struct sita_pvt *pvt = (struct sita_pvt *)tcm->pvt; + u32 pos = x + pvt->width * y; + + if (pos < num_pages) + return -ENOSPC; + + pos -= num_pages; + *xx = pos % pvt->width; + *yy = pos / pvt->width; + return 0; +} + +static s32 move_right(struct tcm *tcm, u16 x, u16 y, u32 num_pages, + u16 *xx, u16 *yy) +{ + struct sita_pvt *pvt = (struct sita_pvt *)tcm->pvt; + u32 pos = x + pvt->width * y; + + if (num_pages > pvt->width * pvt->height - pos) + return -ENOSPC; + + pos += num_pages; + *xx = pos % pvt->width; + *yy = pos / pvt->width; + return 0; +} + diff --git a/drivers/media/video/tiler/tcm/tcm_sita.h b/drivers/media/video/tiler/tcm/tcm_sita.h new file mode 100644 index 000000000000..fb5f8e89192c --- /dev/null +++ b/drivers/media/video/tiler/tcm/tcm_sita.h @@ -0,0 +1,39 @@ +/* + * tcm_sita.h + * + * SImple Tiler Allocator (SiTA) interface. + * + * Author: Ravi Ramachandra <r.ramachandra@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. + */ + +#ifndef TCM_SITA_H_ +#define TCM_SITA_H_ + +#include "tcm.h" + +/** + * Create a SiTA tiler container manager. + * + * @param width Container width + * @param height Container height + * @param attr preferred division point between 64-aligned + * allocation (top left), 32-aligned allocations + * (top right), and page mode allocations (bottom) + * + * @return TCM instance + */ +struct tcm *sita_init(u16 width, u16 height, struct tcm_pt *attr); + +TCM_INIT(sita_init, struct tcm_pt); + +#endif /* TCM_SITA_H_ */ diff --git a/drivers/media/video/tiler/tcm/tcm_utils.h b/drivers/media/video/tiler/tcm/tcm_utils.h new file mode 100644 index 000000000000..7d0ed5c149b8 --- /dev/null +++ b/drivers/media/video/tiler/tcm/tcm_utils.h @@ -0,0 +1,59 @@ +/* + * tcm_utils.h + * + * Utility functions for implementing TILER container managers. + * + * 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. + */ + +#ifndef _TCM_UTILS_H +#define _TCM_UTILS_H + +#include "tcm.h" + +#define AREA_FMT "(%03d %03d)-(%03d %03d)" +#define AREA(area) (area).p0.x, (area).p0.y, (area).p1.x, (area).p1.y + +/* TCM_ALG_NAME must be defined to use the debug methods */ + +#ifdef DEBUG +#define IFDEBUG(x) x +#else +#define IFDEBUG(x) do { if (0) x; } while (0) +#endif + +#define P(level, fmt, ...) \ + IFDEBUG(printk(level TCM_ALG_NAME ":%d:%s()" fmt "\n", \ + __LINE__, __func__, ##__VA_ARGS__)) + +#define P1(fmt, ...) P(KERN_NOTICE, fmt, ##__VA_ARGS__) +#define P2(fmt, ...) P(KERN_INFO, fmt, ##__VA_ARGS__) +#define P3(fmt, ...) P(KERN_DEBUG, fmt, ##__VA_ARGS__) + +#define PA(level, msg, p_area) P##level(msg " " AREA_FMT "\n", AREA(*(p_area))) + +/* 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; +} + +static inline +void dump_area(struct tcm_area *area) +{ + printk(KERN_NOTICE AREA_FMT "\n", AREA(*area)); +} + +#endif diff --git a/drivers/media/video/tiler/tiler.c b/drivers/media/video/tiler/tiler.c new file mode 100644 index 000000000000..0d4e572a3400 --- /dev/null +++ b/drivers/media/video/tiler/tiler.c @@ -0,0 +1,1583 @@ +/* + * tiler.c + * + * TILER driver support functions for TI OMAP processors. + * + * 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/init.h> +#include <linux/module.h> +#include <linux/cdev.h> /* struct cdev */ +#include <linux/kdev_t.h> /* MKDEV() */ +#include <linux/fs.h> /* register_chrdev_region() */ +#include <linux/device.h> /* struct class */ +#include <linux/platform_device.h> /* platform_device() */ +#include <linux/err.h> /* IS_ERR() */ +#include <linux/uaccess.h> /* copy_to_user */ +#include <linux/mm.h> +#include <linux/mm_types.h> +#include <linux/sched.h> +#include <linux/errno.h> +#include <linux/mutex.h> +#include <linux/dma-mapping.h> +#include <linux/pagemap.h> /* page_cache_release() */ +#include <linux/slab.h> + +#include <mach/tiler.h> +#include <mach/dmm.h> +#include "../dmm/tmm.h" +#include "tiler_def.h" +#include "tcm/tcm_sita.h" /* Algo Specific header */ + +#include <linux/syscalls.h> + +struct tiler_dev { + struct cdev cdev; +}; + +struct platform_driver tiler_driver_ldm = { + .driver = { + .owner = THIS_MODULE, + .name = "tiler", + }, + .probe = NULL, + .shutdown = NULL, + .remove = NULL, +}; + +/* per process (thread group) info */ +struct process_info { + struct list_head list; /* other processes */ + struct list_head groups; /* my groups */ + struct list_head bufs; /* my registered buffers */ + pid_t pid; /* really: thread group ID */ + u32 refs; /* open tiler devices, 0 for processes + tracked via kernel APIs */ + bool kernel; /* tracking kernel objects */ +}; + +/* per group info (within a process) */ +struct gid_info { + struct list_head by_pid; /* other groups */ + struct list_head areas; /* all areas in this pid/gid */ + struct list_head reserved; /* areas pre-reserved */ + struct list_head onedim; /* all 1D areas in this pid/gid */ + u32 gid; /* group ID */ + struct process_info *pi; /* parent */ +}; + +struct list_head blocks; +struct list_head procs; +struct list_head orphan_areas; +struct list_head orphan_onedim; + +struct area_info { + struct list_head by_gid; /* areas in this pid/gid */ + struct list_head blocks; /* blocks in this area */ + u32 nblocks; /* # of blocks in this area */ + + struct tcm_area area; /* area details */ + struct gid_info *gi; /* link to parent, if still alive */ +}; + +struct mem_info { + struct list_head global; /* reserved / global blocks */ + u32 sys_addr; /* system space (L3) tiler addr */ + u32 num_pg; /* number of pages in page-list */ + u32 usr; /* user space address */ + u32 *pg_ptr; /* list of mapped struct page pointers */ + struct tcm_area area; + u32 *mem; /* list of alloced phys addresses */ + u32 refs; /* number of times referenced */ + bool alloced; /* still alloced */ + + struct list_head by_area; /* blocks in the same area / 1D */ + void *parent; /* area info for 2D, else group info */ +}; + +struct __buf_info { + struct list_head by_pid; /* list of buffers per pid */ + struct tiler_buf_info buf_info; + struct mem_info *mi[TILER_MAX_NUM_BLOCKS]; /* blocks */ +}; + +#define TILER_FORMATS 4 + +static s32 tiler_major; +static s32 tiler_minor; +static struct tiler_dev *tiler_device; +static struct class *tilerdev_class; +static u32 id; +static struct mutex mtx; +static struct tcm *tcm[TILER_FORMATS]; +static struct tmm *tmm[TILER_FORMATS]; +static u32 *dmac_va; +static dma_addr_t dmac_pa; + +#define TCM(fmt) tcm[(fmt) - TILFMT_8BIT] +#define TCM_SS(ssptr) TCM(TILER_GET_ACC_MODE(ssptr)) +#define TCM_SET(fmt, i) tcm[(fmt) - TILFMT_8BIT] = i +#define TMM(fmt) tmm[(fmt) - TILFMT_8BIT] +#define TMM_SS(ssptr) TMM(TILER_GET_ACC_MODE(ssptr)) +#define TMM_SET(fmt, i) tmm[(fmt) - TILFMT_8BIT] = i + +/* get process info, and increment refs for device tracking */ +static struct process_info *__get_pi(pid_t pid, bool kernel) +{ + struct process_info *pi; + + /* find process context */ + mutex_lock(&mtx); + list_for_each_entry(pi, &procs, list) { + if (pi->pid == pid && pi->kernel == kernel) + goto done; + } + + /* create process context */ + pi = kmalloc(sizeof(*pi), GFP_KERNEL); + if (!pi) + goto done; + + memset(pi, 0, sizeof(*pi)); + pi->pid = pid; + pi->kernel = kernel; + INIT_LIST_HEAD(&pi->groups); + INIT_LIST_HEAD(&pi->bufs); + list_add(&pi->list, &procs); +done: + if (pi && !kernel) + pi->refs++; + mutex_unlock(&mtx); + return pi; +} + +/* allocate an reserved area of size, alignment and link it to gi */ +static struct area_info *area_new(u16 width, u16 height, u16 align, + struct tcm *tcm, struct gid_info *gi) +{ + struct area_info *ai = kmalloc(sizeof(*ai), GFP_KERNEL); + if (!ai) + return NULL; + + /* set up empty area info */ + memset(ai, 0x0, sizeof(*ai)); + INIT_LIST_HEAD(&ai->blocks); + + /* reserve an allocation area */ + if (tcm_reserve_2d(tcm, width, height, align, &ai->area)) { + kfree(ai); + return NULL; + } + + ai->gi = gi; + mutex_lock(&mtx); + list_add_tail(&ai->by_gid, &gi->areas); + mutex_unlock(&mtx); + return ai; +} + +/* (must have mutex) free an area and return NULL */ +static inline void _m_area_free(struct area_info *ai) +{ + if (ai) { + list_del(&ai->by_gid); + kfree(ai); + } +} + +static s32 __analize_area(enum tiler_fmt fmt, u32 width, u32 height, + u16 *x_area, u16 *y_area, u16 *band, + u16 *align, u16 *offs) +{ + /* input: width, height is in pixels, align, offs in bytes */ + /* output: x_area, y_area, band, align, offs in slots */ + + /* slot width, height, and row size */ + u32 slot_w, slot_h, slot_row, bpp; + + /* align must be 2 power */ + if (*align & (*align - 1)) + return -1; + + switch (fmt) { + case TILFMT_8BIT: + slot_w = DMM_PAGE_DIMM_X_MODE_8; + slot_h = DMM_PAGE_DIMM_Y_MODE_8; + break; + case TILFMT_16BIT: + slot_w = DMM_PAGE_DIMM_X_MODE_16; + slot_h = DMM_PAGE_DIMM_Y_MODE_16; + break; + case TILFMT_32BIT: + slot_w = DMM_PAGE_DIMM_X_MODE_32; + slot_h = DMM_PAGE_DIMM_Y_MODE_32; + break; + case TILFMT_PAGE: + /* adjust size to accomodate offset, only do page alignment */ + *align = PAGE_SIZE; + width += *offs & (PAGE_SIZE - 1); + + /* for 1D area keep the height (1), width is in tiler slots */ + *x_area = DIV_ROUND_UP(width, TILER_PAGE); + *y_area = *band = 1; + + if (*x_area * *y_area > TILER_WIDTH * TILER_HEIGHT) + return -1; + return 0; + default: + return -EINVAL; + } + + /* get the # of bytes per row in 1 slot */ + bpp = tilfmt_bpp(fmt); + slot_row = slot_w * bpp; + + /* how many slots are can be accessed via one physical page */ + *band = PAGE_SIZE / slot_row; + + /* minimum alignment is 1 slot, default alignment is page size */ + *align = ALIGN(*align ? : PAGE_SIZE, slot_row); + + /* offset must be multiple of bpp */ + if (*offs & (bpp - 1)) + return -EINVAL; + + /* round down the offset to the nearest slot size, and increase width + to allow space for having the correct offset */ + width += (*offs & (*align - 1)) / bpp; + *offs &= ~(*align - 1); + + /* adjust to slots */ + *x_area = DIV_ROUND_UP(width, slot_w); + *y_area = DIV_ROUND_UP(height, slot_h); + *align /= slot_row; + *offs /= slot_row; + + if (*x_area > TILER_WIDTH || *y_area > TILER_HEIGHT) + return -1; + return 0x0; +} + +/** + * Find a place where a 2D block would fit into a 2D area of the + * same height. + * + * @author a0194118 (3/19/2010) + * + * @param w Width of the block. + * @param align Alignment of the block. + * @param offs Offset of the block (within alignment) + * @param ai Pointer to area info + * @param next Pointer to the variable where the next block + * will be stored. The block should be inserted + * before this block. + * + * @return the end coordinate (x1 + 1) where a block would fit, + * or 0 if it does not fit. + * + * (must have mutex) + */ +static u16 _m_blk_find_fit(u16 w, u16 align, u16 offs, + struct area_info *ai, struct list_head **before) +{ + int x = ai->area.p0.x + w + offs; + struct mem_info *mi; + + /* area blocks are sorted by x */ + list_for_each_entry(mi, &ai->blocks, by_area) { + /* check if buffer would fit before this area */ + if (x <= mi->area.p0.x) { + *before = &mi->by_area; + return x; + } + x = ALIGN(mi->area.p1.x + 1 - offs, align) + w + offs; + } + *before = &ai->blocks; + + /* check if buffer would fit after last area */ + return (x <= ai->area.p1.x + 1) ? x : 0; +} + +/* (must have mutex) adds a block to an area with certain x coordinates */ +static inline +struct mem_info *_m_add2area(struct mem_info *mi, struct area_info *ai, + u16 x0, u16 x1, struct list_head *before) +{ + mi->parent = ai; + mi->area = ai->area; + mi->area.p0.x = x0; + mi->area.p1.x = x1; + list_add_tail(&mi->by_area, before); + ai->nblocks++; + return mi; +} + +static struct mem_info *get_2d_area(u16 w, u16 h, u16 align, u16 offs, u16 band, + struct gid_info *gi, struct tcm *tcm) { + struct area_info *ai = NULL; + struct mem_info *mi = NULL; + struct list_head *before = NULL; + u16 x = 0; /* this holds the end of a potential area */ + + /* allocate map info */ + + /* see if there is available prereserved space */ + mutex_lock(&mtx); + list_for_each_entry(mi, &gi->reserved, global) { + if (mi->area.tcm == tcm && + tcm_aheight(mi->area) == h && + tcm_awidth(mi->area) == w && + (mi->area.p0.x & (align - 1)) == offs) { + /* this area is already set up */ + + /* remove from reserved list */ + list_del(&mi->global); + goto done; + } + } + mutex_unlock(&mtx); + + /* if not, reserve a block struct */ + mi = kmalloc(sizeof(*mi), GFP_KERNEL); + if (!mi) + return mi; + memset(mi, 0, sizeof(*mi)); + + /* see if allocation fits in one of the existing areas */ + /* this sets x, ai and before */ + mutex_lock(&mtx); + list_for_each_entry(ai, &gi->areas, by_gid) { + if (ai->area.tcm == tcm && + tcm_aheight(ai->area) == h) { + x = _m_blk_find_fit(w, align, offs, ai, &before); + if (x) { + _m_add2area(mi, ai, x - w, x - 1, before); + goto done; + } + } + } + mutex_unlock(&mtx); + + /* if no area fit, reserve a new one */ + ai = area_new(ALIGN(w + offs, max(band, align)), h, + max(band, align), tcm, gi); + if (ai) { + mutex_lock(&mtx); + _m_add2area(mi, ai, ai->area.p0.x + offs, + ai->area.p0.x + offs + w - 1, + &ai->blocks); + } else { + /* clean up */ + kfree(mi); + return NULL; + } + +done: + mutex_unlock(&mtx); + return mi; +} + +/* (must have mutex) */ +static void _m_try_free_group(struct gid_info *gi) +{ + if (gi && list_empty(&gi->areas) && list_empty(&gi->onedim)) { + BUG_ON(!list_empty(&gi->reserved)); + list_del(&gi->by_pid); + + /* if group is tracking kernel objects, we may free even + the process info */ + if (gi->pi->kernel && list_empty(&gi->pi->groups)) { + list_del(&gi->pi->list); + kfree(gi->pi); + } + + kfree(gi); + } +} + +static void clear_pat(struct tmm *tmm, struct tcm_area *area) +{ + struct pat_area p_area = {0}; + struct tcm_area slice, area_s; + + tcm_for_each_slice(slice, *area, area_s) { + p_area.x0 = slice.p0.x; + p_area.y0 = slice.p0.y; + p_area.x1 = slice.p1.x; + p_area.y1 = slice.p1.y; + + tmm_clear(tmm, p_area); + } +} + +/* (must have mutex) free block and any freed areas */ +static s32 _m_free(struct mem_info *mi) +{ + struct area_info *ai = NULL; + struct page *page = NULL; + s32 res = 0; + u32 i; + + /* release memory */ + if (mi->pg_ptr) { + for (i = 0; i < mi->num_pg; i++) { + page = (struct page *)mi->pg_ptr[i]; + if (page) { + if (!PageReserved(page)) + SetPageDirty(page); + page_cache_release(page); + } + } + kfree(mi->pg_ptr); + } else if (mi->mem) { + tmm_free(TMM_SS(mi->sys_addr), mi->mem); + } + + /* safe deletion as list may not have been assigned */ + if (mi->global.next) + list_del(&mi->global); + if (mi->by_area.next) + list_del(&mi->by_area); + + /* remove block from area first if 2D */ + if (mi->area.is2d) { + ai = mi->parent; + + /* check to see if area needs removing also */ + if (ai && !--ai->nblocks) { + clear_pat(TMM_SS(mi->sys_addr), &ai->area); + res = tcm_free(&ai->area); + list_del(&ai->by_gid); + /* try to remove parent if it became empty */ + _m_try_free_group(ai->gi); + kfree(ai); + ai = NULL; + } + } else { + /* remove 1D area */ + clear_pat(TMM_SS(mi->sys_addr), &mi->area); + res = tcm_free(&mi->area); + /* try to remove parent if it became empty */ + _m_try_free_group(mi->parent); + } + + kfree(mi); + return res; +} + +/* (must have mutex) returns true if block was freed */ +static bool _m_chk_ref(struct mem_info *mi) +{ + /* check references */ + if (mi->refs) + return 0; + + if (_m_free(mi)) + printk(KERN_ERR "error while removing tiler block\n"); + + return 1; +} + +/* (must have mutex) */ +static inline s32 _m_dec_ref(struct mem_info *mi) +{ + if (mi->refs-- <= 1) + return _m_chk_ref(mi); + + return 0; +} + +/* (must have mutex) */ +static inline void _m_inc_ref(struct mem_info *mi) +{ + mi->refs++; +} + +/* (must have mutex) returns true if block was freed */ +static inline bool _m_try_free(struct mem_info *mi) +{ + if (mi->alloced) { + mi->refs--; + mi->alloced = false; + } + return _m_chk_ref(mi); +} + +static s32 register_buf(struct __buf_info *_b, struct process_info *pi) +{ + struct mem_info *mi = NULL; + struct tiler_buf_info *b = &_b->buf_info; + u32 i, num = b->num_blocks, remain = num; + + /* check validity */ + if (num > TILER_MAX_NUM_BLOCKS) + return -EINVAL; + + mutex_lock(&mtx); + + /* find each block */ + list_for_each_entry(mi, &blocks, global) { + for (i = 0; i < num; i++) { + if (!_b->mi[i] && mi->sys_addr == b->blocks[i].ssptr) { + _b->mi[i] = mi; + + /* quit if found all*/ + if (!--remain) + break; + + } + } + } + + /* if found all, register buffer */ + if (!remain) { + b->offset = id; + id += 0x1000; + + list_add(&_b->by_pid, &pi->bufs); + + /* using each block */ + for (i = 0; i < num; i++) + _m_inc_ref(_b->mi[i]); + } + + mutex_unlock(&mtx); + + return remain ? -EACCES : 0; +} + +/* must have mutex */ +static void _m_unregister_buf(struct __buf_info *_b) +{ + u32 i; + + /* unregister */ + list_del(&_b->by_pid); + + /* no longer using the blocks */ + for (i = 0; i < _b->buf_info.num_blocks; i++) + _m_dec_ref(_b->mi[i]); + + kfree(_b); +} + +/** + * Free all info kept by a process: + * + * all registered buffers, allocated blocks, and unreferenced + * blocks. Any blocks/areas still referenced will move to the + * orphaned lists to avoid issues if a new process is created + * with the same pid. + * + * (must have mutex) + */ +static void _m_free_process_info(struct process_info *pi) +{ + struct area_info *ai, *ai_; + struct mem_info *mi, *mi_; + struct gid_info *gi, *gi_; + struct __buf_info *_b = NULL, *_b_ = NULL; + bool ai_autofreed, need2free; + + /* unregister all buffers */ + list_for_each_entry_safe(_b, _b_, &pi->bufs, by_pid) + _m_unregister_buf(_b); + + BUG_ON(!list_empty(&pi->bufs)); + + /* free all allocated blocks, and remove unreferenced ones */ + list_for_each_entry_safe(gi, gi_, &pi->groups, by_pid) { + + /* + * Group info structs when they become empty on an _m_try_free. + * However, if the group info is already empty, we need to + * remove it manually + */ + need2free = list_empty(&gi->areas) && list_empty(&gi->onedim); + list_for_each_entry_safe(ai, ai_, &gi->areas, by_gid) { + ai_autofreed = true; + list_for_each_entry_safe(mi, mi_, &ai->blocks, by_area) + ai_autofreed &= _m_try_free(mi); + + /* save orphaned areas for later removal */ + if (!ai_autofreed) { + need2free = true; + ai->gi = NULL; + list_move(&ai->by_gid, &orphan_areas); + } + } + + list_for_each_entry_safe(mi, mi_, &gi->onedim, by_area) { + if (!_m_try_free(mi)) { + need2free = true; + /* save orphaned 1D blocks */ + mi->parent = NULL; + list_move(&mi->by_area, &orphan_onedim); + } + } + + /* if group is still alive reserved list should have been + emptied as there should be no reference on those blocks */ + if (need2free) { + BUG_ON(!list_empty(&gi->onedim)); + BUG_ON(!list_empty(&gi->areas)); + _m_try_free_group(gi); + } + } + + BUG_ON(!list_empty(&pi->groups)); + list_del(&pi->list); + kfree(pi); +} + +static s32 get_area(u32 sys_addr, struct tcm_pt *pt) +{ + enum tiler_fmt fmt; + + sys_addr &= TILER_ALIAS_VIEW_CLEAR; + fmt = TILER_GET_ACC_MODE(sys_addr); + + switch (fmt) { + case TILFMT_8BIT: + pt->x = DMM_HOR_X_PAGE_COOR_GET_8(sys_addr); + pt->y = DMM_HOR_Y_PAGE_COOR_GET_8(sys_addr); + break; + case TILFMT_16BIT: + pt->x = DMM_HOR_X_PAGE_COOR_GET_16(sys_addr); + pt->y = DMM_HOR_Y_PAGE_COOR_GET_16(sys_addr); + break; + case TILFMT_32BIT: + pt->x = DMM_HOR_X_PAGE_COOR_GET_32(sys_addr); + pt->y = DMM_HOR_Y_PAGE_COOR_GET_32(sys_addr); + break; + case TILFMT_PAGE: + pt->x = (sys_addr & 0x7FFFFFF) >> 12; + pt->y = pt->x / TILER_WIDTH; + pt->x &= (TILER_WIDTH - 1); + break; + default: + return -EFAULT; + } + return 0x0; +} + +static u32 __get_alias_addr(enum tiler_fmt fmt, u16 x, u16 y) +{ + u32 acc_mode = -1; + u32 x_shft = -1, y_shft = -1; + + switch (fmt) { + case TILFMT_8BIT: + acc_mode = 0; x_shft = 6; y_shft = 20; + break; + case TILFMT_16BIT: + acc_mode = 1; x_shft = 7; y_shft = 20; + break; + case TILFMT_32BIT: + acc_mode = 2; x_shft = 7; y_shft = 20; + break; + case TILFMT_PAGE: + acc_mode = 3; y_shft = 8; + break; + default: + return 0; + break; + } + + if (fmt == TILFMT_PAGE) + return (u32)TIL_ALIAS_ADDR((x | y << y_shft) << 12, acc_mode); + else + return (u32)TIL_ALIAS_ADDR(x << x_shft | y << y_shft, acc_mode); +} + +/* must have mutex */ +static struct gid_info *_m_get_gi(struct process_info *pi, u32 gid) +{ + struct gid_info *gi; + + /* see if group already exist */ + list_for_each_entry(gi, &pi->groups, by_pid) { + if (gi->gid == gid) + return gi; + } + + /* create new group */ + gi = kmalloc(sizeof(*gi), GFP_KERNEL); + if (!gi) + return gi; + + memset(gi, 0, sizeof(*gi)); + INIT_LIST_HEAD(&gi->areas); + INIT_LIST_HEAD(&gi->onedim); + INIT_LIST_HEAD(&gi->reserved); + gi->pi = pi; + gi->gid = gid; + list_add(&gi->by_pid, &pi->groups); + return gi; +} + +static struct mem_info *__get_area(enum tiler_fmt fmt, u32 width, u32 height, + u16 align, u16 offs, struct gid_info *gi) +{ + u16 x, y, band; + struct mem_info *mi = NULL; + + /* calculate dimensions, band, offs and alignment in slots */ + if (__analize_area(fmt, width, height, &x, &y, &band, &align, &offs)) + return NULL; + + if (fmt == TILFMT_PAGE) { + /* 1D areas don't pack */ + mi = kmalloc(sizeof(*mi), GFP_KERNEL); + if (!mi) + return NULL; + memset(mi, 0x0, sizeof(*mi)); + + if (tcm_reserve_1d(TCM(fmt), x * y, &mi->area)) { + kfree(mi); + return NULL; + } + + mutex_lock(&mtx); + mi->parent = gi; + list_add(&mi->by_area, &gi->onedim); + } else { + mi = get_2d_area(x, y, align, offs, band, gi, TCM(fmt)); + if (!mi) + return NULL; + + mutex_lock(&mtx); + } + + list_add(&mi->global, &blocks); + mi->alloced = true; + mi->refs++; + mutex_unlock(&mtx); + + mi->sys_addr = __get_alias_addr(fmt, mi->area.p0.x, mi->area.p0.y); + return mi; +} + +static s32 tiler_mmap(struct file *filp, struct vm_area_struct *vma) +{ + struct __buf_info *_b = NULL; + struct tiler_buf_info *b = NULL; + s32 i = 0, j = 0, k = 0, m = 0, p = 0, bpp = 1; + struct list_head *pos = NULL; + struct process_info *pi = filp->private_data; + + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + + /* don't allow mremap */ + vma->vm_flags |= VM_DONTEXPAND | VM_RESERVED; + + mutex_lock(&mtx); + list_for_each(pos, &pi->bufs) { + _b = list_entry(pos, struct __buf_info, by_pid); + if ((vma->vm_pgoff << PAGE_SHIFT) == _b->buf_info.offset) + break; + } + mutex_unlock(&mtx); + if (!_b) + return -ENXIO; + + b = &_b->buf_info; + + for (i = 0; i < b->num_blocks; i++) { + if (b->blocks[i].fmt >= TILFMT_8BIT && + b->blocks[i].fmt <= TILFMT_32BIT) { + /* get line width */ + bpp = (b->blocks[i].fmt == TILFMT_8BIT ? 1 : + b->blocks[i].fmt == TILFMT_16BIT ? 2 : 4); + p = PAGE_ALIGN(b->blocks[i].dim.area.width * bpp); + + for (j = 0; j < b->blocks[i].dim.area.height; j++) { + /* map each page of the line */ + vma->vm_pgoff = + (b->blocks[i].ssptr + m) >> PAGE_SHIFT; + if (remap_pfn_range(vma, vma->vm_start + k, + (b->blocks[i].ssptr + m) >> PAGE_SHIFT, + p, vma->vm_page_prot)) + return -EAGAIN; + k += p; + if (b->blocks[i].fmt == TILFMT_8BIT) + m += 64*TILER_WIDTH; + else + m += 2*64*TILER_WIDTH; + } + m = 0; + } else if (b->blocks[i].fmt == TILFMT_PAGE) { + vma->vm_pgoff = (b->blocks[i].ssptr) >> PAGE_SHIFT; + p = PAGE_ALIGN(b->blocks[i].dim.len); + if (remap_pfn_range(vma, vma->vm_start + k, + (b->blocks[i].ssptr) >> PAGE_SHIFT, p, + vma->vm_page_prot)) + return -EAGAIN;; + k += p; + } + } + return 0; +} + +static s32 refill_pat(struct tmm *tmm, struct tcm_area *area, u32 *ptr) +{ + s32 res = 0; + s32 size = tcm_sizeof(*area) * sizeof(*ptr); + u32 *page; + dma_addr_t page_pa; + struct pat_area p_area = {0}; + struct tcm_area slice, area_s; + + /* must be a 16-byte aligned physical address */ + page = dma_alloc_coherent(NULL, size, &page_pa, GFP_ATOMIC); + if (!page) + return -ENOMEM; + + tcm_for_each_slice(slice, *area, area_s) { + p_area.x0 = slice.p0.x; + p_area.y0 = slice.p0.y; + p_area.x1 = slice.p1.x; + p_area.y1 = slice.p1.y; + + memcpy(page, ptr, sizeof(*ptr) * tcm_sizeof(slice)); + ptr += tcm_sizeof(slice); + + if (tmm_map(tmm, p_area, page_pa)) { + res = -EFAULT; + break; + } + } + + dma_free_coherent(NULL, size, page, page_pa); + + return res; +} + +static s32 map_block(enum tiler_fmt fmt, u32 width, u32 height, u32 gid, + struct process_info *pi, u32 *sys_addr, u32 usr_addr) +{ + u32 i = 0, tmp = -1, *mem = NULL; + u8 write = 0; + s32 res = -ENOMEM; + struct mem_info *mi = NULL; + struct page *page = NULL; + struct task_struct *curr_task = current; + struct mm_struct *mm = current->mm; + struct vm_area_struct *vma = NULL; + struct gid_info *gi = NULL; + + /* we only support mapping a user buffer in page mode */ + if (fmt != TILFMT_PAGE) + return -EPERM; + + /* check if mapping is supported by tmm */ + if (!tmm_can_map(TMM(fmt))) + return -EPERM; + + /* get group context */ + mutex_lock(&mtx); + gi = _m_get_gi(pi, gid); + mutex_unlock(&mtx); + + if (!gi) + return -ENOMEM; + + /* reserve area in tiler container */ + mi = __get_area(fmt, width, height, 0, 0, gi); + if (!mi) { + mutex_lock(&mtx); + _m_try_free_group(gi); + mutex_unlock(&mtx); + return -ENOMEM; + } + + *sys_addr = mi->sys_addr; + mi->usr = usr_addr; + + /* allocate pages */ + mi->num_pg = tcm_sizeof(mi->area); + + mem = kmalloc(mi->num_pg * sizeof(*mem), GFP_KERNEL); + if (!mem) + goto done; + memset(mem, 0x0, sizeof(*mem) * mi->num_pg); + + mi->pg_ptr = kmalloc(mi->num_pg * sizeof(*mi->pg_ptr), GFP_KERNEL); + if (!mi->pg_ptr) + goto done; + memset(mi->pg_ptr, 0x0, sizeof(*mi->pg_ptr) * mi->num_pg); + + /* + * Important Note: usr_addr is mapped from user + * application process to current process - it must lie + * completely within the current virtual memory address + * space in order to be of use to us here. + */ + down_read(&mm->mmap_sem); + vma = find_vma(mm, mi->usr); + res = -EFAULT; + + /* + * It is observed that under some circumstances, the user + * buffer is spread across several vmas, so loop through + * and check if the entire user buffer is covered. + */ + while ((vma) && (mi->usr + width > vma->vm_end)) { + /* jump to the next VMA region */ + vma = find_vma(mm, vma->vm_end + 1); + } + if (!vma) { + printk(KERN_ERR "Failed to get the vma region for " + "user buffer.\n"); + goto fault; + } + + if (vma->vm_flags & (VM_WRITE | VM_MAYWRITE)) + write = 1; + + tmp = mi->usr; + for (i = 0; i < mi->num_pg; i++) { + if (get_user_pages(curr_task, mm, tmp, 1, write, 1, &page, + NULL)) { + if (page_count(page) < 1) { + printk(KERN_ERR "Bad page count from" + "get_user_pages()\n"); + } + mi->pg_ptr[i] = (u32)page; + mem[i] = page_to_phys(page); + tmp += PAGE_SIZE; + } else { + printk(KERN_ERR "get_user_pages() failed\n"); + goto fault; + } + } + up_read(&mm->mmap_sem); + + /* Ensure the data reaches to main memory before PAT refill */ + wmb(); + + if (refill_pat(TMM(fmt), &mi->area, mem)) + goto fault; + + res = 0; + goto done; +fault: + up_read(&mm->mmap_sem); +done: + if (res) { + mutex_lock(&mtx); + _m_free(mi); + mutex_unlock(&mtx); + } + kfree(mem); + return res; +} + +s32 tiler_mapx(enum tiler_fmt fmt, u32 width, u32 height, u32 gid, + pid_t pid, u32 *sys_addr, u32 usr_addr) +{ + return map_block(fmt, width, height, gid, __get_pi(pid, true), + sys_addr, usr_addr); +} +EXPORT_SYMBOL(tiler_mapx); + +s32 tiler_map(enum tiler_fmt fmt, u32 width, u32 height, u32 *sys_addr, + u32 usr_addr) +{ + return tiler_mapx(fmt, width, height, 0, current->tgid, sys_addr, + usr_addr); +} +EXPORT_SYMBOL(tiler_map); + +s32 free_block(u32 sys_addr, struct process_info *pi) +{ + struct gid_info *gi = NULL; + struct area_info *ai = NULL; + struct mem_info *mi = NULL; + s32 res = -ENOENT; + + mutex_lock(&mtx); + + /* find block in process list and free it */ + list_for_each_entry(gi, &pi->groups, by_pid) { + /* currently we know if block is 1D or 2D by the address */ + if (TILER_GET_ACC_MODE(sys_addr) == TILFMT_PAGE) { + list_for_each_entry(mi, &gi->onedim, by_area) { + if (mi->sys_addr == sys_addr) { + _m_try_free(mi); + res = 0; + goto done; + } + } + } else { + list_for_each_entry(ai, &gi->areas, by_gid) { + list_for_each_entry(mi, &ai->blocks, by_area) { + if (mi->sys_addr == sys_addr) { + _m_try_free(mi); + res = 0; + goto done; + } + } + } + } + } + +done: + mutex_unlock(&mtx); + + /* for debugging, we can set the PAT entries to DMM_LISA_MAP__0 */ + return res; +} + +s32 tiler_free(u32 sys_addr) +{ + struct mem_info *mi; + s32 res = -ENOENT; + + mutex_lock(&mtx); + + /* find block in global list and free it */ + list_for_each_entry(mi, &blocks, global) { + if (mi->sys_addr == sys_addr) { + _m_try_free(mi); + res = 0; + break; + } + } + mutex_unlock(&mtx); + + /* for debugging, we can set the PAT entries to DMM_LISA_MAP__0 */ + return res; +} +EXPORT_SYMBOL(tiler_free); + +/* :TODO: Currently we do not track enough information from alloc to get back + the actual width and height of the container, so we must make a guess. We + do not even have enough information to get the virtual stride of the buffer, + which is the real reason for this ioctl */ +s32 find_block(u32 sys_addr, struct tiler_block_info *blk) +{ + struct mem_info *i; + struct tcm_pt pt; + + if (get_area(sys_addr, &pt)) + return -EFAULT; + + list_for_each_entry(i, &blocks, global) { + if (tcm_is_in(pt, i->area)) + goto found; + } + + blk->fmt = TILFMT_INVALID; + blk->dim.len = blk->stride = blk->ssptr = 0; + return -EFAULT; + +found: + blk->ptr = NULL; + blk->fmt = TILER_GET_ACC_MODE(sys_addr); + blk->ssptr = __get_alias_addr(blk->fmt, i->area.p0.x, i->area.p0.y); + + if (blk->fmt == TILFMT_PAGE) { + blk->dim.len = tcm_sizeof(i->area) * TILER_PAGE; + blk->stride = 0; + } else { + blk->stride = blk->dim.area.width = + tcm_awidth(i->area) * TILER_BLOCK_WIDTH; + blk->dim.area.height = tcm_aheight(i->area) + * TILER_BLOCK_HEIGHT; + if (blk->fmt != TILFMT_8BIT) { + blk->stride <<= 1; + blk->dim.area.height >>= 1; + if (blk->fmt == TILFMT_32BIT) + blk->dim.area.width >>= 1; + } + blk->stride = PAGE_ALIGN(blk->stride); + } + return 0; +} + +static s32 alloc_block(enum tiler_fmt fmt, u32 width, u32 height, + u32 align, u32 offs, u32 gid, struct process_info *pi, + u32 *sys_addr); + +static s32 tiler_ioctl(struct inode *ip, struct file *filp, u32 cmd, + unsigned long arg) +{ + pgd_t *pgd = NULL; + pmd_t *pmd = NULL; + pte_t *ptep = NULL, pte = 0x0; + s32 r = -1; + u32 til_addr = 0x0; + struct process_info *pi = filp->private_data; + + struct __buf_info *_b = NULL; + struct tiler_buf_info buf_info = {0}; + struct tiler_block_info block_info = {0}; + + switch (cmd) { + case TILIOC_GBUF: + if (copy_from_user(&block_info, (void __user *)arg, + sizeof(block_info))) + return -EFAULT; + + switch (block_info.fmt) { + case TILFMT_PAGE: + r = alloc_block(block_info.fmt, block_info.dim.len, 1, + 0, 0, 0, pi, &til_addr); + if (r) + return r; + break; + case TILFMT_8BIT: + case TILFMT_16BIT: + case TILFMT_32BIT: + r = alloc_block(block_info.fmt, + block_info.dim.area.width, + block_info.dim.area.height, + 0, 0, 0, pi, &til_addr); + if (r) + return r; + break; + default: + return -EINVAL; + } + + block_info.ssptr = til_addr; + if (copy_to_user((void __user *)arg, &block_info, + sizeof(block_info))) + return -EFAULT; + break; + case TILIOC_FBUF: + case TILIOC_UMBUF: + if (copy_from_user(&block_info, (void __user *)arg, + sizeof(block_info))) + return -EFAULT; + + /* search current process first, then all processes */ + free_block(block_info.ssptr, pi) ? + tiler_free(block_info.ssptr) : 0; + + /* free always succeeds */ + break; + + case TILIOC_GSSP: + pgd = pgd_offset(current->mm, arg); + if (!(pgd_none(*pgd) || pgd_bad(*pgd))) { + pmd = pmd_offset(pgd, arg); + if (!(pmd_none(*pmd) || pmd_bad(*pmd))) { + ptep = pte_offset_map(pmd, arg); + if (ptep) { + pte = *ptep; + if (pte_present(pte)) + return (pte & PAGE_MASK) | + (~PAGE_MASK & arg); + } + } + } + /* va not in page table */ + return 0x0; + break; + case TILIOC_MBUF: + if (copy_from_user(&block_info, (void __user *)arg, + sizeof(block_info))) + return -EFAULT; + + if (!block_info.ptr) + return -EFAULT; + + if (map_block(block_info.fmt, block_info.dim.len, 1, 0, pi, + &block_info.ssptr, (u32)block_info.ptr)) + return -ENOMEM; + + if (copy_to_user((void __user *)arg, &block_info, + sizeof(block_info))) + return -EFAULT; + break; + case TILIOC_QBUF: + if (copy_from_user(&buf_info, (void __user *)arg, + sizeof(buf_info))) + return -EFAULT; + + mutex_lock(&mtx); + list_for_each_entry(_b, &pi->bufs, by_pid) { + if (buf_info.offset == _b->buf_info.offset) { + if (copy_to_user((void __user *)arg, + &_b->buf_info, + sizeof(_b->buf_info))) { + mutex_unlock(&mtx); + return -EFAULT; + } else { + mutex_unlock(&mtx); + return 0; + } + } + } + mutex_unlock(&mtx); + return -EFAULT; + break; + case TILIOC_RBUF: + _b = kmalloc(sizeof(*_b), GFP_KERNEL); + if (!_b) + return -ENOMEM; + + memset(_b, 0x0, sizeof(*_b)); + + if (copy_from_user(&_b->buf_info, (void __user *)arg, + sizeof(_b->buf_info))) { + kfree(_b); return -EFAULT; + } + + r = register_buf(_b, pi); + if (r) { + kfree(_b); return -EACCES; + } + + if (copy_to_user((void __user *)arg, &_b->buf_info, + sizeof(_b->buf_info))) { + _m_unregister_buf(_b); + return -EFAULT; + } + break; + case TILIOC_URBUF: + if (copy_from_user(&buf_info, (void __user *)arg, + sizeof(buf_info))) + return -EFAULT; + + mutex_lock(&mtx); + /* buffer registration is per process */ + list_for_each_entry(_b, &pi->bufs, by_pid) { + if (buf_info.offset == _b->buf_info.offset) { + _m_unregister_buf(_b); + mutex_unlock(&mtx); + return 0; + } + } + mutex_unlock(&mtx); + return -EFAULT; + break; + case TILIOC_QUERY_BLK: + if (copy_from_user(&block_info, (void __user *)arg, + sizeof(block_info))) + return -EFAULT; + + if (find_block(block_info.ssptr, &block_info)) + return -EFAULT; + + if (copy_to_user((void __user *)arg, &block_info, + sizeof(block_info))) + return -EFAULT; + break; + default: + return -EINVAL; + } + return 0x0; +} + +s32 alloc_block(enum tiler_fmt fmt, u32 width, u32 height, + u32 align, u32 offs, u32 gid, struct process_info *pi, + u32 *sys_addr) +{ + struct mem_info *mi = NULL; + struct gid_info *gi = NULL; + + /* only support up to page alignment */ + if (align > PAGE_SIZE || offs > align || !pi) + return -EINVAL; + + /* get group context */ + mutex_lock(&mtx); + gi = _m_get_gi(pi, gid); + mutex_unlock(&mtx); + + if (!gi) + return -ENOMEM; + + /* reserve area in tiler container */ + mi = __get_area(fmt, width, height, align, offs, gi); + if (!mi) { + mutex_lock(&mtx); + _m_try_free_group(gi); + mutex_unlock(&mtx); + return -ENOMEM; + } + + *sys_addr = mi->sys_addr; + + /* allocate and map if mapping is supported */ + if (tmm_can_map(TMM(fmt))) { + mi->num_pg = tcm_sizeof(mi->area); + + mi->mem = tmm_get(TMM(fmt), mi->num_pg); + if (!mi->mem) + goto cleanup; + + /* Ensure the data reaches to main memory before PAT refill */ + wmb(); + + /* program PAT */ + if (refill_pat(TMM(fmt), &mi->area, mi->mem)) + goto cleanup; + } + return 0; + +cleanup: + mutex_lock(&mtx); + _m_free(mi); + mutex_unlock(&mtx); + return -ENOMEM; + +} + +s32 tiler_allocx(enum tiler_fmt fmt, u32 width, u32 height, + u32 align, u32 offs, u32 gid, pid_t pid, u32 *sys_addr) +{ + return alloc_block(fmt, width, height, align, offs, gid, + __get_pi(pid, true), sys_addr); +} +EXPORT_SYMBOL(tiler_allocx); + +s32 tiler_alloc(enum tiler_fmt fmt, u32 width, u32 height, u32 *sys_addr) +{ + return tiler_allocx(fmt, width, height, 0, 0, + 0, current->tgid, sys_addr); +} +EXPORT_SYMBOL(tiler_alloc); + + +static void reserve_nv12_blocks(u32 n, u32 width, u32 height, + u32 align, u32 offs, u32 gid, pid_t pid) +{ +} + +static void reserve_blocks(u32 n, enum tiler_fmt fmt, u32 width, u32 height, + u32 align, u32 offs, u32 gid, pid_t pid) +{ +} + +/* reserve area for n identical buffers */ +s32 tiler_reservex(u32 n, struct tiler_buf_info *b, pid_t pid) +{ + u32 i; + + if (b->num_blocks > TILER_MAX_NUM_BLOCKS) + return -EINVAL; + + for (i = 0; i < b->num_blocks; i++) { + /* check for NV12 reservations */ + if (i + 1 < b->num_blocks && + b->blocks[i].fmt == TILFMT_8BIT && + b->blocks[i + 1].fmt == TILFMT_16BIT && + b->blocks[i].dim.area.height == + b->blocks[i + 1].dim.area.height && + b->blocks[i].dim.area.width == + b->blocks[i + 1].dim.area.width) { + reserve_nv12_blocks(n, + b->blocks[i].dim.area.width, + b->blocks[i].dim.area.height, + 0, /* align */ + 0, /* offs */ + 0, /* gid */ + pid); + i++; + } else if (b->blocks[i].fmt >= TILFMT_8BIT && + b->blocks[i].fmt <= TILFMT_32BIT) { + /* other 2D reservations */ + reserve_blocks(n, + b->blocks[i].fmt, + b->blocks[i].dim.area.width, + b->blocks[i].dim.area.height, + 0, /* align */ + 0, /* offs */ + 0, /* gid */ + pid); + } else { + return -EINVAL; + } + } + return 0; +} +EXPORT_SYMBOL(tiler_reservex); + +s32 tiler_reserve(u32 n, struct tiler_buf_info *b) +{ + return tiler_reservex(n, b, current->tgid); +} +EXPORT_SYMBOL(tiler_reserve); + +static void __exit tiler_exit(void) +{ + struct process_info *pi = NULL, *pi_ = NULL; + int i, j; + + mutex_lock(&mtx); + + /* free all process data */ + list_for_each_entry_safe(pi, pi_, &procs, list) + _m_free_process_info(pi); + + /* all lists should have cleared */ + BUG_ON(!list_empty(&blocks)); + BUG_ON(!list_empty(&procs)); + BUG_ON(!list_empty(&orphan_onedim)); + BUG_ON(!list_empty(&orphan_areas)); + + mutex_unlock(&mtx); + + dma_free_coherent(NULL, TILER_WIDTH * TILER_HEIGHT * sizeof(*dmac_va), + dmac_va, dmac_pa); + + /* close containers only once */ + for (i = TILFMT_8BIT; i <= TILFMT_MAX; i++) { + /* remove identical containers (tmm is unique per tcm) */ + for (j = i + 1; j <= TILFMT_MAX; j++) + if (TCM(i) == TCM(j)) { + TCM_SET(j, NULL); + TMM_SET(j, NULL); + } + + tcm_deinit(TCM(i)); + tmm_deinit(TMM(i)); + } + + mutex_destroy(&mtx); + platform_driver_unregister(&tiler_driver_ldm); + cdev_del(&tiler_device->cdev); + kfree(tiler_device); + device_destroy(tilerdev_class, MKDEV(tiler_major, tiler_minor)); + class_destroy(tilerdev_class); +} + +static s32 tiler_open(struct inode *ip, struct file *filp) +{ + struct process_info *pi = __get_pi(current->tgid, false); + + if (!pi) + return -ENOMEM; + + filp->private_data = pi; + return 0x0; +} + +static s32 tiler_release(struct inode *ip, struct file *filp) +{ + struct process_info *pi = filp->private_data; + + mutex_lock(&mtx); + /* free resources if last device in this process */ + if (0 == --pi->refs) + _m_free_process_info(pi); + + mutex_unlock(&mtx); + + return 0x0; +} + +static const struct file_operations tiler_fops = { + .open = tiler_open, + .ioctl = tiler_ioctl, + .release = tiler_release, + .mmap = tiler_mmap, +}; + +static s32 __init tiler_init(void) +{ + dev_t dev = 0; + s32 r = -1; + struct device *device = NULL; + struct tcm_pt div_pt; + struct tcm *sita = NULL; + struct tmm *tmm_pat = NULL; + + /* Allocate tiler container manager (we share 1 on OMAP4) */ + div_pt.x = TILER_WIDTH; /* hardcoded default */ + div_pt.y = (3 * TILER_HEIGHT) / 4; + sita = sita_init(TILER_WIDTH, TILER_HEIGHT, (void *)&div_pt); + + TCM_SET(TILFMT_8BIT, sita); + TCM_SET(TILFMT_16BIT, sita); + TCM_SET(TILFMT_32BIT, sita); + TCM_SET(TILFMT_PAGE, sita); + + /* Allocate tiler memory manager (must have 1 unique TMM per TCM ) */ + tmm_pat = tmm_pat_init(0); + TMM_SET(TILFMT_8BIT, tmm_pat); + TMM_SET(TILFMT_16BIT, tmm_pat); + TMM_SET(TILFMT_32BIT, tmm_pat); + TMM_SET(TILFMT_PAGE, tmm_pat); + + /** + * Array of physical pages for PAT programming, which must be a 16-byte + * aligned physical address + */ + dmac_va = dma_alloc_coherent(NULL, TILER_WIDTH * TILER_HEIGHT * + sizeof(*dmac_va), &dmac_pa, GFP_ATOMIC); + if (!dmac_va) + return -ENOMEM; + + tiler_device = kmalloc(sizeof(*tiler_device), GFP_KERNEL); + if (!tiler_device || !sita || !tmm_pat) { + r = -ENOMEM; + goto error; + } + + memset(tiler_device, 0x0, sizeof(*tiler_device)); + if (tiler_major) { + dev = MKDEV(tiler_major, tiler_minor); + r = register_chrdev_region(dev, 1, "tiler"); + } else { + r = alloc_chrdev_region(&dev, tiler_minor, 1, "tiler"); + tiler_major = MAJOR(dev); + } + + cdev_init(&tiler_device->cdev, &tiler_fops); + tiler_device->cdev.owner = THIS_MODULE; + tiler_device->cdev.ops = &tiler_fops; + + r = cdev_add(&tiler_device->cdev, dev, 1); + if (r) + printk(KERN_ERR "cdev_add():failed\n"); + + tilerdev_class = class_create(THIS_MODULE, "tiler"); + + if (IS_ERR(tilerdev_class)) { + printk(KERN_ERR "class_create():failed\n"); + goto error; + } + + device = device_create(tilerdev_class, NULL, dev, NULL, "tiler"); + if (device == NULL) + printk(KERN_ERR "device_create() fail\n"); + + r = platform_driver_register(&tiler_driver_ldm); + + mutex_init(&mtx); + INIT_LIST_HEAD(&blocks); + INIT_LIST_HEAD(&procs); + INIT_LIST_HEAD(&orphan_areas); + INIT_LIST_HEAD(&orphan_onedim); + id = 0xda7a000; + +error: + /* TODO: error handling for device registration */ + if (r) { + kfree(tiler_device); + tcm_deinit(sita); + tmm_deinit(tmm_pat); + } + + return r; +} + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("David Sin <davidsin@ti.com>"); +MODULE_AUTHOR("Lajos Molnar <molnar@ti.com>"); +module_init(tiler_init); +module_exit(tiler_exit); diff --git a/drivers/media/video/tiler/tiler_def.h b/drivers/media/video/tiler/tiler_def.h new file mode 100644 index 000000000000..d92bfde8e452 --- /dev/null +++ b/drivers/media/video/tiler/tiler_def.h @@ -0,0 +1,158 @@ +/* + * tiler_def.h + * + * TILER driver support functions for TI OMAP processors. + * + * 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. + */ + +#ifndef TILER_DEF_H +#define TILER_DEF_H + +#define ROUND_UP_2P(a, b) (((a) + (b) - 1) & ~((b) - 1)) +#define DIVIDE_UP(a, b) (((a) + (b) - 1) / (b)) +#define ROUND_UP(a, b) (DIVIDE_UP(a, b) * (b)) +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define MAX(a, b) ((a) > (b) ? (a) : (b)) + +#define TILER_ACC_MODE_SHIFT (27) +#define TILER_ACC_MODE_MASK (3) +#define TILER_GET_ACC_MODE(x) ((enum tiler_fmt) (1 + \ +(((u32)x & (TILER_ACC_MODE_MASK<<TILER_ACC_MODE_SHIFT))>>TILER_ACC_MODE_SHIFT))) + +#define TILER_ALIAS_BASE (0x60000000) +#define TILER_ACC_MODE_SHIFT (27) +#define DMM_ACC_MODE_SHIFT (27) + +#define TIL_ALIAS_ADDR(x, access_mode)\ +((void *)(TILER_ALIAS_BASE | (u32)x | (access_mode << TILER_ACC_MODE_SHIFT))) + +#define TIL_ADDR(x, r, yi, xi, a)\ +((void *)((u32)x | (r << DMM_ROTATION_SHIFT) |\ +(yi << DMM_Y_INVERT_SHIFT) | (xi << DMM_X_INVERT_SHIFT) |\ +(a << DMM_ACC_MODE_SHIFT))) + +#define TILER_ALIAS_VIEW_CLEAR (~0xE0000000) + +#define DMM_X_INVERT_SHIFT (29) +#define DMM_GET_X_INVERTED(x) ((((u32)x & (1<<DMM_X_INVERT_SHIFT)) > 0) ? 1 : 0) +#define DMM_Y_INVERT_SHIFT (30) +#define DMM_GET_Y_INVERTED(x) ((((u32)x & (1<<DMM_Y_INVERT_SHIFT)) > 0) ? 1 : 0) + +#define DMM_ROTATION_SHIFT (31) +#define DMM_GET_ROTATED(x)\ +((((u32)x & ((u32)1<<DMM_ROTATION_SHIFT)) > 0) ? 1 : 0) + +#define DMM_ALIAS_VIEW_CLEAR (~0xE0000000) + +#define DMM_TILE_DIMM_X_MODE_8 (32) +#define DMM_TILE_DIMM_Y_MODE_8 (32) + +#define DMM_TILE_DIMM_X_MODE_16 (32) +#define DMM_TILE_DIMM_Y_MODE_16 (16) + +#define DMM_TILE_DIMM_X_MODE_32 (16) +#define DMM_TILE_DIMM_Y_MODE_32 (16) + +#define DMM_PAGE_DIMM_X_MODE_8 (DMM_TILE_DIMM_X_MODE_8*2) +#define DMM_PAGE_DIMM_Y_MODE_8 (DMM_TILE_DIMM_Y_MODE_8*2) + +#define DMM_PAGE_DIMM_X_MODE_16 (DMM_TILE_DIMM_X_MODE_16*2) +#define DMM_PAGE_DIMM_Y_MODE_16 (DMM_TILE_DIMM_Y_MODE_16*2) + +#define DMM_PAGE_DIMM_X_MODE_32 (DMM_TILE_DIMM_X_MODE_32*2) +#define DMM_PAGE_DIMM_Y_MODE_32 (DMM_TILE_DIMM_Y_MODE_32*2) + +#define DMM_HOR_X_ADDRSHIFT_8 (0) +#define DMM_HOR_X_ADDRMASK_8 (0x3FFF) +#define DMM_HOR_X_COOR_GET_8(x)\ + (((unsigned long)x >> DMM_HOR_X_ADDRSHIFT_8) & DMM_HOR_X_ADDRMASK_8) +#define DMM_HOR_X_PAGE_COOR_GET_8(x)\ + (DMM_HOR_X_COOR_GET_8(x)/DMM_PAGE_DIMM_X_MODE_8) + +#define DMM_HOR_Y_ADDRSHIFT_8 (14) +#define DMM_HOR_Y_ADDRMASK_8 (0x1FFF) +#define DMM_HOR_Y_COOR_GET_8(x)\ + (((unsigned long)x >> DMM_HOR_Y_ADDRSHIFT_8) & DMM_HOR_Y_ADDRMASK_8) +#define DMM_HOR_Y_PAGE_COOR_GET_8(x)\ + (DMM_HOR_Y_COOR_GET_8(x)/DMM_PAGE_DIMM_Y_MODE_8) + +#define DMM_HOR_X_ADDRSHIFT_16 (1) +#define DMM_HOR_X_ADDRMASK_16 (0x7FFE) +#define DMM_HOR_X_COOR_GET_16(x) (((unsigned long)x >> \ + DMM_HOR_X_ADDRSHIFT_16) & DMM_HOR_X_ADDRMASK_16) +#define DMM_HOR_X_PAGE_COOR_GET_16(x) (DMM_HOR_X_COOR_GET_16(x) / \ + DMM_PAGE_DIMM_X_MODE_16) + +#define DMM_HOR_Y_ADDRSHIFT_16 (15) +#define DMM_HOR_Y_ADDRMASK_16 (0xFFF) +#define DMM_HOR_Y_COOR_GET_16(x) (((unsigned long)x >> \ + DMM_HOR_Y_ADDRSHIFT_16) & DMM_HOR_Y_ADDRMASK_16) +#define DMM_HOR_Y_PAGE_COOR_GET_16(x) (DMM_HOR_Y_COOR_GET_16(x) / \ + DMM_PAGE_DIMM_Y_MODE_16) + +#define DMM_HOR_X_ADDRSHIFT_32 (2) +#define DMM_HOR_X_ADDRMASK_32 (0x7FFC) +#define DMM_HOR_X_COOR_GET_32(x) (((unsigned long)x >> \ + DMM_HOR_X_ADDRSHIFT_32) & DMM_HOR_X_ADDRMASK_32) +#define DMM_HOR_X_PAGE_COOR_GET_32(x) (DMM_HOR_X_COOR_GET_32(x) / \ + DMM_PAGE_DIMM_X_MODE_32) + +#define DMM_HOR_Y_ADDRSHIFT_32 (15) +#define DMM_HOR_Y_ADDRMASK_32 (0xFFF) +#define DMM_HOR_Y_COOR_GET_32(x) (((unsigned long)x >> \ + DMM_HOR_Y_ADDRSHIFT_32) & DMM_HOR_Y_ADDRMASK_32) +#define DMM_HOR_Y_PAGE_COOR_GET_32(x) (DMM_HOR_Y_COOR_GET_32(x) / \ + DMM_PAGE_DIMM_Y_MODE_32) + +#define DMM_VER_X_ADDRSHIFT_8 (14) +#define DMM_VER_X_ADDRMASK_8 (0x1FFF) +#define DMM_VER_X_COOR_GET_8(x)\ + (((unsigned long)x >> DMM_VER_X_ADDRSHIFT_8) & DMM_VER_X_ADDRMASK_8) +#define DMM_VER_X_PAGE_COOR_GET_8(x)\ + (DMM_VER_X_COOR_GET_8(x)/DMM_PAGE_DIMM_X_MODE_8) + +#define DMM_VER_Y_ADDRSHIFT_8 (0) +#define DMM_VER_Y_ADDRMASK_8 (0x3FFF) +#define DMM_VER_Y_COOR_GET_8(x)\ + (((unsigned long)x >> DMM_VER_Y_ADDRSHIFT_8) & DMM_VER_Y_ADDRMASK_8) +#define DMM_VER_Y_PAGE_COOR_GET_8(x)\ + (DMM_VER_Y_COOR_GET_8(x)/DMM_PAGE_DIMM_Y_MODE_8) + +#define DMM_VER_X_ADDRSHIFT_16 (14) +#define DMM_VER_X_ADDRMASK_16 (0x1FFF) +#define DMM_VER_X_COOR_GET_16(x) (((unsigned long)x >> \ + DMM_VER_X_ADDRSHIFT_16) & DMM_VER_X_ADDRMASK_16) +#define DMM_VER_X_PAGE_COOR_GET_16(x) (DMM_VER_X_COOR_GET_16(x) / \ + DMM_PAGE_DIMM_X_MODE_16) + +#define DMM_VER_Y_ADDRSHIFT_16 (0) +#define DMM_VER_Y_ADDRMASK_16 (0x3FFF) +#define DMM_VER_Y_COOR_GET_16(x) (((unsigned long)x >> \ + DMM_VER_Y_ADDRSHIFT_16) & DMM_VER_Y_ADDRMASK_16) +#define DMM_VER_Y_PAGE_COOR_GET_16(x) (DMM_VER_Y_COOR_GET_16(x) / \ + DMM_PAGE_DIMM_Y_MODE_16) + +#define DMM_VER_X_ADDRSHIFT_32 (15) +#define DMM_VER_X_ADDRMASK_32 (0xFFF) +#define DMM_VER_X_COOR_GET_32(x) (((unsigned long)x >> \ + DMM_VER_X_ADDRSHIFT_32) & DMM_VER_X_ADDRMASK_32) +#define DMM_VER_X_PAGE_COOR_GET_32(x) (DMM_VER_X_COOR_GET_32(x) / \ + DMM_PAGE_DIMM_X_MODE_32) + +#define DMM_VER_Y_ADDRSHIFT_32 (0) +#define DMM_VER_Y_ADDRMASK_32 (0x7FFF) +#define DMM_VER_Y_COOR_GET_32(x) (((unsigned long)x >> \ + DMM_VER_Y_ADDRSHIFT_32) & DMM_VER_Y_ADDRMASK_32) +#define DMM_VER_Y_PAGE_COOR_GET_32(x) (DMM_VER_Y_COOR_GET_32(x) / \ + DMM_PAGE_DIMM_Y_MODE_32) + +#endif diff --git a/drivers/media/video/tiler/tiler_pack.c b/drivers/media/video/tiler/tiler_pack.c new file mode 100644 index 000000000000..e21846909bc3 --- /dev/null +++ b/drivers/media/video/tiler/tiler_pack.c @@ -0,0 +1,269 @@ +/* + * tiler_pack.c + * + * TILER driver support functions for TI OMAP processors. + * + * 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/init.h> +#include <linux/module.h> +#include <mach/tiler.h> +#include "tiler_def.h" + +void tiler_alloc_packed(s32 *count, enum tiler_fmt fmt, u32 width, u32 height, + void **sysptr, void **allocptr, s32 aligned) +{ + int til_width, bpp, bpt, buf_width, alloc_width, map_width; + int buf_map_width, n_per_m, m_per_a, i = 0, m, n; + + /* Check input parameters for correctness */ + if (!width || !height || !sysptr || !allocptr || !count || + *count <= 0 || fmt < TILFMT_8BIT || fmt > TILFMT_32BIT) { + if (count) + *count = 0; + return; + } + + /* tiler page width in pixels, bytes per pixel, tiler page in bytes */ + til_width = fmt == TILFMT_32BIT ? 32 : 64; + bpp = 1 << (fmt - TILFMT_8BIT); + bpt = til_width * bpp; + + /* width of buffer in tiled pages */ + buf_width = DIVIDE_UP(width, til_width); + + /* :TODO: for now tiler allocation width is 64-multiple */ + alloc_width = ROUND_UP_2P(buf_width, 64); + map_width = TILER_PAGE / bpt; + + /* ensure alignment if needed */ + buf_map_width = ROUND_UP_2P(buf_width, map_width); + + /* number of buffers in a map window */ + n_per_m = aligned ? 1 : (buf_map_width / buf_width); + + /* number of map windows per allocation */ + m_per_a = alloc_width / buf_map_width; + + printk(KERN_INFO "packing %d*%d buffers into an allocation\n", + n_per_m, m_per_a); + + while (i < *count) { + /* allocate required width of a frame to fit remaining + frames */ + int n_alloc, m_alloc, tiles, res; + void *base; + + n_alloc = MIN(*count - i, m_per_a * n_per_m); + m_alloc = DIVIDE_UP(n_alloc, n_per_m); + tiles = ((m_alloc - 1) * map_width + + buf_width * (n_alloc - (m_alloc - 1) * n_per_m)); + + res = tiler_alloc(fmt, til_width * tiles, height, + (u32 *)sysptr + i); + if (res != 0) + break; + + /* mark allocation */ + base = allocptr[i] = sysptr[i]; + i++; + + /* portion out remaining buffers */ + for (m = 0; m < m_per_a; m++, base += bpt * buf_map_width) { + for (n = 0; n < n_per_m; n++) { + /* first buffer is already allocated */ + if (n + m == 0) + continue; + + /* stop if we are done */ + if (i == *count) + break; + + /* set buffer address */ + sysptr[i] = base + bpt * n * buf_width; + allocptr[i++] = NULL; + } + } + } + + /* mark how many buffers we allocated */ + *count = i; +} +EXPORT_SYMBOL(tiler_alloc_packed); + +static int layout_packed_nv12(char *offsets, int y_width, int uv_width, + void **buf, int blocks, int i, + void **y_sysptr, void **uv_sysptr, + void **y_allocptr, void **uv_allocptr) +{ + int j; + for (j = 0; j < blocks; j++, offsets += 3) { + int page_offset = (63 & (int) offsets[0]) + + y_width * ((int) offsets[1]) + + uv_width * (int) offsets[2]; + void *base = buf[offsets[0] >> 6] + 64 * page_offset; + + if (j & 1) { + /* convert 8-bit to 16-bit view */ + /* this formula only works for even ys */ + uv_sysptr[i] = base + (0x3FFF & (unsigned long) base) + + 0x8000000; + uv_allocptr[i] = page_offset ? NULL : uv_sysptr[i]; + i++; + } else { + y_sysptr[i] = base; + y_allocptr[i] = page_offset ? NULL : y_sysptr[i]; + } + } + return i; +} + +void tiler_alloc_packed_nv12(s32 *count, u32 width, u32 height, void **y_sysptr, + void **uv_sysptr, void **y_allocptr, + void **uv_allocptr, s32 aligned) +{ + /* optimized packing table */ + /* we read this table from beginning to end, and determine whether + the optimization meets our requirement (e.g. allocating at least + i buffers, with max w y-width, and alignment a. If not, we get + to the next element. Otherwise we do the allocation. The table + is constructed in such a way that if an interim tiler allocation + fails, the next matching rule for the scenario will be able to + use the buffers already allocated. */ + +#define MAX_BUFS_TO_PACK 3 + void *buf[MAX_BUFS_TO_PACK]; + int n_buf, buf_w[MAX_BUFS_TO_PACK]; + + char packing[] = { + /* min(i), max(w), aligned, buffers to alloc */ + 5, 16, 0, 2, + /* buffer widths in a + b * w(y) + c * w(uv) */ + 64, 0, 0, 64, 0, 0, + /* tiler-page offsets in + a + b * w(y) + c * w(uv) */ + 0, 0, 0, 32, 0, 0, + 16, 0, 0, 40, 0, 0, + 64, 0, 0, 96, 0, 0, + 80, 0, 0, 104, 0, 0, + 112, 0, 0, 56, 0, 0, + + 2, 16, 0, 1, + 32, 0, 2, + 0, 0, 0, 32, 0, 0, + 0, 0, 2, 32, 0, 1, + + 2, 20, 0, 1, + 42, 1, 0, + 0, 0, 0, 32, 0, 0, + 42, 0, 0, 21, 0, 0, + + 3, 24, 0, 2, + 48, 0, 1, 32, 1, 0, + 0, 0, 0, 64, 0, 0, + 24, 0, 0, 76, 0, 0, + 96, 0, 0, 48, 0, 0, + + 4, 32, 0, 3, + 48, 0, 1, 32, 1, 0, 32, 1, 0, + 0, 0, 0, 32, 0, 0, + 96, 0, 0, 48, 0, 0, + 64, 0, 0, 128, 0, 0, + 160, 0, 0, 144, 0, 0, + + /* this is needed for soft landing if prior allocation fails + after two buffers */ + 2, 32, 1, 2, + 32, 0, 1, 32, 0, 1, + 0, 0, 0, 32, 0, 0, + 64, 0, 0, 96, 0, 0, + + 1, 32, 1, 1, + 32, 0, 1, + 0, 0, 0, 32, 0, 0, + + 2, 64, 1, 3, + 0, 1, 0, 32, 0, 1, 0, 1, 0, + 0, 0, 0, 64, 0, 0, + 128, 0, 0, 96, 0, 0, + /* this is the basic NV12 allocation using 2 buffers */ + 1, 0, 1, 2, + 0, 1, 0, 0, 0, 1, + 0, 0, 0, 64, 0, 0, + 0 }; + int y_width, uv_width, i = 0; + + /* Check input parameters for correctness */ + if (!width || !height || !y_sysptr || !y_allocptr || !count || + !uv_sysptr || !uv_allocptr || *count <= 0) { + if (count) + *count = 0; + return; + } + + y_width = DIVIDE_UP(width, 64); + uv_width = DIVIDE_UP(width >> 1, 64); + + while (i < *count) { + int n_alloc = *count - i; + char *p = packing; + n_buf = 0; + + /* skip packings that do not apply */ + while (*p) { + /* see if this packing applies */ + if (p[0] <= n_alloc && + (!p[1] || p[1] >= y_width) && + (!aligned || p[2])) { + + /* allocate buffers */ + while (n_buf < p[3]) { + buf_w[n_buf] = p[4 + 3 * n_buf] + + y_width * p[5 + 3 * n_buf] + + uv_width * p[6 + 3 * n_buf]; + + if (0 != tiler_alloc( + TILFMT_8BIT, buf_w[n_buf] * 64, + height, (u32 *)buf + n_buf)) + break; + n_buf++; + } + + /* if successfully allocated buffers */ + if (n_buf >= p[3]) { + i = layout_packed_nv12(p + 4 + 3 * p[3], + y_width, + uv_width, + buf, 2 * p[0], i, + y_sysptr, + uv_sysptr, + y_allocptr, + uv_allocptr); + break; + } + } + + p += 4 + 3 * p[3] + 6 * p[0]; + } + + /* if allocation failed free any outstanding buffers and stop */ + if (!*p) { + while (n_buf > 0) + tiler_free((unsigned long)(buf[--n_buf])); + break; + } + } + + /* mark how many buffers we allocated */ + *count = i; +} +EXPORT_SYMBOL(tiler_alloc_packed_nv12); diff --git a/drivers/media/video/tiler/tiler_rot.c b/drivers/media/video/tiler/tiler_rot.c new file mode 100644 index 000000000000..aa38d72187db --- /dev/null +++ b/drivers/media/video/tiler/tiler_rot.c @@ -0,0 +1,239 @@ +/* + * tiler_rot.c + * + * TILER driver support functions for TI OMAP processors. + * + * 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/init.h> +#include <linux/module.h> +#include <mach/tiler.h> +#include "tiler_def.h" + +#define DMM_SHIFT_PER_X_8 0 +#define DMM_SHIFT_PER_Y_8 0 +#define DMM_SHIFT_PER_X_16 0 +#define DMM_SHIFT_PER_Y_16 1 +#define DMM_SHIFT_PER_X_32 1 +#define DMM_SHIFT_PER_Y_32 1 +#define DMM_SHIFT_PER_X_PAGE 6 +#define DMM_SHIFT_PER_Y_PAGE 6 + +#define DMM_TILER_THE(NAME) (1 << DMM_TILER_##NAME##_BITS) +#define DMM_TILER_THE_(N, NAME) (1 << DMM_TILER_##NAME##_BITS_(N)) + +#define DMM_TILER_CONT_WIDTH_BITS 14 +#define DMM_TILER_CONT_HEIGHT_BITS 13 + +#define DMM_SHIFT_PER_P_(N) (DMM_SHIFT_PER_X_##N + DMM_SHIFT_PER_Y_##N) + +#define DMM_TILER_CONT_HEIGHT_BITS_(N) \ + (DMM_TILER_CONT_HEIGHT_BITS - DMM_SHIFT_PER_Y_##N) +#define DMM_TILER_CONT_WIDTH_BITS_(N) \ + (DMM_TILER_CONT_WIDTH_BITS - DMM_SHIFT_PER_X_##N) + +#define DMM_TILER_MASK(bits) ((1 << (bits)) - 1) + +#define DMM_TILER_GET_OFFSET_(N, var) \ + ((((u32) var) & DMM_TILER_MASK(DMM_TILER_CONT_WIDTH_BITS + \ + DMM_TILER_CONT_HEIGHT_BITS)) >> DMM_SHIFT_PER_P_(N)) + +#define DMM_TILER_GET_0_X_(N, var) \ + (DMM_TILER_GET_OFFSET_(N, var) & \ + DMM_TILER_MASK(DMM_TILER_CONT_WIDTH_BITS_(N))) +#define DMM_TILER_GET_0_Y_(N, var) \ + (DMM_TILER_GET_OFFSET_(N, var) >> DMM_TILER_CONT_WIDTH_BITS_(N)) +#define DMM_TILER_GET_90_X_(N, var) \ + (DMM_TILER_GET_OFFSET_(N, var) & \ + DMM_TILER_MASK(DMM_TILER_CONT_HEIGHT_BITS_(N))) +#define DMM_TILER_GET_90_Y_(N, var) \ + (DMM_TILER_GET_OFFSET_(N, var) >> DMM_TILER_CONT_HEIGHT_BITS_(N)) + +#define DMM_TILER_STRIDE_0_(N) \ + (DMM_TILER_THE(CONT_WIDTH) << DMM_SHIFT_PER_Y_##N) +#define DMM_TILER_STRIDE_90_(N) \ + (DMM_TILER_THE(CONT_HEIGHT) << DMM_SHIFT_PER_X_##N) + +void tiler_get_natural_xy(u32 tsptr, u32 *x, u32 *y) +{ + u32 x_bits, y_bits, offset; + enum tiler_fmt fmt; + + fmt = TILER_GET_ACC_MODE(tsptr); + + switch (fmt) { + case TILFMT_8BIT: + x_bits = DMM_TILER_CONT_WIDTH_BITS_(8); + y_bits = DMM_TILER_CONT_HEIGHT_BITS_(8); + offset = DMM_TILER_GET_OFFSET_(8, tsptr); + break; + case TILFMT_16BIT: + x_bits = DMM_TILER_CONT_WIDTH_BITS_(16); + y_bits = DMM_TILER_CONT_HEIGHT_BITS_(16); + offset = DMM_TILER_GET_OFFSET_(16, tsptr); + break; + case TILFMT_32BIT: + x_bits = DMM_TILER_CONT_WIDTH_BITS_(32); + y_bits = DMM_TILER_CONT_HEIGHT_BITS_(32); + offset = DMM_TILER_GET_OFFSET_(32, tsptr); + break; + case TILFMT_PAGE: + default: + x_bits = DMM_TILER_CONT_WIDTH_BITS_(PAGE); + y_bits = DMM_TILER_CONT_HEIGHT_BITS_(PAGE); + offset = DMM_TILER_GET_OFFSET_(PAGE, tsptr); + break; + } + + if (DMM_GET_ROTATED(tsptr)) { + *x = offset >> y_bits; + *y = offset & DMM_TILER_MASK(y_bits); + } else { + *x = offset & DMM_TILER_MASK(x_bits); + *y = offset >> x_bits; + } + + if (DMM_GET_X_INVERTED(tsptr)) + *x ^= DMM_TILER_MASK(x_bits); + if (DMM_GET_Y_INVERTED(tsptr)) + *y ^= DMM_TILER_MASK(y_bits); +} + +u32 tiler_get_address(struct tiler_view_orient orient, + enum tiler_fmt fmt, u32 x, u32 y) +{ + u32 x_bits, y_bits, tmp, x_mask, y_mask, alignment; + + switch (fmt) { + case TILFMT_8BIT: + x_bits = DMM_TILER_CONT_WIDTH_BITS_(8); + y_bits = DMM_TILER_CONT_HEIGHT_BITS_(8); + alignment = DMM_SHIFT_PER_P_(8); + break; + case TILFMT_16BIT: + x_bits = DMM_TILER_CONT_WIDTH_BITS_(16); + y_bits = DMM_TILER_CONT_HEIGHT_BITS_(16); + alignment = DMM_SHIFT_PER_P_(16); + break; + case TILFMT_32BIT: + x_bits = DMM_TILER_CONT_WIDTH_BITS_(32); + y_bits = DMM_TILER_CONT_HEIGHT_BITS_(32); + alignment = DMM_SHIFT_PER_P_(32); + break; + case TILFMT_PAGE: + default: + x_bits = DMM_TILER_CONT_WIDTH_BITS_(PAGE); + y_bits = DMM_TILER_CONT_HEIGHT_BITS_(PAGE); + alignment = DMM_SHIFT_PER_P_(PAGE); + break; + } + + x_mask = DMM_TILER_MASK(x_bits); + y_mask = DMM_TILER_MASK(y_bits); + if (x < 0 || x > x_mask || y < 0 || y > y_mask) + return 0; + + if (orient.x_invert) + x ^= x_mask; + if (orient.y_invert) + y ^= y_mask; + + if (orient.rotate_90) + tmp = ((x << y_bits) + y); + else + tmp = ((y << x_bits) + x); + + return (u32) + TIL_ADDR((tmp << alignment), (orient.rotate_90 ? 1 : 0), + (orient.y_invert ? 1 : 0), (orient.x_invert ? 1 : 0), + (fmt - 1)); +} + +u32 tiler_reorient_addr(u32 tsptr, struct tiler_view_orient orient) +{ + u32 x, y; + + tiler_get_natural_xy(tsptr, &x, &y); + return tiler_get_address(orient, TILER_GET_ACC_MODE(tsptr), x, y); +} +EXPORT_SYMBOL(tiler_reorient_addr); + +u32 tiler_get_natural_addr(void *sys_ptr) +{ + return (u32)sys_ptr & DMM_ALIAS_VIEW_CLEAR; +} +EXPORT_SYMBOL(tiler_get_natural_addr); + +u32 tiler_reorient_topleft(u32 tsptr, struct tiler_view_orient orient, + u32 width, u32 height) +{ + enum tiler_fmt fmt; + u32 x, y; + + fmt = TILER_GET_ACC_MODE(tsptr); + + tiler_get_natural_xy(tsptr, &x, &y); + + if (DMM_GET_X_INVERTED(tsptr)) + x -= width - 1; + if (DMM_GET_Y_INVERTED(tsptr)) + y -= height - 1; + + if (orient.x_invert) + x += width - 1; + if (orient.y_invert) + y += height - 1; + + return tiler_get_address(orient, fmt, x, y); +} +EXPORT_SYMBOL(tiler_reorient_topleft); + +u32 tiler_stride(u32 tsptr) +{ + enum tiler_fmt fmt; + + fmt = TILER_GET_ACC_MODE(tsptr); + + switch (fmt) { + case TILFMT_8BIT: + return DMM_GET_ROTATED(tsptr) ? + DMM_TILER_STRIDE_90_(8) : DMM_TILER_STRIDE_0_(8); + case TILFMT_16BIT: + return DMM_GET_ROTATED(tsptr) ? + DMM_TILER_STRIDE_90_(16) : DMM_TILER_STRIDE_0_(16); + case TILFMT_32BIT: + return DMM_GET_ROTATED(tsptr) ? + DMM_TILER_STRIDE_90_(32) : DMM_TILER_STRIDE_0_(32); + default: + return 0; + } +} +EXPORT_SYMBOL(tiler_stride); + +void tiler_rotate_view(struct tiler_view_orient *orient, u32 rotation) +{ + rotation = (rotation / 90) & 3; + + if (rotation & 2) { + orient->x_invert = !orient->x_invert; + orient->y_invert = !orient->y_invert; + } + + if (rotation & 1) { + if (orient->rotate_90) + orient->y_invert = !orient->y_invert; + else + orient->x_invert = !orient->x_invert; + orient->rotate_90 = !orient->rotate_90; + } +} +EXPORT_SYMBOL(tiler_rotate_view); |