// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2015 - 2024 Intel Corporation

#include <linux/bitmap.h>
#include <linux/errno.h>
#include <linux/gfp.h>
#include <linux/slab.h>
#include <linux/device.h>

#include <uapi/linux/ipu-psys.h>

#include "ipu-fw-psys.h"
#include "ipu-psys.h"

struct ipu6_psys_hw_res_variant hw_var;
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 10, 0)
void ipu6_psys_hw_res_variant_init(void)
{
	if (ipu_ver == IPU_VER_6SE) {
		hw_var.queue_num = IPU6SE_FW_PSYS_N_PSYS_CMD_QUEUE_ID;
		hw_var.cell_num = IPU6SE_FW_PSYS_N_CELL_ID;
	} else if (ipu_ver == IPU_VER_6) {
		hw_var.queue_num = IPU6_FW_PSYS_N_PSYS_CMD_QUEUE_ID;
		hw_var.cell_num = IPU6_FW_PSYS_N_CELL_ID;
	} else if (ipu_ver == IPU_VER_6EP || ipu_ver == IPU_VER_6EP_MTL) {
		hw_var.queue_num = IPU6_FW_PSYS_N_PSYS_CMD_QUEUE_ID;
		hw_var.cell_num = IPU6EP_FW_PSYS_N_CELL_ID;
	} else {
		WARN(1, "ipu6 psys res var is not initialised correctly.");
	}

	hw_var.set_proc_dev_chn = ipu6_fw_psys_set_proc_dev_chn;
	hw_var.set_proc_dfm_bitmap = ipu6_fw_psys_set_proc_dfm_bitmap;
	hw_var.set_proc_ext_mem = ipu6_fw_psys_set_process_ext_mem;
	hw_var.get_pgm_by_proc =
		ipu6_fw_psys_get_pgm_by_process;
}

static const struct ipu_fw_resource_definitions *get_res(void)
{
	if (ipu_ver == IPU_VER_6SE)
		return ipu6se_res_defs;

	if (ipu_ver == IPU_VER_6EP || ipu_ver == IPU_VER_6EP_MTL)
		return ipu6ep_res_defs;

	return ipu6_res_defs;
}
#else
void ipu6_psys_hw_res_variant_init(void)
{
	if (ipu_ver == IPU6_VER_6SE) {
		hw_var.queue_num = IPU6SE_FW_PSYS_N_PSYS_CMD_QUEUE_ID;
		hw_var.cell_num = IPU6SE_FW_PSYS_N_CELL_ID;
	} else if (ipu_ver == IPU6_VER_6) {
		hw_var.queue_num = IPU6_FW_PSYS_N_PSYS_CMD_QUEUE_ID;
		hw_var.cell_num = IPU6_FW_PSYS_N_CELL_ID;
	} else if (ipu_ver == IPU6_VER_6EP || ipu_ver == IPU6_VER_6EP_MTL) {
		hw_var.queue_num = IPU6_FW_PSYS_N_PSYS_CMD_QUEUE_ID;
		hw_var.cell_num = IPU6EP_FW_PSYS_N_CELL_ID;
	} else {
		WARN(1, "ipu6 psys res var is not initialised correctly.");
	}

	hw_var.set_proc_dev_chn = ipu6_fw_psys_set_proc_dev_chn;
	hw_var.set_proc_dfm_bitmap = ipu6_fw_psys_set_proc_dfm_bitmap;
	hw_var.set_proc_ext_mem = ipu6_fw_psys_set_process_ext_mem;
	hw_var.get_pgm_by_proc = ipu6_fw_psys_get_pgm_by_process;
}

static const struct ipu_fw_resource_definitions *get_res(void)
{
	if (ipu_ver == IPU6_VER_6SE)
		return ipu6se_res_defs;

	if (ipu_ver == IPU6_VER_6EP || ipu_ver == IPU6_VER_6EP_MTL)
		return ipu6ep_res_defs;

	return ipu6_res_defs;
}
#endif

static int ipu_resource_init(struct ipu_resource *res, u32 id, int elements)
{
	if (elements <= 0) {
		res->bitmap = NULL;
		return 0;
	}

	res->bitmap = bitmap_zalloc(elements, GFP_KERNEL);
	if (!res->bitmap)
		return -ENOMEM;
	res->elements = elements;
	res->id = id;
	return 0;
}

