lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [thread-next>] [day] [month] [year] [list]
Message-Id: <1373451678-20065-1-git-send-email-a13xp0p0v88@gmail.com>
Date:	Wed, 10 Jul 2013 14:21:18 +0400
From:	Alexander Popov <a13xp0p0v88@...il.com>
To:	Benjamin Herrenschmidt <benh@...nel.crashing.org>,
	Paul Mackerras <paulus@...ba.org>,
	Anatolij Gustschin <agust@...x.de>,
	Grant Likely <grant.likely@...aro.org>,
	Rob Herring <rob.herring@...xeda.com>,
	Chris Ball <cjb@...top.org>,
	Sascha Hauer <s.hauer@...gutronix.de>,
	Gerhard Sittig <gsi@...x.de>, Timur Tabi <timur@...i.org>
Cc:	linuxppc-dev@...ts.ozlabs.org, linux-kernel@...r.kernel.org
Subject: [V2 2/2] powerpc/512x: add LocalPlus Bus FIFO device driver

This is SCLPC device driver for the Freescale MPC512x.
It is needed for Direct Memory Access to the devices on LocalPlus Bus.

Signed-off-by: Alexander Popov <a13xp0p0v88@...il.com>
---
 arch/powerpc/boot/dts/mpc5121.dtsi            |   8 +-
 arch/powerpc/include/asm/mpc5121.h            |  32 ++
 arch/powerpc/platforms/512x/Kconfig           |   6 +
 arch/powerpc/platforms/512x/Makefile          |   1 +
 arch/powerpc/platforms/512x/mpc512x_lpbfifo.c | 485 ++++++++++++++++++++++++++
 5 files changed, 531 insertions(+), 1 deletion(-)
 create mode 100644 arch/powerpc/platforms/512x/mpc512x_lpbfifo.c

