// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
 * Copyright (c) 2019 Amlogic, Inc. All rights reserved.
 */

#include <linux/types.h>
#include <linux/vmalloc.h>
#include <linux/mutex.h>
#include <linux/log2.h>
#include "bridge_ringbuffer.h"

static u32 roundup_power_of_2(unsigned int a)
{
	u32 i;
	u32 position = 0;

	if (a == 0)
		return 0;

	for (i = a; i != 0; i >>= 1)
		position++;

	return (u32)(1 << position);
}

struct ring_buffer *bridge_ring_buffer_init(u32 size)
{
	char *buf = NULL;
	struct ring_buffer *ring_buf = NULL;

	if (!is_power_of_2(size))
		size = roundup_power_of_2(size);

	buf = vmalloc(size);
	if (!buf)
		return ring_buf;

	ring_buf = vmalloc(sizeof(*ring_buf));
	if (!ring_buf) {
		vfree(buf);
		buf = NULL;
		pr_err("Failed to malloc rb struct!\n");
		return ring_buf;
	}

	memset(buf, 0, size);
	memset(ring_buf, 0, sizeof(struct ring_buffer));
	ring_buf->buffer = buf;
	ring_buf->size = size;
	ring_buf->in = 0;
	ring_buf->out = 0;
	ring_buf->go_empty = false;
	return ring_buf;
}

int bridge_ring_buffer_release(struct ring_buffer *ring_buf)
{
	if (ring_buf) {
		vfree(ring_buf->buffer);
		ring_buf->buffer = NULL;
		vfree(ring_buf);
		ring_buf = NULL;
	}
	return 0;
}

static u32 __ring_buffer_len(const struct ring_buffer *ring_buf)
{
	return(ring_buf->in - ring_buf->out);
}

static u32 __ring_buffer_get(struct ring_buffer *ring_buf, void *buffer, u32 size)
{
	u32 len = 0;

	if (!ring_buf || !buffer)
		return 0;

	size  = min(size, ring_buf->in - ring_buf->out);
	/* first get the data from fifo->out until the end of the buffer */
	len = min(size, ring_buf->size - (ring_buf->out & (ring_buf->size - 1)));
	memcpy(buffer, ring_buf->buffer + (ring_buf->out & (ring_buf->size - 1)), len);
	/* then get the rest (if any) from the beginning of the buffer */
	memcpy(buffer + len, ring_buf->buffer, size - len);
	ring_buf->out += size;
	return size;
}

static u32 __ring_buffer_put(struct ring_buffer *ring_buf, void *buffer, u32 size)
{
	u32 len = 0;

	if (!ring_buf || !buffer)
		return 0;

	size = min(size, ring_buf->size - ring_buf->in + ring_buf->out);
	/* first put the data starting from fifo->in to buffer end */
	len  = min(size, ring_buf->size - (ring_buf->in & (ring_buf->size - 1)));
	memcpy(ring_buf->buffer + (ring_buf->in & (ring_buf->size - 1)), buffer, len);
	/* then put the rest (if any) at the beginning of the buffer */
	memcpy(ring_buf->buffer, buffer + len, size - len);
	ring_buf->in += size;
	return size;
}

u32 bridge_ring_buffer_len(const struct ring_buffer *ring_buf)
{
	return ring_buf->size;
}

u32 bridge_ring_buffer_used_len(const struct ring_buffer *ring_buf)
{
	return __ring_buffer_len(ring_buf);
}

u32 bridge_ring_buffer_free_len(const struct ring_buffer *ring_buf)
{
	return ring_buf->size - __ring_buffer_len(ring_buf);
}

u32 bridge_ring_buffer_get(struct ring_buffer *ring_buf, void *buffer, u32 size)
{
	u32 ret;

	if (ring_buf->go_empty) {
		ring_buf->go_empty = false;
		ring_buf->out = ring_buf->in;
	}
	if (bridge_ring_buffer_used_len(ring_buf) < size)
		return 0;
	ret = __ring_buffer_get(ring_buf, buffer, size);
	return ret;
}

u32 bridge_ring_buffer_put(struct ring_buffer *ring_buf, void *buffer, u32 size)
{
	u32 ret;

	ret = __ring_buffer_put(ring_buf, buffer, size);
	return ret;
}

u32 bridge_ring_buffer_go_empty(struct ring_buffer *ring_buf)
{
	ring_buf->go_empty = true;
	return 0;
}