static unsigned long
ipu_resource_alloc_with_pos(struct ipu_resource *res, int n,
			    int pos,
			    struct ipu_resource_alloc *alloc,
			    enum ipu_resource_type type)
{
	unsigned long p;

	if (n <= 0) {
		alloc->elements = 0;
		return 0;
	}

	if (!res->bitmap || pos >= res->elements)
		return (unsigned long)(-ENOSPC);

	p = bitmap_find_next_zero_area(res->bitmap, res->elements, pos, n, 0);
	alloc->resource = NULL;

	if (p != pos)
		return (unsigned long)(-ENOSPC);
	bitmap_set(res->bitmap, p, n);
	alloc->resource = res;
	alloc->elements = n;
	alloc->pos = p;
	alloc->type = type;

	return pos;
}

static unsigned long
ipu_resource_alloc(struct ipu_resource *res, int n,
		   struct ipu_resource_alloc *alloc,
		   enum ipu_resource_type type)
{
	unsigned long p;

	if (n <= 0) {
		alloc->elements = 0;
		return 0;
	}

	if (!res->bitmap)
		return (unsigned long)(-ENOSPC);

	p = bitmap_find_next_zero_area(res->bitmap, res->elements, 0, n, 0);
	alloc->resource = NULL;

	if (p >= res->elements)
		return (unsigned long)(-ENOSPC);
	bitmap_set(res->bitmap, p, n);
	alloc->resource = res;
	alloc->elements = n;
	alloc->pos = p;
	alloc->type = type;

	return p;
}

static void ipu_resource_free(struct ipu_resource_alloc *alloc)
{
	if (alloc->elements <= 0)
		return;

	if (alloc->type == IPU_RESOURCE_DFM)
		*alloc->resource->bitmap &= ~(unsigned long)(alloc->elements);
	else
		bitmap_clear(alloc->resource->bitmap, alloc->pos,
			     alloc->elements);
	alloc->resource = NULL;
}

static void ipu_resource_cleanup(struct ipu_resource *res)
{
	bitmap_free(res->bitmap);
	res->bitmap = NULL;
}

/********** IPU PSYS-specific resource handling **********/
int ipu_psys_res_pool_init(struct ipu_psys_resource_pool *pool)
{
	int i, j, k, ret;
	const struct ipu_fw_resource_definitions *res_defs;

	res_defs = get_res();

	spin_lock_init(&pool->queues_lock);
	pool->cells = 0;

	for (i = 0; i < res_defs->num_dev_channels; i++) {
		ret = ipu_resource_init(&pool->dev_channels[i], i,
					res_defs->dev_channels[i]);
		if (ret)
			goto error;
	}

	for (j = 0; j < res_defs->num_ext_mem_ids; j++) {
		ret = ipu_resource_init(&pool->ext_memory[j], j,
					res_defs->ext_mem_ids[j]);
		if (ret)
			goto memory_error;
	}

	for (k = 0; k < res_defs->num_dfm_ids; k++) {
		ret = ipu_resource_init(&pool->dfms[k], k, res_defs->dfms[k]);
		if (ret)
			goto dfm_error;
	}

	spin_lock(&pool->queues_lock);
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 10, 0)
	if (ipu_ver == IPU_VER_6SE)
#else
	if (ipu_ver == IPU6_VER_6SE)
#endif
		bitmap_zero(pool->cmd_queues,
			    IPU6SE_FW_PSYS_N_PSYS_CMD_QUEUE_ID);
	else
		bitmap_zero(pool->cmd_queues,
			    IPU6_FW_PSYS_N_PSYS_CMD_QUEUE_ID);
	spin_unlock(&pool->queues_lock);

	return 0;

dfm_error:
	for (k--; k >= 0; k--)
		ipu_resource_cleanup(&pool->dfms[k]);

memory_error:
	for (j--; j >= 0; j--)
		ipu_resource_cleanup(&pool->ext_memory[j]);

error:
	for (i--; i >= 0; i--)
		ipu_resource_cleanup(&pool->dev_channels[i]);
	return ret;
}

void ipu_psys_resource_copy(struct ipu_psys_resource_pool *src,
			    struct ipu_psys_resource_pool *dest)
{
	int i;
	const struct ipu_fw_resource_definitions *res_defs;

	res_defs = get_res();

	dest->cells = src->cells;
	for (i = 0; i < res_defs->num_dev_channels; i++)
		*dest->dev_channels[i].bitmap = *src->dev_channels[i].bitmap;

