root/drivers/media/pci/cobalt/cobalt-omnitek.c
// 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;
}