diff --git a/arch/powerpc/boot/dts/mpc5121.dtsi b/arch/powerpc/boot/dts/mpc5121.dtsi
index bd14c00..6e0b0c0 100644
--- a/arch/powerpc/boot/dts/mpc5121.dtsi
+++ b/arch/powerpc/boot/dts/mpc5121.dtsi
@@ -261,7 +261,13 @@
 		/* LocalPlus controller */
 		lpc@...00 {
 			compatible = "fsl,mpc5121-lpc";
-			reg = <0x10000 0x200>;
+			reg = <0x10000 0x100>;
+		};
+
+		sclpc@...00 {
+			compatible = "fsl,mpc512x-lpbfifo";
+			reg = <0x10100 0x50>;
+			interrupts = <7 0x8>;
 		};
 
 		pata@...00 {
diff --git a/arch/powerpc/include/asm/mpc5121.h b/arch/powerpc/include/asm/mpc5121.h
index 8ae133e..f416a7e 100644
--- a/arch/powerpc/include/asm/mpc5121.h
+++ b/arch/powerpc/include/asm/mpc5121.h
@@ -69,4 +69,36 @@ struct mpc512x_lpc {
 
 int mpc512x_cs_config(unsigned int cs, u32 val);
 
+/*
+ * SCLPC Module (LPB FIFO)
+ */
+enum lpb_dev_portsize {
+	LPB_DEV_PORTSIZE_UNDEFINED = 0,
+	LPB_DEV_PORTSIZE_1_BYTE = 1,
+	LPB_DEV_PORTSIZE_2_BYTES = 2,
+	LPB_DEV_PORTSIZE_4_BYTES = 4,
+	LPB_DEV_PORTSIZE_8_BYTES = 8,
+};
+
+enum mpc512x_lpbfifo_req_dir {
+	MPC512X_LPBFIFO_REQ_DIR_READ,
+	MPC512X_LPBFIFO_REQ_DIR_WRITE,
+};
+
+struct mpc512x_lpbfifo_request {
+	unsigned int cs;
+	phys_addr_t bus_phys;	/* physical address of some device on lpb */
+	void *ram_virt;		/* virtual address of some region in ram */
+
+	/* Details of transfer */
+	u32 size;
+	enum lpb_dev_portsize portsize;
+	enum mpc512x_lpbfifo_req_dir dir;
+
+	/* Call when the transfer is finished */
+	void (*callback)(struct mpc512x_lpbfifo_request *);
+};
+
+extern int mpc512x_lpbfifo_submit(struct mpc512x_lpbfifo_request *req);
+
 #endif /* __ASM_POWERPC_MPC5121_H__ */
diff --git a/arch/powerpc/platforms/512x/Kconfig b/arch/powerpc/platforms/512x/Kconfig
index fc9c1cb..0db8aa9 100644
--- a/arch/powerpc/platforms/512x/Kconfig
+++ b/arch/powerpc/platforms/512x/Kconfig
@@ -10,6 +10,12 @@ config PPC_MPC512x
 	select USB_EHCI_BIG_ENDIAN_MMIO
 	select USB_EHCI_BIG_ENDIAN_DESC
 
+config PPC_MPC512x_LPBFIFO
+	tristate "MPC512x LocalPlus bus FIFO driver"
+	depends on PPC_MPC512x && MPC512X_DMA
+	help
+	  Enable support for the Freescale MPC512x SCLPC.
+
 config MPC5121_ADS
 	bool "Freescale MPC5121E ADS"
 	depends on PPC_MPC512x
diff --git a/arch/powerpc/platforms/512x/Makefile b/arch/powerpc/platforms/512x/Makefile
index 72fb934..df932fa 100644
--- a/arch/powerpc/platforms/512x/Makefile
+++ b/arch/powerpc/platforms/512x/Makefile
@@ -2,6 +2,7 @@
 # Makefile for the Freescale PowerPC 512x linux kernel.
 #
 obj-y				+= clock.o mpc512x_shared.o
+obj-$(CONFIG_PPC_MPC512x_LPBFIFO) += mpc512x_lpbfifo.o
 obj-$(CONFIG_MPC5121_ADS)	+= mpc5121_ads.o mpc5121_ads_cpld.o
 obj-$(CONFIG_MPC512x_GENERIC)	+= mpc512x_generic.o
 obj-$(CONFIG_PDM360NG)		+= pdm360ng.o
diff --git a/arch/powerpc/platforms/512x/mpc512x_lpbfifo.c b/arch/powerpc/platforms/512x/mpc512x_lpbfifo.c
new file mode 100644
index 0000000..6bd8aab
--- /dev/null
+++ b/arch/powerpc/platforms/512x/mpc512x_lpbfifo.c
@@ -0,0 +1,485 @@
+/*
+ * LocalPlus Bus SCLPC driver for the Freescale MPC512x.
+ *
+ * Copyright (C) Promcontroller, 2013.
+ *
+ * Author is Alexander Popov <a13xp0p0v88@...il.com>.
+ *
+ * The driver design is based on mpc52xx_lpbfifo driver
+ * written by Grant Likely <grant.likely@...retlab.ca>.
+ *
+ * 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 <asm/io.h>
+#include <linux/spinlock.h>
+#include <linux/slab.h>
+#include <asm/mpc5121.h>
+#include <linux/dmaengine.h>
+#include <linux/dma-direction.h>
+#include <linux/dma-mapping.h>
+
+MODULE_AUTHOR("Alexander Popov <a13xp0p0v88@...il.com>");
+MODULE_DESCRIPTION("MPC512x LocalPlus FIFO device driver");
+MODULE_LICENSE("GPL");
+
+#define DRV_NAME "mpc512x_lpbfifo"
+
+#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_STATUS		(0x14)
+#define LPBFIFO_REG_BYTES_DONE		(0x18)
+#define LPBFIFO_REG_EMB_SHARE_COUNTER	(0x1C)
+#define LPBFIFO_REG_EMB_PAUSE_CONTROL	(0x20)
+#define LPBFIFO_REG_FIFO_DATA		(0x40)
+#define LPBFIFO_REG_FIFO_STATUS		(0x44)
+#define LPBFIFO_REG_FIFO_CONTROL	(0x48)
+#define LPBFIFO_REG_FIFO_ALARM		(0x4C)
+
+#define DMA_LPC_CHANNEL_NUMBER		26
+#define DEFAULT_WORDS_PER_TRANSFER	1
+
+static struct mpc512x_lpbfifo {
+	struct device *dev;
+	struct resource res;
+	void __iomem *regs;
+	int irq;
+	spinlock_t lock;
+
+	/* Current state data */
+	int im_last; /* For "last one out turns off the lights" principle */
+	struct mpc512x_lpbfifo_request *req;
+	dma_addr_t ram_bus_addr;
+	struct dma_chan *chan;
+} lpbfifo;
+
+/*
+ * Before we can wrap up handling current mpc512x_lpbfifo_request
+ * and execute the callback registered in it we should:
+ *  1. recognize that everything is really done,
+ *  2. free memory and dmaengine channel.
+ *
+ * Writing from RAM to registers of some device on LPB (transmit)
+ * is not really done until the LPB FIFO completion irq triggers.
+ *
+ * For being sure that writing from registers of some device on LPB
+ * to RAM (receive) is really done we should wait
+ * for mpc512x_lpbfifo_callback() to be called by DMA driver.
+ * In this case LPB FIFO completion irq will not appear at all.
+ *
+ * Moreover, freeing memory and dmaengine channel is not safe until
+ * mpc512x_lpbfifo_callback() is called.
+ *
+ * So to make it simple:
+ * last one out turns off the lights.
+ */
+
+/*
+ * mpc512x_lpbfifo_irq - IRQ handler for LPB FIFO
+ */
+static irqreturn_t mpc512x_lpbfifo_irq(int irq, void *dev_id)
+{
+	struct mpc512x_lpbfifo_request *req;
+	unsigned long flags;
+	u32 status;
+
+	spin_lock_irqsave(&lpbfifo.lock, flags);
+
+	req = lpbfifo.req;
+	if (!req) {
+		spin_unlock_irqrestore(&lpbfifo.lock, flags);
+		pr_err("bogus LPBFIFO IRQ\n");
+		return IRQ_HANDLED;
+	}
+
+	if (req->dir == MPC512X_LPBFIFO_REQ_DIR_READ) {
+		spin_unlock_irqrestore(&lpbfifo.lock, flags);
+		pr_err("bogus LPBFIFO IRQ (we are waiting DMA IRQ)\n");
+		return IRQ_HANDLED;
+	}
+
+	/* Clear the interrupt flag */
+	status = in_8(lpbfifo.regs + LPBFIFO_REG_STATUS);
+	if (status & 0x01)
+		out_8(lpbfifo.regs + LPBFIFO_REG_STATUS, 0x01);
+	else
+		out_be32(lpbfifo.regs + LPBFIFO_REG_ENABLE, 0x01010000);
+
+	if (!lpbfifo.im_last) {
+		/*  I'm not the last: DMA is still in progress. */
+		lpbfifo.im_last = 1;
+		spin_unlock_irqrestore(&lpbfifo.lock, flags);
+	} else {
+		/* I'm last. Let's wrap up. */
+		/* Set the FIFO as idle */
+		req = lpbfifo.req;
+		lpbfifo.req = NULL;
+
+		/* The spinlock must be dropped
+		 * before executing the callback,
+		 * otherwise we could end up with a deadlock
+		 * or nested spinlock condition. */
+		spin_unlock_irqrestore(&lpbfifo.lock, flags);
+		if (req->callback)
+			req->callback(req);
+	}
+
+	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;
+	enum dma_data_direction dir;
+
+	spin_lock_irqsave(&lpbfifo.lock, flags);
+
+	/* Free resources */
+	if (lpbfifo.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, lpbfifo.req->size, dir);
+	dma_release_channel(lpbfifo.chan);
+
+	if (lpbfifo.req->dir == MPC512X_LPBFIFO_REQ_DIR_WRITE &&
+							!lpbfifo.im_last) {
+		/* I'm not the last: LPB FIFO is still writing data. */
+		lpbfifo.im_last = 1;
+		spin_unlock_irqrestore(&lpbfifo.lock, flags);
+	} else {
+		/* I'm the last or alone here. Let's wrap up. */
+		/* Set the FIFO as idle */
+		req = lpbfifo.req;
+		lpbfifo.req = NULL;
+
+		/* The spinlock must be dropped
+		 * before executing the callback,
+		 * otherwise we could end up with a deadlock
+		 * or nested spinlock condition. */
+		spin_unlock_irqrestore(&lpbfifo.lock, flags);
+		if (req->callback)
+			req->callback(req);
+	}
+}
+
+static bool channel_filter(struct dma_chan *chan, void *filter_param)
+{
+	if (chan->chan_id == DMA_LPC_CHANNEL_NUMBER)
+		return true;
+	else
+		return false;
+}
+
+static int mpc512x_lpbfifo_kick(struct mpc512x_lpbfifo_request *req)
+{
+	u32 bits;
+	int no_incr = 0;
+	u32 bpt;
+	dma_cap_mask_t cap_mask;
+	dma_filter_fn fn = channel_filter;
+	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 e;
+
+	/* 1. Check requirements: */
+	/* Packet size must be a multiple of 4 bytes since
+	 * FIFO Data Word Register (which provides data to DMA controller)
+	 * allows only "full-word" (4 bytes) access
+	 * according Reference Manual */
+	if (!IS_ALIGNED(req->size, 4)) {
+		e = -EINVAL;
+		goto err_align;
+	}
+
+	/* Physical address of the device on LPB and packet size
+	 * must be aligned/multiple of BPT (bytes per transaction)
+	 * according Reference Manual */
+	if (req->portsize != LPB_DEV_PORTSIZE_UNDEFINED) {
+		bpt = req->portsize;
+		no_incr = 1;
+	} else
+		bpt = DEFAULT_WORDS_PER_TRANSFER << 2; /* makes life easier */
+
+	if (!IS_ALIGNED(req->bus_phys | req->size, bpt)) {
+			e = -EFAULT;
+			goto err_align;
+	}
+
+	/* 2. Prepare DMA */
+	dma_cap_zero(cap_mask);
+	dma_cap_set(DMA_SLAVE, cap_mask);
+	lpbfifo.chan = dma_request_channel(cap_mask, fn, NULL);
+	if (!lpbfifo.chan) {
+		e = -ENODEV;
+		goto err_align;
+	}
+	dma_dev = lpbfifo.chan->device;
+
+	sg_init_table(&sg, 1);
+	if (req->dir == MPC512X_LPBFIFO_REQ_DIR_WRITE)
+		dir = DMA_TO_DEVICE;
+	else
+		dir = DMA_FROM_DEVICE;
+	sg_dma_address(&sg) = dma_map_single(dma_dev->dev,
+			req->ram_virt, req->size, dir);
+	if (dma_mapping_error(dma_dev->dev, sg_dma_address(&sg))) {
+		pr_err("dma_mapping_error\n");
+		e = -EFAULT;
+		goto err_dma_map;
+	}
+	lpbfifo.ram_bus_addr = sg_dma_address(&sg); /* will free it later */
+	sg_dma_len(&sg) = req->size;
+
+	/* We should limit the maximum number of words
+	 * (units with FIFO Data Register size)
+	 * that can be read from / written to the FIFO
+	 * in one DMA burst.
+	 * This measure and FIFO watermarks will prevent
+	 * DMA controller from overtaking FIFO
+	 * and causing FIFO underflow / overflow error. */
+	dma_conf.dst_maxburst = DEFAULT_WORDS_PER_TRANSFER;
+	dma_conf.src_maxburst = DEFAULT_WORDS_PER_TRANSFER;
+
+	if (req->dir == MPC512X_LPBFIFO_REQ_DIR_WRITE) {
+		dma_conf.direction = DMA_MEM_TO_DEV;
+		dma_conf.dst_addr = lpbfifo.res.start + LPBFIFO_REG_FIFO_DATA;
+	} else {
+		dma_conf.direction = DMA_DEV_TO_MEM;
+		dma_conf.src_addr = lpbfifo.res.start + LPBFIFO_REG_FIFO_DATA;
+	}
+	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_control(lpbfifo.chan,
+				DMA_SLAVE_CONFIG, (unsigned long)&dma_conf)) {
+		goto err_dma_prep;
+	}
+
+	dma_tx = dmaengine_prep_slave_sg(lpbfifo.chan, &sg,
+						1, dma_conf.direction, 0);
+	if (!dma_tx) {
+		pr_err("dmaengine_prep_slave_sg failed\n");
+		e = -ENOSPC;
+		goto err_dma_prep;
+	}
+
+	dma_tx->callback = mpc512x_lpbfifo_callback;
+	dma_tx->callback_param = NULL;
+
+	/* 3. Prepare FIFO */
+	/* Set and clear the reset bits;
+	 * is good practice in Reference Manual */
+	out_be32(lpbfifo.regs + LPBFIFO_REG_ENABLE, 0x01010000);
+	out_be32(lpbfifo.regs + LPBFIFO_REG_ENABLE, 0x0);
+
+	/* Configure the watermarks.
+	 *
+	 * RAM->DMA->FIFO->LPB_DEV (write):
+	 *  High watermark (7 * 4) free bytes (according Reference Manual)
+	 *  Low watermark 996 bytes (whole FIFO - 28 bytes)
+	 *
+	 * LPB_DEV->FIFO->DMA->RAM (read):
+	 *  High watermark (1024 - 4) free bytes
+	 *   (whole FIFO - DEFAULT_WORDS_PER_TRANSFER)
+	 *  Low watermark 0 bytes
+	 */
+	if (req->dir == MPC512X_LPBFIFO_REQ_DIR_WRITE) {
+		out_be16(lpbfifo.regs + LPBFIFO_REG_FIFO_CONTROL, 0x0700);
+		out_be32(lpbfifo.regs + LPBFIFO_REG_FIFO_ALARM, 0x000003e4);
+	} else {
+		out_be16(lpbfifo.regs + LPBFIFO_REG_FIFO_CONTROL, 0x0);
+		out_be32(lpbfifo.regs + LPBFIFO_REG_FIFO_ALARM, 0x000003fc);
+	}
+
+	/* Start address is a physical address of the region
+	 * which belongs to the device on localplus bus */
+	out_be32(lpbfifo.regs + LPBFIFO_REG_START_ADDRESS, req->bus_phys);
+
+	/* Configure chip select, transfer direction,
+	 * address increment option and bytes per transfer option */
+	bits = (req->cs & 0x7) << 24;
+	if (req->dir == MPC512X_LPBFIFO_REQ_DIR_READ)
+		bits |= 3 << 16; /* read mode bit and flush bit */
+	if (no_incr)
+		bits |= 1 << 8;
+	bits |= bpt & 0x3f;
+	out_be32(lpbfifo.regs + LPBFIFO_REG_CONTROL, bits);
+
+	/* Unmask irqs */
+	bits = 0x00000201; /* set error irq & master enabled bit */
+	if (req->dir == MPC512X_LPBFIFO_REQ_DIR_WRITE)
+		bits |= 0x00000100; /* set completion irq too */
+	out_be32(lpbfifo.regs + LPBFIFO_REG_ENABLE, bits);
+
+	/* 4. Set packet size and kick FIFO off */
+	bits = req->size;
+	bits |= (1<<31); /* set restart bit */
+	out_be32(lpbfifo.regs + LPBFIFO_REG_PACKET_SIZE, bits);
+
+
+	/* 5. Then kick DMA off */
+	cookie = dma_tx->tx_submit(dma_tx);
+	if (dma_submit_error(cookie)) {
+		pr_err("DMA tx_submit failed\n");
+		e = -ENOSPC;
+		goto err_dma_submit;
+	}
+
+	return 0;
+
+ err_dma_submit:
+
+ err_dma_prep:
+	dma_unmap_single(dma_dev->dev, sg_dma_address(&sg), req->size, dir);
+
+ err_dma_map:
+	sg_dma_address(&sg) = 0;
+	dma_release_channel(lpbfifo.chan);
+
+ err_align:
+	return e;
+}
+
+int mpc512x_lpbfifo_submit(struct mpc512x_lpbfifo_request *req)
+{
+	unsigned long flags;
+	int result = 0;
+
+	if (!lpbfifo.regs)
+		return -ENODEV;
+
+	spin_lock_irqsave(&lpbfifo.lock, flags);
+
+	/* A transfer is in progress
+	 * if the req pointer is already set */
+	if (lpbfifo.req) {
+		spin_unlock_irqrestore(&lpbfifo.lock, flags);
+		return -EBUSY;
+	}
+
+	/* (128 kBytes - 4 Bytes) is a maximum packet size
+	 * that LPB FIFO and DMA controller can handle together
+	 * while exchanging DEFAULT_WORDS_PER_TRANSFER = 1
+	 * per hardware request */
+	if (req->size > 131068) {
+		spin_unlock_irqrestore(&lpbfifo.lock, flags);
+		return -ENOSPC;
+	}
+
+	/* Setup the transfer */
+	lpbfifo.im_last = 0;
+	lpbfifo.req = req;
+
+	result = mpc512x_lpbfifo_kick(req);
+	if (result != 0)
+		lpbfifo.req = NULL;	/* Set the FIFO as idle */
+
+	spin_unlock_irqrestore(&lpbfifo.lock, flags);
+
+	return result;
+}
+EXPORT_SYMBOL(mpc512x_lpbfifo_submit);
+
+static int mpc512x_lpbfifo_probe(struct platform_device *pdev)
+{
+	struct resource *r = &lpbfifo.res;
+	int e = 0, rc = -ENOMEM;
+
+	if (of_address_to_resource(pdev->dev.of_node, 0, r)) {
+		e = -ENODEV;
+		goto err_res;
+	}
+
+	if (!request_mem_region(r->start, resource_size(r), DRV_NAME)) {
+		e = -EBUSY;
+		goto err_res;
+	}
+
+	lpbfifo.irq = irq_of_parse_and_map(pdev->dev.of_node, 0);
+	if (!lpbfifo.irq) {
+		e = -ENODEV;
+		goto err_res;
+	}
+
+	lpbfifo.regs = ioremap(r->start, resource_size(r));
+	if (!lpbfifo.regs) {
+		e = -ENOMEM;
+		goto err_regs;
+	}
+
+	spin_lock_init(&lpbfifo.lock);
+
+	/* Put FIFO into reset state */
+	out_be32(lpbfifo.regs + LPBFIFO_REG_ENABLE, 0x01010000);
+
+	lpbfifo.dev = &pdev->dev;
+
+	rc = request_irq(lpbfifo.irq,
+				mpc512x_lpbfifo_irq, 0, DRV_NAME, &lpbfifo);
+	if (rc) {
+		e = -ENODEV;
+		goto err_irq;
+	}
+
+	return 0;
+
+ err_irq:
+	iounmap(lpbfifo.regs);
+	lpbfifo.regs = NULL;
+
+ err_regs:
+	release_mem_region(r->start, resource_size(r));
+
+ err_res:
+	dev_err(&pdev->dev, "mpc512x_lpbfifo_probe() failed\n");
+	return e;
+}
+
+static int mpc512x_lpbfifo_remove(struct platform_device *pdev)
+{
+	/* Put FIFO in reset */
+	out_be32(lpbfifo.regs + LPBFIFO_REG_ENABLE, 0x01010000);
+
+	free_irq(lpbfifo.irq, &lpbfifo);
+	iounmap(lpbfifo.regs);
+	release_mem_region(lpbfifo.res.start, resource_size(&lpbfifo.res));
+	lpbfifo.regs = NULL;
+	lpbfifo.dev = NULL;
+
+	return 0;
+}
+
+static const struct of_device_id mpc512x_lpbfifo_match[] = {
+	{ .compatible = "fsl,mpc512x-lpbfifo", },
+	{},
+};
+
+static struct platform_driver mpc512x_lpbfifo_driver = {
+	.probe = mpc512x_lpbfifo_probe,
+	.remove = mpc512x_lpbfifo_remove,
+	.driver = {
+		.name = DRV_NAME,
+		.owner = THIS_MODULE,
+		.of_match_table = mpc512x_lpbfifo_match,
+	},
+};
+module_platform_driver(mpc512x_lpbfifo_driver);
-- 
1.7.11.3

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@...r.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