root/arch/powerpc/platforms/512x/mpc512x_lpbfifo.c
// SPDX-License-Identifier: GPL-2.0-only
/*
 * The driver for Freescale MPC512x LocalPlus Bus FIFO
 * (called SCLPC in the Reference Manual).
 *
 * Copyright (C) 2013-2015 Alexander Popov <alex.popov@linux.com>.
 */

#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/platform_device.h>
#include <asm/mpc5121.h>
#include <asm/io.h>
#include <linux/spinlock.h>
#include <linux/slab.h>
#include <linux/dmaengine.h>
#include <linux/dma-direction.h>
#include <linux/dma-mapping.h>

#define DRV_NAME "mpc512x_lpbfifo"

struct cs_range {
        u32 csnum;
        u32 base; /* must be zero */
        u32 addr;
        u32 size;
};

static struct lpbfifo_data {
        spinlock_t lock; /* for protecting lpbfifo_data */
        phys_addr_t regs_phys;
        resource_size_t regs_size;
        struct mpc512x_lpbfifo __iomem *regs;
        int irq;
        struct cs_range *cs_ranges;
        size_t cs_n;
        struct dma_chan *chan;
        struct mpc512x_lpbfifo_request *req;
        dma_addr_t ram_bus_addr;
        bool wait_lpbfifo_irq;
        bool wait_lpbfifo_callback;
} lpbfifo;

/*
 * A data transfer from RAM to some device on LPB is finished
 * when both mpc512x_lpbfifo_irq() and mpc512x_lpbfifo_callback()
 * have been called. We execute the callback registered in
 * mpc512x_lpbfifo_request just after that.
 * But for a data transfer from some device on LPB to RAM we don't enable
 * LPBFIFO interrupt because clearing MPC512X_SCLPC_SUCCESS interrupt flag
 * automatically disables LPBFIFO reading request to the DMA controller
 * and the data transfer hangs. So the callback registered in
 * mpc512x_lpbfifo_request is executed at the end of mpc512x_lpbfifo_callback().
 */

/*
 * mpc512x_lpbfifo_irq - IRQ handler for LPB FIFO
 */
static irqreturn_t mpc512x_lpbfifo_irq(int irq, void *param)
{
        struct device *dev = (struct device *)param;
        struct mpc512x_lpbfifo_request *req = NULL;
        unsigned long flags;
        u32 status;

        spin_lock_irqsave(&lpbfifo.lock, flags);

        if (!lpbfifo.regs)
                goto end;

        req = lpbfifo.req;
        if (!req || req->dir == MPC512X_LPBFIFO_REQ_DIR_READ) {
                dev_err(dev, "bogus LPBFIFO IRQ\n");
                goto end;
        }

        status = in_be32(&lpbfifo.regs->status);
        if (status != MPC512X_SCLPC_SUCCESS) {
                dev_err(dev, "DMA transfer from RAM to peripheral failed\n");
                out_be32(&lpbfifo.regs->enable,
                                MPC512X_SCLPC_RESET | MPC512X_SCLPC_FIFO_RESET);
                goto end;
        }
        /* Clear the interrupt flag */
        out_be32(&lpbfifo.regs->status, MPC512X_SCLPC_SUCCESS);

        lpbfifo.wait_lpbfifo_irq = false;

        if (lpbfifo.wait_lpbfifo_callback)
                goto end;

        /* Transfer is finished, set the FIFO as idle */
        lpbfifo.req = NULL;

        spin_unlock_irqrestore(&lpbfifo.lock, flags);

        if (req->callback)
                req->callback(req);

        return IRQ_HANDLED;

 end:
        spin_unlock_irqrestore(&lpbfifo.lock, flags);
        return IRQ_HANDLED;
}

/*
 * mpc512x_lpbfifo_callback is called by DMA driver when
 * DMA transaction is finished.
 */
static void mpc512x_lpbfifo_callback(void *param)
{
        unsigned long flags;
        struct mpc512x_lpbfifo_request *req = NULL;
        enum dma_data_direction dir;

        spin_lock_irqsave(&lpbfifo.lock, flags);

        if (!lpbfifo.regs) {
                spin_unlock_irqrestore(&lpbfifo.lock, flags);
                return;
        }

        req = lpbfifo.req;
        if (!req) {
                pr_err("bogus LPBFIFO callback\n");
                spin_unlock_irqrestore(&lpbfifo.lock, flags);
                return;
        }

        /* Release the mapping */
        if (req->dir == MPC512X_LPBFIFO_REQ_DIR_WRITE)
                dir = DMA_TO_DEVICE;
        else
                dir = DMA_FROM_DEVICE;
        dma_unmap_single(lpbfifo.chan->device->dev,
                        lpbfifo.ram_bus_addr, req->size, dir);

        lpbfifo.wait_lpbfifo_callback = false;

        if (!lpbfifo.wait_lpbfifo_irq) {
                /* Transfer is finished, set the FIFO as idle */
                lpbfifo.req = NULL;

                spin_unlock_irqrestore(&lpbfifo.lock, flags);

                if (req->callback)
                        req->callback(req);
        } else {
                spin_unlock_irqrestore(&lpbfifo.lock, flags);
        }
}

