// 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 #include #include #include #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) == false) 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; }