	for (i = 0; i < res_defs->num_ext_mem_ids; i++)
		*dest->ext_memory[i].bitmap = *src->ext_memory[i].bitmap;

	for (i = 0; i < res_defs->num_dfm_ids; i++)
		*dest->dfms[i].bitmap = *src->dfms[i].bitmap;
}

void ipu_psys_res_pool_cleanup(struct ipu_psys_resource_pool *pool)
{
	u32 i;
	const struct ipu_fw_resource_definitions *res_defs;

	res_defs = get_res();
	for (i = 0; i < res_defs->num_dev_channels; i++)
		ipu_resource_cleanup(&pool->dev_channels[i]);

	for (i = 0; i < res_defs->num_ext_mem_ids; i++)
		ipu_resource_cleanup(&pool->ext_memory[i]);

	for (i = 0; i < res_defs->num_dfm_ids; i++)
		ipu_resource_cleanup(&pool->dfms[i]);
}

static int __alloc_one_resrc(const struct device *dev,
			     struct ipu_fw_psys_process *process,
			     struct ipu_resource *resource,
			     struct ipu_fw_generic_program_manifest *pm,
			     u32 resource_id,
			     struct ipu_psys_resource_alloc *alloc)
{
	const u16 resource_req = pm->dev_chn_size[resource_id];
	const u16 resource_offset_req = pm->dev_chn_offset[resource_id];
	unsigned long retl;

	if (!resource_req)
		return -ENXIO;

	if (alloc->resources >= IPU_MAX_RESOURCES) {
		dev_err(dev, "out of resource handles\n");
		return -ENOSPC;
	}
	if (resource_offset_req != (u16)(-1))
		retl = ipu_resource_alloc_with_pos
		    (resource,
		     resource_req,
		     resource_offset_req,
		     &alloc->resource_alloc[alloc->resources],
		     IPU_RESOURCE_DEV_CHN);
	else
		retl = ipu_resource_alloc
		    (resource, resource_req,
		     &alloc->resource_alloc[alloc->resources],
		     IPU_RESOURCE_DEV_CHN);
	if (IS_ERR_VALUE(retl)) {
		dev_dbg(dev, "out of device channel resources\n");
		return (int)retl;
	}
	alloc->resources++;

	return 0;
}

static int ipu_psys_allocate_one_dfm(const struct device *dev,
				     struct ipu_fw_psys_process *process,
				     struct ipu_resource *resource,
				     struct ipu_fw_generic_program_manifest *pm,
				     u32 resource_id,
				     struct ipu_psys_resource_alloc *alloc)
{
	u32 dfm_bitmap_req = pm->dfm_port_bitmap[resource_id];
	u32 active_dfm_bitmap_req = pm->dfm_active_port_bitmap[resource_id];
	const u8 is_relocatable = pm->is_dfm_relocatable[resource_id];
	struct ipu_resource_alloc *alloc_resource;
	unsigned long p = 0;

	if (!dfm_bitmap_req)
		return -ENXIO;

	if (alloc->resources >= IPU_MAX_RESOURCES) {
		dev_err(dev, "out of resource handles\n");
		return -ENOSPC;
	}

	if (!resource->bitmap)
		return -ENOSPC;

	if (!is_relocatable) {
		if (*resource->bitmap & dfm_bitmap_req) {
			dev_warn(dev,
				 "out of dfm resources, req 0x%x, get 0x%lx\n",
				 dfm_bitmap_req, *resource->bitmap);
			return -ENOSPC;
		}
		*resource->bitmap |= dfm_bitmap_req;
	} else {
		unsigned int n = hweight32(dfm_bitmap_req);

		p = bitmap_find_next_zero_area(resource->bitmap,
					       resource->elements, 0, n, 0);

		if (p >= resource->elements)
			return -ENOSPC;

		bitmap_set(resource->bitmap, p, n);
		dfm_bitmap_req = dfm_bitmap_req << p;
		active_dfm_bitmap_req = active_dfm_bitmap_req << p;
	}

	alloc_resource = &alloc->resource_alloc[alloc->resources];
	alloc_resource->resource = resource;
	/* Using elements to indicate the bitmap */
	alloc_resource->elements = dfm_bitmap_req;
	alloc_resource->pos = p;
	alloc_resource->type = IPU_RESOURCE_DFM;

	alloc->resources++;

