Merge commit 'gcl/next' into next

This commit is contained in:
Benjamin Herrenschmidt
2009-12-09 17:10:22 +11:00
17 changed files with 1609 additions and 320 deletions

View File

@@ -62,3 +62,8 @@ config PPC_MPC5200_GPIO
select GENERIC_GPIO
help
Enable gpiolib support for mpc5200 based boards
config PPC_MPC5200_LPBFIFO
tristate "MPC5200 LocalPlus bus FIFO driver"
depends on PPC_MPC52xx
select PPC_BESTCOMM_GEN_BD

View File

@@ -15,3 +15,4 @@ ifeq ($(CONFIG_PPC_LITE5200),y)
endif
obj-$(CONFIG_PPC_MPC5200_GPIO) += mpc52xx_gpio.o
obj-$(CONFIG_PPC_MPC5200_LPBFIFO) += mpc52xx_lpbfifo.o

View File

@@ -16,8 +16,14 @@
* output signals or measure input signals.
*
* This driver supports the GPIO and IRQ controller functions of the GPT
* device. Timer functions are not yet supported, nor is the watchdog
* timer.
* device. Timer functions are not yet supported.
*
* The timer gpt0 can be used as watchdog (wdt). If the wdt mode is used,
* this prevents the use of any gpt0 gpt function (i.e. they will fail with
* -EBUSY). Thus, the safety wdt function always has precedence over the gpt
* function. If the kernel has been compiled with CONFIG_WATCHDOG_NOWAYOUT,
* this means that gpt0 is locked in wdt mode until the next reboot - this
* may be a requirement in safety applications.
*
* To use the GPIO function, the following two properties must be added
* to the device tree node for the gpt device (typically in the .dts file
@@ -46,17 +52,24 @@
* the output mode. This driver does not change the output mode setting.
*/
#include <linux/device.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/list.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/of_gpio.h>
#include <linux/kernel.h>
#include <linux/watchdog.h>
#include <linux/miscdevice.h>
#include <linux/uaccess.h>
#include <asm/div64.h>
#include <asm/mpc52xx.h>
MODULE_DESCRIPTION("Freescale MPC52xx gpt driver");
MODULE_AUTHOR("Sascha Hauer, Grant Likely");
MODULE_AUTHOR("Sascha Hauer, Grant Likely, Albrecht Dreß");
MODULE_LICENSE("GPL");
/**
@@ -66,18 +79,27 @@ MODULE_LICENSE("GPL");
* @lock: spinlock to coordinate between different functions.
* @of_gc: of_gpio_chip instance structure; used when GPIO is enabled
* @irqhost: Pointer to irq_host instance; used when IRQ mode is supported
* @wdt_mode: only relevant for gpt0: bit 0 (MPC52xx_GPT_CAN_WDT) indicates
* if the gpt may be used as wdt, bit 1 (MPC52xx_GPT_IS_WDT) indicates
* if the timer is actively used as wdt which blocks gpt functions
*/
struct mpc52xx_gpt_priv {
struct list_head list; /* List of all GPT devices */
struct device *dev;
struct mpc52xx_gpt __iomem *regs;
spinlock_t lock;
struct irq_host *irqhost;
u32 ipb_freq;
u8 wdt_mode;
#if defined(CONFIG_GPIOLIB)
struct of_gpio_chip of_gc;
#endif
};
LIST_HEAD(mpc52xx_gpt_list);
DEFINE_MUTEX(mpc52xx_gpt_list_mutex);
#define MPC52xx_GPT_MODE_MS_MASK (0x07)
#define MPC52xx_GPT_MODE_MS_IC (0x01)
#define MPC52xx_GPT_MODE_MS_OC (0x02)
@@ -88,15 +110,25 @@ struct mpc52xx_gpt_priv {
#define MPC52xx_GPT_MODE_GPIO_OUT_LOW (0x20)
#define MPC52xx_GPT_MODE_GPIO_OUT_HIGH (0x30)
#define MPC52xx_GPT_MODE_COUNTER_ENABLE (0x1000)
#define MPC52xx_GPT_MODE_CONTINUOUS (0x0400)
#define MPC52xx_GPT_MODE_OPEN_DRAIN (0x0200)
#define MPC52xx_GPT_MODE_IRQ_EN (0x0100)
#define MPC52xx_GPT_MODE_WDT_EN (0x8000)
#define MPC52xx_GPT_MODE_ICT_MASK (0x030000)
#define MPC52xx_GPT_MODE_ICT_RISING (0x010000)
#define MPC52xx_GPT_MODE_ICT_FALLING (0x020000)
#define MPC52xx_GPT_MODE_ICT_TOGGLE (0x030000)
#define MPC52xx_GPT_MODE_WDT_PING (0xa5)
#define MPC52xx_GPT_STATUS_IRQMASK (0x000f)
#define MPC52xx_GPT_CAN_WDT (1 << 0)
#define MPC52xx_GPT_IS_WDT (1 << 1)
/* ---------------------------------------------------------------------
* Cascaded interrupt controller hooks
*/
@@ -190,7 +222,7 @@ static int mpc52xx_gpt_irq_xlate(struct irq_host *h, struct device_node *ct,
dev_dbg(gpt->dev, "%s: flags=%i\n", __func__, intspec[0]);
if ((intsize < 1) || (intspec[0] < 1) || (intspec[0] > 3)) {
if ((intsize < 1) || (intspec[0] > 3)) {
dev_err(gpt->dev, "bad irq specifier in %s\n", ct->full_name);
return -EINVAL;
}
@@ -211,13 +243,11 @@ mpc52xx_gpt_irq_setup(struct mpc52xx_gpt_priv *gpt, struct device_node *node)
{
int cascade_virq;
unsigned long flags;
/* Only setup cascaded IRQ if device tree claims the GPT is
* an interrupt controller */
if (!of_find_property(node, "interrupt-controller", NULL))
return;
u32 mode;
cascade_virq = irq_of_parse_and_map(node, 0);
if (!cascade_virq)
return;
gpt->irqhost = irq_alloc_host(node, IRQ_HOST_MAP_LINEAR, 1,
&mpc52xx_gpt_irq_ops, -1);
@@ -227,14 +257,16 @@ mpc52xx_gpt_irq_setup(struct mpc52xx_gpt_priv *gpt, struct device_node *node)
}
gpt->irqhost->host_data = gpt;
set_irq_data(cascade_virq, gpt);
set_irq_chained_handler(cascade_virq, mpc52xx_gpt_irq_cascade);
/* Set to Input Capture mode */
/* If the GPT is currently disabled, then change it to be in Input
* Capture mode. If the mode is non-zero, then the pin could be
* already in use for something. */
spin_lock_irqsave(&gpt->lock, flags);
clrsetbits_be32(&gpt->regs->mode, MPC52xx_GPT_MODE_MS_MASK,
MPC52xx_GPT_MODE_MS_IC);
mode = in_be32(&gpt->regs->mode);
if ((mode & MPC52xx_GPT_MODE_MS_MASK) == 0)
out_be32(&gpt->regs->mode, mode | MPC52xx_GPT_MODE_MS_IC);
spin_unlock_irqrestore(&gpt->lock, flags);
dev_dbg(gpt->dev, "%s() complete. virq=%i\n", __func__, cascade_virq);
@@ -335,6 +367,354 @@ static void
mpc52xx_gpt_gpio_setup(struct mpc52xx_gpt_priv *p, struct device_node *np) { }
#endif /* defined(CONFIG_GPIOLIB) */
/***********************************************************************
* Timer API
*/
/**
* mpc52xx_gpt_from_irq - Return the GPT device associated with an IRQ number
* @irq: irq of timer.
*/
struct mpc52xx_gpt_priv *mpc52xx_gpt_from_irq(int irq)
{
struct mpc52xx_gpt_priv *gpt;
struct list_head *pos;
/* Iterate over the list of timers looking for a matching device */
mutex_lock(&mpc52xx_gpt_list_mutex);
list_for_each(pos, &mpc52xx_gpt_list) {
gpt = container_of(pos, struct mpc52xx_gpt_priv, list);
if (gpt->irqhost && irq == irq_linear_revmap(gpt->irqhost, 0)) {
mutex_unlock(&mpc52xx_gpt_list_mutex);
return gpt;
}
}
mutex_unlock(&mpc52xx_gpt_list_mutex);
return NULL;
}
EXPORT_SYMBOL(mpc52xx_gpt_from_irq);
static int mpc52xx_gpt_do_start(struct mpc52xx_gpt_priv *gpt, u64 period,
int continuous, int as_wdt)
{
u32 clear, set;
u64 clocks;
u32 prescale;
unsigned long flags;
clear = MPC52xx_GPT_MODE_MS_MASK | MPC52xx_GPT_MODE_CONTINUOUS;
set = MPC52xx_GPT_MODE_MS_GPIO | MPC52xx_GPT_MODE_COUNTER_ENABLE;
if (as_wdt) {
clear |= MPC52xx_GPT_MODE_IRQ_EN;
set |= MPC52xx_GPT_MODE_WDT_EN;
} else if (continuous)
set |= MPC52xx_GPT_MODE_CONTINUOUS;
/* Determine the number of clocks in the requested period. 64 bit
* arithmatic is done here to preserve the precision until the value
* is scaled back down into the u32 range. Period is in 'ns', bus
* frequency is in Hz. */
clocks = period * (u64)gpt->ipb_freq;
do_div(clocks, 1000000000); /* Scale it down to ns range */
/* This device cannot handle a clock count greater than 32 bits */
if (clocks > 0xffffffff)
return -EINVAL;
/* Calculate the prescaler and count values from the clocks value.
* 'clocks' is the number of clock ticks in the period. The timer
* has 16 bit precision and a 16 bit prescaler. Prescaler is
* calculated by integer dividing the clocks by 0x10000 (shifting
* down 16 bits) to obtain the smallest possible divisor for clocks
* to get a 16 bit count value.
*
* Note: the prescale register is '1' based, not '0' based. ie. a
* value of '1' means divide the clock by one. 0xffff divides the
* clock by 0xffff. '0x0000' does not divide by zero, but wraps
* around and divides by 0x10000. That is why prescale must be
* a u32 variable, not a u16, for this calculation. */
prescale = (clocks >> 16) + 1;
do_div(clocks, prescale);
if (clocks > 0xffff) {
pr_err("calculation error; prescale:%x clocks:%llx\n",
prescale, clocks);
return -EINVAL;
}
/* Set and enable the timer, reject an attempt to use a wdt as gpt */
spin_lock_irqsave(&gpt->lock, flags);
if (as_wdt)
gpt->wdt_mode |= MPC52xx_GPT_IS_WDT;
else if ((gpt->wdt_mode & MPC52xx_GPT_IS_WDT) != 0) {
spin_unlock_irqrestore(&gpt->lock, flags);
return -EBUSY;
}
out_be32(&gpt->regs->count, prescale << 16 | clocks);
clrsetbits_be32(&gpt->regs->mode, clear, set);
spin_unlock_irqrestore(&gpt->lock, flags);
return 0;
}
/**
* mpc52xx_gpt_start_timer - Set and enable the GPT timer
* @gpt: Pointer to gpt private data structure
* @period: period of timer in ns; max. ~130s @ 33MHz IPB clock
* @continuous: set to 1 to make timer continuous free running
*
* An interrupt will be generated every time the timer fires
*/
int mpc52xx_gpt_start_timer(struct mpc52xx_gpt_priv *gpt, u64 period,
int continuous)
{
return mpc52xx_gpt_do_start(gpt, period, continuous, 0);
}
EXPORT_SYMBOL(mpc52xx_gpt_start_timer);
/**
* mpc52xx_gpt_stop_timer - Stop a gpt
* @gpt: Pointer to gpt private data structure
*
* Returns an error if attempting to stop a wdt
*/
int mpc52xx_gpt_stop_timer(struct mpc52xx_gpt_priv *gpt)
{
unsigned long flags;
/* reject the operation if the timer is used as watchdog (gpt 0 only) */
spin_lock_irqsave(&gpt->lock, flags);
if ((gpt->wdt_mode & MPC52xx_GPT_IS_WDT) != 0) {
spin_unlock_irqrestore(&gpt->lock, flags);
return -EBUSY;
}
clrbits32(&gpt->regs->mode, MPC52xx_GPT_MODE_COUNTER_ENABLE);
spin_unlock_irqrestore(&gpt->lock, flags);
return 0;
}
EXPORT_SYMBOL(mpc52xx_gpt_stop_timer);
/**
* mpc52xx_gpt_timer_period - Read the timer period
* @gpt: Pointer to gpt private data structure
*
* Returns the timer period in ns
*/
u64 mpc52xx_gpt_timer_period(struct mpc52xx_gpt_priv *gpt)
{
u64 period;
u64 prescale;
unsigned long flags;
spin_lock_irqsave(&gpt->lock, flags);
period = in_be32(&gpt->regs->count);
spin_unlock_irqrestore(&gpt->lock, flags);
prescale = period >> 16;
period &= 0xffff;
if (prescale == 0)
prescale = 0x10000;
period = period * prescale * 1000000000ULL;
do_div(period, (u64)gpt->ipb_freq);
return period;
}
EXPORT_SYMBOL(mpc52xx_gpt_timer_period);
#if defined(CONFIG_MPC5200_WDT)
/***********************************************************************
* Watchdog API for gpt0
*/
#define WDT_IDENTITY "mpc52xx watchdog on GPT0"
/* wdt_is_active stores wether or not the /dev/watchdog device is opened */
static unsigned long wdt_is_active;
/* wdt-capable gpt */
static struct mpc52xx_gpt_priv *mpc52xx_gpt_wdt;
/* low-level wdt functions */
static inline void mpc52xx_gpt_wdt_ping(struct mpc52xx_gpt_priv *gpt_wdt)
{
unsigned long flags;
spin_lock_irqsave(&gpt_wdt->lock, flags);
out_8((u8 *) &gpt_wdt->regs->mode, MPC52xx_GPT_MODE_WDT_PING);
spin_unlock_irqrestore(&gpt_wdt->lock, flags);
}
/* wdt misc device api */
static ssize_t mpc52xx_wdt_write(struct file *file, const char __user *data,
size_t len, loff_t *ppos)
{
struct mpc52xx_gpt_priv *gpt_wdt = file->private_data;
mpc52xx_gpt_wdt_ping(gpt_wdt);
return 0;
}
static struct watchdog_info mpc5200_wdt_info = {
.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING,
.identity = WDT_IDENTITY,
};
static long mpc52xx_wdt_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
struct mpc52xx_gpt_priv *gpt_wdt = file->private_data;
int __user *data = (int __user *)arg;
int timeout;
u64 real_timeout;
int ret = 0;
switch (cmd) {
case WDIOC_GETSUPPORT:
ret = copy_to_user(data, &mpc5200_wdt_info,
sizeof(mpc5200_wdt_info));
if (ret)
ret = -EFAULT;
break;
case WDIOC_GETSTATUS:
case WDIOC_GETBOOTSTATUS:
ret = put_user(0, data);
break;
case WDIOC_KEEPALIVE:
mpc52xx_gpt_wdt_ping(gpt_wdt);
break;
case WDIOC_SETTIMEOUT:
ret = get_user(timeout, data);
if (ret)
break;
real_timeout = (u64) timeout * 1000000000ULL;
ret = mpc52xx_gpt_do_start(gpt_wdt, real_timeout, 0, 1);
if (ret)
break;
/* fall through and return the timeout */
case WDIOC_GETTIMEOUT:
/* we need to round here as to avoid e.g. the following
* situation:
* - timeout requested is 1 second;
* - real timeout @33MHz is 999997090ns
* - the int divide by 10^9 will return 0.
*/
real_timeout =
mpc52xx_gpt_timer_period(gpt_wdt) + 500000000ULL;
do_div(real_timeout, 1000000000ULL);
timeout = (int) real_timeout;
ret = put_user(timeout, data);
break;
default:
ret = -ENOTTY;
}
return ret;
}
static int mpc52xx_wdt_open(struct inode *inode, struct file *file)
{
int ret;
/* sanity check */
if (!mpc52xx_gpt_wdt)
return -ENODEV;
/* /dev/watchdog can only be opened once */
if (test_and_set_bit(0, &wdt_is_active))
return -EBUSY;
/* Set and activate the watchdog with 30 seconds timeout */
ret = mpc52xx_gpt_do_start(mpc52xx_gpt_wdt, 30ULL * 1000000000ULL,
0, 1);
if (ret) {
clear_bit(0, &wdt_is_active);
return ret;
}
file->private_data = mpc52xx_gpt_wdt;
return nonseekable_open(inode, file);
}
static int mpc52xx_wdt_release(struct inode *inode, struct file *file)
{
/* note: releasing the wdt in NOWAYOUT-mode does not stop it */
#if !defined(CONFIG_WATCHDOG_NOWAYOUT)
struct mpc52xx_gpt_priv *gpt_wdt = file->private_data;
unsigned long flags;
spin_lock_irqsave(&gpt_wdt->lock, flags);
clrbits32(&gpt_wdt->regs->mode,
MPC52xx_GPT_MODE_COUNTER_ENABLE | MPC52xx_GPT_MODE_WDT_EN);
gpt_wdt->wdt_mode &= ~MPC52xx_GPT_IS_WDT;
spin_unlock_irqrestore(&gpt_wdt->lock, flags);
#endif
clear_bit(0, &wdt_is_active);
return 0;
}
static const struct file_operations mpc52xx_wdt_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.write = mpc52xx_wdt_write,
.unlocked_ioctl = mpc52xx_wdt_ioctl,
.open = mpc52xx_wdt_open,
.release = mpc52xx_wdt_release,
};
static struct miscdevice mpc52xx_wdt_miscdev = {
.minor = WATCHDOG_MINOR,
.name = "watchdog",
.fops = &mpc52xx_wdt_fops,
};
static int __devinit mpc52xx_gpt_wdt_init(void)
{
int err;
/* try to register the watchdog misc device */
err = misc_register(&mpc52xx_wdt_miscdev);
if (err)
pr_err("%s: cannot register watchdog device\n", WDT_IDENTITY);
else
pr_info("%s: watchdog device registered\n", WDT_IDENTITY);
return err;
}
static int mpc52xx_gpt_wdt_setup(struct mpc52xx_gpt_priv *gpt,
const u32 *period)
{
u64 real_timeout;
/* remember the gpt for the wdt operation */
mpc52xx_gpt_wdt = gpt;
/* configure the wdt if the device tree contained a timeout */
if (!period || *period == 0)
return 0;
real_timeout = (u64) *period * 1000000000ULL;
if (mpc52xx_gpt_do_start(gpt, real_timeout, 0, 1))
dev_warn(gpt->dev, "starting as wdt failed\n");
else
dev_info(gpt->dev, "watchdog set to %us timeout\n", *period);
return 0;
}
#else
static int __devinit mpc52xx_gpt_wdt_init(void)
{
return 0;
}
#define mpc52xx_gpt_wdt_setup(x, y) (0)
#endif /* CONFIG_MPC5200_WDT */
/* ---------------------------------------------------------------------
* of_platform bus binding code
*/
@@ -349,6 +729,7 @@ static int __devinit mpc52xx_gpt_probe(struct of_device *ofdev,
spin_lock_init(&gpt->lock);
gpt->dev = &ofdev->dev;
gpt->ipb_freq = mpc5xxx_get_bus_frequency(ofdev->node);
gpt->regs = of_iomap(ofdev->node, 0);
if (!gpt->regs) {
kfree(gpt);
@@ -360,6 +741,26 @@ static int __devinit mpc52xx_gpt_probe(struct of_device *ofdev,
mpc52xx_gpt_gpio_setup(gpt, ofdev->node);
mpc52xx_gpt_irq_setup(gpt, ofdev->node);
mutex_lock(&mpc52xx_gpt_list_mutex);
list_add(&gpt->list, &mpc52xx_gpt_list);
mutex_unlock(&mpc52xx_gpt_list_mutex);
/* check if this device could be a watchdog */
if (of_get_property(ofdev->node, "fsl,has-wdt", NULL) ||
of_get_property(ofdev->node, "has-wdt", NULL)) {
const u32 *on_boot_wdt;
gpt->wdt_mode = MPC52xx_GPT_CAN_WDT;
on_boot_wdt = of_get_property(ofdev->node, "fsl,wdt-on-boot",
NULL);
if (on_boot_wdt) {
dev_info(gpt->dev, "used as watchdog\n");
gpt->wdt_mode |= MPC52xx_GPT_IS_WDT;
} else
dev_info(gpt->dev, "can function as watchdog\n");
mpc52xx_gpt_wdt_setup(gpt, on_boot_wdt);
}
return 0;
}
@@ -394,3 +795,4 @@ static int __init mpc52xx_gpt_init(void)
/* Make sure GPIOs and IRQs get set up before anyone tries to use them */
subsys_initcall(mpc52xx_gpt_init);
device_initcall(mpc52xx_gpt_wdt_init);

View File

@@ -0,0 +1,560 @@
/*
* LocalPlus Bus FIFO driver for the Freescale MPC52xx.
*
* Copyright (C) 2009 Secret Lab Technologies Ltd.
*
* This file is released under the GPLv2
*
* Todo:
* - Add support for multiple requests to be queued.
*/
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/spinlock.h>
#include <asm/io.h>
#include <asm/prom.h>
#include <asm/mpc52xx.h>
#include <asm/time.h>
#include <sysdev/bestcomm/bestcomm.h>
#include <sysdev/bestcomm/bestcomm_priv.h>
#include <sysdev/bestcomm/gen_bd.h>
MODULE_AUTHOR("Grant Likely <grant.likely@secretlab.ca>");
MODULE_DESCRIPTION("MPC5200 LocalPlus FIFO device driver");
MODULE_LICENSE("GPL");
#define LPBFIFO_REG_PACKET_SIZE (0x00)
#define LPBFIFO_REG_START_ADDRESS (0x04)
#define LPBFIFO_REG_CONTROL (0x08)
#define LPBFIFO_REG_ENABLE (0x0C)
#define LPBFIFO_REG_BYTES_DONE_STATUS (0x14)
#define LPBFIFO_REG_FIFO_DATA (0x40)
#define LPBFIFO_REG_FIFO_STATUS (0x44)
#define LPBFIFO_REG_FIFO_CONTROL (0x48)
#define LPBFIFO_REG_FIFO_ALARM (0x4C)
struct mpc52xx_lpbfifo {
struct device *dev;
phys_addr_t regs_phys;
void __iomem *regs;
int irq;
spinlock_t lock;
struct bcom_task *bcom_tx_task;
struct bcom_task *bcom_rx_task;
struct bcom_task *bcom_cur_task;
/* Current state data */
struct mpc52xx_lpbfifo_request *req;
int dma_irqs_enabled;
};
/* The MPC5200 has only one fifo, so only need one instance structure */
static struct mpc52xx_lpbfifo lpbfifo;
/**
* mpc52xx_lpbfifo_kick - Trigger the next block of data to be transfered
*/
static void mpc52xx_lpbfifo_kick(struct mpc52xx_lpbfifo_request *req)
{
size_t transfer_size = req->size - req->pos;
struct bcom_bd *bd;
void __iomem *reg;
u32 *data;
int i;
int bit_fields;
int dma = !(req->flags & MPC52XX_LPBFIFO_FLAG_NO_DMA);
int write = req->flags & MPC52XX_LPBFIFO_FLAG_WRITE;
int poll_dma = req->flags & MPC52XX_LPBFIFO_FLAG_POLL_DMA;
/* Set and clear the reset bits; is good practice in User Manual */
out_be32(lpbfifo.regs + LPBFIFO_REG_ENABLE, 0x01010000);
/* set master enable bit */
out_be32(lpbfifo.regs + LPBFIFO_REG_ENABLE, 0x00000001);
if (!dma) {
/* While the FIFO can be setup for transfer sizes as large as
* 16M-1, the FIFO itself is only 512 bytes deep and it does
* not generate interrupts for FIFO full events (only transfer
* complete will raise an IRQ). Therefore when not using
* Bestcomm to drive the FIFO it needs to either be polled, or
* transfers need to constrained to the size of the fifo.
*
* This driver restricts the size of the transfer
*/
if (transfer_size > 512)
transfer_size = 512;
/* Load the FIFO with data */
if (write) {
reg = lpbfifo.regs + LPBFIFO_REG_FIFO_DATA;
data = req->data + req->pos;
for (i = 0; i < transfer_size; i += 4)
out_be32(reg, *data++);
}
/* Unmask both error and completion irqs */
out_be32(lpbfifo.regs + LPBFIFO_REG_ENABLE, 0x00000301);
} else {
/* Choose the correct direction
*
* Configure the watermarks so DMA will always complete correctly.
* It may be worth experimenting with the ALARM value to see if
* there is a performance impacit. However, if it is wrong there
* is a risk of DMA not transferring the last chunk of data
*/
if (write) {
out_be32(lpbfifo.regs + LPBFIFO_REG_FIFO_ALARM, 0x1e4);
out_8(lpbfifo.regs + LPBFIFO_REG_FIFO_CONTROL, 7);
lpbfifo.bcom_cur_task = lpbfifo.bcom_tx_task;
} else {
out_be32(lpbfifo.regs + LPBFIFO_REG_FIFO_ALARM, 0x1ff);
out_8(lpbfifo.regs + LPBFIFO_REG_FIFO_CONTROL, 0);
lpbfifo.bcom_cur_task = lpbfifo.bcom_rx_task;
if (poll_dma) {
if (lpbfifo.dma_irqs_enabled) {
disable_irq(bcom_get_task_irq(lpbfifo.bcom_rx_task));
lpbfifo.dma_irqs_enabled = 0;
}
} else {
if (!lpbfifo.dma_irqs_enabled) {
enable_irq(bcom_get_task_irq(lpbfifo.bcom_rx_task));
lpbfifo.dma_irqs_enabled = 1;
}
}
}
bd = bcom_prepare_next_buffer(lpbfifo.bcom_cur_task);
bd->status = transfer_size;
if (!write) {
/*
* In the DMA read case, the DMA doesn't complete,
* possibly due to incorrect watermarks in the ALARM
* and CONTROL regs. For now instead of trying to
* determine the right watermarks that will make this
* work, just increase the number of bytes the FIFO is
* expecting.
*
* When submitting another operation, the FIFO will get
* reset, so the condition of the FIFO waiting for a
* non-existent 4 bytes will get cleared.
*/
transfer_size += 4; /* BLECH! */
}
bd->data[0] = req->data_phys + req->pos;
bcom_submit_next_buffer(lpbfifo.bcom_cur_task, NULL);
/* error irq & master enabled bit */
bit_fields = 0x00000201;
/* Unmask irqs */
if (write && (!poll_dma))
bit_fields |= 0x00000100; /* completion irq too */
out_be32(lpbfifo.regs + LPBFIFO_REG_ENABLE, bit_fields);
}
/* Set transfer size, width, chip select and READ mode */
out_be32(lpbfifo.regs + LPBFIFO_REG_START_ADDRESS,
req->offset + req->pos);
out_be32(lpbfifo.regs + LPBFIFO_REG_PACKET_SIZE, transfer_size);
bit_fields = req->cs << 24 | 0x000008;
if (!write)
bit_fields |= 0x010000; /* read mode */
out_be32(lpbfifo.regs + LPBFIFO_REG_CONTROL, bit_fields);
/* Kick it off */
out_8(lpbfifo.regs + LPBFIFO_REG_PACKET_SIZE, 0x01);
if (dma)
bcom_enable(lpbfifo.bcom_cur_task);
}
/**
* mpc52xx_lpbfifo_irq - IRQ handler for LPB FIFO
*
* On transmit, the dma completion irq triggers before the fifo completion
* triggers. Handle the dma completion here instead of the LPB FIFO Bestcomm
* task completion irq becuase everyting is not really done until the LPB FIFO
* completion irq triggers.
*
* In other words:
* For DMA, on receive, the "Fat Lady" is the bestcom completion irq. on
* transmit, the fifo completion irq is the "Fat Lady". The opera (or in this
* case the DMA/FIFO operation) is not finished until the "Fat Lady" sings.
*
* Reasons for entering this routine:
* 1) PIO mode rx and tx completion irq
* 2) DMA interrupt mode tx completion irq
* 3) DMA polled mode tx
*
* Exit conditions:
* 1) Transfer aborted
* 2) FIFO complete without DMA; more data to do
* 3) FIFO complete without DMA; all data transfered
* 4) FIFO complete using DMA
*
* Condition 1 can occur regardless of whether or not DMA is used.
* It requires executing the callback to report the error and exiting
* immediately.
*
* Condition 2 requires programming the FIFO with the next block of data
*
* Condition 3 requires executing the callback to report completion
*
* Condition 4 means the same as 3, except that we also retrieve the bcom
* buffer so DMA doesn't get clogged up.
*
* To make things trickier, the spinlock must be dropped before
* executing the callback, otherwise we could end up with a deadlock
* or nested spinlock condition. The out path is non-trivial, so
* extra fiddling is done to make sure all paths lead to the same
* outbound code.
*/
static irqreturn_t mpc52xx_lpbfifo_irq(int irq, void *dev_id)
{
struct mpc52xx_lpbfifo_request *req;
u32 status = in_8(lpbfifo.regs + LPBFIFO_REG_BYTES_DONE_STATUS);
void __iomem *reg;
u32 *data;
int count, i;
int do_callback = 0;
u32 ts;
unsigned long flags;
int dma, write, poll_dma;
spin_lock_irqsave(&lpbfifo.lock, flags);
ts = get_tbl();
req = lpbfifo.req;
if (!req) {
spin_unlock_irqrestore(&lpbfifo.lock, flags);
pr_err("bogus LPBFIFO IRQ\n");
return IRQ_HANDLED;
}
dma = !(req->flags & MPC52XX_LPBFIFO_FLAG_NO_DMA);
write = req->flags & MPC52XX_LPBFIFO_FLAG_WRITE;
poll_dma = req->flags & MPC52XX_LPBFIFO_FLAG_POLL_DMA;
if (dma && !write) {
spin_unlock_irqrestore(&lpbfifo.lock, flags);
pr_err("bogus LPBFIFO IRQ (dma and not writting)\n");
return IRQ_HANDLED;
}
if ((status & 0x01) == 0) {
goto out;
}
/* check abort bit */
if (status & 0x10) {
out_be32(lpbfifo.regs + LPBFIFO_REG_ENABLE, 0x01010000);
do_callback = 1;
goto out;
}
/* Read result from hardware */
count = in_be32(lpbfifo.regs + LPBFIFO_REG_BYTES_DONE_STATUS);
count &= 0x00ffffff;
if (!dma && !write) {
/* copy the data out of the FIFO */
reg = lpbfifo.regs + LPBFIFO_REG_FIFO_DATA;
data = req->data + req->pos;
for (i = 0; i < count; i += 4)
*data++ = in_be32(reg);
}
/* Update transfer position and count */
req->pos += count;
/* Decide what to do next */
if (req->size - req->pos)
mpc52xx_lpbfifo_kick(req); /* more work to do */
else
do_callback = 1;
out:
/* Clear the IRQ */
out_8(lpbfifo.regs + LPBFIFO_REG_BYTES_DONE_STATUS, 0x01);
if (dma && (status & 0x11)) {
/*
* Count the DMA as complete only when the FIFO completion
* status or abort bits are set.
*
* (status & 0x01) should always be the case except sometimes
* when using polled DMA.
*
* (status & 0x10) {transfer aborted}: This case needs more
* testing.
*/
bcom_retrieve_buffer(lpbfifo.bcom_cur_task, &status, NULL);
}
req->last_byte = ((u8 *)req->data)[req->size - 1];
/* When the do_callback flag is set; it means the transfer is finished
* so set the FIFO as idle */
if (do_callback)
lpbfifo.req = NULL;
if (irq != 0) /* don't increment on polled case */
req->irq_count++;
req->irq_ticks += get_tbl() - ts;
spin_unlock_irqrestore(&lpbfifo.lock, flags);
/* Spinlock is released; it is now safe to call the callback */
if (do_callback && req->callback)
req->callback(req);
return IRQ_HANDLED;
}
/**
* mpc52xx_lpbfifo_bcom_irq - IRQ handler for LPB FIFO Bestcomm task
*
* Only used when receiving data.
*/
static irqreturn_t mpc52xx_lpbfifo_bcom_irq(int irq, void *dev_id)
{
struct mpc52xx_lpbfifo_request *req;
unsigned long flags;
u32 status;
u32 ts;
spin_lock_irqsave(&lpbfifo.lock, flags);
ts = get_tbl();
req = lpbfifo.req;
if (!req || (req->flags & MPC52XX_LPBFIFO_FLAG_NO_DMA)) {
spin_unlock_irqrestore(&lpbfifo.lock, flags);
return IRQ_HANDLED;
}
if (irq != 0) /* don't increment on polled case */
req->irq_count++;
if (!bcom_buffer_done(lpbfifo.bcom_cur_task)) {
spin_unlock_irqrestore(&lpbfifo.lock, flags);
req->buffer_not_done_cnt++;
if ((req->buffer_not_done_cnt % 1000) == 0)
pr_err("transfer stalled\n");
return IRQ_HANDLED;
}
bcom_retrieve_buffer(lpbfifo.bcom_cur_task, &status, NULL);
req->last_byte = ((u8 *)req->data)[req->size - 1];
req->pos = status & 0x00ffffff;
/* Mark the FIFO as idle */
lpbfifo.req = NULL;
/* Release the lock before calling out to the callback. */
req->irq_ticks += get_tbl() - ts;
spin_unlock_irqrestore(&lpbfifo.lock, flags);
if (req->callback)
req->callback(req);
return IRQ_HANDLED;
}
/**
* mpc52xx_lpbfifo_bcom_poll - Poll for DMA completion
*/
void mpc52xx_lpbfifo_poll(void)
{
struct mpc52xx_lpbfifo_request *req = lpbfifo.req;
int dma = !(req->flags & MPC52XX_LPBFIFO_FLAG_NO_DMA);
int write = req->flags & MPC52XX_LPBFIFO_FLAG_WRITE;
/*
* For more information, see comments on the "Fat Lady"
*/
if (dma && write)
mpc52xx_lpbfifo_irq(0, NULL);
else
mpc52xx_lpbfifo_bcom_irq(0, NULL);
}
EXPORT_SYMBOL(mpc52xx_lpbfifo_poll);
/**
* mpc52xx_lpbfifo_submit - Submit an LPB FIFO transfer request.
* @req: Pointer to request structure
*/
int mpc52xx_lpbfifo_submit(struct mpc52xx_lpbfifo_request *req)
{
unsigned long flags;
if (!lpbfifo.regs)
return -ENODEV;
spin_lock_irqsave(&lpbfifo.lock, flags);
/* If the req pointer is already set, then a transfer is in progress */
if (lpbfifo.req) {
spin_unlock_irqrestore(&lpbfifo.lock, flags);
return -EBUSY;
}
/* Setup the transfer */
lpbfifo.req = req;
req->irq_count = 0;
req->irq_ticks = 0;
req->buffer_not_done_cnt = 0;
req->pos = 0;
mpc52xx_lpbfifo_kick(req);
spin_unlock_irqrestore(&lpbfifo.lock, flags);
return 0;
}
EXPORT_SYMBOL(mpc52xx_lpbfifo_submit);
void mpc52xx_lpbfifo_abort(struct mpc52xx_lpbfifo_request *req)
{
unsigned long flags;
spin_lock_irqsave(&lpbfifo.lock, flags);
if (lpbfifo.req == req) {
/* Put it into reset and clear the state */
bcom_gen_bd_rx_reset(lpbfifo.bcom_rx_task);
bcom_gen_bd_tx_reset(lpbfifo.bcom_tx_task);
out_be32(lpbfifo.regs + LPBFIFO_REG_ENABLE, 0x01010000);
lpbfifo.req = NULL;
}
spin_unlock_irqrestore(&lpbfifo.lock, flags);
}
EXPORT_SYMBOL(mpc52xx_lpbfifo_abort);
static int __devinit
mpc52xx_lpbfifo_probe(struct of_device *op, const struct of_device_id *match)
{
struct resource res;
int rc = -ENOMEM;
if (lpbfifo.dev != NULL)
return -ENOSPC;
lpbfifo.irq = irq_of_parse_and_map(op->node, 0);
if (!lpbfifo.irq)
return -ENODEV;
if (of_address_to_resource(op->node, 0, &res))
return -ENODEV;
lpbfifo.regs_phys = res.start;
lpbfifo.regs = of_iomap(op->node, 0);
if (!lpbfifo.regs)
return -ENOMEM;
spin_lock_init(&lpbfifo.lock);
/* Put FIFO into reset */
out_be32(lpbfifo.regs + LPBFIFO_REG_ENABLE, 0x01010000);
/* Register the interrupt handler */
rc = request_irq(lpbfifo.irq, mpc52xx_lpbfifo_irq, 0,
"mpc52xx-lpbfifo", &lpbfifo);
if (rc)
goto err_irq;
/* Request the Bestcomm receive (fifo --> memory) task and IRQ */
lpbfifo.bcom_rx_task =
bcom_gen_bd_rx_init(2, res.start + LPBFIFO_REG_FIFO_DATA,
BCOM_INITIATOR_SCLPC, BCOM_IPR_SCLPC,
16*1024*1024);
if (!lpbfifo.bcom_rx_task)
goto err_bcom_rx;
rc = request_irq(bcom_get_task_irq(lpbfifo.bcom_rx_task),
mpc52xx_lpbfifo_bcom_irq, 0,
"mpc52xx-lpbfifo-rx", &lpbfifo);
if (rc)
goto err_bcom_rx_irq;
/* Request the Bestcomm transmit (memory --> fifo) task and IRQ */
lpbfifo.bcom_tx_task =
bcom_gen_bd_tx_init(2, res.start + LPBFIFO_REG_FIFO_DATA,
BCOM_INITIATOR_SCLPC, BCOM_IPR_SCLPC);
if (!lpbfifo.bcom_tx_task)
goto err_bcom_tx;
lpbfifo.dev = &op->dev;
return 0;
err_bcom_tx:
free_irq(bcom_get_task_irq(lpbfifo.bcom_rx_task), &lpbfifo);
err_bcom_rx_irq:
bcom_gen_bd_rx_release(lpbfifo.bcom_rx_task);
err_bcom_rx:
err_irq:
iounmap(lpbfifo.regs);
lpbfifo.regs = NULL;
dev_err(&op->dev, "mpc52xx_lpbfifo_probe() failed\n");
return -ENODEV;
}
static int __devexit mpc52xx_lpbfifo_remove(struct of_device *op)
{
if (lpbfifo.dev != &op->dev)
return 0;
/* Put FIFO in reset */
out_be32(lpbfifo.regs + LPBFIFO_REG_ENABLE, 0x01010000);
/* Release the bestcomm transmit task */
free_irq(bcom_get_task_irq(lpbfifo.bcom_tx_task), &lpbfifo);
bcom_gen_bd_tx_release(lpbfifo.bcom_tx_task);
/* Release the bestcomm receive task */
free_irq(bcom_get_task_irq(lpbfifo.bcom_rx_task), &lpbfifo);
bcom_gen_bd_rx_release(lpbfifo.bcom_rx_task);
free_irq(lpbfifo.irq, &lpbfifo);
iounmap(lpbfifo.regs);
lpbfifo.regs = NULL;
lpbfifo.dev = NULL;
return 0;
}
static struct of_device_id mpc52xx_lpbfifo_match[] __devinitconst = {
{ .compatible = "fsl,mpc5200-lpbfifo", },
{},
};
static struct of_platform_driver mpc52xx_lpbfifo_driver = {
.owner = THIS_MODULE,
.name = "mpc52xx-lpbfifo",
.match_table = mpc52xx_lpbfifo_match,
.probe = mpc52xx_lpbfifo_probe,
.remove = __devexit_p(mpc52xx_lpbfifo_remove),
};
/***********************************************************************
* Module init/exit
*/
static int __init mpc52xx_lpbfifo_init(void)
{
pr_debug("Registering LocalPlus bus FIFO driver\n");
return of_register_platform_driver(&mpc52xx_lpbfifo_driver);
}
module_init(mpc52xx_lpbfifo_init);
static void __exit mpc52xx_lpbfifo_exit(void)
{
pr_debug("Unregistering LocalPlus bus FIFO driver\n");
of_unregister_platform_driver(&mpc52xx_lpbfifo_driver);
}
module_exit(mpc52xx_lpbfifo_exit);