Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2f9ea1bd authored by Sylvain Munaut's avatar Sylvain Munaut Committed by Grant Likely
Browse files

[POWERPC] bestcomm: core bestcomm support for Freescale MPC5200



This patch adds support for the core of the BestComm API
for the Freescale MPC5200(b). The BestComm engine is a
microcode-controlled / tasks-based DMA used by several
of the onchip devices.

Setting up the tasks / memory allocation and all common
low level functions are handled by this patch.
The specifics details of each tasks and their microcode
are split-out in separate patches.

This is not the official API, but a much cleaner one.
(hopefully)

Signed-off-by: default avatarSylvain Munaut <tnt@246tNt.com>
Signed-off-by: default avatarGrant Likely <grant.likely@secretlab.ca>
parent 07e6e931
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -315,4 +315,6 @@ config FSL_ULI1575
config CPM
	bool

source "arch/powerpc/sysdev/bestcomm/Kconfig"

endmenu
+1 −0
Original line number Diff line number Diff line
@@ -14,6 +14,7 @@ obj-$(CONFIG_FSL_SOC) += fsl_soc.o
obj-$(CONFIG_FSL_PCI)		+= fsl_pci.o
obj-$(CONFIG_TSI108_BRIDGE)	+= tsi108_pci.o tsi108_dev.o
obj-$(CONFIG_QUICC_ENGINE)	+= qe_lib/
obj-$(CONFIG_PPC_BESTCOMM)	+= bestcomm/
mv64x60-$(CONFIG_PCI)		+= mv64x60_pci.o
obj-$(CONFIG_MV64X60)		+= $(mv64x60-y) mv64x60_pic.o mv64x60_dev.o \
				   mv64x60_udbg.o
+18 −0
Original line number Diff line number Diff line
#
# Kconfig options for Bestcomm
#

config PPC_BESTCOMM
	tristate "Bestcomm DMA engine support"
	depends on PPC_MPC52xx
	default n
	select PPC_LIB_RHEAP
	help
	  BestComm is the name of the communication coprocessor found
	  on the Freescale MPC5200 family of processor. It's usage is
	  optionnal for some drivers (like ATA), but required for
	  others (like FEC).

	  If you want to use drivers that require DMA operations,
	  answer Y or M. Otherwise say N.
+8 −0
Original line number Diff line number Diff line
#
# Makefile for BestComm & co
#

bestcomm-core-objs	:= bestcomm.o sram.o

obj-$(CONFIG_PPC_BESTCOMM)		+= bestcomm-core.o
 
+528 −0
Original line number Diff line number Diff line
/*
 * 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 "sram.h"
#include "bestcomm_priv.h"
#include "bestcomm.h"

#define DRIVER_NAME "bestcomm-core"


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;

	/* 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 __devinit
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, apparently it's not reliable yet */
	/* FIXME: This should be done on 5200 and not 5200B ... */
	out_be16(&bcom_eng->regs->PtdCntrl, in_be16(&bcom_eng->regs->PtdCntrl) | 1);

	/* 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 __devinit
mpc52xx_bcom_probe(struct of_device *op, const struct of_device_id *match)
{
	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->node);

	/* Prepare SRAM */
	ofn_sram = of_find_compatible_node(NULL, "sram", "mpc5200-sram");
	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->node;

	/* Get, reserve & map io */
	if (of_address_to_resource(op->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, sizeof(struct mpc52xx_sdma),
				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",
		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->node);

	printk(KERN_ERR "DMA: MPC52xx BestComm init failed !\n");

	return rv;
}


static int
mpc52xx_bcom_remove(struct of_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[] = {
	{
		.type		= "dma-controller",
		.compatible	= "mpc5200-bestcomm",
	},
	{},
};

MODULE_DEVICE_TABLE(of, mpc52xx_bcom_of_match);


static struct of_platform_driver mpc52xx_bcom_of_platform_driver = {
	.owner		= THIS_MODULE,
	.name		= DRIVER_NAME,
	.match_table	= mpc52xx_bcom_of_match,
	.probe		= mpc52xx_bcom_probe,
	.remove		= mpc52xx_bcom_remove,
	.driver		= {
		.name	= DRIVER_NAME,
		.owner	= THIS_MODULE,
	},
};


/* ======================================================================== */
/* Module                                                                   */
/* ======================================================================== */

static int __init
mpc52xx_bcom_init(void)
{
	return of_register_platform_driver(&mpc52xx_bcom_of_platform_driver);
}

static void __exit
mpc52xx_bcom_exit(void)
{
	of_unregister_platform_driver(&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");
Loading