linux/drivers/dma/bestcomm/bestcomm.c
Linus Torvalds 9d3cae26ac Merge branch 'next' of git://git.kernel.org/pub/scm/linux/kernel/git/benh/powerpc
Pull powerpc updates from Benjamin Herrenschmidt:
 "So from the depth of frozen Minnesota, here's the powerpc pull request
  for 3.9.  It has a few interesting highlights, in addition to the
  usual bunch of bug fixes, minor updates, embedded device tree updates
  and new boards:

   - Hand tuned asm implementation of SHA1 (by Paulus & Michael
     Ellerman)

   - Support for Doorbell interrupts on Power8 (kind of fast
     thread-thread IPIs) by Ian Munsie

   - Long overdue cleanup of the way we handle relocation of our open
     firmware trampoline (prom_init.c) on 64-bit by Anton Blanchard

   - Support for saving/restoring & context switching the PPR (Processor
     Priority Register) on server processors that support it.  This
     allows the kernel to preserve thread priorities established by
     userspace.  By Haren Myneni.

   - DAWR (new watchpoint facility) support on Power8 by Michael Neuling

   - Ability to change the DSCR (Data Stream Control Register) which
     controls cache prefetching on a running process via ptrace by
     Alexey Kardashevskiy

   - Support for context switching the TAR register on Power8 (new
     branch target register meant to be used by some new specific
     userspace perf event interrupt facility which is yet to be enabled)
     by Ian Munsie.

   - Improve preservation of the CFAR register (which captures the
     origin of a branch) on various exception conditions by Paulus.

   - Move the Bestcomm DMA driver from arch powerpc to drivers/dma where
     it belongs by Philippe De Muyter

   - Support for Transactional Memory on Power8 by Michael Neuling
     (based on original work by Matt Evans).  For those curious about
     the feature, the patch contains a pretty good description."

(See commit db8ff90702: "powerpc: Documentation for transactional
memory on powerpc" for the mentioned description added to the file
Documentation/powerpc/transactional_memory.txt)

* 'next' of git://git.kernel.org/pub/scm/linux/kernel/git/benh/powerpc: (140 commits)
  powerpc/kexec: Disable hard IRQ before kexec
  powerpc/85xx: l2sram - Add compatible string for BSC9131 platform
  powerpc/85xx: bsc9131 - Correct typo in SDHC device node
  powerpc/e500/qemu-e500: enable coreint
  powerpc/mpic: allow coreint to be determined by MPIC version
  powerpc/fsl_pci: Store the pci ctlr device ptr in the pci ctlr struct
  powerpc/85xx: Board support for ppa8548
  powerpc/fsl: remove extraneous DIU platform functions
  arch/powerpc/platforms/85xx/p1022_ds.c: adjust duplicate test
  powerpc: Documentation for transactional memory on powerpc
  powerpc: Add transactional memory to pseries and ppc64 defconfigs
  powerpc: Add config option for transactional memory
  powerpc: Add transactional memory to POWER8 cpu features
  powerpc: Add new transactional memory state to the signal context
  powerpc: Hook in new transactional memory code
  powerpc: Routines for FP/VSX/VMX unavailable during a transaction
  powerpc: Add transactional memory unavaliable execption handler
  powerpc: Add reclaim and recheckpoint functions for context switching transactional memory processes
  powerpc: Add FP/VSX and VMX register load functions for transactional memory
  powerpc: Add helper functions for transactional memory context switching
  ...
2013-02-23 17:09:55 -08:00

531 lines
13 KiB
C