	return 0;
}

/*
 * ext_mem_type_id is a generic type id for memory (like DMEM, VMEM)
 * ext_mem_bank_id is detailed type id for  memory (like DMEM0, DMEM1 etc.)
 */
static int __alloc_mem_resrc(const struct device *dev,
			     struct ipu_fw_psys_process *process,
			     struct ipu_resource *resource,
			     struct ipu_fw_generic_program_manifest *pm,
			     u32 ext_mem_type_id, u32 ext_mem_bank_id,
			     struct ipu_psys_resource_alloc *alloc)
{
	const u16 memory_resource_req = pm->ext_mem_size[ext_mem_type_id];
	const u16 memory_offset_req = pm->ext_mem_offset[ext_mem_type_id];

	unsigned long retl;

	if (!memory_resource_req)
		return -ENXIO;

	if (alloc->resources >= IPU_MAX_RESOURCES) {
		dev_err(dev, "out of resource handles\n");
		return -ENOSPC;
	}
	if (memory_offset_req != (u16)(-1))
		retl = ipu_resource_alloc_with_pos
		    (resource,
		     memory_resource_req, memory_offset_req,
		     &alloc->resource_alloc[alloc->resources],
		     IPU_RESOURCE_EXT_MEM);
	else
		retl = ipu_resource_alloc
		    (resource, memory_resource_req,
		     &alloc->resource_alloc[alloc->resources],
		     IPU_RESOURCE_EXT_MEM);
	if (IS_ERR_VALUE(retl)) {
		dev_dbg(dev, "out of memory resources\n");
		return (int)retl;
	}

	alloc->resources++;

	return 0;
}