static int mpc512x_lpbfifo_kick(void)
{
        u32 bits;
        bool no_incr = false;
        u32 bpt = 32; /* max bytes per LPBFIFO transaction involving DMA */
        u32 cs = 0;
        size_t i;
        struct dma_device *dma_dev = NULL;
        struct scatterlist sg;
        enum dma_data_direction dir;
        struct dma_slave_config dma_conf = {};
        struct dma_async_tx_descriptor *dma_tx = NULL;
        dma_cookie_t cookie;
        int ret;

        /*
         * 1. Fit the requirements:
         * - the packet size must be a multiple of 4 since FIFO Data Word
         *    Register allows only full-word access according the Reference
         *    Manual;
         * - the physical address of the device on LPB and the packet size
         *    must be aligned on BPT (bytes per transaction) or 8-bytes
         *    boundary according the Reference Manual;
         * - but we choose DMA maxburst equal (or very close to) BPT to prevent
         *    DMA controller from overtaking FIFO and causing FIFO underflow
         *    error. So we force the packet size to be aligned on BPT boundary
         *    not to confuse DMA driver which requires the packet size to be
         *    aligned on maxburst boundary;
         * - BPT should be set to the LPB device port size for operation with
         *    disabled auto-incrementing according Reference Manual.
         */
        if (lpbfifo.req->size == 0 || !IS_ALIGNED(lpbfifo.req->size, 4))
                return -EINVAL;

        if (lpbfifo.req->portsize != LPB_DEV_PORTSIZE_UNDEFINED) {
                bpt = lpbfifo.req->portsize;
                no_incr = true;
        }

        while (bpt > 1) {
                if (IS_ALIGNED(lpbfifo.req->dev_phys_addr, min(bpt, 0x8u)) &&
                                        IS_ALIGNED(lpbfifo.req->size, bpt)) {
                        break;
                }

                if (no_incr)
                        return -EINVAL;

                bpt >>= 1;
        }
        dma_conf.dst_maxburst = max(bpt, 0x4u) / 4;
        dma_conf.src_maxburst = max(bpt, 0x4u) / 4;

        for (i = 0; i < lpbfifo.cs_n; i++) {
                phys_addr_t cs_start = lpbfifo.cs_ranges[i].addr;
                phys_addr_t cs_end = cs_start + lpbfifo.cs_ranges[i].size;
                phys_addr_t access_start = lpbfifo.req->dev_phys_addr;
                phys_addr_t access_end = access_start + lpbfifo.req->size;

                if (access_start >= cs_start && access_end <= cs_end) {
                        cs = lpbfifo.cs_ranges[i].csnum;
                        break;
                }
        }
        if (i == lpbfifo.cs_n)
                return -EFAULT;

        /* 2. Prepare DMA */
        dma_dev = lpbfifo.chan->device;

        if (lpbfifo.req->dir == MPC512X_LPBFIFO_REQ_DIR_WRITE) {
                dir = DMA_TO_DEVICE;
                dma_conf.direction = DMA_MEM_TO_DEV;
                dma_conf.dst_addr = lpbfifo.regs_phys +
                                offsetof(struct mpc512x_lpbfifo, data_word);
        } else {
                dir = DMA_FROM_DEVICE;
                dma_conf.direction = DMA_DEV_TO_MEM;
                dma_conf.src_addr = lpbfifo.regs_phys +
                                offsetof(struct mpc512x_lpbfifo, data_word);
        }
        dma_conf.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
        dma_conf.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;

        /* Make DMA channel work with LPB FIFO data register */
        if (dma_dev->device_config(lpbfifo.chan, &dma_conf))
                return -EINVAL;

        sg_init_table(&sg, 1);

        sg_dma_address(&sg) = dma_map_single(dma_dev->dev,
                        lpbfifo.req->ram_virt_addr, lpbfifo.req->size, dir);
        if (dma_mapping_error(dma_dev->dev, sg_dma_address(&sg)))
                return -EFAULT;

        lpbfifo.ram_bus_addr = sg_dma_address(&sg); /* For freeing later */

        sg_dma_len(&sg) = lpbfifo.req->size;

        dma_tx = dmaengine_prep_slave_sg(lpbfifo.chan, &sg,
                                                1, dma_conf.direction, 0);
        if (!dma_tx) {
                ret = -ENOSPC;
                goto err_dma_prep;
        }
        dma_tx->callback = mpc512x_lpbfifo_callback;
        dma_tx->callback_param = NULL;

        /* 3. Prepare FIFO */
        out_be32(&lpbfifo.regs->enable,
                                MPC512X_SCLPC_RESET | MPC512X_SCLPC_FIFO_RESET);
        out_be32(&lpbfifo.regs->enable, 0x0);

        /*
         * Configure the watermarks for write operation (RAM->DMA->FIFO->dev):
         * - high watermark 7 words according the Reference Manual,
         * - low watermark 512 bytes (half of the FIFO).
         * These watermarks don't work for read operation since the
         * MPC512X_SCLPC_FLUSH bit is set (according the Reference Manual).
         */
        out_be32(&lpbfifo.regs->fifo_ctrl, MPC512X_SCLPC_FIFO_CTRL(0x7));
        out_be32(&lpbfifo.regs->fifo_alarm, MPC512X_SCLPC_FIFO_ALARM(0x200));

        /*
         * Start address is a physical address of the region which belongs
         * to the device on the LocalPlus Bus
         */
        out_be32(&lpbfifo.regs->start_addr, lpbfifo.req->dev_phys_addr);

        /*
         * Configure chip select, transfer direction, address increment option
         * and bytes per transaction option
         */
        bits = MPC512X_SCLPC_CS(cs);
        if (lpbfifo.req->dir == MPC512X_LPBFIFO_REQ_DIR_READ)
                bits |= MPC512X_SCLPC_READ | MPC512X_SCLPC_FLUSH;
        if (no_incr)
                bits |= MPC512X_SCLPC_DAI;
        bits |= MPC512X_SCLPC_BPT(bpt);
        out_be32(&lpbfifo.regs->ctrl, bits);

        /* Unmask irqs */
        bits = MPC512X_SCLPC_ENABLE | MPC512X_SCLPC_ABORT_INT_ENABLE;
        if (lpbfifo.req->dir == MPC512X_LPBFIFO_REQ_DIR_WRITE)
                bits |= MPC512X_SCLPC_NORM_INT_ENABLE;
        else
                lpbfifo.wait_lpbfifo_irq = false;

        out_be32(&lpbfifo.regs->enable, bits);

        /* 4. Set packet size and kick FIFO off */
        bits = lpbfifo.req->size | MPC512X_SCLPC_START;
        out_be32(&lpbfifo.regs->pkt_size, bits);

        /* 5. Finally kick DMA off */
        cookie = dma_tx->tx_submit(dma_tx);
        if (dma_submit_error(cookie)) {
                ret = -ENOSPC;
                goto err_dma_submit;
        }

        return 0;

 err_dma_submit:
        out_be32(&lpbfifo.regs->enable,
                                MPC512X_SCLPC_RESET | MPC512X_SCLPC_FIFO_RESET);
 err_dma_prep:
        dma_unmap_single(dma_dev->dev, sg_dma_address(&sg),
                                                lpbfifo.req->size, dir);
        return ret;
}