/*
* Driver for MPC52xx processor BestComm peripheral controller
*
*
* Copyright (C) 2006-2007 Sylvain Munaut <tnt@246tNt.com>
* Copyright (C) 2005 Varma Electronics Oy,
* ( by Andrey Volkov <avolkov@varma-el.com> )
* Copyright (C) 2003-2004 MontaVista, Software, Inc.
* ( by Dale Farnsworth <dfarnsworth@mvista.com> )
*
* This file is licensed under the terms of the GNU General Public License
* version 2. This program is licensed "as is" without any warranty of any
* kind, whether express or implied.
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_platform.h>
#include <asm/io.h>
#include <asm/irq.h>
#include <asm/mpc52xx.h>
#include <linux/fsl/bestcomm/sram.h>
#include <linux/fsl/bestcomm/bestcomm_priv.h>
#include "linux/fsl/bestcomm/bestcomm.h"
#define DRIVER_NAME "bestcomm-core"
/* MPC5200 device tree match tables */
static struct of_device_id mpc52xx_sram_ids[] = {
{ .compatible = "fsl,mpc5200-sram", },
{ .compatible = "mpc5200-sram", },
{}
};
struct bcom_engine *bcom_eng = NULL;
EXPORT_SYMBOL_GPL(bcom_eng); /* needed for inline functions */
/* ======================================================================== */
/* Public and private API */
/* ======================================================================== */
/* Private API */
struct bcom_task *
bcom_task_alloc(int bd_count, int bd_size, int priv_size)
{
int i, tasknum = -1;
struct bcom_task *tsk;
/* Don't try to do anything if bestcomm init failed */
if (!bcom_eng)
return NULL;
/* Get and reserve a task num */
spin_lock(&bcom_eng->lock);
for (i=0; i<BCOM_MAX_TASKS; i++)
if (!bcom_eng->tdt[i].stop) { /* we use stop as a marker */
bcom_eng->tdt[i].stop = 0xfffffffful; /* dummy addr */
tasknum = i;
break;
}
spin_unlock(&bcom_eng->lock);
if (tasknum < 0)
return NULL;
/* Allocate our structure */
tsk = kzalloc(sizeof(struct bcom_task) + priv_size, GFP_KERNEL);
if (!tsk)
goto error;
tsk->tasknum = tasknum;
if (priv_size)
tsk->priv = (void*)tsk + sizeof(struct bcom_task);
/* Get IRQ of that task */
tsk->irq = irq_of_parse_and_map(bcom_eng->ofnode, tsk->tasknum);
if (tsk->irq == NO_IRQ)
goto error;
/* Init the BDs, if needed */
if (bd_count) {
tsk->cookie = kmalloc(sizeof(void*) * bd_count, GFP_KERNEL);
if (!tsk->cookie)
goto error;
tsk->bd = bcom_sram_alloc(bd_count * bd_size, 4, &tsk->bd_pa);
if (!tsk->bd)
goto error;
memset(tsk->bd, 0x00, bd_count * bd_size);
tsk->num_bd = bd_count;
tsk->bd_size = bd_size;
}
return tsk;
error:
if (tsk) {
if (tsk->irq != NO_IRQ)
irq_dispose_mapping(tsk->irq);
bcom_sram_free(tsk->bd);
kfree(tsk->cookie);
kfree(tsk);
}
bcom_eng->tdt[tasknum].stop = 0;
return NULL;
}
EXPORT_SYMBOL_GPL(bcom_task_alloc);
void
bcom_task_free(struct bcom_task *tsk)
{
/* Stop the task */
bcom_disable_task(tsk->tasknum);
/* Clear TDT */
bcom_eng->tdt[tsk->tasknum].start = 0;
bcom_eng->tdt[tsk->tasknum].stop = 0;
/* Free everything */
irq_dispose_mapping(tsk->irq);
bcom_sram_free(tsk->bd);
kfree(tsk->cookie);
kfree(tsk);
}
EXPORT_SYMBOL_GPL(bcom_task_free);
int
bcom_load_image(int task, u32 *task_image)
{
struct bcom_task_header *hdr = (struct bcom_task_header *)task_image;
struct bcom_tdt *tdt;
u32 *desc, *var, *inc;
u32 *desc_src, *var_src, *inc_src;
/* Safety checks */
if (hdr->magic != BCOM_TASK_MAGIC) {
printk(KERN_ERR DRIVER_NAME
": Trying to load invalid microcode\n");
return -EINVAL;
}
if ((task < 0) || (task >= BCOM_MAX_TASKS)) {
printk(KERN_ERR DRIVER_NAME
": Trying to load invalid task %d\n", task);
return -EINVAL;
}
/* Initial load or reload */
tdt = &bcom_eng->tdt[task];
if (tdt->start) {
desc = bcom_task_desc(task);
if (hdr->desc_size != bcom_task_num_descs(task)) {
printk(KERN_ERR DRIVER_NAME
": Trying to reload wrong task image "
"(%d size %d/%d)!\n",
task,
hdr->desc_size,
bcom_task_num_descs(task));
return -EINVAL;
}
} else {
phys_addr_t start_pa;
desc = bcom_sram_alloc(hdr->desc_size * sizeof(u32), 4, &start_pa);
if (!desc)
return -ENOMEM;
tdt->start = start_pa;
tdt->stop = start_pa + ((hdr->desc_size-1) * sizeof(u32));
}
var = bcom_task_var(task);
inc = bcom_task_inc(task);
/* Clear & copy */
memset(var, 0x00, BCOM_VAR_SIZE);
memset(inc, 0x00, BCOM_INC_SIZE);
desc_src = (u32 *)(hdr + 1);
var_src = desc_src + hdr->desc_size;
inc_src = var_src + hdr->var_size;
memcpy(desc, desc_src, hdr->desc_size * sizeof(u32));
memcpy(var + hdr->first_var, var_src, hdr->var_size * sizeof(u32));
memcpy(inc, inc_src, hdr->inc_size * sizeof(u32));
return 0;
}
EXPORT_SYMBOL_GPL(bcom_load_image);
void
bcom_set_initiator(int task, int initiator)
{
int i;
int num_descs;
u32 *desc;
int next_drd_has_initiator;
bcom_set_tcr_initiator(task, initiator);
/* Just setting tcr is apparently not enough due to some problem */
/* with it. So we just go thru all the microcode and replace in */
/* the DRD directly */
desc = bcom_task_desc(task);
next_drd_has_initiator = 1;
num_descs = bcom_task_num_descs(task);
for (i=0; i<num_descs; i++, desc++) {
if (!bcom_desc_is_drd(*desc))
continue;
if (next_drd_has_initiator)
if (bcom_desc_initiator(*desc) != BCOM_INITIATOR_ALWAYS)
bcom_set_desc_initiator(desc, initiator);
next_drd_has_initiator = !bcom_drd_is_extended(*desc);
}
}
EXPORT_SYMBOL_GPL(bcom_set_initiator);
/* Public API */
void
bcom_enable(struct bcom_task *tsk)
{
bcom_enable_task(tsk->tasknum);
}
EXPORT_SYMBOL_GPL(bcom_enable);
void
bcom_disable(struct bcom_task *tsk)
{
bcom_disable_task(tsk->tasknum);
}
EXPORT_SYMBOL_GPL(bcom_disable);
/* ======================================================================== */
/* Engine init/cleanup */
/* ======================================================================== */
/* Function Descriptor table */
/* this will need to be updated if Freescale changes their task code FDT */
static u32 fdt_ops[] = {
0xa0045670, /* FDT[48] - load_acc() */
0x80045670, /* FDT[49] - unload_acc() */
0x21800000, /* FDT[50] - and() */
0x21e00000, /* FDT[51] - or() */
0x21500000, /* FDT[52] - xor() */
0x21400000, /* FDT[53] - andn() */
0x21500000, /* FDT[54] - not() */
0x20400000, /* FDT[55] - add() */
0x20500000, /* FDT[56] - sub() */
0x20800000, /* FDT[57] - lsh() */
0x20a00000, /* FDT[58] - rsh() */
0xc0170000, /* FDT[59] - crc8() */
0xc0145670, /* FDT[60] - crc16() */
0xc0345670, /* FDT[61] - crc32() */
0xa0076540, /* FDT[62] - endian32() */
0xa0000760, /* FDT[63] - endian16() */
};
static int bcom_engine_init(void)
{
int task;
phys_addr_t tdt_pa, ctx_pa, var_pa, fdt_pa;
unsigned int tdt_size, ctx_size, var_size, fdt_size;
/* Allocate & clear SRAM zones for FDT, TDTs, contexts and vars/incs */
tdt_size = BCOM_MAX_TASKS * sizeof(struct bcom_tdt);
ctx_size = BCOM_MAX_TASKS * BCOM_CTX_SIZE;
var_size = BCOM_MAX_TASKS * (BCOM_VAR_SIZE + BCOM_INC_SIZE);
fdt_size = BCOM_FDT_SIZE;
bcom_eng->tdt = bcom_sram_alloc(tdt_size, sizeof(u32), &tdt_pa);
bcom_eng->ctx = bcom_sram_alloc(ctx_size, BCOM_CTX_ALIGN, &ctx_pa);
bcom_eng->var = bcom_sram_alloc(var_size, BCOM_VAR_ALIGN, &var_pa);
bcom_eng->fdt = bcom_sram_alloc(fdt_size, BCOM_FDT_ALIGN, &fdt_pa);
if (!bcom_eng->tdt || !bcom_eng->ctx || !bcom_eng->var || !bcom_eng->fdt) {
printk(KERN_ERR "DMA: SRAM alloc failed in engine init !\n");
bcom_sram_free(bcom_eng->tdt);
bcom_sram_free(bcom_eng->ctx);
bcom_sram_free(bcom_eng->var);
bcom_sram_free(bcom_eng->fdt);
return -ENOMEM;
}
memset(bcom_eng->tdt, 0x00, tdt_size);
memset(bcom_eng->ctx, 0x00, ctx_size);
memset(bcom_eng->var, 0x00, var_size);
memset(bcom_eng->fdt, 0x00, fdt_size);
/* Copy the FDT for the EU#3 */
memcpy(&bcom_eng->fdt[48], fdt_ops, sizeof(fdt_ops));
/* Initialize Task base structure */
for (task=0; task<BCOM_MAX_TASKS; task++)
{
out_be16(&bcom_eng->regs->tcr[task], 0);
out_8(&bcom_eng->regs->ipr[task], 0);
bcom_eng->tdt[task].context = ctx_pa;
bcom_eng->tdt[task].var = var_pa;
bcom_eng->tdt[task].fdt = fdt_pa;
var_pa += BCOM_VAR_SIZE + BCOM_INC_SIZE;
ctx_pa += BCOM_CTX_SIZE;
}
out_be32(&bcom_eng->regs->taskBar, tdt_pa);
/* Init 'always' initiator */
out_8(&bcom_eng->regs->ipr[BCOM_INITIATOR_ALWAYS], BCOM_IPR_ALWAYS);
/* Disable COMM Bus Prefetch on the original 5200; it's broken */
if ((mfspr(SPRN_SVR) & MPC5200_SVR_MASK) == MPC5200_SVR)
bcom_disable_prefetch();
/* Init lock */
spin_lock_init(&bcom_eng->lock);
return 0;
}
static void
bcom_engine_cleanup(void)
{
int task;
/* Stop all tasks */
for (task=0; task<BCOM_MAX_TASKS; task++)
{
out_be16(&bcom_eng->regs->tcr[task], 0);
out_8(&bcom_eng->regs->ipr[task], 0);
}
out_be32(&bcom_eng->regs->taskBar, 0ul);
/* Release the SRAM zones */
bcom_sram_free(bcom_eng->tdt);
bcom_sram_free(bcom_eng->ctx);
bcom_sram_free(bcom_eng->var);
bcom_sram_free(bcom_eng->fdt);
}
/* ======================================================================== */
/* OF platform driver */
/* ======================================================================== */
static int mpc52xx_bcom_probe(struct platform_device *op)
{
struct device_node *ofn_sram;
struct resource res_bcom;
int rv;
/* Inform user we're ok so far */
printk(KERN_INFO "DMA: MPC52xx BestComm driver\n");
/* Get the bestcomm node */
of_node_get(op->dev.of_node);
/* Prepare SRAM */
ofn_sram = of_find_matching_node(NULL, mpc52xx_sram_ids);
if (!ofn_sram) {
printk(KERN_ERR DRIVER_NAME ": "
"No SRAM found in device tree\n");
rv = -ENODEV;
goto error_ofput;
}
rv = bcom_sram_init(ofn_sram, DRIVER_NAME);
of_node_put(ofn_sram);
if (rv) {
printk(KERN_ERR DRIVER_NAME ": "
"Error in SRAM init\n");
goto error_ofput;
}
/* Get a clean struct */
bcom_eng = kzalloc(sizeof(struct bcom_engine), GFP_KERNEL);
if (!bcom_eng) {
printk(KERN_ERR DRIVER_NAME ": "
"Can't allocate state structure\n");
rv = -ENOMEM;
goto error_sramclean;
}
/* Save the node */
bcom_eng->ofnode = op->dev.of_node;
/* Get, reserve & map io */
if (of_address_to_resource(op->dev.of_node, 0, &res_bcom)) {
printk(KERN_ERR DRIVER_NAME ": "
"Can't get resource\n");
rv = -EINVAL;
goto error_sramclean;
}
if (!request_mem_region(res_bcom.start, resource_size(&res_bcom),
DRIVER_NAME)) {
printk(KERN_ERR DRIVER_NAME ": "
"Can't request registers region\n");
rv = -EBUSY;
goto error_sramclean;
}
bcom_eng->regs_base = res_bcom.start;
bcom_eng->regs = ioremap(res_bcom.start, sizeof(struct mpc52xx_sdma));
if (!bcom_eng->regs) {
printk(KERN_ERR DRIVER_NAME ": "
"Can't map registers\n");
rv = -ENOMEM;
goto error_release;
}
/* Now, do the real init */
rv = bcom_engine_init();
if (rv)
goto error_unmap;
/* Done ! */
printk(KERN_INFO "DMA: MPC52xx BestComm engine @%08lx ok !\n",
(long)bcom_eng->regs_base);
return 0;
/* Error path */
error_unmap:
iounmap(bcom_eng->regs);
error_release:
release_mem_region(res_bcom.start, sizeof(struct mpc52xx_sdma));
error_sramclean:
kfree(bcom_eng);
bcom_sram_cleanup();
error_ofput:
of_node_put(op->dev.of_node);
printk(KERN_ERR "DMA: MPC52xx BestComm init failed !\n");
return rv;
}
static int mpc52xx_bcom_remove(struct platform_device *op)
{
/* Clean up the engine */
bcom_engine_cleanup();
/* Cleanup SRAM */
bcom_sram_cleanup();
/* Release regs */
iounmap(bcom_eng->regs);
release_mem_region(bcom_eng->regs_base, sizeof(struct mpc52xx_sdma));
/* Release the node */
of_node_put(bcom_eng->ofnode);
/* Release memory */
kfree(bcom_eng);
bcom_eng = NULL;
return 0;
}
static struct of_device_id mpc52xx_bcom_of_match[] = {
{ .compatible = "fsl,mpc5200-bestcomm", },
{ .compatible = "mpc5200-bestcomm", },
{},
};
MODULE_DEVICE_TABLE(of, mpc52xx_bcom_of_match);
static struct platform_driver mpc52xx_bcom_of_platform_driver = {
.probe = mpc52xx_bcom_probe,
.remove = mpc52xx_bcom_remove,
.driver = {
.name = DRIVER_NAME,
.owner = THIS_MODULE,
.of_match_table = mpc52xx_bcom_of_match,
},
};
/* ======================================================================== */
/* Module */
/* ======================================================================== */
static int __init
mpc52xx_bcom_init(void)
{
return platform_driver_register(&mpc52xx_bcom_of_platform_driver);
}
static void __exit
mpc52xx_bcom_exit(void)
{
platform_driver_unregister(&mpc52xx_bcom_of_platform_driver);
}
/* If we're not a module, we must make sure everything is setup before */
/* anyone tries to use us ... that's why we use subsys_initcall instead */
/* of module_init. */
subsys_initcall(mpc52xx_bcom_init);
module_exit(mpc52xx_bcom_exit);
MODULE_DESCRIPTION("Freescale MPC52xx BestComm DMA");
MODULE_AUTHOR("Sylvain Munaut <tnt@246tNt.com>");
MODULE_AUTHOR("Andrey Volkov <avolkov@varma-el.com>");
MODULE_AUTHOR("Dale Farnsworth <dfarnsworth@mvista.com>");
MODULE_LICENSE("GPL v2");