cregit-Linux how code gets into the kernel

Release 4.14 arch/powerpc/platforms/512x/mpc512x_lpbfifo.c

/*
 * The driver for Freescale MPC512x LocalPlus Bus FIFO
 * (called SCLPC in the Reference Manual).
 *
 * Copyright (C) 2013-2015 Alexander Popov <alex.popov@linux.com>.
 *
 * This file is released under the GPLv2.
 */

#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/of_address.h>
#include <linux/of_irq.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; }

Contributors

PersonTokensPropCommitsCommitProp
Alexander Popov208100.00%1100.00%
Total208100.00%1100.00%

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

Contributors

PersonTokensPropCommitsCommitProp
Alexander Popov181100.00%1100.00%
Total181100.00%1100.00%


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

Contributors

PersonTokensPropCommitsCommitProp
Alexander Popov790100.00%1100.00%
Total790100.00%1100.00%


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

Contributors

PersonTokensPropCommitsCommitProp
Alexander Popov77100.00%1100.00%
Total77100.00%1100.00%


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

Contributors

PersonTokensPropCommitsCommitProp
Alexander Popov49100.00%1100.00%
Total49100.00%1100.00%

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; const u32 *addr_cells_p; const u32 *size_cells_p; int proplen; size_t i; lb_node = of_find_compatible_node(NULL, NULL, "fsl,mpc5121-localbus"); if (!lb_node) return ret; /* * The node defined as compatible with 'fsl,mpc5121-localbus' * should have two address cells and one size cell. * Every item of its ranges property should consist of: * - the first address cell which is the chipselect number; * - the second address cell which is the offset in the chipselect, * must be zero. * - CPU address of the beginning of an access window; * - the only size cell which is the size of an access window. */ addr_cells_p = of_get_property(lb_node, "#address-cells", NULL); size_cells_p = of_get_property(lb_node, "#size-cells", NULL); if (addr_cells_p == NULL || *addr_cells_p != 2 || size_cells_p == NULL || *size_cells_p != 1) { goto end; } proplen = of_property_count_u32_elems(lb_node, "ranges"); if (proplen <= 0 || proplen % 4 != 0) goto end; lpbfifo.cs_n = proplen / 4; lpbfifo.cs_ranges = devm_kcalloc(dev, lpbfifo.cs_n, sizeof(struct cs_range), GFP_KERNEL); if (!lpbfifo.cs_ranges) goto end; if (of_property_read_u32_array(lb_node, "ranges", (u32 *)lpbfifo.cs_ranges, proplen) != 0) { goto end; } for (i = 0; i < lpbfifo.cs_n; i++) { if (lpbfifo.cs_ranges[i].base != 0) goto end; } ret = 0; end: of_node_put(lb_node); return ret; }

Contributors

PersonTokensPropCommitsCommitProp
Alexander Popov241100.00%1100.00%
Total241100.00%1100.00%


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_slave_channel(&pdev->dev, "rx-tx"); if (lpbfifo.chan == NULL) return -EPROBE_DEFER; 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; }

Contributors

PersonTokensPropCommitsCommitProp
Alexander Popov37599.73%150.00%
Michael Ellerman10.27%150.00%
Total376100.00%2100.00%


static int 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); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
Alexander Popov116100.00%1100.00%
Total116100.00%1100.00%

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");

Overall Contributors

PersonTokensPropCommitsCommitProp
Alexander Popov224199.96%150.00%
Michael Ellerman10.04%150.00%
Total2242100.00%2100.00%
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with cregit.