// SPDX-License-Identifier: GPL-2.0-only
/*
 *  Omnitek Scatter-Gather DMA Controller
 *
 *  Copyright 2012-2015 Cisco Systems, Inc. and/or its affiliates.
 *  All rights reserved.
 */

#include <linux/string.h>
#include <linux/io.h>
#include <linux/pci_regs.h>
#include <linux/spinlock.h>

#include "cobalt-driver.h"
#include "cobalt-omnitek.h"

/* descriptor */
#define END_OF_CHAIN		(1 << 1)
#define INTERRUPT_ENABLE	(1 << 2)
#define WRITE_TO_PCI		(1 << 3)
#define READ_FROM_PCI		(0 << 3)
#define DESCRIPTOR_FLAG_MSK	(END_OF_CHAIN | INTERRUPT_ENABLE | WRITE_TO_PCI)
#define NEXT_ADRS_MSK		0xffffffe0

/* control/status register */
#define ENABLE                  (1 << 0)
#define START                   (1 << 1)
#define ABORT                   (1 << 2)
#define DONE                    (1 << 4)
#define SG_INTERRUPT            (1 << 5)
#define EVENT_INTERRUPT         (1 << 6)
#define SCATTER_GATHER_MODE     (1 << 8)
#define DISABLE_VIDEO_RESYNC    (1 << 9)
#define EVENT_INTERRUPT_ENABLE  (1 << 10)
#define DIRECTIONAL_MSK         (3 << 16)
#define INPUT_ONLY              (0 << 16)
#define OUTPUT_ONLY             (1 << 16)
#define BIDIRECTIONAL           (2 << 16)
#define DMA_TYPE_MEMORY         (0 << 18)
#define DMA_TYPE_FIFO		(1 << 18)

#define BASE			(cobalt->bar0)
#define CAPABILITY_HEADER	(BASE)
#define CAPABILITY_REGISTER	(BASE + 0x04)
#define PCI_64BIT		(1 << 8)
#define LOCAL_64BIT		(1 << 9)
#define INTERRUPT_STATUS	(BASE + 0x08)
#define PCI(c)			(BASE + 0x40 + ((c) * 0x40))
#define SIZE(c)			(BASE + 0x58 + ((c) * 0x40))
#define DESCRIPTOR(c)		(BASE + 0x50 + ((c) * 0x40))
#define CS_REG(c)		(BASE + 0x60 + ((c) * 0x40))
#define BYTES_TRANSFERRED(c)	(BASE + 0x64 + ((c) * 0x40))


static char *get_dma_direction(u32 status)
{
	switch (status & DIRECTIONAL_MSK) {
	case INPUT_ONLY: return "Input";
	case OUTPUT_ONLY: return "Output";
	case BIDIRECTIONAL: return "Bidirectional";
	}
	return "";
}

static void show_dma_capability(struct cobalt *cobalt)
{
	u32 header = ioread32(CAPABILITY_HEADER);
	u32 capa = ioread32(CAPABILITY_REGISTER);
	u32 i;

	cobalt_info("Omnitek DMA capability: ID 0x%02x Version 0x%02x Next 0x%x Size 0x%x\n",
		    header & 0xff, (header >> 8) & 0xff,
		    (header >> 16) & 0xffff, (capa >> 24) & 0xff);

	switch ((capa >> 8) & 0x3) {
	case 0:
		cobalt_info("Omnitek DMA: 32 bits PCIe and Local\n");
		break;
	case 1:
		cobalt_info("Omnitek DMA: 64 bits PCIe, 32 bits Local\n");
		break;
	case 3:
		cobalt_info("Omnitek DMA: 64 bits PCIe and Local\n");
		break;
	}

	for (i = 0;  i < (capa & 0xf);  i++) {
		u32 status = ioread32(CS_REG(i));

		cobalt_info("Omnitek DMA channel #%d: %s %s\n", i,
			    status & DMA_TYPE_FIFO ? "FIFO" : "MEMORY",
			    get_dma_direction(status));
	}
}

