Author | Tokens | Token Proportion | Commits | Commit Proportion |
---|---|---|---|---|
Alexander Popov | 2108 | 95.73% | 1 | 14.29% |
Rob Herring | 79 | 3.59% | 2 | 28.57% |
Peter Ujfalusi | 10 | 0.45% | 1 | 14.29% |
Uwe Kleine-König | 2 | 0.09% | 1 | 14.29% |
Thomas Gleixner | 2 | 0.09% | 1 | 14.29% |
Michael Ellerman | 1 | 0.05% | 1 | 14.29% |
Total | 2202 | 7 |
// 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)) { ret = -EINVAL; goto err_dma_prep; } 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(®s->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_new = 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");
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with Cregit http://github.com/cregit/cregit
Version 2.0-RC1