/*
   forward-sdma.c - VideoDMA driver for SoftLab-NSK Forward video boards

   Copyright (C) 2017 - 2024 SoftLab-NSK <forward@softlab.tv>

   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public License
   as published by the Free Software Foundation; either version 2
   of the License, or (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

#include "forward-sdma.h"
#include "forward-ioctl.h"

#include <linux/vmalloc.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/version.h>
#include <linux/bitmap.h>

#define FORWARD_SDMA_DESC_SIZE 16
#define FORWARD_SDMA_MAX_DESCRIPTORS ((1024 * 4096) / FORWARD_SDMA_DESC_SIZE)

int forward_sdma_probe(struct forward_dev *dev)
{
	int i;
	struct forward_sdma *sdma = devm_kzalloc(dev->dev, sizeof(struct forward_sdma), GFP_KERNEL);
	int size = dev->cfg.sdma_size;

	if (!sdma) {
		forward_err(dev, "sdma: failed to allocate SDMA context.\n");
		return -ENOMEM;
	}

	sdma->rings = devm_kzalloc(
		dev->dev, dev->cfg.sdma_rings * sizeof(struct forward_sdma_io_ring), GFP_KERNEL);
	if (!sdma) {
		forward_err(dev, "sdma: failed to allocate SDMA ring context.\n");
		devm_kfree(dev->dev, sdma);
		return -ENOMEM;
	}
	sdma->num_rings = dev->cfg.sdma_rings;

	spin_lock_init(&sdma->lock);

	sdma->dev = dev;

	if (!dma_set_mask(dev->parent_dev, DMA_BIT_MASK(44)))
		sdma->dma44bit = 1;
	else if (!dma_set_mask(dev->parent_dev, DMA_BIT_MASK(32)))
		sdma->dma44bit = 0;
	else {
		forward_err(dev, "sdma: failed to set SDMA mask.\n");
		return -ENODEV;
	}
	sdma->map_size = ((size * FORWARD_SDMA_DESC_SIZE - 1) >> PAGE_SHIFT) + 1;

	sdma->map = (u32 *)get_zeroed_page(GFP_DMA);
	sdma->map_addr = dma_map_single(dev->parent_dev, sdma->map, PAGE_SIZE, DMA_TO_DEVICE);

	sdma->descriptors = vzalloc(size * FORWARD_SDMA_DESC_SIZE);
	sdma->cookies = vzalloc(size * (sizeof(void *)));

	for (i = 0; i < sdma->map_size; i++) {
		struct page *dp = vmalloc_to_page(((void *)sdma->descriptors) + (i << PAGE_SHIFT));
		sdma->map[i] = dma_map_page(dev->parent_dev, dp, 0, PAGE_SIZE, DMA_BIDIRECTIONAL) >>
			       PAGE_SHIFT;
	}

	sdma->dummy_data = (u32 *)get_zeroed_page(GFP_DMA);
	sdma->dummy_addr =
		dma_map_single(dev->parent_dev, sdma->dummy_data, PAGE_SIZE, DMA_BIDIRECTIONAL);
	sdma->dummy.address = sdma->dummy_addr;
	sdma->dummy.cmd = 0x01;
	sdma->dummy.length = PAGE_SIZE;

	for (i = 0; i < size; i++)
		sdma->descriptors[i] = sdma->dummy;

	for (i = 0; i < sdma->num_rings; i++) {
		sdma->rings[i].sdma = sdma;
		sdma->rings[i].id = i;
		sdma->rings[i].size = (size / sdma->num_rings);
		sdma->rings[i].desc = &sdma->descriptors[i * sdma->rings[i].size];
		sdma->rings[i].cookies = &sdma->cookies[i * sdma->rings[i].size];
		forward_sdma_reset(&sdma->rings[i]);
	}

	forward_info(dev, "sdma: using %d-bit SDMA with %d io-rings, %d descriptors each\n",
		     sdma->dma44bit ? 44 : 32, sdma->num_rings, size / sdma->num_rings);

	dev->sdma = sdma;

	return 0;
}

void forward_sdma_remove(struct forward_dev *dev)
{
	int i;
	struct forward_sdma *sdma = dev->sdma;

	if (!sdma)
		return;

	dev->sdma = NULL;

	forward_sdma_disable(dev);

	for (i = 0; i < sdma->map_size; i++) {
		dma_unmap_page(dev->parent_dev, sdma->map[i] << PAGE_SHIFT, PAGE_SIZE,
			       DMA_BIDIRECTIONAL);
	}

	if (sdma->dummy_addr)
		dma_unmap_single(dev->parent_dev, sdma->dummy_addr, PAGE_SIZE, DMA_BIDIRECTIONAL);
	if (sdma->dummy_data)
		free_page((unsigned long)sdma->dummy_data);

	if (sdma->cookies)
		vfree(sdma->cookies);

	if (sdma->descriptors)
		vfree(sdma->descriptors);

	if (sdma->map_addr)
		dma_unmap_single(dev->parent_dev, sdma->map_addr, PAGE_SIZE, DMA_TO_DEVICE);
	if (sdma->map)
		free_page((unsigned long)sdma->map);
}

void forward_sdma_enable(struct forward_dev *dev)
{
	struct forward_sdma *sdma = dev->sdma;

	if (!sdma)
		return;

	dev->cfg.enable_sdma(dev, sdma->map_addr >> PAGE_SHIFT);
}
EXPORT_SYMBOL(forward_sdma_enable);

void forward_sdma_disable(struct forward_dev *dev)
{
	struct forward_sdma *sdma = dev->sdma;

	if (!sdma)
		return;

	dev->cfg.disable_sdma(dev);
}
EXPORT_SYMBOL(forward_sdma_disable);

void forward_sdma_enable_ring(struct forward_sdma_io_ring *ring)
{
	struct forward_dev *dev = ring->sdma->dev;
	dev->cfg.sdma_enable_ring(dev, ring->id);
}
EXPORT_SYMBOL(forward_sdma_enable_ring);

void forward_sdma_disable_ring(struct forward_sdma_io_ring *ring)
{
	struct forward_dev *dev = ring->sdma->dev;
	dev->cfg.sdma_disable_ring(dev, ring->id);
}
EXPORT_SYMBOL(forward_sdma_disable_ring);

void forward_sdma_debug_show(struct forward_sdma *sdma)
{
	unsigned long i, si = 0;
	u32 prev_desc = 0;
	u32 start = 0;

	forward_info(sdma->dev, "SDMA table at %16llx: \n", sdma->map_addr);
	for (i = 0; i < sdma->map_size; i++) {
		u32 desc = sdma->map[i];

		if (i == 0) {
			start = desc;
			si = i;
		}

		if ((i != sdma->map_size - 1) &&
		    ((((int)desc - (int)prev_desc) == 1) || (((int)desc - (int)prev_desc) == -1) ||
		     (prev_desc == desc))) {
			prev_desc = desc;
			continue;
		}
		if (((start != 0) && (prev_desc != 0)) || (i == sdma->map_size - 1)) {
			forward_info(sdma->dev, "[%08lx:%08lx]: %016llx - %016llx\n",
				     si * (PAGE_SIZE / FORWARD_SDMA_DESC_SIZE) * 4096,
				     (i - 1) * (PAGE_SIZE / FORWARD_SDMA_DESC_SIZE) * 4096,
				     (u64)start * 4096, (u64)prev_desc * 4096);
		}
		start = desc;
		si = i;
		prev_desc = desc;
	}
	forward_info(sdma->dev, "SDMA dummy address: %16llx, dummy descriptor: %16llx%16llx\n",
		     sdma->dummy_addr, *(((u64 *)&sdma->dummy) + 0), *(((u64 *)&sdma->dummy) + 1));
}
EXPORT_SYMBOL(forward_sdma_debug_show);

void forward_sdma_update(struct forward_sdma_io_ring *ring)
{
	struct forward_sdma *sdma = ring->sdma;
	sdma->dev->cfg.sdma_get_hw_pointer(sdma->dev, ring->id, &ring->hw);
}
EXPORT_SYMBOL(forward_sdma_update);

void forward_sdma_reset(struct forward_sdma_io_ring *ring)
{
	struct forward_sdma *sdma = ring->sdma;
	ring->sw = 0;
	ring->hw = 0;
	sdma->dev->cfg.sdma_set_pointers(sdma->dev, ring->id, ring->sw, true, ring->hw, true);
	sdma->dev->cfg.sdma_get_hw_pointer(sdma->dev, ring->id, &ring->hw);
}
EXPORT_SYMBOL(forward_sdma_reset);

void forward_sdma_advance(struct forward_sdma_io_ring *ring, u16 amount)
{
	struct forward_sdma *sdma = ring->sdma;
	u16 new_sw = ring->sw + amount;

	if (new_sw >= ring->size)
		new_sw -= ring->size;

	ring->sw = new_sw;
	sdma->dev->cfg.sdma_set_pointers(sdma->dev, ring->id, ring->hw, false, ring->sw, true);
}
EXPORT_SYMBOL(forward_sdma_advance);