void omni_sg_dma_start(struct cobalt_stream *s, struct sg_dma_desc_info *desc)
{
	struct cobalt *cobalt = s->cobalt;

	iowrite32((u32)((u64)desc->bus >> 32), DESCRIPTOR(s->dma_channel) + 4);
	iowrite32((u32)desc->bus & NEXT_ADRS_MSK, DESCRIPTOR(s->dma_channel));
	iowrite32(ENABLE | SCATTER_GATHER_MODE | START, CS_REG(s->dma_channel));
}

bool is_dma_done(struct cobalt_stream *s)
{
	struct cobalt *cobalt = s->cobalt;

	if (ioread32(CS_REG(s->dma_channel)) & DONE)
		return true;

	return false;
}

void omni_sg_dma_abort_channel(struct cobalt_stream *s)
{
	struct cobalt *cobalt = s->cobalt;

	if (!is_dma_done(s))
		iowrite32(ABORT, CS_REG(s->dma_channel));
}

int omni_sg_dma_init(struct cobalt *cobalt)
{
	u32 capa = ioread32(CAPABILITY_REGISTER);
	int i;

	cobalt->first_fifo_channel = 0;
	cobalt->dma_channels = capa & 0xf;
	if (capa & PCI_64BIT)
		cobalt->pci_32_bit = false;
	else
		cobalt->pci_32_bit = true;

	for (i = 0; i < cobalt->dma_channels; i++) {
		u32 status = ioread32(CS_REG(i));
		u32 ctrl = ioread32(CS_REG(i));

		if (!(ctrl & DONE))
			iowrite32(ABORT, CS_REG(i));

		if (!(status & DMA_TYPE_FIFO))
			cobalt->first_fifo_channel++;
	}
	show_dma_capability(cobalt);
	return 0;
}

int descriptor_list_create(struct cobalt *cobalt,
		struct scatterlist *scatter_list, bool to_pci, unsigned sglen,
		unsigned size, unsigned width, unsigned stride,
		struct sg_dma_desc_info *desc)
{
	struct sg_dma_descriptor *d = (struct sg_dma_descriptor *)desc->virt;
	dma_addr_t next = desc->bus;
	unsigned offset = 0;
	unsigned copy_bytes = width;
	unsigned copied = 0;
	bool first = true;

	/* Must be 4-byte aligned */
	WARN_ON(sg_dma_address(scatter_list) & 3);
	WARN_ON(size & 3);
	WARN_ON(next & 3);
	WARN_ON(stride & 3);
	WARN_ON(stride < width);
	if (width >= stride)
		copy_bytes = stride = size;