static int mpc512x_lpbfifo_submit_locked(struct mpc512x_lpbfifo_request *req)
{
        int ret = 0;

        if (!lpbfifo.regs)
                return -ENODEV;

        /* Check whether a transfer is in progress */
        if (lpbfifo.req)
                return -EBUSY;

        lpbfifo.wait_lpbfifo_irq = true;
        lpbfifo.wait_lpbfifo_callback = true;
        lpbfifo.req = req;

        ret = mpc512x_lpbfifo_kick();
        if (ret != 0)
                lpbfifo.req = NULL; /* Set the FIFO as idle */

        return ret;
}

int mpc512x_lpbfifo_submit(struct mpc512x_lpbfifo_request *req)
{
        unsigned long flags;
        int ret = 0;

        spin_lock_irqsave(&lpbfifo.lock, flags);
        ret = mpc512x_lpbfifo_submit_locked(req);
        spin_unlock_irqrestore(&lpbfifo.lock, flags);

        return ret;
}
EXPORT_SYMBOL(mpc512x_lpbfifo_submit);

/*
 * LPBFIFO driver uses "ranges" property of "localbus" device tree node
 * for being able to determine the chip select number of a client device
 * ordering a DMA transfer.
 */
static int get_cs_ranges(struct device *dev)
{
        int ret = -ENODEV;
        struct device_node *lb_node;
        size_t i = 0;
        struct of_range_parser parser;
        struct of_range range;

        lb_node = of_find_compatible_node(NULL, NULL, "fsl,mpc5121-localbus");
        if (!lb_node)
                return ret;

        of_range_parser_init(&parser, lb_node);
        lpbfifo.cs_n = of_range_count(&parser);

        lpbfifo.cs_ranges = devm_kcalloc(dev, lpbfifo.cs_n,
                                        sizeof(struct cs_range), GFP_KERNEL);
        if (!lpbfifo.cs_ranges)
                goto end;

        for_each_of_range(&parser, &range) {
                u32 base = lower_32_bits(range.bus_addr);
                if (base)
                        goto end;

                lpbfifo.cs_ranges[i].csnum = upper_32_bits(range.bus_addr);
                lpbfifo.cs_ranges[i].base = base;
                lpbfifo.cs_ranges[i].addr = range.cpu_addr;
                lpbfifo.cs_ranges[i].size = range.size;
                i++;
        }

        ret = 0;

 end:
        of_node_put(lb_node);
        return ret;
}

