162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci *  Omnitek Scatter-Gather DMA Controller
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci *  Copyright 2012-2015 Cisco Systems, Inc. and/or its affiliates.
662306a36Sopenharmony_ci *  All rights reserved.
762306a36Sopenharmony_ci */
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci#include <linux/string.h>
1062306a36Sopenharmony_ci#include <linux/io.h>
1162306a36Sopenharmony_ci#include <linux/pci_regs.h>
1262306a36Sopenharmony_ci#include <linux/spinlock.h>
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_ci#include "cobalt-driver.h"
1562306a36Sopenharmony_ci#include "cobalt-omnitek.h"
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_ci/* descriptor */
1862306a36Sopenharmony_ci#define END_OF_CHAIN		(1 << 1)
1962306a36Sopenharmony_ci#define INTERRUPT_ENABLE	(1 << 2)
2062306a36Sopenharmony_ci#define WRITE_TO_PCI		(1 << 3)
2162306a36Sopenharmony_ci#define READ_FROM_PCI		(0 << 3)
2262306a36Sopenharmony_ci#define DESCRIPTOR_FLAG_MSK	(END_OF_CHAIN | INTERRUPT_ENABLE | WRITE_TO_PCI)
2362306a36Sopenharmony_ci#define NEXT_ADRS_MSK		0xffffffe0
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_ci/* control/status register */
2662306a36Sopenharmony_ci#define ENABLE                  (1 << 0)
2762306a36Sopenharmony_ci#define START                   (1 << 1)
2862306a36Sopenharmony_ci#define ABORT                   (1 << 2)
2962306a36Sopenharmony_ci#define DONE                    (1 << 4)
3062306a36Sopenharmony_ci#define SG_INTERRUPT            (1 << 5)
3162306a36Sopenharmony_ci#define EVENT_INTERRUPT         (1 << 6)
3262306a36Sopenharmony_ci#define SCATTER_GATHER_MODE     (1 << 8)
3362306a36Sopenharmony_ci#define DISABLE_VIDEO_RESYNC    (1 << 9)
3462306a36Sopenharmony_ci#define EVENT_INTERRUPT_ENABLE  (1 << 10)
3562306a36Sopenharmony_ci#define DIRECTIONAL_MSK         (3 << 16)
3662306a36Sopenharmony_ci#define INPUT_ONLY              (0 << 16)
3762306a36Sopenharmony_ci#define OUTPUT_ONLY             (1 << 16)
3862306a36Sopenharmony_ci#define BIDIRECTIONAL           (2 << 16)
3962306a36Sopenharmony_ci#define DMA_TYPE_MEMORY         (0 << 18)
4062306a36Sopenharmony_ci#define DMA_TYPE_FIFO		(1 << 18)
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_ci#define BASE			(cobalt->bar0)
4362306a36Sopenharmony_ci#define CAPABILITY_HEADER	(BASE)
4462306a36Sopenharmony_ci#define CAPABILITY_REGISTER	(BASE + 0x04)
4562306a36Sopenharmony_ci#define PCI_64BIT		(1 << 8)
4662306a36Sopenharmony_ci#define LOCAL_64BIT		(1 << 9)
4762306a36Sopenharmony_ci#define INTERRUPT_STATUS	(BASE + 0x08)
4862306a36Sopenharmony_ci#define PCI(c)			(BASE + 0x40 + ((c) * 0x40))
4962306a36Sopenharmony_ci#define SIZE(c)			(BASE + 0x58 + ((c) * 0x40))
5062306a36Sopenharmony_ci#define DESCRIPTOR(c)		(BASE + 0x50 + ((c) * 0x40))
5162306a36Sopenharmony_ci#define CS_REG(c)		(BASE + 0x60 + ((c) * 0x40))
5262306a36Sopenharmony_ci#define BYTES_TRANSFERRED(c)	(BASE + 0x64 + ((c) * 0x40))
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_cistatic char *get_dma_direction(u32 status)
5662306a36Sopenharmony_ci{
5762306a36Sopenharmony_ci	switch (status & DIRECTIONAL_MSK) {
5862306a36Sopenharmony_ci	case INPUT_ONLY: return "Input";
5962306a36Sopenharmony_ci	case OUTPUT_ONLY: return "Output";
6062306a36Sopenharmony_ci	case BIDIRECTIONAL: return "Bidirectional";
6162306a36Sopenharmony_ci	}
6262306a36Sopenharmony_ci	return "";
6362306a36Sopenharmony_ci}
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_cistatic void show_dma_capability(struct cobalt *cobalt)
6662306a36Sopenharmony_ci{
6762306a36Sopenharmony_ci	u32 header = ioread32(CAPABILITY_HEADER);
6862306a36Sopenharmony_ci	u32 capa = ioread32(CAPABILITY_REGISTER);
6962306a36Sopenharmony_ci	u32 i;
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci	cobalt_info("Omnitek DMA capability: ID 0x%02x Version 0x%02x Next 0x%x Size 0x%x\n",
7262306a36Sopenharmony_ci		    header & 0xff, (header >> 8) & 0xff,
7362306a36Sopenharmony_ci		    (header >> 16) & 0xffff, (capa >> 24) & 0xff);
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci	switch ((capa >> 8) & 0x3) {
7662306a36Sopenharmony_ci	case 0:
7762306a36Sopenharmony_ci		cobalt_info("Omnitek DMA: 32 bits PCIe and Local\n");
7862306a36Sopenharmony_ci		break;
7962306a36Sopenharmony_ci	case 1:
8062306a36Sopenharmony_ci		cobalt_info("Omnitek DMA: 64 bits PCIe, 32 bits Local\n");
8162306a36Sopenharmony_ci		break;
8262306a36Sopenharmony_ci	case 3:
8362306a36Sopenharmony_ci		cobalt_info("Omnitek DMA: 64 bits PCIe and Local\n");
8462306a36Sopenharmony_ci		break;
8562306a36Sopenharmony_ci	}
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_ci	for (i = 0;  i < (capa & 0xf);  i++) {
8862306a36Sopenharmony_ci		u32 status = ioread32(CS_REG(i));
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_ci		cobalt_info("Omnitek DMA channel #%d: %s %s\n", i,
9162306a36Sopenharmony_ci			    status & DMA_TYPE_FIFO ? "FIFO" : "MEMORY",
9262306a36Sopenharmony_ci			    get_dma_direction(status));
9362306a36Sopenharmony_ci	}
9462306a36Sopenharmony_ci}
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_civoid omni_sg_dma_start(struct cobalt_stream *s, struct sg_dma_desc_info *desc)
9762306a36Sopenharmony_ci{
9862306a36Sopenharmony_ci	struct cobalt *cobalt = s->cobalt;
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_ci	iowrite32((u32)((u64)desc->bus >> 32), DESCRIPTOR(s->dma_channel) + 4);
10162306a36Sopenharmony_ci	iowrite32((u32)desc->bus & NEXT_ADRS_MSK, DESCRIPTOR(s->dma_channel));
10262306a36Sopenharmony_ci	iowrite32(ENABLE | SCATTER_GATHER_MODE | START, CS_REG(s->dma_channel));
10362306a36Sopenharmony_ci}
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_cibool is_dma_done(struct cobalt_stream *s)
10662306a36Sopenharmony_ci{
10762306a36Sopenharmony_ci	struct cobalt *cobalt = s->cobalt;
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ci	if (ioread32(CS_REG(s->dma_channel)) & DONE)
11062306a36Sopenharmony_ci		return true;
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ci	return false;
11362306a36Sopenharmony_ci}
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_civoid omni_sg_dma_abort_channel(struct cobalt_stream *s)
11662306a36Sopenharmony_ci{
11762306a36Sopenharmony_ci	struct cobalt *cobalt = s->cobalt;
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_ci	if (!is_dma_done(s))
12062306a36Sopenharmony_ci		iowrite32(ABORT, CS_REG(s->dma_channel));
12162306a36Sopenharmony_ci}
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_ciint omni_sg_dma_init(struct cobalt *cobalt)
12462306a36Sopenharmony_ci{
12562306a36Sopenharmony_ci	u32 capa = ioread32(CAPABILITY_REGISTER);
12662306a36Sopenharmony_ci	int i;
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci	cobalt->first_fifo_channel = 0;
12962306a36Sopenharmony_ci	cobalt->dma_channels = capa & 0xf;
13062306a36Sopenharmony_ci	if (capa & PCI_64BIT)
13162306a36Sopenharmony_ci		cobalt->pci_32_bit = false;
13262306a36Sopenharmony_ci	else
13362306a36Sopenharmony_ci		cobalt->pci_32_bit = true;
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_ci	for (i = 0; i < cobalt->dma_channels; i++) {
13662306a36Sopenharmony_ci		u32 status = ioread32(CS_REG(i));
13762306a36Sopenharmony_ci		u32 ctrl = ioread32(CS_REG(i));
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_ci		if (!(ctrl & DONE))
14062306a36Sopenharmony_ci			iowrite32(ABORT, CS_REG(i));
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_ci		if (!(status & DMA_TYPE_FIFO))
14362306a36Sopenharmony_ci			cobalt->first_fifo_channel++;
14462306a36Sopenharmony_ci	}
14562306a36Sopenharmony_ci	show_dma_capability(cobalt);
14662306a36Sopenharmony_ci	return 0;
14762306a36Sopenharmony_ci}
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_ciint descriptor_list_create(struct cobalt *cobalt,
15062306a36Sopenharmony_ci		struct scatterlist *scatter_list, bool to_pci, unsigned sglen,
15162306a36Sopenharmony_ci		unsigned size, unsigned width, unsigned stride,
15262306a36Sopenharmony_ci		struct sg_dma_desc_info *desc)
15362306a36Sopenharmony_ci{
15462306a36Sopenharmony_ci	struct sg_dma_descriptor *d = (struct sg_dma_descriptor *)desc->virt;
15562306a36Sopenharmony_ci	dma_addr_t next = desc->bus;
15662306a36Sopenharmony_ci	unsigned offset = 0;
15762306a36Sopenharmony_ci	unsigned copy_bytes = width;
15862306a36Sopenharmony_ci	unsigned copied = 0;
15962306a36Sopenharmony_ci	bool first = true;
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci	/* Must be 4-byte aligned */
16262306a36Sopenharmony_ci	WARN_ON(sg_dma_address(scatter_list) & 3);
16362306a36Sopenharmony_ci	WARN_ON(size & 3);
16462306a36Sopenharmony_ci	WARN_ON(next & 3);
16562306a36Sopenharmony_ci	WARN_ON(stride & 3);
16662306a36Sopenharmony_ci	WARN_ON(stride < width);
16762306a36Sopenharmony_ci	if (width >= stride)
16862306a36Sopenharmony_ci		copy_bytes = stride = size;
16962306a36Sopenharmony_ci
17062306a36Sopenharmony_ci	while (size) {
17162306a36Sopenharmony_ci		dma_addr_t addr = sg_dma_address(scatter_list) + offset;
17262306a36Sopenharmony_ci		unsigned bytes;
17362306a36Sopenharmony_ci
17462306a36Sopenharmony_ci		if (addr == 0)
17562306a36Sopenharmony_ci			return -EFAULT;
17662306a36Sopenharmony_ci		if (cobalt->pci_32_bit) {
17762306a36Sopenharmony_ci			WARN_ON((u64)addr >> 32);
17862306a36Sopenharmony_ci			if ((u64)addr >> 32)
17962306a36Sopenharmony_ci				return -EFAULT;
18062306a36Sopenharmony_ci		}
18162306a36Sopenharmony_ci
18262306a36Sopenharmony_ci		/* PCIe address */
18362306a36Sopenharmony_ci		d->pci_l = addr & 0xffffffff;
18462306a36Sopenharmony_ci		/* If dma_addr_t is 32 bits, then addr >> 32 is actually the
18562306a36Sopenharmony_ci		   equivalent of addr >> 0 in gcc. So must cast to u64. */
18662306a36Sopenharmony_ci		d->pci_h = (u64)addr >> 32;
18762306a36Sopenharmony_ci
18862306a36Sopenharmony_ci		/* Sync to start of streaming frame */
18962306a36Sopenharmony_ci		d->local = 0;
19062306a36Sopenharmony_ci		d->reserved0 = 0;
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_ci		/* Transfer bytes */
19362306a36Sopenharmony_ci		bytes = min(sg_dma_len(scatter_list) - offset,
19462306a36Sopenharmony_ci				copy_bytes - copied);
19562306a36Sopenharmony_ci
19662306a36Sopenharmony_ci		if (first) {
19762306a36Sopenharmony_ci			if (to_pci)
19862306a36Sopenharmony_ci				d->local = 0x11111111;
19962306a36Sopenharmony_ci			first = false;
20062306a36Sopenharmony_ci			if (sglen == 1) {
20162306a36Sopenharmony_ci				/* Make sure there are always at least two
20262306a36Sopenharmony_ci				 * descriptors */
20362306a36Sopenharmony_ci				d->bytes = (bytes / 2) & ~3;
20462306a36Sopenharmony_ci				d->reserved1 = 0;
20562306a36Sopenharmony_ci				size -= d->bytes;
20662306a36Sopenharmony_ci				copied += d->bytes;
20762306a36Sopenharmony_ci				offset += d->bytes;
20862306a36Sopenharmony_ci				addr += d->bytes;
20962306a36Sopenharmony_ci				next += sizeof(struct sg_dma_descriptor);
21062306a36Sopenharmony_ci				d->next_h = (u32)((u64)next >> 32);
21162306a36Sopenharmony_ci				d->next_l = (u32)next |
21262306a36Sopenharmony_ci					(to_pci ? WRITE_TO_PCI : 0);
21362306a36Sopenharmony_ci				bytes -= d->bytes;
21462306a36Sopenharmony_ci				d++;
21562306a36Sopenharmony_ci				/* PCIe address */
21662306a36Sopenharmony_ci				d->pci_l = addr & 0xffffffff;
21762306a36Sopenharmony_ci				/* If dma_addr_t is 32 bits, then addr >> 32
21862306a36Sopenharmony_ci				 * is actually the equivalent of addr >> 0 in
21962306a36Sopenharmony_ci				 * gcc. So must cast to u64. */
22062306a36Sopenharmony_ci				d->pci_h = (u64)addr >> 32;
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_ci				/* Sync to start of streaming frame */
22362306a36Sopenharmony_ci				d->local = 0;
22462306a36Sopenharmony_ci				d->reserved0 = 0;
22562306a36Sopenharmony_ci			}
22662306a36Sopenharmony_ci		}
22762306a36Sopenharmony_ci
22862306a36Sopenharmony_ci		d->bytes = bytes;
22962306a36Sopenharmony_ci		d->reserved1 = 0;
23062306a36Sopenharmony_ci		size -= bytes;
23162306a36Sopenharmony_ci		copied += bytes;
23262306a36Sopenharmony_ci		offset += bytes;
23362306a36Sopenharmony_ci
23462306a36Sopenharmony_ci		if (copied == copy_bytes) {
23562306a36Sopenharmony_ci			while (copied < stride) {
23662306a36Sopenharmony_ci				bytes = min(sg_dma_len(scatter_list) - offset,
23762306a36Sopenharmony_ci						stride - copied);
23862306a36Sopenharmony_ci				copied += bytes;
23962306a36Sopenharmony_ci				offset += bytes;
24062306a36Sopenharmony_ci				size -= bytes;
24162306a36Sopenharmony_ci				if (sg_dma_len(scatter_list) == offset) {
24262306a36Sopenharmony_ci					offset = 0;
24362306a36Sopenharmony_ci					scatter_list = sg_next(scatter_list);
24462306a36Sopenharmony_ci				}
24562306a36Sopenharmony_ci			}
24662306a36Sopenharmony_ci			copied = 0;
24762306a36Sopenharmony_ci		} else {
24862306a36Sopenharmony_ci			offset = 0;
24962306a36Sopenharmony_ci			scatter_list = sg_next(scatter_list);
25062306a36Sopenharmony_ci		}
25162306a36Sopenharmony_ci
25262306a36Sopenharmony_ci		/* Next descriptor + control bits */
25362306a36Sopenharmony_ci		next += sizeof(struct sg_dma_descriptor);
25462306a36Sopenharmony_ci		if (size == 0) {
25562306a36Sopenharmony_ci			/* Loopback to the first descriptor */
25662306a36Sopenharmony_ci			d->next_h = (u32)((u64)desc->bus >> 32);
25762306a36Sopenharmony_ci			d->next_l = (u32)desc->bus |
25862306a36Sopenharmony_ci				(to_pci ? WRITE_TO_PCI : 0) | INTERRUPT_ENABLE;
25962306a36Sopenharmony_ci			if (!to_pci)
26062306a36Sopenharmony_ci				d->local = 0x22222222;
26162306a36Sopenharmony_ci			desc->last_desc_virt = d;
26262306a36Sopenharmony_ci		} else {
26362306a36Sopenharmony_ci			d->next_h = (u32)((u64)next >> 32);
26462306a36Sopenharmony_ci			d->next_l = (u32)next | (to_pci ? WRITE_TO_PCI : 0);
26562306a36Sopenharmony_ci		}
26662306a36Sopenharmony_ci		d++;
26762306a36Sopenharmony_ci	}
26862306a36Sopenharmony_ci	return 0;
26962306a36Sopenharmony_ci}
27062306a36Sopenharmony_ci
27162306a36Sopenharmony_civoid descriptor_list_chain(struct sg_dma_desc_info *this,
27262306a36Sopenharmony_ci			   struct sg_dma_desc_info *next)
27362306a36Sopenharmony_ci{
27462306a36Sopenharmony_ci	struct sg_dma_descriptor *d = this->last_desc_virt;
27562306a36Sopenharmony_ci	u32 direction = d->next_l & WRITE_TO_PCI;
27662306a36Sopenharmony_ci
27762306a36Sopenharmony_ci	if (next == NULL) {
27862306a36Sopenharmony_ci		d->next_h = 0;
27962306a36Sopenharmony_ci		d->next_l = direction | INTERRUPT_ENABLE | END_OF_CHAIN;
28062306a36Sopenharmony_ci	} else {
28162306a36Sopenharmony_ci		d->next_h = (u32)((u64)next->bus >> 32);
28262306a36Sopenharmony_ci		d->next_l = (u32)next->bus | direction | INTERRUPT_ENABLE;
28362306a36Sopenharmony_ci	}
28462306a36Sopenharmony_ci}
28562306a36Sopenharmony_ci
28662306a36Sopenharmony_civoid *descriptor_list_allocate(struct sg_dma_desc_info *desc, size_t bytes)
28762306a36Sopenharmony_ci{
28862306a36Sopenharmony_ci	desc->size = bytes;
28962306a36Sopenharmony_ci	desc->virt = dma_alloc_coherent(desc->dev, bytes,
29062306a36Sopenharmony_ci					&desc->bus, GFP_KERNEL);
29162306a36Sopenharmony_ci	return desc->virt;
29262306a36Sopenharmony_ci}
29362306a36Sopenharmony_ci
29462306a36Sopenharmony_civoid descriptor_list_free(struct sg_dma_desc_info *desc)
29562306a36Sopenharmony_ci{
29662306a36Sopenharmony_ci	if (desc->virt)
29762306a36Sopenharmony_ci		dma_free_coherent(desc->dev, desc->size,
29862306a36Sopenharmony_ci				  desc->virt, desc->bus);
29962306a36Sopenharmony_ci	desc->virt = NULL;
30062306a36Sopenharmony_ci}
30162306a36Sopenharmony_ci
30262306a36Sopenharmony_civoid descriptor_list_interrupt_enable(struct sg_dma_desc_info *desc)
30362306a36Sopenharmony_ci{
30462306a36Sopenharmony_ci	struct sg_dma_descriptor *d = desc->last_desc_virt;
30562306a36Sopenharmony_ci
30662306a36Sopenharmony_ci	d->next_l |= INTERRUPT_ENABLE;
30762306a36Sopenharmony_ci}
30862306a36Sopenharmony_ci
30962306a36Sopenharmony_civoid descriptor_list_interrupt_disable(struct sg_dma_desc_info *desc)
31062306a36Sopenharmony_ci{
31162306a36Sopenharmony_ci	struct sg_dma_descriptor *d = desc->last_desc_virt;
31262306a36Sopenharmony_ci
31362306a36Sopenharmony_ci	d->next_l &= ~INTERRUPT_ENABLE;
31462306a36Sopenharmony_ci}
31562306a36Sopenharmony_ci
31662306a36Sopenharmony_civoid descriptor_list_loopback(struct sg_dma_desc_info *desc)
31762306a36Sopenharmony_ci{
31862306a36Sopenharmony_ci	struct sg_dma_descriptor *d = desc->last_desc_virt;
31962306a36Sopenharmony_ci
32062306a36Sopenharmony_ci	d->next_h = (u32)((u64)desc->bus >> 32);
32162306a36Sopenharmony_ci	d->next_l = (u32)desc->bus | (d->next_l & DESCRIPTOR_FLAG_MSK);
32262306a36Sopenharmony_ci}
32362306a36Sopenharmony_ci
32462306a36Sopenharmony_civoid descriptor_list_end_of_chain(struct sg_dma_desc_info *desc)
32562306a36Sopenharmony_ci{
32662306a36Sopenharmony_ci	struct sg_dma_descriptor *d = desc->last_desc_virt;
32762306a36Sopenharmony_ci
32862306a36Sopenharmony_ci	d->next_l |= END_OF_CHAIN;
32962306a36Sopenharmony_ci}
330