	while (size) {
		dma_addr_t addr = sg_dma_address(scatter_list) + offset;
		unsigned bytes;

		if (addr == 0)
			return -EFAULT;
		if (cobalt->pci_32_bit) {
			WARN_ON((u64)addr >> 32);
			if ((u64)addr >> 32)
				return -EFAULT;
		}

		/* PCIe address */
		d->pci_l = addr & 0xffffffff;
		/* If dma_addr_t is 32 bits, then addr >> 32 is actually the
		   equivalent of addr >> 0 in gcc. So must cast to u64. */
		d->pci_h = (u64)addr >> 32;

		/* Sync to start of streaming frame */
		d->local = 0;
		d->reserved0 = 0;

		/* Transfer bytes */
		bytes = min(sg_dma_len(scatter_list) - offset,
				copy_bytes - copied);

		if (first) {
			if (to_pci)
				d->local = 0x11111111;
			first = false;
			if (sglen == 1) {
				/* Make sure there are always at least two
				 * descriptors */
				d->bytes = (bytes / 2) & ~3;
				d->reserved1 = 0;
				size -= d->bytes;
				copied += d->bytes;
				offset += d->bytes;
				addr += d->bytes;
				next += sizeof(struct sg_dma_descriptor);
				d->next_h = (u32)((u64)next >> 32);
				d->next_l = (u32)next |
					(to_pci ? WRITE_TO_PCI : 0);
				bytes -= d->bytes;
				d++;
				/* PCIe address */
				d->pci_l = addr & 0xffffffff;
				/* If dma_addr_t is 32 bits, then addr >> 32
				 * is actually the equivalent of addr >> 0 in
				 * gcc. So must cast to u64. */
				d->pci_h = (u64)addr >> 32;

				/* Sync to start of streaming frame */
				d->local = 0;
				d->reserved0 = 0;
			}
		}

		d->bytes = bytes;
		d->reserved1 = 0;
		size -= bytes;
		copied += bytes;
		offset += bytes;

		if (copied == copy_bytes) {
			while (copied < stride) {
				bytes = min(sg_dma_len(scatter_list) - offset,
						stride - copied);
				copied += bytes;
				offset += bytes;
				size -= bytes;
				if (sg_dma_len(scatter_list) == offset) {
					offset = 0;
					scatter_list = sg_next(scatter_list);
				}
			}
			copied = 0;
		} else {
			offset = 0;
			scatter_list = sg_next(scatter_list);
		}

		/* Next descriptor + control bits */
		next += sizeof(struct sg_dma_descriptor);
		if (size == 0) {
			/* Loopback to the first descriptor */
			d->next_h = (u32)((u64)desc->bus >> 32);
			d->next_l = (u32)desc->bus |
				(to_pci ? WRITE_TO_PCI : 0) | INTERRUPT_ENABLE;
			if (!to_pci)
				d->local = 0x22222222;
			desc->last_desc_virt = d;
		} else {
			d->next_h = (u32)((u64)next >> 32);
			d->next_l = (u32)next | (to_pci ? WRITE_TO_PCI : 0);
		}
		d++;
	}
	return 0;
}

void descriptor_list_chain(struct sg_dma_desc_info *this,
			   struct sg_dma_desc_info *next)
{
	struct sg_dma_descriptor *d = this->last_desc_virt;
	u32 direction = d->next_l & WRITE_TO_PCI;

	if (next == NULL) {
		d->next_h = 0;
		d->next_l = direction | INTERRUPT_ENABLE | END_OF_CHAIN;
	} else {
		d->next_h = (u32)((u64)next->bus >> 32);
		d->next_l = (u32)next->bus | direction | INTERRUPT_ENABLE;
	}
}

void *descriptor_list_allocate(struct sg_dma_desc_info *desc, size_t bytes)
{
	desc->size = bytes;
	desc->virt = dma_alloc_coherent(desc->dev, bytes,
					&desc->bus, GFP_KERNEL);
	return desc->virt;
}

void descriptor_list_free(struct sg_dma_desc_info *desc)
{
	if (desc->virt)
		dma_free_coherent(desc->dev, desc->size,
				  desc->virt, desc->bus);
	desc->virt = NULL;
}

void descriptor_list_interrupt_enable(struct sg_dma_desc_info *desc)
{
	struct sg_dma_descriptor *d = desc->last_desc_virt;

	d->next_l |= INTERRUPT_ENABLE;
}

void descriptor_list_interrupt_disable(struct sg_dma_desc_info *desc)
{
	struct sg_dma_descriptor *d = desc->last_desc_virt;

	d->next_l &= ~INTERRUPT_ENABLE;
}

void descriptor_list_loopback(struct sg_dma_desc_info *desc)
{
	struct sg_dma_descriptor *d = desc->last_desc_virt;

	d->next_h = (u32)((u64)desc->bus >> 32);
	d->next_l = (u32)desc->bus | (d->next_l & DESCRIPTOR_FLAG_MSK);
}

void descriptor_list_end_of_chain(struct sg_dma_desc_info *desc)
{
	struct sg_dma_descriptor *d = desc->last_desc_virt;

	d->next_l |= END_OF_CHAIN;
}