int ipu_psys_allocate_cmd_queue_res(struct ipu_psys_resource_pool *pool)
{
	unsigned long p;
	int size, start;

	size = IPU6_FW_PSYS_N_PSYS_CMD_QUEUE_ID;
	start = IPU6_FW_PSYS_CMD_QUEUE_PPG0_COMMAND_ID;

#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 10, 0)
	if (ipu_ver == IPU_VER_6SE) {
#else
	if (ipu_ver == IPU6_VER_6SE) {
#endif
		size = IPU6SE_FW_PSYS_N_PSYS_CMD_QUEUE_ID;
		start = IPU6SE_FW_PSYS_CMD_QUEUE_PPG0_COMMAND_ID;
	}

	spin_lock(&pool->queues_lock);
	/* find available cmd queue from ppg0_cmd_id */
	p = bitmap_find_next_zero_area(pool->cmd_queues, size, start, 1, 0);

	if (p >= size) {
		spin_unlock(&pool->queues_lock);
		return -ENOSPC;
	}

	bitmap_set(pool->cmd_queues, p, 1);
	spin_unlock(&pool->queues_lock);

	return p;
}

void ipu_psys_free_cmd_queue_res(struct ipu_psys_resource_pool *pool,
				 u8 queue_id)
{
	spin_lock(&pool->queues_lock);
	bitmap_clear(pool->cmd_queues, queue_id, 1);
	spin_unlock(&pool->queues_lock);
}

int ipu_psys_try_allocate_resources(struct device *dev,
				    struct ipu_fw_psys_process_group *pg,
				    void *pg_manifest,
				    struct ipu_psys_resource_pool *pool)
{
	u32 id, idx;
	u32 mem_type_id;
	int ret, i;
	u16 *process_offset_table;
	u8 processes;
	u32 cells = 0;
	struct ipu_psys_resource_alloc *alloc;
	const struct ipu_fw_resource_definitions *res_defs;

	if (!pg)
		return -EINVAL;
	process_offset_table = (u16 *)((u8 *)pg + pg->processes_offset);
	processes = pg->process_count;

	alloc = kzalloc(sizeof(*alloc), GFP_KERNEL);
	if (!alloc)
		return -ENOMEM;

	res_defs = get_res();
	for (i = 0; i < processes; i++) {
		u32 cell;
		struct ipu_fw_psys_process *process =
			(struct ipu_fw_psys_process *)
			((char *)pg + process_offset_table[i]);
		struct ipu_fw_generic_program_manifest pm;

		memset(&pm, 0, sizeof(pm));

		if (!process) {
			dev_err(dev, "can not get process\n");
			ret = -ENOENT;
			goto free_out;
		}

		ret = ipu_fw_psys_get_pgm_by_process(&pm, pg_manifest, process);
		if (ret < 0) {
			dev_err(dev, "can not get manifest\n");
			goto free_out;
		}

		if (pm.cell_id == res_defs->num_cells &&
		    pm.cell_type_id == res_defs->num_cells_type) {
			cell = res_defs->num_cells;
		} else if ((pm.cell_id != res_defs->num_cells &&
			    pm.cell_type_id == res_defs->num_cells_type)) {
			cell = pm.cell_id;
		} else {
			/* Find a free cell of desired type */
			u32 type = pm.cell_type_id;

			for (cell = 0; cell < res_defs->num_cells; cell++)
				if (res_defs->cells[cell] == type &&
				    ((pool->cells | cells) & (1 << cell)) == 0)
					break;
			if (cell >= res_defs->num_cells) {
				dev_dbg(dev, "no free cells of right type\n");
				ret = -ENOSPC;
				goto free_out;
			}
		}
		if (cell < res_defs->num_cells)
			cells |= 1 << cell;
		if (pool->cells & cells) {
			dev_dbg(dev, "out of cell resources\n");
			ret = -ENOSPC;
			goto free_out;
		}

		if (pm.dev_chn_size) {
			for (id = 0; id < res_defs->num_dev_channels; id++) {
				ret = __alloc_one_resrc(dev, process,
							&pool->dev_channels[id],
							&pm, id, alloc);
				if (ret == -ENXIO)
					continue;

				if (ret)
					goto free_out;
			}
		}

		if (pm.dfm_port_bitmap) {
			for (id = 0; id < res_defs->num_dfm_ids; id++) {
				ret = ipu_psys_allocate_one_dfm
					(dev, process,
					 &pool->dfms[id], &pm, id, alloc);
				if (ret == -ENXIO)
					continue;

				if (ret)
					goto free_out;
			}
		}

		if (pm.ext_mem_size) {
			for (mem_type_id = 0;
			     mem_type_id < res_defs->num_ext_mem_types;
			     mem_type_id++) {
				u32 bank = res_defs->num_ext_mem_ids;

				if (cell != res_defs->num_cells) {
					idx = res_defs->cell_mem_row * cell +
						mem_type_id;
					bank = res_defs->cell_mem[idx];
				}

				if (bank == res_defs->num_ext_mem_ids)
					continue;

				ret = __alloc_mem_resrc(dev, process,
							&pool->ext_memory[bank],
							&pm, mem_type_id, bank,
							alloc);
				if (ret == -ENXIO)
					continue;

				if (ret)
					goto free_out;
			}
		}
	}

	pool->cells |= cells;

	kfree(alloc);
	return 0;

free_out:
	dev_dbg(dev, "failed to try_allocate resource\n");
	kfree(alloc);
	return ret;
}

/*
 * Allocate resources for pg from `pool'. Mark the allocated
 * resources into `alloc'. Returns 0 on success, -ENOSPC
 * if there are no enough resources, in which cases resources
 * are not allocated at all, or some other error on other conditions.
 */
int ipu_psys_allocate_resources(const struct device *dev,
				struct ipu_fw_psys_process_group *pg,
				void *pg_manifest,
				struct ipu_psys_resource_alloc *alloc,
				struct ipu_psys_resource_pool *pool)
{
	u32 id;
	u32 mem_type_id;
	int ret, i;
	u16 *process_offset_table;
	u8 processes;
	u32 cells = 0;
	int p, idx;
	u32 bmp, a_bmp;
	const struct ipu_fw_resource_definitions *res_defs;

	if (!pg)
		return -EINVAL;

	res_defs = get_res();
	process_offset_table = (u16 *)((u8 *)pg + pg->processes_offset);
	processes = pg->process_count;

	for (i = 0; i < processes; i++) {
		u32 cell;
		struct ipu_fw_psys_process *process =
		    (struct ipu_fw_psys_process *)
		    ((char *)pg + process_offset_table[i]);
		struct ipu_fw_generic_program_manifest pm;

		memset(&pm, 0, sizeof(pm));
		if (!process) {
			dev_err(dev, "can not get process\n");
			ret = -ENOENT;
			goto free_out;
		}

		ret = ipu_fw_psys_get_pgm_by_process(&pm, pg_manifest, process);
		if (ret < 0) {
			dev_err(dev, "can not get manifest\n");
			goto free_out;
		}

		if (pm.cell_id == res_defs->num_cells &&
		    pm.cell_type_id == res_defs->num_cells_type) {
			cell = res_defs->num_cells;
		} else if ((pm.cell_id != res_defs->num_cells &&
			    pm.cell_type_id == res_defs->num_cells_type)) {
			cell = pm.cell_id;
		} else {
			/* Find a free cell of desired type */
			u32 type = pm.cell_type_id;

			for (cell = 0; cell < res_defs->num_cells; cell++)
				if (res_defs->cells[cell] == type &&
				    ((pool->cells | cells) & (1 << cell)) == 0)
					break;
			if (cell >= res_defs->num_cells) {
				dev_dbg(dev, "no free cells of right type\n");
				ret = -ENOSPC;
				goto free_out;
			}
			ret = ipu_fw_psys_set_process_cell_id(process, 0, cell);
			if (ret)
				goto free_out;
		}
		if (cell < res_defs->num_cells)
			cells |= 1 << cell;
		if (pool->cells & cells) {
			dev_dbg(dev, "out of cell resources\n");
			ret = -ENOSPC;
			goto free_out;
		}

		if (pm.dev_chn_size) {
			for (id = 0; id < res_defs->num_dev_channels; id++) {
				ret = __alloc_one_resrc(dev, process,
							&pool->dev_channels[id],
							&pm, id, alloc);
				if (ret == -ENXIO)
					continue;

				if (ret)
					goto free_out;

				idx = alloc->resources - 1;
				p = alloc->resource_alloc[idx].pos;
				ret = ipu_fw_psys_set_proc_dev_chn(process, id,
								   p);
				if (ret)
					goto free_out;
			}
		}

		if (pm.dfm_port_bitmap) {
			for (id = 0; id < res_defs->num_dfm_ids; id++) {
				ret = ipu_psys_allocate_one_dfm(dev, process,
								&pool->dfms[id],
								&pm, id, alloc);
				if (ret == -ENXIO)
					continue;

				if (ret)
					goto free_out;

				idx = alloc->resources - 1;
				p = alloc->resource_alloc[idx].pos;
				bmp = pm.dfm_port_bitmap[id];
				bmp = bmp << p;
				a_bmp = pm.dfm_active_port_bitmap[id];
				a_bmp = a_bmp << p;
				ret = ipu_fw_psys_set_proc_dfm_bitmap(process,
								      id, bmp,
								      a_bmp);
				if (ret)
					goto free_out;
			}
		}

		if (pm.ext_mem_size) {
			for (mem_type_id = 0;
			     mem_type_id < res_defs->num_ext_mem_types;
			     mem_type_id++) {
				u32 bank = res_defs->num_ext_mem_ids;

				if (cell != res_defs->num_cells) {
					idx = res_defs->cell_mem_row * cell +
						mem_type_id;
					bank = res_defs->cell_mem[idx];
				}
				if (bank == res_defs->num_ext_mem_ids)
					continue;

				ret = __alloc_mem_resrc(dev, process,
							&pool->ext_memory[bank],
							&pm, mem_type_id,
							bank, alloc);
				if (ret == -ENXIO)
					continue;

				if (ret)
					goto free_out;

				/* no return value check here because fw api
				 * will do some checks, and would return
				 * non-zero except mem_type_id == 0.
				 * This maybe caused by that above flow of
				 * allocating mem_bank_id is improper.
				 */
				idx = alloc->resources - 1;
				p = alloc->resource_alloc[idx].pos;
				ipu_fw_psys_set_process_ext_mem(process,
								mem_type_id,
								bank, p);
			}
		}
	}
	alloc->cells |= cells;
	pool->cells |= cells;
	return 0;

free_out:
	dev_err(dev, "failed to allocate resources, ret %d\n", ret);
	ipu_psys_reset_process_cell(dev, pg, pg_manifest, i + 1);
	ipu_psys_free_resources(alloc, pool);
	return ret;
}

int ipu_psys_move_resources(const struct device *dev,
			    struct ipu_psys_resource_alloc *alloc,
			    struct ipu_psys_resource_pool
			    *source_pool, struct ipu_psys_resource_pool
			    *target_pool)
{
	int i;

	if (target_pool->cells & alloc->cells) {
		dev_dbg(dev, "out of cell resources\n");
		return -ENOSPC;
	}

	for (i = 0; i < alloc->resources; i++) {
		unsigned long bitmap = 0;
		unsigned int id = alloc->resource_alloc[i].resource->id;
		unsigned long fbit, end;

		switch (alloc->resource_alloc[i].type) {
		case IPU_RESOURCE_DEV_CHN:
			bitmap_set(&bitmap, alloc->resource_alloc[i].pos,
				   alloc->resource_alloc[i].elements);
			if (*target_pool->dev_channels[id].bitmap & bitmap)
				return -ENOSPC;
			break;
		case IPU_RESOURCE_EXT_MEM:
			end = alloc->resource_alloc[i].elements +
			    alloc->resource_alloc[i].pos;

			fbit = find_next_bit(target_pool->ext_memory[id].bitmap,
					     end, alloc->resource_alloc[i].pos);
			/* if find_next_bit returns "end" it didn't find 1bit */
			if (end != fbit)
				return -ENOSPC;
			break;
		case IPU_RESOURCE_DFM:
			bitmap = alloc->resource_alloc[i].elements;
			if (*target_pool->dfms[id].bitmap & bitmap)
				return -ENOSPC;
			break;
		default:
			dev_err(dev, "Illegal resource type\n");
			return -EINVAL;
		}
	}

	for (i = 0; i < alloc->resources; i++) {
		u32 id = alloc->resource_alloc[i].resource->id;

		switch (alloc->resource_alloc[i].type) {
		case IPU_RESOURCE_DEV_CHN:
			bitmap_set(target_pool->dev_channels[id].bitmap,
				   alloc->resource_alloc[i].pos,
				   alloc->resource_alloc[i].elements);
			ipu_resource_free(&alloc->resource_alloc[i]);
			alloc->resource_alloc[i].resource =
			    &target_pool->dev_channels[id];
			break;
		case IPU_RESOURCE_EXT_MEM:
			bitmap_set(target_pool->ext_memory[id].bitmap,
				   alloc->resource_alloc[i].pos,
				   alloc->resource_alloc[i].elements);
			ipu_resource_free(&alloc->resource_alloc[i]);
			alloc->resource_alloc[i].resource =
			    &target_pool->ext_memory[id];
			break;
		case IPU_RESOURCE_DFM:
			*target_pool->dfms[id].bitmap |=
			    alloc->resource_alloc[i].elements;
			*alloc->resource_alloc[i].resource->bitmap &=
			    ~(alloc->resource_alloc[i].elements);
			alloc->resource_alloc[i].resource =
			    &target_pool->dfms[id];
			break;
		default:
			/*
			 * Just keep compiler happy. This case failed already
			 * in above loop.
			 */
			break;
		}
	}

	target_pool->cells |= alloc->cells;
	source_pool->cells &= ~alloc->cells;

	return 0;
}

void ipu_psys_reset_process_cell(const struct device *dev,
				 struct ipu_fw_psys_process_group *pg,
				 void *pg_manifest,
				 int process_count)
{
	int i;
	u16 *process_offset_table;
	const struct ipu_fw_resource_definitions *res_defs;

	if (!pg)
		return;

	res_defs = get_res();
	process_offset_table = (u16 *)((u8 *)pg + pg->processes_offset);
	for (i = 0; i < process_count; i++) {
		struct ipu_fw_psys_process *process =
		    (struct ipu_fw_psys_process *)
		    ((char *)pg + process_offset_table[i]);
		struct ipu_fw_generic_program_manifest pm;
		int ret;

		if (!process)
			break;

		ret = ipu_fw_psys_get_pgm_by_process(&pm, pg_manifest, process);
		if (ret < 0) {
			dev_err(dev, "can not get manifest\n");
			break;
		}
		if ((pm.cell_id != res_defs->num_cells &&
		     pm.cell_type_id == res_defs->num_cells_type))
			continue;
		/* no return value check here because if finding free cell
		 * failed, process cell would not set then calling clear_cell
		 * will return non-zero.
		 */
		ipu_fw_psys_clear_process_cell(process);
	}
}

/* Free resources marked in `alloc' from `resources' */
void ipu_psys_free_resources(struct ipu_psys_resource_alloc
			     *alloc, struct ipu_psys_resource_pool *pool)
{
	unsigned int i;

	pool->cells &= ~alloc->cells;
	alloc->cells = 0;
	for (i = 0; i < alloc->resources; i++)
		ipu_resource_free(&alloc->resource_alloc[i]);
	alloc->resources = 0;
}
