| // 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; |
| } |