static int mpc512x_lpbfifo_probe(struct platform_device *pdev)
{
        struct resource r;
        int ret = 0;

        memset(&lpbfifo, 0, sizeof(struct lpbfifo_data));
        spin_lock_init(&lpbfifo.lock);

        lpbfifo.chan = dma_request_chan(&pdev->dev, "rx-tx");
        if (IS_ERR(lpbfifo.chan))
                return PTR_ERR(lpbfifo.chan);

        if (of_address_to_resource(pdev->dev.of_node, 0, &r) != 0) {
                dev_err(&pdev->dev, "bad 'reg' in 'sclpc' device tree node\n");
                ret = -ENODEV;
                goto err0;
        }

        lpbfifo.regs_phys = r.start;
        lpbfifo.regs_size = resource_size(&r);

        if (!devm_request_mem_region(&pdev->dev, lpbfifo.regs_phys,
                                        lpbfifo.regs_size, DRV_NAME)) {
                dev_err(&pdev->dev, "unable to request region\n");
                ret = -EBUSY;
                goto err0;
        }

        lpbfifo.regs = devm_ioremap(&pdev->dev,
                                        lpbfifo.regs_phys, lpbfifo.regs_size);
        if (!lpbfifo.regs) {
                dev_err(&pdev->dev, "mapping registers failed\n");
                ret = -ENOMEM;
                goto err0;
        }

        out_be32(&lpbfifo.regs->enable,
                                MPC512X_SCLPC_RESET | MPC512X_SCLPC_FIFO_RESET);

        if (get_cs_ranges(&pdev->dev) != 0) {
                dev_err(&pdev->dev, "bad '/localbus' device tree node\n");
                ret = -ENODEV;
                goto err0;
        }

        lpbfifo.irq = irq_of_parse_and_map(pdev->dev.of_node, 0);
        if (!lpbfifo.irq) {
                dev_err(&pdev->dev, "mapping irq failed\n");
                ret = -ENODEV;
                goto err0;
        }

        if (request_irq(lpbfifo.irq, mpc512x_lpbfifo_irq, 0,
                                                DRV_NAME, &pdev->dev) != 0) {
                dev_err(&pdev->dev, "requesting irq failed\n");
                ret = -ENODEV;
                goto err1;
        }

        dev_info(&pdev->dev, "probe succeeded\n");
        return 0;

 err1:
        irq_dispose_mapping(lpbfifo.irq);
 err0:
        dma_release_channel(lpbfifo.chan);
        return ret;
}

static void mpc512x_lpbfifo_remove(struct platform_device *pdev)
{
        unsigned long flags;
        struct dma_device *dma_dev = lpbfifo.chan->device;
        struct mpc512x_lpbfifo __iomem *regs = NULL;

        spin_lock_irqsave(&lpbfifo.lock, flags);
        regs = lpbfifo.regs;
        lpbfifo.regs = NULL;
        spin_unlock_irqrestore(&lpbfifo.lock, flags);

        dma_dev->device_terminate_all(lpbfifo.chan);
        out_be32(&regs->enable, MPC512X_SCLPC_RESET | MPC512X_SCLPC_FIFO_RESET);

        free_irq(lpbfifo.irq, &pdev->dev);
        irq_dispose_mapping(lpbfifo.irq);
        dma_release_channel(lpbfifo.chan);
}

static const struct of_device_id mpc512x_lpbfifo_match[] = {
        { .compatible = "fsl,mpc512x-lpbfifo", },
        {},
};
MODULE_DEVICE_TABLE(of, mpc512x_lpbfifo_match);

static struct platform_driver mpc512x_lpbfifo_driver = {
        .probe = mpc512x_lpbfifo_probe,
        .remove = mpc512x_lpbfifo_remove,
        .driver = {
                .name = DRV_NAME,
                .of_match_table = mpc512x_lpbfifo_match,
        },
};

module_platform_driver(mpc512x_lpbfifo_driver);

MODULE_AUTHOR("Alexander Popov <alex.popov@linux.com>");
MODULE_DESCRIPTION("MPC512x LocalPlus Bus FIFO device driver");
MODULE_LICENSE("GPL v2");