linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH] spi: Added spi master driver for Freescale MPC83xx SPI controller
@ 2006-04-06 18:30 Kumar Gala
  2006-04-07  5:22 ` David Brownell
  2006-04-10 17:38 ` [PATCH][UPDATE] " Kumar Gala
  0 siblings, 2 replies; 18+ messages in thread
From: Kumar Gala @ 2006-04-06 18:30 UTC (permalink / raw)
  To: david-b; +Cc: Greg KH, linux-kernel, spi-devel-general

This driver supports the SPI controller on the MPC83xx SoC devices from Freescale.
Note, this driver supports only the simple shift register SPI controller and not
the descriptor based CPM or QUICCEngine SPI controller.

Signed-off-by: Kumar Gala <galak@kernel.crashing.org>

---
commit bb0fc026ee00c413125fad3e6b3bb18c10aacd55
tree 4e24181f85ed0fac042605a0af3175511f3695ed
parent bc33ba02f8414e91a3b2afa877be2c54d6fce564
author Kumar Gala <galak@kernel.crashing.org> Thu, 06 Apr 2006 13:30:17 -0500
committer Kumar Gala <galak@kernel.crashing.org> Thu, 06 Apr 2006 13:30:17 -0500

 drivers/spi/Kconfig         |   10 +
 drivers/spi/Makefile        |    1 
 drivers/spi/spi_mpc83xx.c   |  349 +++++++++++++++++++++++++++++++++++++++++++
 include/linux/fsl_devices.h |   11 +
 4 files changed, 371 insertions(+), 0 deletions(-)

diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index 7a75fae..af937bc 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -75,6 +75,16 @@ config SPI_BUTTERFLY
 	  inexpensive battery powered microcontroller evaluation board.
 	  This same cable can be used to flash new firmware.
 
+config SPI_MPC83xx
+	tristate "Freescale MPC83xx SPI controller"
+	depends on SPI_MASTER && PPC_83xx && EXPERIMENTAL
+	select SPI_BITBANG
+	help
+	  This enables using the Freescale MPC83xx SPI controller in master mode.
+	  Note, this driver uniquely supports the SPI controller on the MPC83xx
+	  family of PowerPC processors.  The MPC83xx uses a simple set of shift
+	  registers for data (opposed to the CPM based descriptor model).
+
 #
 # Add new SPI master controllers in alphabetical order above this line
 #
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index c2c87e8..502ac0b 100644
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -13,6 +13,7 @@ obj-$(CONFIG_SPI_MASTER)		+= spi.o
 # SPI master controller drivers (bus)
 obj-$(CONFIG_SPI_BITBANG)		+= spi_bitbang.o
 obj-$(CONFIG_SPI_BUTTERFLY)		+= spi_butterfly.o
+obj-$(CONFIG_SPI_MPC83xx)		+= spi_mpc83xx.o
 # 	... add above this line ...
 
 # SPI protocol drivers (device/link on bus)
diff --git a/drivers/spi/spi_mpc83xx.c b/drivers/spi/spi_mpc83xx.c
new file mode 100644
index 0000000..a18e768
--- /dev/null
+++ b/drivers/spi/spi_mpc83xx.c
@@ -0,0 +1,349 @@
+/*
+ * MPC83xx SPI controller driver.
+ *
+ * Maintainer: Kumar Gala
+ *
+ * This program is free software; you can redistribute  it and/or modify it
+ * under  the terms of  the GNU General  Public License as published by the
+ * Free Software Foundation;  either version 2 of the  License, or (at your
+ * option) any later version.
+ */
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/completion.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/device.h>
+#include <linux/spi/spi.h>
+#include <linux/spi/spi_bitbang.h>
+#include <linux/platform_device.h>
+#include <linux/fsl_devices.h>
+
+#include <asm/irq.h>
+#include <asm/io.h>
+
+/* SPI Controller register offsets */
+#define	SPMODE_REG	0x20
+#define	SPIE_REG	0x24
+#define	SPIM_REG	0x28
+#define	SPCOM_REG	0x2C
+#define	SPITD_REG	0x30
+#define	SPIRD_REG	0x34
+
+/* SPI Controller mode register definitions */
+#define	SPMODE_CI_INACTIVEHIGH	(1 << 29)
+#define	SPMODE_CP_RISE_EDGECLK	(1 << 28)
+#define	SPMODE_DIV16		(1 << 27)
+#define	SPMODE_REV		(1 << 26)
+#define	SPMODE_MS		(1 << 25)
+#define	SPMODE_ENABLE		(1 << 24)
+#define	SPMODE_LEN(x)		((x) << 20)
+#define	SPMODE_PM(x)		((x) << 16)
+
+/* Default for SPI Mode, slowest speed, MSB, inactive high, 8-bit char */
+#define	SPMODE_INIT_VAL (SPMODE_CI_INACTIVEHIGH | SPMODE_CP_RISE_EDGECLK | \
+			 SPMODE_DIV16 | SPMODE_REV | SPMODE_MS | \
+			 SPMODE_LEN(7) | SPMODE_PM(0xf))
+
+/* SPIE register values */
+#define	SPIE_NE		0x00000200	/* Not empty */
+#define	SPIE_NF		0x00000100	/* Not full */
+
+/* SPIM register values */
+#define	SPIM_NE		0x00000200	/* Not empty */
+#define	SPIM_NF		0x00000100	/* Not full */
+
+/* SPI Controller driver's private data. */
+struct mpc83xx_spi {
+	/* bitbang has to be first */
+	struct spi_bitbang bitbang;
+	struct completion tx_done, rx_ready;
+
+	u32 __iomem *base;
+	u32 irq;
+
+	u32 rx_data;
+
+	u32 sysclk;
+	void (*activate_cs) (u8 cs, u8 polarity);
+	void (*deactivate_cs) (u8 cs, u8 polarity);
+};
+
+static inline void mpc83xx_spi_write_reg(__be32 * base, u32 reg, u32 val)
+{
+	out_be32(base + (reg >> 2), val);
+}
+
+static inline u32 mpc83xx_spi_read_reg(__be32 * base, u32 reg)
+{
+	return in_be32(base + (reg >> 2));
+}
+
+static
+int mpc83xx_spi_setup_transfer(struct spi_device *spi, struct spi_transfer *t)
+{
+	struct mpc83xx_spi *mpc83xx_spi;
+	u32 regval;
+	u32 len = t->bits_per_word - 1;
+
+	if (len == 32)
+		len = 0;
+
+	mpc83xx_spi = spi_master_get_devdata(spi->master);
+	regval = mpc83xx_spi_read_reg(mpc83xx_spi->base, SPMODE_REG);
+
+	/* Mask out length */
+	regval &= 0xff0fffff;
+	regval |= SPMODE_LEN(len);
+
+	mpc83xx_spi_write_reg(mpc83xx_spi->base, SPMODE_REG, regval);
+
+	return spi_bitbang_setup_transfer(spi, t);
+}
+
+static void mpc83xx_spi_chipselect(struct spi_device *spi, int value)
+{
+	struct mpc83xx_spi *mpc83xx_spi;
+	u8 pol = spi->mode & SPI_CS_HIGH ? 1 : 0;
+
+	mpc83xx_spi = spi_master_get_devdata(spi->master);
+
+	if (value == BITBANG_CS_INACTIVE) {
+		if (mpc83xx_spi->deactivate_cs)
+			mpc83xx_spi->deactivate_cs(spi->chip_select, pol);
+	}
+
+	if (value == BITBANG_CS_ACTIVE) {
+		u32 regval =
+		    mpc83xx_spi_read_reg(mpc83xx_spi->base, SPMODE_REG);
+		u32 len = spi->bits_per_word - 1;
+		if (len == 32)
+			len = 0;
+
+		/* mask out bits we are going to set */
+		regval &= ~0x38ff0000;
+
+		if (spi->mode & SPI_CPHA)
+			regval |= SPMODE_CP_RISE_EDGECLK;
+		if ((spi->mode & SPI_CPOL) == 0)
+			regval |= SPMODE_CI_INACTIVEHIGH;
+
+		regval |= SPMODE_LEN(len);
+
+		if ((mpc83xx_spi->sysclk / spi->max_speed_hz) >= 64) {
+			u8 pm = mpc83xx_spi->sysclk / (spi->max_speed_hz * 64);
+			regval |= SPMODE_PM(pm) | SPMODE_DIV16;
+		} else {
+			u8 pm = mpc83xx_spi->sysclk / (spi->max_speed_hz * 4);
+			regval |= SPMODE_PM(pm);
+		}
+
+		mpc83xx_spi_write_reg(mpc83xx_spi->base, SPMODE_REG, regval);
+		if (mpc83xx_spi->activate_cs)
+			mpc83xx_spi->activate_cs(spi->chip_select, pol);
+	}
+}
+
+static u32
+mpc83xx_spi_txrx(struct spi_device *spi, unsigned nsecs, u32 word, u8 bits)
+{
+	struct mpc83xx_spi *mpc83xx_spi;
+	mpc83xx_spi = spi_master_get_devdata(spi->master);
+
+	INIT_COMPLETION(mpc83xx_spi->tx_done);
+	INIT_COMPLETION(mpc83xx_spi->rx_ready);
+
+	/* enable tx/rx ints */
+	mpc83xx_spi_write_reg(mpc83xx_spi->base, SPIM_REG, SPIM_NF | SPIM_NE);
+
+	/* transmit word */
+	mpc83xx_spi_write_reg(mpc83xx_spi->base, SPITD_REG, word);
+
+	/* wait for both a tx & rx interrupt */
+	wait_for_completion(&mpc83xx_spi->tx_done);
+	wait_for_completion(&mpc83xx_spi->rx_ready);
+
+	return mpc83xx_spi->rx_data;
+}
+
+irqreturn_t mpc83xx_spi_irq(s32 irq, void *context_data,
+			    struct pt_regs * ptregs)
+{
+	struct mpc83xx_spi *mpc83xx_spi = context_data;
+	u32 regval, event;
+
+	/* Get interrupt events(tx/rx) */
+	event = mpc83xx_spi_read_reg(mpc83xx_spi->base, SPIE_REG);
+
+	/* We need handle RX first */
+	if (event & SPIE_NE) {
+		mpc83xx_spi->rx_data =
+		    mpc83xx_spi_read_reg(mpc83xx_spi->base, SPIRD_REG);
+
+		/* disable rx ints */
+		regval = mpc83xx_spi_read_reg(mpc83xx_spi->base, SPIM_REG);
+		regval &= ~SPIM_NE;
+		mpc83xx_spi_write_reg(mpc83xx_spi->base, SPIM_REG, regval);
+
+		complete(&mpc83xx_spi->rx_ready);
+	}
+
+	if (event & SPIE_NF) {
+		/* disable tx ints */
+		regval = mpc83xx_spi_read_reg(mpc83xx_spi->base, SPIM_REG);
+		regval &= ~SPIM_NF;
+		mpc83xx_spi_write_reg(mpc83xx_spi->base, SPIM_REG, regval);
+
+		complete(&mpc83xx_spi->tx_done);
+	}
+
+	/* Clear the events */
+	mpc83xx_spi_write_reg(mpc83xx_spi->base, SPIE_REG, event);
+
+	return IRQ_HANDLED;
+}
+
+static int __devinit mpc83xx_spi_probe(struct platform_device *dev)
+{
+	struct spi_master *master;
+	struct mpc83xx_spi *mpc83xx_spi;
+	struct fsl_spi_platform_data *pdata;
+	struct resource *r;
+	u32 regval;
+	int ret = 0;
+
+	/* Get resources(memory, IRQ) associated with the device */
+	master = spi_alloc_master(&dev->dev, sizeof(struct mpc83xx_spi));
+
+	if (master == NULL) {
+		ret = -ENOMEM;
+		goto err;
+	}
+
+	platform_set_drvdata(dev, master);
+	pdata = dev->dev.platform_data;
+
+	if (pdata == NULL) {
+		ret = -ENODEV;
+		goto free_master;
+	}
+
+	r = platform_get_resource(dev, IORESOURCE_MEM, 0);
+	if (r == NULL) {
+		ret = -ENODEV;
+		goto free_master;
+	}
+
+	mpc83xx_spi = spi_master_get_devdata(master);
+	mpc83xx_spi->bitbang.master = spi_master_get(master);
+	mpc83xx_spi->bitbang.chipselect = mpc83xx_spi_chipselect;
+	mpc83xx_spi->bitbang.setup_transfer = mpc83xx_spi_setup_transfer;
+	mpc83xx_spi->bitbang.txrx_word[SPI_MODE_0] = mpc83xx_spi_txrx;
+	mpc83xx_spi->bitbang.txrx_word[SPI_MODE_1] = mpc83xx_spi_txrx;
+	mpc83xx_spi->bitbang.txrx_word[SPI_MODE_2] = mpc83xx_spi_txrx;
+	mpc83xx_spi->bitbang.txrx_word[SPI_MODE_3] = mpc83xx_spi_txrx;
+	mpc83xx_spi->sysclk = pdata->sysclk;
+	mpc83xx_spi->activate_cs = pdata->activate_cs;
+	mpc83xx_spi->deactivate_cs = pdata->deactivate_cs;
+
+	init_completion(&mpc83xx_spi->tx_done);
+	init_completion(&mpc83xx_spi->rx_ready);
+
+	mpc83xx_spi->base = ioremap(r->start, r->end - r->start + 1);
+	if (mpc83xx_spi->base == NULL) {
+		ret = -ENOMEM;
+		goto put_master;
+	}
+
+	mpc83xx_spi->irq = platform_get_irq(dev, 0);
+
+	if (mpc83xx_spi->irq < 0) {
+		ret = -ENXIO;
+		goto unmap_io;
+	}
+
+	/* Register for SPI Interrupt */
+	ret = request_irq(mpc83xx_spi->irq, mpc83xx_spi_irq,
+			  0, "mpc83xx_spi", mpc83xx_spi);
+
+	if (ret != 0)
+		goto unmap_io;
+
+	master->bus_num = pdata->bus_num;
+	master->num_chipselect = pdata->max_chipselect;
+
+	/* SPI controller initializations */
+	mpc83xx_spi_write_reg(mpc83xx_spi->base, SPMODE_REG, 0);
+	mpc83xx_spi_write_reg(mpc83xx_spi->base, SPIM_REG, 0);
+	mpc83xx_spi_write_reg(mpc83xx_spi->base, SPCOM_REG, 0);
+	mpc83xx_spi_write_reg(mpc83xx_spi->base, SPIE_REG, 0xffffffff);
+
+	/* Enable SPI interface */
+	regval = pdata->initial_spmode | SPMODE_INIT_VAL | SPMODE_ENABLE;
+	mpc83xx_spi_write_reg(mpc83xx_spi->base, SPMODE_REG, regval);
+
+	ret = spi_bitbang_start(&mpc83xx_spi->bitbang);
+
+	if (ret != 0)
+		goto free_irq;
+
+	printk(KERN_INFO
+	       "%s: MPC83xx SPI Controller driver at 0x%p (irq = %d)\n",
+	       dev->dev.bus_id, mpc83xx_spi->base, mpc83xx_spi->irq);
+
+	return ret;
+
+free_irq:
+	free_irq(mpc83xx_spi->irq, mpc83xx_spi);
+unmap_io:
+	iounmap(mpc83xx_spi->base);
+put_master:
+	spi_master_put(master);
+free_master:
+	kfree(master);
+err:
+	return ret;
+}
+
+static int __devexit mpc83xx_spi_remove(struct platform_device *dev)
+{
+	struct mpc83xx_spi *mpc83xx_spi;
+	struct spi_master *master;
+
+	master = platform_get_drvdata(dev);
+	mpc83xx_spi = spi_master_get_devdata(master);
+
+	spi_bitbang_stop(&mpc83xx_spi->bitbang);
+	free_irq(mpc83xx_spi->irq, mpc83xx_spi);
+	iounmap(mpc83xx_spi->base);
+	spi_master_put(mpc83xx_spi->bitbang.master);
+
+	return 0;
+}
+
+static struct platform_driver mpc83xx_spi_driver = {
+	.probe = mpc83xx_spi_probe,
+	.remove = __devexit_p(mpc83xx_spi_remove),
+	.driver = {
+		   .name = "mpc83xx_spi",
+		   },
+};
+
+static int __init mpc83xx_spi_init(void)
+{
+	return platform_driver_register(&mpc83xx_spi_driver);
+}
+
+static void __exit mpc83xx_spi_exit(void)
+{
+	platform_driver_unregister(&mpc83xx_spi_driver);
+}
+
+module_init(mpc83xx_spi_init);
+module_exit(mpc83xx_spi_exit);
+
+MODULE_AUTHOR("Kumar Gala");
+MODULE_DESCRIPTION("Simple Platform SPI Driver");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/fsl_devices.h b/include/linux/fsl_devices.h
index a3a0e07..121b860 100644
--- a/include/linux/fsl_devices.h
+++ b/include/linux/fsl_devices.h
@@ -110,5 +110,16 @@ struct fsl_usb2_platform_data {
 #define FSL_USB2_PORT0_ENABLED	0x00000001
 #define FSL_USB2_PORT1_ENABLED	0x00000002
 
+struct fsl_spi_platform_data {
+	u32 	initial_spmode;	/* initial SPMODE value */
+	u16	bus_num;	
+
+	/* board specific information */
+	u16	max_chipselect;
+	void	(*activate_cs)(u8 cs, u8 polarity);
+	void	(*deactivate_cs)(u8 cs, u8 polarity);
+	u32	sysclk;
+};
+
 #endif				/* _FSL_DEVICE_H_ */
 #endif				/* __KERNEL__ */


^ permalink raw reply related	[flat|nested] 18+ messages in thread

* Re: [PATCH] spi: Added spi master driver for Freescale MPC83xx SPI controller
  2006-04-06 18:30 [PATCH] spi: Added spi master driver for Freescale MPC83xx SPI controller Kumar Gala
@ 2006-04-07  5:22 ` David Brownell
  2006-04-07  9:16   ` [spi-devel-general] " Vitaly Wool
  2006-04-07 14:04   ` Kumar Gala
  2006-04-10 17:38 ` [PATCH][UPDATE] " Kumar Gala
  1 sibling, 2 replies; 18+ messages in thread
From: David Brownell @ 2006-04-07  5:22 UTC (permalink / raw)
  To: Kumar Gala; +Cc: Greg KH, linux-kernel, spi-devel-general

> This driver supports the SPI controller on the MPC83xx SoC devices from Freescale.
> Note, this driver supports only the simple shift register SPI controller and not
> the descriptor based CPM or QUICCEngine SPI controller.

Or the QSPI on Coldfire; there's a driver for that floating around, but for an
older version of this framework (sans lists).


> --- /dev/null
> +++ b/drivers/spi/spi_mpc83xx.c
> @@ -0,0 +1,349 @@
> +/*
> + * MPC83xx SPI controller driver.
> + *
> + * Maintainer: Kumar Gala

Needs a "Copyright (C) 2006 <NAME>" for the GPL to be valid; it's
the copyright holder who licences the code.


> + *
> + * This program is free software; you can redistribute  it and/or modify it
> + * under  the terms of  the GNU General  Public License as published by the
> + * Free Software Foundation;  either version 2 of the  License, or (at your
> + * option) any later version.
> + */
> ...
> +
> +/* Default for SPI Mode, slowest speed, MSB, inactive high, 8-bit char */
> +#define	SPMODE_INIT_VAL (SPMODE_CI_INACTIVEHIGH | SPMODE_CP_RISE_EDGECLK | \
> +			 SPMODE_DIV16 | SPMODE_REV | SPMODE_MS | \
> +			 SPMODE_LEN(7) | SPMODE_PM(0xf))

Hmm, it will of course be overridden as soon as needed, but shouldn't
that default be "inactive low" clock?  SPI mode 0 that is.  That stands
out mostly because you were interpreting CPOL=0 as inactive high, and
that's not my understanding of how that signal works...


> +struct mpc83xx_spi {
> +	/* bitbang has to be first */
> +	struct spi_bitbang bitbang;
> +	struct completion tx_done, rx_ready;
> +
> +	u32 __iomem *base;

Erm, OK, but fwiw my preference is to have pointer-to-struct and let
the compiler calculate the offsets (and tell you when you pass the wrong
kind of pointer).  Otherwise such pointers should use "void __iomem *"
(or maybe in your case "__be32 *"?) for explicit {base,offset} addressing.


> +static inline void mpc83xx_spi_write_reg(__be32 * base, u32 reg, u32 val)
> +{
> +	out_be32(base + (reg >> 2), val);
> +}
> +
> +static inline u32 mpc83xx_spi_read_reg(__be32 * base, u32 reg)
> +{
> +	return in_be32(base + (reg >> 2));
> +}

... here you use "__be32" not "u32", and no "__iomem" annotation.  So
this is inconsistent with the declaration above.  Note that if you
just made this "&bank->regname" you'd be having the compiler do any
offset calculation magic, and the source code will be more obvious.


> +static
> +int mpc83xx_spi_setup_transfer(struct spi_device *spi, struct spi_transfer *t)
> +{
> +	struct mpc83xx_spi *mpc83xx_spi;
> +	u32 regval;
> +	u32 len = t->bits_per_word - 1;
> +
> +	if (len == 32)
> +		len = 0;

So the hardware handles 1-33 bit words?  It'd be good to filter
the spi_setup() path directly then, returning EINVAL for illegal
word lengths (and clock speeds).



> +static u32
> +mpc83xx_spi_txrx(struct spi_device *spi, unsigned nsecs, u32 word, u8 bits)
> +{
> +	struct mpc83xx_spi *mpc83xx_spi;
> +	mpc83xx_spi = spi_master_get_devdata(spi->master);
> +
> +	INIT_COMPLETION(mpc83xx_spi->tx_done);
> +	INIT_COMPLETION(mpc83xx_spi->rx_ready);
> +
> +	/* enable tx/rx ints */
> +	mpc83xx_spi_write_reg(mpc83xx_spi->base, SPIM_REG, SPIM_NF | SPIM_NE);
> +
> +	/* transmit word */
> +	mpc83xx_spi_write_reg(mpc83xx_spi->base, SPITD_REG, word);
> +
> +	/* wait for both a tx & rx interrupt */
> +	wait_for_completion(&mpc83xx_spi->tx_done);
> +	wait_for_completion(&mpc83xx_spi->rx_ready);

I guess I'm surprised you're not using txrx_buffers() and having
that whole thing be IRQ driven, so the per-word cost eliminates
the task scheduling.  You already paid for IRQ handling ... why
not have it store the rx byte into the buffer, and write the tx
byte froom the other buffer?  That'd be cheaper than what you're
doing now ... in both time and code.  Only wake up a task at
the end of a given spi_transfer().

Plus, your IRQ handler should _not_ always return IRQ_HANDLED.
Only return it if you actually do enter one of those branches...


> +	mpc83xx_spi->sysclk = pdata->sysclk;

When MPC/PPC starts to support <linux/clk.h> would seem to be
the right sort of time to

	mpc83xx-spi->clk = clk_get(&pdev->dev, "spi_clk");

or whatever.


> +MODULE_DESCRIPTION("Simple Platform SPI Driver");

How about "Simple MPC83xx SPI driver"?

- Dave

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [spi-devel-general] Re: [PATCH] spi: Added spi master driver for Freescale MPC83xx SPI controller
  2006-04-07  5:22 ` David Brownell
@ 2006-04-07  9:16   ` Vitaly Wool
  2006-04-07 16:09     ` David Brownell
  2006-04-07 14:04   ` Kumar Gala
  1 sibling, 1 reply; 18+ messages in thread
From: Vitaly Wool @ 2006-04-07  9:16 UTC (permalink / raw)
  To: David Brownell; +Cc: Kumar Gala, Greg KH, linux-kernel, spi-devel-general

Hi,

> I guess I'm surprised you're not using txrx_buffers() and having
> that whole thing be IRQ driven, so the per-word cost eliminates
> the task scheduling.  You already paid for IRQ handling ... why
> not have it store the rx byte into the buffer, and write the tx
> byte froom the other buffer?  That'd be cheaper than what you're
> doing now ... in both time and code.  Only wake up a task at
> the end of a given spi_transfer().
>   
I might be completely wrong here, but I was asking myself this very 
question, and it looks like that's the way to implement full duplex 
transfers.
For txrx_buffers to be properly implemented, you need to take a lot of 
things into account. The main idea is not to lose the data in the 
receive buffer due to overflow, and thus you need to set up 'Rx buffer 
not free' int or whatever similar which will actually trigger after the 
first word is sent. So therefore implementing txrx_buffers within these 
conditions doesn't make much sense IMHO, unless you meant having a 
separate thread to read from the Rx buffer, which is woken up on, say, 
half-full Rx buffer.

Vitaly

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [PATCH] spi: Added spi master driver for Freescale MPC83xx SPI controller
  2006-04-07  5:22 ` David Brownell
  2006-04-07  9:16   ` [spi-devel-general] " Vitaly Wool
@ 2006-04-07 14:04   ` Kumar Gala
  2006-04-07 15:54     ` David Brownell
  1 sibling, 1 reply; 18+ messages in thread
From: Kumar Gala @ 2006-04-07 14:04 UTC (permalink / raw)
  To: David Brownell; +Cc: Greg KH, linux-kernel, spi-devel-general


On Apr 7, 2006, at 12:22 AM, David Brownell wrote:

>> This driver supports the SPI controller on the MPC83xx SoC devices  
>> from Freescale.
>> Note, this driver supports only the simple shift register SPI  
>> controller and not
>> the descriptor based CPM or QUICCEngine SPI controller.
>
> Or the QSPI on Coldfire; there's a driver for that floating around,  
> but for an
> older version of this framework (sans lists).

I'll leave that for others to figure out :)

>
>
>> --- /dev/null
>> +++ b/drivers/spi/spi_mpc83xx.c
>> @@ -0,0 +1,349 @@
>> +/*
>> + * MPC83xx SPI controller driver.
>> + *
>> + * Maintainer: Kumar Gala
>
> Needs a "Copyright (C) 2006 <NAME>" for the GPL to be valid; it's
> the copyright holder who licences the code.

Will fix.

>> + *
>> + * This program is free software; you can redistribute  it and/or  
>> modify it
>> + * under  the terms of  the GNU General  Public License as  
>> published by the
>> + * Free Software Foundation;  either version 2 of the  License,  
>> or (at your
>> + * option) any later version.
>> + */
>> ...
>> +
>> +/* Default for SPI Mode, slowest speed, MSB, inactive high, 8-bit  
>> char */
>> +#define	SPMODE_INIT_VAL (SPMODE_CI_INACTIVEHIGH |  
>> SPMODE_CP_RISE_EDGECLK | \
>> +			 SPMODE_DIV16 | SPMODE_REV | SPMODE_MS | \
>> +			 SPMODE_LEN(7) | SPMODE_PM(0xf))
>
> Hmm, it will of course be overridden as soon as needed, but shouldn't
> that default be "inactive low" clock?  SPI mode 0 that is.  That  
> stands
> out mostly because you were interpreting CPOL=0 as inactive high, and
> that's not my understanding of how that signal works...

I'll change it, not sure what I was thinking but SPI mode 0 being the  
default makes
sense.

>> +struct mpc83xx_spi {
>> +	/* bitbang has to be first */
>> +	struct spi_bitbang bitbang;
>> +	struct completion tx_done, rx_ready;
>> +
>> +	u32 __iomem *base;
>
> Erm, OK, but fwiw my preference is to have pointer-to-struct and let
> the compiler calculate the offsets (and tell you when you pass the  
> wrong
> kind of pointer).  Otherwise such pointers should use "void __iomem *"
> (or maybe in your case "__be32 *"?) for explicit {base,offset}  
> addressing.

I'll make that change.

>> +static inline void mpc83xx_spi_write_reg(__be32 * base, u32 reg,  
>> u32 val)
>> +{
>> +	out_be32(base + (reg >> 2), val);
>> +}
>> +
>> +static inline u32 mpc83xx_spi_read_reg(__be32 * base, u32 reg)
>> +{
>> +	return in_be32(base + (reg >> 2));
>> +}
>
> ... here you use "__be32" not "u32", and no "__iomem" annotation.  So
> this is inconsistent with the declaration above.  Note that if you
> just made this "&bank->regname" you'd be having the compiler do any
> offset calculation magic, and the source code will be more obvious.

Yep, I know what you mean.

>> +static
>> +int mpc83xx_spi_setup_transfer(struct spi_device *spi, struct  
>> spi_transfer *t)
>> +{
>> +	struct mpc83xx_spi *mpc83xx_spi;
>> +	u32 regval;
>> +	u32 len = t->bits_per_word - 1;
>> +
>> +	if (len == 32)
>> +		len = 0;
>
> So the hardware handles 1-33 bit words?  It'd be good to filter
> the spi_setup() path directly then, returning EINVAL for illegal
> word lengths (and clock speeds).

Uhh, no.  The HW supports 4-bit to 32-bit words.  However the  
encoding of 32-bit is 0 in the register field, and 8-bit is a value  
of 7, etc.. (bit encodings 1 & 2 are invalid).

I'm not following you on spi_setup(), but I think you mean to error  
change bits_per_word there and return EINVAL if its not one we support.

>> +static u32
>> +mpc83xx_spi_txrx(struct spi_device *spi, unsigned nsecs, u32  
>> word, u8 bits)
>> +{
>> +	struct mpc83xx_spi *mpc83xx_spi;
>> +	mpc83xx_spi = spi_master_get_devdata(spi->master);
>> +
>> +	INIT_COMPLETION(mpc83xx_spi->tx_done);
>> +	INIT_COMPLETION(mpc83xx_spi->rx_ready);
>> +
>> +	/* enable tx/rx ints */
>> +	mpc83xx_spi_write_reg(mpc83xx_spi->base, SPIM_REG, SPIM_NF |  
>> SPIM_NE);
>> +
>> +	/* transmit word */
>> +	mpc83xx_spi_write_reg(mpc83xx_spi->base, SPITD_REG, word);
>> +
>> +	/* wait for both a tx & rx interrupt */
>> +	wait_for_completion(&mpc83xx_spi->tx_done);
>> +	wait_for_completion(&mpc83xx_spi->rx_ready);
>
> I guess I'm surprised you're not using txrx_buffers() and having
> that whole thing be IRQ driven, so the per-word cost eliminates
> the task scheduling.  You already paid for IRQ handling ... why
> not have it store the rx byte into the buffer, and write the tx
> byte froom the other buffer?  That'd be cheaper than what you're
> doing now ... in both time and code.  Only wake up a task at
> the end of a given spi_transfer().

I dont follow you at all here.  What are you suggesting I do?

>
> Plus, your IRQ handler should _not_ always return IRQ_HANDLED.
> Only return it if you actually do enter one of those branches...
>
>
>> +	mpc83xx_spi->sysclk = pdata->sysclk;
>
> When MPC/PPC starts to support <linux/clk.h> would seem to be
> the right sort of time to
>
> 	mpc83xx-spi->clk = clk_get(&pdev->dev, "spi_clk");
>
> or whatever.

Will do that once we support <linux/clk.h>

>> +MODULE_DESCRIPTION("Simple Platform SPI Driver");
>
> How about "Simple MPC83xx SPI driver"?

Will change.

- k

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [PATCH] spi: Added spi master driver for Freescale MPC83xx SPI controller
  2006-04-07 14:04   ` Kumar Gala
@ 2006-04-07 15:54     ` David Brownell
  2006-04-07 16:44       ` Kumar Gala
  0 siblings, 1 reply; 18+ messages in thread
From: David Brownell @ 2006-04-07 15:54 UTC (permalink / raw)
  To: Kumar Gala; +Cc: Greg KH, linux-kernel, spi-devel-general


> > Hmm, it will of course be overridden as soon as needed, but shouldn't
> > that default be "inactive low" clock?  SPI mode 0 that is.  That  
> > stands
> > out mostly because you were interpreting CPOL=0 as inactive high, and
> > that's not my understanding of how that signal works...
> 
> I'll change it, not sure what I was thinking but SPI mode 0 being the  
> default makes sense.

The default doesn't really matter, since it will be overridden ... I was
more concerned about CPOL=0 being misinterpreted ...



> > ... here you use "__be32" not "u32", and no "__iomem" annotation.  So
> > this is inconsistent with the declaration above.  Note that if you
> > just made this "&bank->regname" you'd be having the compiler do any
> > offset calculation magic, and the source code will be more obvious.
> 
> Yep, I know what you mean.

Good rule of thumb:  run "sparse -Wbitwise" on your drivers, and have it
tell you about goofed up things.  (Assuming the asm-ppc headers are safe
to run that on!)  It's nice having tools tell you about bugs before you
run into them "live", and GCC only goes so far.


> >> +static
> >> +int mpc83xx_spi_setup_transfer(struct spi_device *spi, struct  
> >> spi_transfer *t)
> >> +{
> >> +	struct mpc83xx_spi *mpc83xx_spi;
> >> +	u32 regval;
> >> +	u32 len = t->bits_per_word - 1;
> >> +
> >> +	if (len == 32)
> >> +		len = 0;
> >
> > So the hardware handles 1-33 bit words?  It'd be good to filter
> > the spi_setup() path directly then, returning EINVAL for illegal
> > word lengths (and clock speeds).
> 
> Uhh, no.  The HW supports 4-bit to 32-bit words.  However the  
> encoding of 32-bit is 0 in the register field, and 8-bit is a value  
> of 7, etc.. (bit encodings 1 & 2 are invalid).

So that test should be "len == 31" too ...

> I'm not following you on spi_setup(), but I think you mean to error  
> change bits_per_word there and return EINVAL if its not one we support.

Yes, but do it early:  provide your own code to implement spi_setup(), which
makes a range test and then either fails immediately or else delegates the
rest of the work to spi_bitbang_setup() ... rather than only using that as
the default.

Your current code would claim to accept transfers with 64 bit words,
but it wouldn't actually handle them correctly...
 

> > I guess I'm surprised you're not using txrx_buffers() and having
> > that whole thing be IRQ driven, so the per-word cost eliminates
> > the task scheduling.  You already paid for IRQ handling ... why
> > not have it store the rx byte into the buffer, and write the tx
> > byte froom the other buffer?  That'd be cheaper than what you're
> > doing now ... in both time and code.  Only wake up a task at
> > the end of a given spi_transfer().
> 
> I dont follow you at all here.  What are you suggesting I do?

Don't do word-at-a-time I/O with spi_bitbang; you're using IRQs, and
that's oriented towards polling.  Don't fill bitbang->txrx_word[]; don't
use the default spi_bitbang_setup().

Instead, provide your own setup(), and provide bitbang->txrx_buffers.

Then when the generic not-really-bitbang core calls your txrx_buffers(),
your code would record the "current" spi_transfer buffer pair and length
then kickstart the I/O by writing the first byte from the TX buffer
(or maybe zero if there is none).  Wait on some completion event; return
when the whole transfer has completed (or stopped after an error).

Then the rest will be IRQ driven; you'll care only about "rx word ready"
or whatever.  When you get that IRQ, read the word ... and if there's
an RX buffer, store it in the next location (else discard it).  Decrement
the length (by 1, 2, or 4 bytes).  If length is nonzero, kickstart the
next step by writing the next word from the TX buffer (or zero).  When
length is zero, trigger txrx_buffers() completion.  Return from IRQ handler.

See for example how bitbang_txrx_8() works; you'd basically be doing that
as an irq-driven copy, instead of polling txrx_word().  The first version
of your IRQ handler might be easier if it only handles 4-8 bit words,
leaving 9-16 bits (and 17-32 bits) till later.

- Dave

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [spi-devel-general] Re: [PATCH] spi: Added spi master driver for Freescale MPC83xx SPI controller
  2006-04-07  9:16   ` [spi-devel-general] " Vitaly Wool
@ 2006-04-07 16:09     ` David Brownell
  2006-04-07 17:04       ` Kumar Gala
  0 siblings, 1 reply; 18+ messages in thread
From: David Brownell @ 2006-04-07 16:09 UTC (permalink / raw)
  To: Vitaly Wool; +Cc: Kumar Gala, Greg KH, linux-kernel, spi-devel-general

On Friday 07 April 2006 2:16 am, Vitaly Wool wrote:
> Hi,
> 
> > I guess I'm surprised you're not using txrx_buffers() and having
> > that whole thing be IRQ driven, so the per-word cost eliminates
> > the task scheduling.  You already paid for IRQ handling ... why
> > not have it store the rx byte into the buffer, and write the tx
> > byte froom the other buffer?  That'd be cheaper than what you're
> > doing now ... in both time and code.  Only wake up a task at
> > the end of a given spi_transfer().
> >   
> I might be completely wrong here, but I was asking myself this very 
> question, and it looks like that's the way to implement full duplex 
> transfers.

Well, not the _only_ way.  The polling-type txrx_word() calls are
also full duplex.  My point is more that it's bad/inefficient to
incur both IRQ _and_ task switch overheads per word, when it would
be a lot simpler to just have the IRQ handler do its normal job.

(And that's even true if you've turned hard IRQ handlers into threads
for PREEMPT_RT or whatever.  In that case the "IRQ overhead" is a
task switch, but you're still saving _additional_ task switches.)


> For txrx_buffers to be properly implemented, you need to take a lot of 
> things into account. 

No more than the usual sort of driver thing.  SPI is a lot simpler
than most hardware, it's just a fancy shift register.  There's not
a lot of configuration possible, and not much can go wrong.


> The main idea is not to lose the data in the  
> receive buffer due to overflow, and thus you need to set up 'Rx buffer 
> not free' int or whatever similar which will actually trigger after the 
> first word is sent.

I think of it more as "after first word is received", but they are
the same event ... you can't send a word without receiving one, or
receive one without sending one.  SPI is fundamentally full duplex.

Now, if you have a FIFO as well as a shift register, then yes the
synchronization can get tricky.  It should still be possible to
have the RX and TX buffers be identical though.


> So therefore implementing txrx_buffers within these  
> conditions doesn't make much sense IMHO, unless you meant having a 
> separate thread to read from the Rx buffer, which is woken up on, say, 
> half-full Rx buffer.

I'll disagree on any need for a separate thread.  Kumar's IRQ handler
was already reading the RX byte and storing it.  However, he stored it
in a scratch byte ... rather than putting it right into the RX buffer.
There's no point to incurring extra costs like that (or like the extra
context switch overhead).

- Dave


^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [PATCH] spi: Added spi master driver for Freescale MPC83xx SPI controller
  2006-04-07 15:54     ` David Brownell
@ 2006-04-07 16:44       ` Kumar Gala
  0 siblings, 0 replies; 18+ messages in thread
From: Kumar Gala @ 2006-04-07 16:44 UTC (permalink / raw)
  To: David Brownell; +Cc: Greg KH, linux-kernel, spi-devel-general


On Apr 7, 2006, at 10:54 AM, David Brownell wrote:

>
>>> Hmm, it will of course be overridden as soon as needed, but  
>>> shouldn't
>>> that default be "inactive low" clock?  SPI mode 0 that is.  That
>>> stands
>>> out mostly because you were interpreting CPOL=0 as inactive high,  
>>> and
>>> that's not my understanding of how that signal works...
>>
>> I'll change it, not sure what I was thinking but SPI mode 0 being the
>> default makes sense.
>
> The default doesn't really matter, since it will be overridden ...  
> I was
> more concerned about CPOL=0 being misinterpreted ...

Gotcha, I see that I am misinterpreting CPOL/CPHA so I'll fix that up.

>>> ... here you use "__be32" not "u32", and no "__iomem"  
>>> annotation.  So
>>> this is inconsistent with the declaration above.  Note that if you
>>> just made this "&bank->regname" you'd be having the compiler do any
>>> offset calculation magic, and the source code will be more obvious.
>>
>> Yep, I know what you mean.
>
> Good rule of thumb:  run "sparse -Wbitwise" on your drivers, and  
> have it
> tell you about goofed up things.  (Assuming the asm-ppc headers are  
> safe
> to run that on!)  It's nice having tools tell you about bugs before  
> you
> run into them "live", and GCC only goes so far.

Yeah, forgot to run sparse the first time.

>>>> +static
>>>> +int mpc83xx_spi_setup_transfer(struct spi_device *spi, struct
>>>> spi_transfer *t)
>>>> +{
>>>> +	struct mpc83xx_spi *mpc83xx_spi;
>>>> +	u32 regval;
>>>> +	u32 len = t->bits_per_word - 1;
>>>> +
>>>> +	if (len == 32)
>>>> +		len = 0;
>>>
>>> So the hardware handles 1-33 bit words?  It'd be good to filter
>>> the spi_setup() path directly then, returning EINVAL for illegal
>>> word lengths (and clock speeds).
>>
>> Uhh, no.  The HW supports 4-bit to 32-bit words.  However the
>> encoding of 32-bit is 0 in the register field, and 8-bit is a value
>> of 7, etc.. (bit encodings 1 & 2 are invalid).
>
> So that test should be "len == 31" too ...

Good catch, I had it testing bits_per_word directly before.   
Actually, we support 4-bit to 16-bit, and 32-bit transfers.

>> I'm not following you on spi_setup(), but I think you mean to error
>> change bits_per_word there and return EINVAL if its not one we  
>> support.
>
> Yes, but do it early:  provide your own code to implement spi_setup 
> (), which
> makes a range test and then either fails immediately or else  
> delegates the
> rest of the work to spi_bitbang_setup() ... rather than only using  
> that as
> the default.
>
> Your current code would claim to accept transfers with 64 bit words,
> but it wouldn't actually handle them correctly...

Right.  However, do you think its really necessary to implement my  
own spi_setup(), spi_bitbang_setup() is going to call my  
setup_transfer() which will do the range checking for me (and I'll  
need range checking in transfer_setup() anyways).

>>> I guess I'm surprised you're not using txrx_buffers() and having
>>> that whole thing be IRQ driven, so the per-word cost eliminates
>>> the task scheduling.  You already paid for IRQ handling ... why
>>> not have it store the rx byte into the buffer, and write the tx
>>> byte froom the other buffer?  That'd be cheaper than what you're
>>> doing now ... in both time and code.  Only wake up a task at
>>> the end of a given spi_transfer().
>>
>> I dont follow you at all here.  What are you suggesting I do?
>
> Don't do word-at-a-time I/O with spi_bitbang; you're using IRQs, and
> that's oriented towards polling.  Don't fill bitbang->txrx_word[];  
> don't
> use the default spi_bitbang_setup().
>
> Instead, provide your own setup(), and provide bitbang->txrx_buffers.
>
> Then when the generic not-really-bitbang core calls your  
> txrx_buffers(),
> your code would record the "current" spi_transfer buffer pair and  
> length
> then kickstart the I/O by writing the first byte from the TX buffer
> (or maybe zero if there is none).  Wait on some completion event;  
> return
> when the whole transfer has completed (or stopped after an error).
>
> Then the rest will be IRQ driven; you'll care only about "rx word  
> ready"
> or whatever.  When you get that IRQ, read the word ... and if there's
> an RX buffer, store it in the next location (else discard it).   
> Decrement
> the length (by 1, 2, or 4 bytes).  If length is nonzero, kickstart the
> next step by writing the next word from the TX buffer (or zero).  When
> length is zero, trigger txrx_buffers() completion.  Return from IRQ  
> handler.
>
> See for example how bitbang_txrx_8() works; you'd basically be  
> doing that
> as an irq-driven copy, instead of polling txrx_word().  The first  
> version
> of your IRQ handler might be easier if it only handles 4-8 bit words,
> leaving 9-16 bits (and 17-32 bits) till later.

Maybe I'm missing where the polling is occurring now, but I felt like  
I was effectively doing what you are describing.




^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [spi-devel-general] Re: [PATCH] spi: Added spi master driver for Freescale MPC83xx SPI controller
  2006-04-07 16:09     ` David Brownell
@ 2006-04-07 17:04       ` Kumar Gala
  2006-04-08  1:25         ` David Brownell
  0 siblings, 1 reply; 18+ messages in thread
From: Kumar Gala @ 2006-04-07 17:04 UTC (permalink / raw)
  To: David Brownell; +Cc: Vitaly Wool, Greg KH, linux-kernel, spi-devel-general


On Apr 7, 2006, at 11:09 AM, David Brownell wrote:

> On Friday 07 April 2006 2:16 am, Vitaly Wool wrote:
>> Hi,
>>
>>> I guess I'm surprised you're not using txrx_buffers() and having
>>> that whole thing be IRQ driven, so the per-word cost eliminates
>>> the task scheduling.  You already paid for IRQ handling ... why
>>> not have it store the rx byte into the buffer, and write the tx
>>> byte froom the other buffer?  That'd be cheaper than what you're
>>> doing now ... in both time and code.  Only wake up a task at
>>> the end of a given spi_transfer().
>>>
>> I might be completely wrong here, but I was asking myself this very
>> question, and it looks like that's the way to implement full duplex
>> transfers.
>
> Well, not the _only_ way.  The polling-type txrx_word() calls are
> also full duplex.  My point is more that it's bad/inefficient to
> incur both IRQ _and_ task switch overheads per word, when it would
> be a lot simpler to just have the IRQ handler do its normal job.
>
> (And that's even true if you've turned hard IRQ handlers into threads
> for PREEMPT_RT or whatever.  In that case the "IRQ overhead" is a
> task switch, but you're still saving _additional_ task switches.)

This makes more sense about what I'm doing that is wasteful.   
However, I'm not sure exactly where I should plug into things.

I think you are saying to continue using spi_bitbang_transfer &  
spi_bitbang_work, but have spi_bitbang_work call my own bitbang- 
 >txrx_bufs().

- kumar

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [spi-devel-general] Re: [PATCH] spi: Added spi master driver for Freescale MPC83xx SPI controller
  2006-04-07 17:04       ` Kumar Gala
@ 2006-04-08  1:25         ` David Brownell
  0 siblings, 0 replies; 18+ messages in thread
From: David Brownell @ 2006-04-08  1:25 UTC (permalink / raw)
  To: Kumar Gala; +Cc: Vitaly Wool, Greg KH, linux-kernel, spi-devel-general

On Friday 07 April 2006 10:04 am, Kumar Gala wrote:

> > Well, not the _only_ way.  The polling-type txrx_word() calls are
> > also full duplex.  My point is more that it's bad/inefficient to
> > incur both IRQ _and_ task switch overheads per word, when it would
> > be a lot simpler to just have the IRQ handler do its normal job.

Not that you actually _need_ an IRQ handler to be correct, in any case.


> > (And that's even true if you've turned hard IRQ handlers into threads
> > for PREEMPT_RT or whatever.  In that case the "IRQ overhead" is a
> > task switch, but you're still saving _additional_ task switches.)
> 
> This makes more sense about what I'm doing that is wasteful.   
> However, I'm not sure exactly where I should plug into things.

Only using interfaces below the line in spi_bitbang that says
it's the "SECOND PART".


> I think you are saying to continue using spi_bitbang_transfer &  
> spi_bitbang_work, but have spi_bitbang_work call my own bitbang- 
>  >txrx_bufs().

Yes.  Consider several different ways to implement that I/O loop:

	- Interrupt plus two context switches per byte (what you have now),
	  no per-buffer context switch

	- Interrupt per byte, plus one context switch pair per buffer
	  (what I've described)

	- pure PIO per byte, no context switches (as if you polled
	  the registers rather than using an IRQ)

Any of them could be correct, but one of them is a lot worse in terms
of CPU overhead when you aim at tranfer rates of even just a few MBytes
per second.  (It's the one with lots of needless context switching.)

That pure PIO model will sometimes be very appropriate; if the SPI clock
is fast enough, it can be less overhead than the IRQ driven one.

- Dave




^ permalink raw reply	[flat|nested] 18+ messages in thread

* [PATCH][UPDATE] spi: Added spi master driver for Freescale MPC83xx SPI controller
  2006-04-06 18:30 [PATCH] spi: Added spi master driver for Freescale MPC83xx SPI controller Kumar Gala
  2006-04-07  5:22 ` David Brownell
@ 2006-04-10 17:38 ` Kumar Gala
  2006-04-10 19:01   ` David Brownell
  1 sibling, 1 reply; 18+ messages in thread
From: Kumar Gala @ 2006-04-10 17:38 UTC (permalink / raw)
  To: david-b; +Cc: Greg KH, linux-kernel, spi-devel-general

This driver supports the SPI controller on the MPC83xx SoC devices from
Freescale.  Note, this driver supports only the simple shift register SPI
controller and not the descriptor based CPM or QUICCEngine SPI controller.

Signed-off-by: Kumar Gala <galak@kernel.crashing.org>

---
commit 1e01024d79c1805e880d8863e03b6db91fc2dd25
tree 21744404d18abbee7bc8387bd74ec018e413fab8
parent bc33ba02f8414e91a3b2afa877be2c54d6fce564
author Kumar Gala <galak@kernel.crashing.org> Mon, 10 Apr 2006 12:38:11 -0500
committer Kumar Gala <galak@kernel.crashing.org> Mon, 10 Apr 2006 12:38:11 -0500

 drivers/spi/Kconfig       |   10 +
 drivers/spi/Makefile      |    1 
 drivers/spi/spi_mpc83xx.c |  488 +++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 499 insertions(+), 0 deletions(-)

diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index 7a75fae..af937bc 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -75,6 +75,16 @@ config SPI_BUTTERFLY
 	  inexpensive battery powered microcontroller evaluation board.
 	  This same cable can be used to flash new firmware.
 
+config SPI_MPC83xx
+	tristate "Freescale MPC83xx SPI controller"
+	depends on SPI_MASTER && PPC_83xx && EXPERIMENTAL
+	select SPI_BITBANG
+	help
+	  This enables using the Freescale MPC83xx SPI controller in master mode.
+	  Note, this driver uniquely supports the SPI controller on the MPC83xx
+	  family of PowerPC processors.  The MPC83xx uses a simple set of shift
+	  registers for data (opposed to the CPM based descriptor model).
+
 #
 # Add new SPI master controllers in alphabetical order above this line
 #
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index c2c87e8..502ac0b 100644
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -13,6 +13,7 @@ obj-$(CONFIG_SPI_MASTER)		+= spi.o
 # SPI master controller drivers (bus)
 obj-$(CONFIG_SPI_BITBANG)		+= spi_bitbang.o
 obj-$(CONFIG_SPI_BUTTERFLY)		+= spi_butterfly.o
+obj-$(CONFIG_SPI_MPC83xx)		+= spi_mpc83xx.o
 # 	... add above this line ...
 
 # SPI protocol drivers (device/link on bus)
diff --git a/drivers/spi/spi_mpc83xx.c b/drivers/spi/spi_mpc83xx.c
new file mode 100644
index 0000000..a5ecdec
--- /dev/null
+++ b/drivers/spi/spi_mpc83xx.c
@@ -0,0 +1,488 @@
+/*
+ * MPC83xx SPI controller driver.
+ *
+ * Maintainer: Kumar Gala
+ *
+ * Copyright (C) 2006 Polycom, Inc.
+ *
+ * This program is free software; you can redistribute  it and/or modify it
+ * under  the terms of  the GNU General  Public License as published by the
+ * Free Software Foundation;  either version 2 of the  License, or (at your
+ * option) any later version.
+ */
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/completion.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/irq.h>
+#include <linux/device.h>
+#include <linux/spi/spi.h>
+#include <linux/spi/spi_bitbang.h>
+#include <linux/platform_device.h>
+#include <linux/fsl_devices.h>
+
+#include <asm/irq.h>
+#include <asm/io.h>
+
+/* SPI Controller registers */
+struct mpc83xx_spi_reg {
+	u8 res1[0x20];
+	__be32 mode;
+	__be32 event;
+	__be32 mask;
+	__be32 command;
+	__be32 transmit;
+	__be32 receive;
+};
+
+/* SPI Controller mode register definitions */
+#define	SPMODE_CI_INACTIVEHIGH	(1 << 29)
+#define	SPMODE_CP_BEGIN_EDGECLK	(1 << 28)
+#define	SPMODE_DIV16		(1 << 27)
+#define	SPMODE_REV		(1 << 26)
+#define	SPMODE_MS		(1 << 25)
+#define	SPMODE_ENABLE		(1 << 24)
+#define	SPMODE_LEN(x)		((x) << 20)
+#define	SPMODE_PM(x)		((x) << 16)
+
+/*
+ * Default for SPI Mode:
+ * 	SPI MODE 0 (inactive low, phase middle, MSB, 8-bit length, slow clk
+ */
+#define	SPMODE_INIT_VAL (SPMODE_CI_INACTIVEHIGH | SPMODE_DIV16 | SPMODE_REV | \
+			 SPMODE_MS | SPMODE_LEN(7) | SPMODE_PM(0xf))
+
+/* SPIE register values */
+#define	SPIE_NE		0x00000200	/* Not empty */
+#define	SPIE_NF		0x00000100	/* Not full */
+
+/* SPIM register values */
+#define	SPIM_NE		0x00000200	/* Not empty */
+#define	SPIM_NF		0x00000100	/* Not full */
+
+/* SPI Controller driver's private data. */
+struct mpc83xx_spi {
+	/* bitbang has to be first */
+	struct spi_bitbang bitbang;
+	struct completion done;
+
+	struct mpc83xx_spi_reg __iomem *base;
+
+	/* rx & tx bufs from the spi_transfer */
+	const void *tx;
+	void *rx;
+
+	/* functions to deal with different sized buffers */
+	void (*get_rx) (u32 rx_data, struct mpc83xx_spi *);
+	u32(*get_tx) (struct mpc83xx_spi *);
+
+	unsigned int count;
+	u32 irq;
+
+	unsigned nsecs;		/* (clock cycle time)/2 */
+
+	u32 sysclk;
+	void (*activate_cs) (u8 cs, u8 polarity);
+	void (*deactivate_cs) (u8 cs, u8 polarity);
+};
+
+static inline void mpc83xx_spi_write_reg(__be32 __iomem * reg, u32 val)
+{
+	out_be32(reg, val);
+}
+
+static inline u32 mpc83xx_spi_read_reg(__be32 __iomem * reg)
+{
+	return in_be32(reg);
+}
+
+#define MPC83XX_SPI_RX_BUF(type) 					  \
+void mpc83xx_spi_rx_buf_##type(u32 data, struct mpc83xx_spi *mpc83xx_spi) \
+{									  \
+	type * rx = mpc83xx_spi->rx;					  \
+	*rx++ = (type)data;						  \
+	mpc83xx_spi->rx = rx;						  \
+}
+
+#define MPC83XX_SPI_TX_BUF(type)				\
+u32 mpc83xx_spi_tx_buf_##type(struct mpc83xx_spi *mpc83xx_spi)	\
+{								\
+	u32 data;						\
+	const type * tx = mpc83xx_spi->tx;			\
+	data = *tx++;						\
+	mpc83xx_spi->tx = tx;					\
+	return data;						\
+}
+
+MPC83XX_SPI_RX_BUF(u8)
+MPC83XX_SPI_RX_BUF(u16)
+MPC83XX_SPI_RX_BUF(u32)
+MPC83XX_SPI_TX_BUF(u8)
+MPC83XX_SPI_TX_BUF(u16)
+MPC83XX_SPI_TX_BUF(u32)
+
+static void mpc83xx_spi_chipselect(struct spi_device *spi, int value)
+{
+	struct mpc83xx_spi *mpc83xx_spi;
+	u8 pol = spi->mode & SPI_CS_HIGH ? 1 : 0;
+
+	mpc83xx_spi = spi_master_get_devdata(spi->master);
+
+	if (value == BITBANG_CS_INACTIVE) {
+		if (mpc83xx_spi->deactivate_cs)
+			mpc83xx_spi->deactivate_cs(spi->chip_select, pol);
+	}
+
+	if (value == BITBANG_CS_ACTIVE) {
+		u32 regval = mpc83xx_spi_read_reg(&mpc83xx_spi->base->mode);
+		u32 len = spi->bits_per_word;
+		if (len == 32)
+			len = 0;
+		else
+			len = len - 1;
+
+		/* mask out bits we are going to set */
+		regval &= ~0x38ff0000;
+
+		if (spi->mode & SPI_CPHA)
+			regval |= SPMODE_CP_BEGIN_EDGECLK;
+		if (spi->mode & SPI_CPOL)
+			regval |= SPMODE_CI_INACTIVEHIGH;
+
+		regval |= SPMODE_LEN(len);
+
+		if ((mpc83xx_spi->sysclk / spi->max_speed_hz) >= 64) {
+			u8 pm = mpc83xx_spi->sysclk / (spi->max_speed_hz * 64);
+			regval |= SPMODE_PM(pm) | SPMODE_DIV16;
+		} else {
+			u8 pm = mpc83xx_spi->sysclk / (spi->max_speed_hz * 4);
+			regval |= SPMODE_PM(pm);
+		}
+
+		mpc83xx_spi_write_reg(&mpc83xx_spi->base->mode, regval);
+		if (mpc83xx_spi->activate_cs)
+			mpc83xx_spi->activate_cs(spi->chip_select, pol);
+	}
+}
+
+static
+int mpc83xx_spi_setup_transfer(struct spi_device *spi, struct spi_transfer *t)
+{
+	struct mpc83xx_spi *mpc83xx_spi;
+	u32 regval;
+	u8 bits_per_word;
+	u32 hz;
+
+	mpc83xx_spi = spi_master_get_devdata(spi->master);
+
+	if (t) {
+		bits_per_word = t->bits_per_word;
+		hz = t->speed_hz;
+	} else {
+		bits_per_word = 0;
+		hz = 0;
+	}
+
+	/* spi_transfer level calls that work per-word */
+	if (!bits_per_word)
+		bits_per_word = spi->bits_per_word;
+
+	/* Make sure its a bit width we support [4..16, 32] */
+	if ((bits_per_word < 4)
+	    || ((bits_per_word > 16) && (bits_per_word != 32)))
+		return -EINVAL;
+
+	if (bits_per_word <= 8) {
+		mpc83xx_spi->get_rx = mpc83xx_spi_rx_buf_u8;
+		mpc83xx_spi->get_tx = mpc83xx_spi_tx_buf_u8;
+	} else {
+		if (bits_per_word <= 16) {
+			mpc83xx_spi->get_rx = mpc83xx_spi_rx_buf_u16;
+			mpc83xx_spi->get_tx = mpc83xx_spi_tx_buf_u16;
+		} else {
+			if (bits_per_word <= 32) {
+				mpc83xx_spi->get_rx = mpc83xx_spi_rx_buf_u32;
+				mpc83xx_spi->get_tx = mpc83xx_spi_tx_buf_u32;
+			} else {
+				return -EINVAL;
+			}
+		}
+	}
+
+	/* nsecs = (clock period)/2 */
+	if (!hz)
+		hz = spi->max_speed_hz;
+	mpc83xx_spi->nsecs = (1000000000 / 2) / hz;
+	if (mpc83xx_spi->nsecs > MAX_UDELAY_MS * 1000)
+		return -EINVAL;
+
+	if (bits_per_word == 32)
+		bits_per_word = 0;
+	else
+		bits_per_word = bits_per_word - 1;
+
+	regval = mpc83xx_spi_read_reg(&mpc83xx_spi->base->mode);
+
+	/* Mask out bits_per_wordgth */
+	regval &= 0xff0fffff;
+	regval |= SPMODE_LEN(bits_per_word);
+
+	mpc83xx_spi_write_reg(&mpc83xx_spi->base->mode, regval);
+
+	return 0;
+}
+
+static int mpc83xx_spi_setup(struct spi_device *spi)
+{
+	struct spi_bitbang *bitbang;
+	struct mpc83xx_spi *mpc83xx_spi;
+	int retval;
+
+	if (!spi->max_speed_hz)
+		return -EINVAL;
+
+	bitbang = spi_master_get_devdata(spi->master);
+	mpc83xx_spi = spi_master_get_devdata(spi->master);
+
+	if (!spi->bits_per_word)
+		spi->bits_per_word = 8;
+
+	retval = mpc83xx_spi_setup_transfer(spi, NULL);
+	if (retval < 0)
+		return retval;
+
+	dev_dbg(&spi->dev, "%s, mode %d, %u bits/w, %u nsec\n",
+		__FUNCTION__, spi->mode & (SPI_CPOL | SPI_CPHA),
+		spi->bits_per_word, 2 * mpc83xx_spi->nsecs);
+
+	/* NOTE we _need_ to call chipselect() early, ideally with adapter
+	 * setup, unless the hardware defaults cooperate to avoid confusion
+	 * between normal (active low) and inverted chipselects.
+	 */
+
+	/* deselect chip (low or high) */
+	spin_lock(&bitbang->lock);
+	if (!bitbang->busy) {
+		bitbang->chipselect(spi, BITBANG_CS_INACTIVE);
+		ndelay(mpc83xx_spi->nsecs);
+	}
+	spin_unlock(&bitbang->lock);
+
+	return 0;
+}
+
+static int mpc83xx_spi_bufs(struct spi_device *spi, struct spi_transfer *t)
+{
+	struct mpc83xx_spi *mpc83xx_spi;
+	u32 word;
+
+	mpc83xx_spi = spi_master_get_devdata(spi->master);
+
+	mpc83xx_spi->tx = t->tx_buf;
+	mpc83xx_spi->rx = t->rx_buf;
+	mpc83xx_spi->count = t->len;
+	INIT_COMPLETION(mpc83xx_spi->done);
+
+	/* enable rx ints */
+	mpc83xx_spi_write_reg(&mpc83xx_spi->base->mask, SPIM_NE);
+
+	/* transmit word */
+	word = mpc83xx_spi->get_tx(mpc83xx_spi);
+	mpc83xx_spi_write_reg(&mpc83xx_spi->base->transmit, word);
+
+	wait_for_completion(&mpc83xx_spi->done);
+
+	/* disable rx ints */
+	mpc83xx_spi_write_reg(&mpc83xx_spi->base->mask, 0);
+
+	return t->len - mpc83xx_spi->count;
+}
+
+irqreturn_t mpc83xx_spi_irq(s32 irq, void *context_data,
+			    struct pt_regs * ptregs)
+{
+	struct mpc83xx_spi *mpc83xx_spi = context_data;
+	u32 event;
+	irqreturn_t ret = IRQ_NONE;
+
+	/* Get interrupt events(tx/rx) */
+	event = mpc83xx_spi_read_reg(&mpc83xx_spi->base->event);
+
+	/* We need handle RX first */
+	if (event & SPIE_NE) {
+		u32 rx_data = mpc83xx_spi_read_reg(&mpc83xx_spi->base->receive);
+
+		if (mpc83xx_spi->rx)
+			mpc83xx_spi->get_rx(rx_data, mpc83xx_spi);
+
+		ret = IRQ_HANDLED;
+	}
+
+	if ((event & SPIE_NF) == 0)
+		/* spin until TX is done */
+		while (((event =
+			 mpc83xx_spi_read_reg(&mpc83xx_spi->base->event)) &
+						SPIE_NF) == 0)
+			 ;
+
+	mpc83xx_spi->count -= 1;
+	if (mpc83xx_spi->count) {
+		if (mpc83xx_spi->tx) {
+			u32 word = mpc83xx_spi->get_tx(mpc83xx_spi);
+			mpc83xx_spi_write_reg(&mpc83xx_spi->base->transmit,
+					      word);
+		}
+	} else {
+		complete(&mpc83xx_spi->done);
+	}
+
+	/* Clear the events */
+	mpc83xx_spi_write_reg(&mpc83xx_spi->base->event, event);
+
+	return ret;
+}
+
+static int __devinit mpc83xx_spi_probe(struct platform_device *dev)
+{
+	struct spi_master *master;
+	struct mpc83xx_spi *mpc83xx_spi;
+	struct fsl_spi_platform_data *pdata;
+	struct resource *r;
+	u32 regval;
+	int ret = 0;
+
+	/* Get resources(memory, IRQ) associated with the device */
+	master = spi_alloc_master(&dev->dev, sizeof(struct mpc83xx_spi));
+
+	if (master == NULL) {
+		ret = -ENOMEM;
+		goto err;
+	}
+
+	platform_set_drvdata(dev, master);
+	pdata = dev->dev.platform_data;
+
+	if (pdata == NULL) {
+		ret = -ENODEV;
+		goto free_master;
+	}
+
+	r = platform_get_resource(dev, IORESOURCE_MEM, 0);
+	if (r == NULL) {
+		ret = -ENODEV;
+		goto free_master;
+	}
+
+	mpc83xx_spi = spi_master_get_devdata(master);
+	mpc83xx_spi->bitbang.master = spi_master_get(master);
+	mpc83xx_spi->bitbang.chipselect = mpc83xx_spi_chipselect;
+	mpc83xx_spi->bitbang.setup_transfer = mpc83xx_spi_setup_transfer;
+	mpc83xx_spi->bitbang.txrx_bufs = mpc83xx_spi_bufs;
+	mpc83xx_spi->sysclk = pdata->sysclk;
+	mpc83xx_spi->activate_cs = pdata->activate_cs;
+	mpc83xx_spi->deactivate_cs = pdata->deactivate_cs;
+	mpc83xx_spi->get_rx = mpc83xx_spi_rx_buf_u8;
+	mpc83xx_spi->get_tx = mpc83xx_spi_tx_buf_u8;
+
+	mpc83xx_spi->bitbang.master->setup = mpc83xx_spi_setup;
+	init_completion(&mpc83xx_spi->done);
+
+	mpc83xx_spi->base = ioremap(r->start, r->end - r->start + 1);
+	if (mpc83xx_spi->base == NULL) {
+		ret = -ENOMEM;
+		goto put_master;
+	}
+
+	mpc83xx_spi->irq = platform_get_irq(dev, 0);
+
+	if (mpc83xx_spi->irq < 0) {
+		ret = -ENXIO;
+		goto unmap_io;
+	}
+
+	/* Register for SPI Interrupt */
+	ret = request_irq(mpc83xx_spi->irq, mpc83xx_spi_irq,
+			  0, "mpc83xx_spi", mpc83xx_spi);
+
+	if (ret != 0)
+		goto unmap_io;
+
+	master->bus_num = pdata->bus_num;
+	master->num_chipselect = pdata->max_chipselect;
+
+	/* SPI controller initializations */
+	mpc83xx_spi_write_reg(&mpc83xx_spi->base->mode, 0);
+	mpc83xx_spi_write_reg(&mpc83xx_spi->base->mask, 0);
+	mpc83xx_spi_write_reg(&mpc83xx_spi->base->command, 0);
+	mpc83xx_spi_write_reg(&mpc83xx_spi->base->event, 0xffffffff);
+
+	/* Enable SPI interface */
+	regval = pdata->initial_spmode | SPMODE_INIT_VAL | SPMODE_ENABLE;
+	mpc83xx_spi_write_reg(&mpc83xx_spi->base->mode, regval);
+
+	ret = spi_bitbang_start(&mpc83xx_spi->bitbang);
+
+	if (ret != 0)
+		goto free_irq;
+
+	printk(KERN_INFO
+	       "%s: MPC83xx SPI Controller driver at 0x%p (irq = %d)\n",
+	       dev->dev.bus_id, mpc83xx_spi->base, mpc83xx_spi->irq);
+
+	return ret;
+
+free_irq:
+	free_irq(mpc83xx_spi->irq, mpc83xx_spi);
+unmap_io:
+	iounmap(mpc83xx_spi->base);
+put_master:
+	spi_master_put(master);
+free_master:
+	kfree(master);
+err:
+	return ret;
+}
+
+static int __devexit mpc83xx_spi_remove(struct platform_device *dev)
+{
+	struct mpc83xx_spi *mpc83xx_spi;
+	struct spi_master *master;
+
+	master = platform_get_drvdata(dev);
+	mpc83xx_spi = spi_master_get_devdata(master);
+
+	spi_bitbang_stop(&mpc83xx_spi->bitbang);
+	free_irq(mpc83xx_spi->irq, mpc83xx_spi);
+	iounmap(mpc83xx_spi->base);
+	spi_master_put(mpc83xx_spi->bitbang.master);
+
+	return 0;
+}
+
+static struct platform_driver mpc83xx_spi_driver = {
+	.probe = mpc83xx_spi_probe,
+	.remove = __devexit_p(mpc83xx_spi_remove),
+	.driver = {
+		   .name = "mpc83xx_spi",
+	},
+};
+
+static int __init mpc83xx_spi_init(void)
+{
+	return platform_driver_register(&mpc83xx_spi_driver);
+}
+
+static void __exit mpc83xx_spi_exit(void)
+{
+	platform_driver_unregister(&mpc83xx_spi_driver);
+}
+
+module_init(mpc83xx_spi_init);
+module_exit(mpc83xx_spi_exit);
+
+MODULE_AUTHOR("Kumar Gala");
+MODULE_DESCRIPTION("Simple MPC83xx SPI Driver");
+MODULE_LICENSE("GPL");


^ permalink raw reply related	[flat|nested] 18+ messages in thread

* Re: [PATCH][UPDATE] spi: Added spi master driver for Freescale MPC83xx SPI controller
  2006-04-10 17:38 ` [PATCH][UPDATE] " Kumar Gala
@ 2006-04-10 19:01   ` David Brownell
  2006-04-10 19:05     ` Kumar Gala
  0 siblings, 1 reply; 18+ messages in thread
From: David Brownell @ 2006-04-10 19:01 UTC (permalink / raw)
  To: Kumar Gala; +Cc: Greg KH, linux-kernel, spi-devel-general

On Monday 10 April 2006 10:38 am, Kumar Gala wrote:
> This driver supports the SPI controller on the MPC83xx SoC devices from
> Freescale.  Note, this driver supports only the simple shift register SPI
> controller and not the descriptor based CPM or QUICCEngine SPI controller.
> 
> Signed-off-by: Kumar Gala <galak@kernel.crashing.org>

Looks much better.  It could be improved further, but this doesn't
have enough glitches that I'd object.  However, I think you may need
to re-diff against the MM tree (or at least Greg's patches) since
the Makefile and Kconfig updates will conflict with pxa2xx_spi.

Signed-off-by: David Brownell <dbrownell@users.sourceforge.net>

> 
> ---
> commit 1e01024d79c1805e880d8863e03b6db91fc2dd25
> tree 21744404d18abbee7bc8387bd74ec018e413fab8
> parent bc33ba02f8414e91a3b2afa877be2c54d6fce564
> author Kumar Gala <galak@kernel.crashing.org> Mon, 10 Apr 2006 12:38:11 -0500
> committer Kumar Gala <galak@kernel.crashing.org> Mon, 10 Apr 2006 12:38:11 -0500
> 
>  drivers/spi/Kconfig       |   10 +
>  drivers/spi/Makefile      |    1 
>  drivers/spi/spi_mpc83xx.c |  488 +++++++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 499 insertions(+), 0 deletions(-)
> 
> diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
> index 7a75fae..af937bc 100644
> --- a/drivers/spi/Kconfig
> +++ b/drivers/spi/Kconfig
> @@ -75,6 +75,16 @@ config SPI_BUTTERFLY
>  	  inexpensive battery powered microcontroller evaluation board.
>  	  This same cable can be used to flash new firmware.
>  
> +config SPI_MPC83xx
> +	tristate "Freescale MPC83xx SPI controller"
> +	depends on SPI_MASTER && PPC_83xx && EXPERIMENTAL
> +	select SPI_BITBANG
> +	help
> +	  This enables using the Freescale MPC83xx SPI controller in master mode.
> +	  Note, this driver uniquely supports the SPI controller on the MPC83xx
> +	  family of PowerPC processors.  The MPC83xx uses a simple set of shift
> +	  registers for data (opposed to the CPM based descriptor model).
> +
>  #
>  # Add new SPI master controllers in alphabetical order above this line
>  #
> diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
> index c2c87e8..502ac0b 100644
> --- a/drivers/spi/Makefile
> +++ b/drivers/spi/Makefile
> @@ -13,6 +13,7 @@ obj-$(CONFIG_SPI_MASTER)		+= spi.o
>  # SPI master controller drivers (bus)
>  obj-$(CONFIG_SPI_BITBANG)		+= spi_bitbang.o
>  obj-$(CONFIG_SPI_BUTTERFLY)		+= spi_butterfly.o
> +obj-$(CONFIG_SPI_MPC83xx)		+= spi_mpc83xx.o
>  # 	... add above this line ...
>  
>  # SPI protocol drivers (device/link on bus)
> diff --git a/drivers/spi/spi_mpc83xx.c b/drivers/spi/spi_mpc83xx.c
> new file mode 100644
> index 0000000..a5ecdec
> --- /dev/null
> +++ b/drivers/spi/spi_mpc83xx.c
> @@ -0,0 +1,488 @@
> +/*
> + * MPC83xx SPI controller driver.
> + *
> + * Maintainer: Kumar Gala
> + *
> + * Copyright (C) 2006 Polycom, Inc.
> + *
> + * This program is free software; you can redistribute  it and/or modify it
> + * under  the terms of  the GNU General  Public License as published by the
> + * Free Software Foundation;  either version 2 of the  License, or (at your
> + * option) any later version.
> + */
> +#include <linux/module.h>
> +#include <linux/init.h>
> +#include <linux/types.h>
> +#include <linux/kernel.h>
> +#include <linux/completion.h>
> +#include <linux/interrupt.h>
> +#include <linux/delay.h>
> +#include <linux/irq.h>
> +#include <linux/device.h>
> +#include <linux/spi/spi.h>
> +#include <linux/spi/spi_bitbang.h>
> +#include <linux/platform_device.h>
> +#include <linux/fsl_devices.h>
> +
> +#include <asm/irq.h>
> +#include <asm/io.h>
> +
> +/* SPI Controller registers */
> +struct mpc83xx_spi_reg {
> +	u8 res1[0x20];
> +	__be32 mode;
> +	__be32 event;
> +	__be32 mask;
> +	__be32 command;
> +	__be32 transmit;
> +	__be32 receive;
> +};
> +
> +/* SPI Controller mode register definitions */
> +#define	SPMODE_CI_INACTIVEHIGH	(1 << 29)
> +#define	SPMODE_CP_BEGIN_EDGECLK	(1 << 28)
> +#define	SPMODE_DIV16		(1 << 27)
> +#define	SPMODE_REV		(1 << 26)
> +#define	SPMODE_MS		(1 << 25)
> +#define	SPMODE_ENABLE		(1 << 24)
> +#define	SPMODE_LEN(x)		((x) << 20)
> +#define	SPMODE_PM(x)		((x) << 16)
> +
> +/*
> + * Default for SPI Mode:
> + * 	SPI MODE 0 (inactive low, phase middle, MSB, 8-bit length, slow clk
> + */
> +#define	SPMODE_INIT_VAL (SPMODE_CI_INACTIVEHIGH | SPMODE_DIV16 | SPMODE_REV | \
> +			 SPMODE_MS | SPMODE_LEN(7) | SPMODE_PM(0xf))
> +
> +/* SPIE register values */
> +#define	SPIE_NE		0x00000200	/* Not empty */
> +#define	SPIE_NF		0x00000100	/* Not full */
> +
> +/* SPIM register values */
> +#define	SPIM_NE		0x00000200	/* Not empty */
> +#define	SPIM_NF		0x00000100	/* Not full */
> +
> +/* SPI Controller driver's private data. */
> +struct mpc83xx_spi {
> +	/* bitbang has to be first */
> +	struct spi_bitbang bitbang;
> +	struct completion done;
> +
> +	struct mpc83xx_spi_reg __iomem *base;
> +
> +	/* rx & tx bufs from the spi_transfer */
> +	const void *tx;
> +	void *rx;
> +
> +	/* functions to deal with different sized buffers */
> +	void (*get_rx) (u32 rx_data, struct mpc83xx_spi *);
> +	u32(*get_tx) (struct mpc83xx_spi *);
> +
> +	unsigned int count;
> +	u32 irq;
> +
> +	unsigned nsecs;		/* (clock cycle time)/2 */
> +
> +	u32 sysclk;
> +	void (*activate_cs) (u8 cs, u8 polarity);
> +	void (*deactivate_cs) (u8 cs, u8 polarity);
> +};
> +
> +static inline void mpc83xx_spi_write_reg(__be32 __iomem * reg, u32 val)
> +{
> +	out_be32(reg, val);
> +}
> +
> +static inline u32 mpc83xx_spi_read_reg(__be32 __iomem * reg)
> +{
> +	return in_be32(reg);
> +}
> +
> +#define MPC83XX_SPI_RX_BUF(type) 					  \
> +void mpc83xx_spi_rx_buf_##type(u32 data, struct mpc83xx_spi *mpc83xx_spi) \
> +{									  \
> +	type * rx = mpc83xx_spi->rx;					  \
> +	*rx++ = (type)data;						  \
> +	mpc83xx_spi->rx = rx;						  \
> +}
> +
> +#define MPC83XX_SPI_TX_BUF(type)				\
> +u32 mpc83xx_spi_tx_buf_##type(struct mpc83xx_spi *mpc83xx_spi)	\
> +{								\
> +	u32 data;						\
> +	const type * tx = mpc83xx_spi->tx;			\
> +	data = *tx++;						\
> +	mpc83xx_spi->tx = tx;					\
> +	return data;						\
> +}
> +
> +MPC83XX_SPI_RX_BUF(u8)
> +MPC83XX_SPI_RX_BUF(u16)
> +MPC83XX_SPI_RX_BUF(u32)
> +MPC83XX_SPI_TX_BUF(u8)
> +MPC83XX_SPI_TX_BUF(u16)
> +MPC83XX_SPI_TX_BUF(u32)
> +
> +static void mpc83xx_spi_chipselect(struct spi_device *spi, int value)
> +{
> +	struct mpc83xx_spi *mpc83xx_spi;
> +	u8 pol = spi->mode & SPI_CS_HIGH ? 1 : 0;
> +
> +	mpc83xx_spi = spi_master_get_devdata(spi->master);
> +
> +	if (value == BITBANG_CS_INACTIVE) {
> +		if (mpc83xx_spi->deactivate_cs)
> +			mpc83xx_spi->deactivate_cs(spi->chip_select, pol);
> +	}
> +
> +	if (value == BITBANG_CS_ACTIVE) {
> +		u32 regval = mpc83xx_spi_read_reg(&mpc83xx_spi->base->mode);
> +		u32 len = spi->bits_per_word;
> +		if (len == 32)
> +			len = 0;
> +		else
> +			len = len - 1;
> +
> +		/* mask out bits we are going to set */
> +		regval &= ~0x38ff0000;
> +
> +		if (spi->mode & SPI_CPHA)
> +			regval |= SPMODE_CP_BEGIN_EDGECLK;
> +		if (spi->mode & SPI_CPOL)
> +			regval |= SPMODE_CI_INACTIVEHIGH;
> +
> +		regval |= SPMODE_LEN(len);
> +
> +		if ((mpc83xx_spi->sysclk / spi->max_speed_hz) >= 64) {
> +			u8 pm = mpc83xx_spi->sysclk / (spi->max_speed_hz * 64);
> +			regval |= SPMODE_PM(pm) | SPMODE_DIV16;
> +		} else {
> +			u8 pm = mpc83xx_spi->sysclk / (spi->max_speed_hz * 4);
> +			regval |= SPMODE_PM(pm);
> +		}
> +
> +		mpc83xx_spi_write_reg(&mpc83xx_spi->base->mode, regval);
> +		if (mpc83xx_spi->activate_cs)
> +			mpc83xx_spi->activate_cs(spi->chip_select, pol);
> +	}
> +}
> +
> +static
> +int mpc83xx_spi_setup_transfer(struct spi_device *spi, struct spi_transfer *t)
> +{
> +	struct mpc83xx_spi *mpc83xx_spi;
> +	u32 regval;
> +	u8 bits_per_word;
> +	u32 hz;
> +
> +	mpc83xx_spi = spi_master_get_devdata(spi->master);
> +
> +	if (t) {
> +		bits_per_word = t->bits_per_word;
> +		hz = t->speed_hz;
> +	} else {
> +		bits_per_word = 0;
> +		hz = 0;
> +	}
> +
> +	/* spi_transfer level calls that work per-word */
> +	if (!bits_per_word)
> +		bits_per_word = spi->bits_per_word;
> +
> +	/* Make sure its a bit width we support [4..16, 32] */
> +	if ((bits_per_word < 4)
> +	    || ((bits_per_word > 16) && (bits_per_word != 32)))
> +		return -EINVAL;
> +
> +	if (bits_per_word <= 8) {
> +		mpc83xx_spi->get_rx = mpc83xx_spi_rx_buf_u8;
> +		mpc83xx_spi->get_tx = mpc83xx_spi_tx_buf_u8;
> +	} else {
> +		if (bits_per_word <= 16) {
> +			mpc83xx_spi->get_rx = mpc83xx_spi_rx_buf_u16;
> +			mpc83xx_spi->get_tx = mpc83xx_spi_tx_buf_u16;
> +		} else {
> +			if (bits_per_word <= 32) {
> +				mpc83xx_spi->get_rx = mpc83xx_spi_rx_buf_u32;
> +				mpc83xx_spi->get_tx = mpc83xx_spi_tx_buf_u32;
> +			} else {
> +				return -EINVAL;
> +			}
> +		}
> +	}
> +
> +	/* nsecs = (clock period)/2 */
> +	if (!hz)
> +		hz = spi->max_speed_hz;
> +	mpc83xx_spi->nsecs = (1000000000 / 2) / hz;
> +	if (mpc83xx_spi->nsecs > MAX_UDELAY_MS * 1000)
> +		return -EINVAL;
> +
> +	if (bits_per_word == 32)
> +		bits_per_word = 0;
> +	else
> +		bits_per_word = bits_per_word - 1;
> +
> +	regval = mpc83xx_spi_read_reg(&mpc83xx_spi->base->mode);
> +
> +	/* Mask out bits_per_wordgth */
> +	regval &= 0xff0fffff;
> +	regval |= SPMODE_LEN(bits_per_word);
> +
> +	mpc83xx_spi_write_reg(&mpc83xx_spi->base->mode, regval);
> +
> +	return 0;
> +}
> +
> +static int mpc83xx_spi_setup(struct spi_device *spi)
> +{
> +	struct spi_bitbang *bitbang;
> +	struct mpc83xx_spi *mpc83xx_spi;
> +	int retval;
> +
> +	if (!spi->max_speed_hz)
> +		return -EINVAL;
> +
> +	bitbang = spi_master_get_devdata(spi->master);
> +	mpc83xx_spi = spi_master_get_devdata(spi->master);
> +
> +	if (!spi->bits_per_word)
> +		spi->bits_per_word = 8;
> +
> +	retval = mpc83xx_spi_setup_transfer(spi, NULL);
> +	if (retval < 0)
> +		return retval;
> +
> +	dev_dbg(&spi->dev, "%s, mode %d, %u bits/w, %u nsec\n",
> +		__FUNCTION__, spi->mode & (SPI_CPOL | SPI_CPHA),
> +		spi->bits_per_word, 2 * mpc83xx_spi->nsecs);
> +
> +	/* NOTE we _need_ to call chipselect() early, ideally with adapter
> +	 * setup, unless the hardware defaults cooperate to avoid confusion
> +	 * between normal (active low) and inverted chipselects.
> +	 */
> +
> +	/* deselect chip (low or high) */
> +	spin_lock(&bitbang->lock);
> +	if (!bitbang->busy) {
> +		bitbang->chipselect(spi, BITBANG_CS_INACTIVE);
> +		ndelay(mpc83xx_spi->nsecs);
> +	}
> +	spin_unlock(&bitbang->lock);
> +
> +	return 0;
> +}
> +
> +static int mpc83xx_spi_bufs(struct spi_device *spi, struct spi_transfer *t)
> +{
> +	struct mpc83xx_spi *mpc83xx_spi;
> +	u32 word;
> +
> +	mpc83xx_spi = spi_master_get_devdata(spi->master);
> +
> +	mpc83xx_spi->tx = t->tx_buf;
> +	mpc83xx_spi->rx = t->rx_buf;
> +	mpc83xx_spi->count = t->len;
> +	INIT_COMPLETION(mpc83xx_spi->done);
> +
> +	/* enable rx ints */
> +	mpc83xx_spi_write_reg(&mpc83xx_spi->base->mask, SPIM_NE);
> +
> +	/* transmit word */
> +	word = mpc83xx_spi->get_tx(mpc83xx_spi);
> +	mpc83xx_spi_write_reg(&mpc83xx_spi->base->transmit, word);
> +
> +	wait_for_completion(&mpc83xx_spi->done);
> +
> +	/* disable rx ints */
> +	mpc83xx_spi_write_reg(&mpc83xx_spi->base->mask, 0);
> +
> +	return t->len - mpc83xx_spi->count;
> +}
> +
> +irqreturn_t mpc83xx_spi_irq(s32 irq, void *context_data,
> +			    struct pt_regs * ptregs)
> +{
> +	struct mpc83xx_spi *mpc83xx_spi = context_data;
> +	u32 event;
> +	irqreturn_t ret = IRQ_NONE;
> +
> +	/* Get interrupt events(tx/rx) */
> +	event = mpc83xx_spi_read_reg(&mpc83xx_spi->base->event);
> +
> +	/* We need handle RX first */
> +	if (event & SPIE_NE) {
> +		u32 rx_data = mpc83xx_spi_read_reg(&mpc83xx_spi->base->receive);
> +
> +		if (mpc83xx_spi->rx)
> +			mpc83xx_spi->get_rx(rx_data, mpc83xx_spi);
> +
> +		ret = IRQ_HANDLED;
> +	}
> +
> +	if ((event & SPIE_NF) == 0)
> +		/* spin until TX is done */
> +		while (((event =
> +			 mpc83xx_spi_read_reg(&mpc83xx_spi->base->event)) &
> +						SPIE_NF) == 0)
> +			 ;
> +
> +	mpc83xx_spi->count -= 1;
> +	if (mpc83xx_spi->count) {
> +		if (mpc83xx_spi->tx) {
> +			u32 word = mpc83xx_spi->get_tx(mpc83xx_spi);
> +			mpc83xx_spi_write_reg(&mpc83xx_spi->base->transmit,
> +					      word);
> +		}
> +	} else {
> +		complete(&mpc83xx_spi->done);
> +	}
> +
> +	/* Clear the events */
> +	mpc83xx_spi_write_reg(&mpc83xx_spi->base->event, event);
> +
> +	return ret;
> +}
> +
> +static int __devinit mpc83xx_spi_probe(struct platform_device *dev)
> +{
> +	struct spi_master *master;
> +	struct mpc83xx_spi *mpc83xx_spi;
> +	struct fsl_spi_platform_data *pdata;
> +	struct resource *r;
> +	u32 regval;
> +	int ret = 0;
> +
> +	/* Get resources(memory, IRQ) associated with the device */
> +	master = spi_alloc_master(&dev->dev, sizeof(struct mpc83xx_spi));
> +
> +	if (master == NULL) {
> +		ret = -ENOMEM;
> +		goto err;
> +	}
> +
> +	platform_set_drvdata(dev, master);
> +	pdata = dev->dev.platform_data;
> +
> +	if (pdata == NULL) {
> +		ret = -ENODEV;
> +		goto free_master;
> +	}
> +
> +	r = platform_get_resource(dev, IORESOURCE_MEM, 0);
> +	if (r == NULL) {
> +		ret = -ENODEV;
> +		goto free_master;
> +	}
> +
> +	mpc83xx_spi = spi_master_get_devdata(master);
> +	mpc83xx_spi->bitbang.master = spi_master_get(master);
> +	mpc83xx_spi->bitbang.chipselect = mpc83xx_spi_chipselect;
> +	mpc83xx_spi->bitbang.setup_transfer = mpc83xx_spi_setup_transfer;
> +	mpc83xx_spi->bitbang.txrx_bufs = mpc83xx_spi_bufs;
> +	mpc83xx_spi->sysclk = pdata->sysclk;
> +	mpc83xx_spi->activate_cs = pdata->activate_cs;
> +	mpc83xx_spi->deactivate_cs = pdata->deactivate_cs;
> +	mpc83xx_spi->get_rx = mpc83xx_spi_rx_buf_u8;
> +	mpc83xx_spi->get_tx = mpc83xx_spi_tx_buf_u8;
> +
> +	mpc83xx_spi->bitbang.master->setup = mpc83xx_spi_setup;
> +	init_completion(&mpc83xx_spi->done);
> +
> +	mpc83xx_spi->base = ioremap(r->start, r->end - r->start + 1);
> +	if (mpc83xx_spi->base == NULL) {
> +		ret = -ENOMEM;
> +		goto put_master;
> +	}
> +
> +	mpc83xx_spi->irq = platform_get_irq(dev, 0);
> +
> +	if (mpc83xx_spi->irq < 0) {
> +		ret = -ENXIO;
> +		goto unmap_io;
> +	}
> +
> +	/* Register for SPI Interrupt */
> +	ret = request_irq(mpc83xx_spi->irq, mpc83xx_spi_irq,
> +			  0, "mpc83xx_spi", mpc83xx_spi);
> +
> +	if (ret != 0)
> +		goto unmap_io;
> +
> +	master->bus_num = pdata->bus_num;
> +	master->num_chipselect = pdata->max_chipselect;
> +
> +	/* SPI controller initializations */
> +	mpc83xx_spi_write_reg(&mpc83xx_spi->base->mode, 0);
> +	mpc83xx_spi_write_reg(&mpc83xx_spi->base->mask, 0);
> +	mpc83xx_spi_write_reg(&mpc83xx_spi->base->command, 0);
> +	mpc83xx_spi_write_reg(&mpc83xx_spi->base->event, 0xffffffff);
> +
> +	/* Enable SPI interface */
> +	regval = pdata->initial_spmode | SPMODE_INIT_VAL | SPMODE_ENABLE;
> +	mpc83xx_spi_write_reg(&mpc83xx_spi->base->mode, regval);
> +
> +	ret = spi_bitbang_start(&mpc83xx_spi->bitbang);
> +
> +	if (ret != 0)
> +		goto free_irq;
> +
> +	printk(KERN_INFO
> +	       "%s: MPC83xx SPI Controller driver at 0x%p (irq = %d)\n",
> +	       dev->dev.bus_id, mpc83xx_spi->base, mpc83xx_spi->irq);
> +
> +	return ret;
> +
> +free_irq:
> +	free_irq(mpc83xx_spi->irq, mpc83xx_spi);
> +unmap_io:
> +	iounmap(mpc83xx_spi->base);
> +put_master:
> +	spi_master_put(master);
> +free_master:
> +	kfree(master);
> +err:
> +	return ret;
> +}
> +
> +static int __devexit mpc83xx_spi_remove(struct platform_device *dev)
> +{
> +	struct mpc83xx_spi *mpc83xx_spi;
> +	struct spi_master *master;
> +
> +	master = platform_get_drvdata(dev);
> +	mpc83xx_spi = spi_master_get_devdata(master);
> +
> +	spi_bitbang_stop(&mpc83xx_spi->bitbang);
> +	free_irq(mpc83xx_spi->irq, mpc83xx_spi);
> +	iounmap(mpc83xx_spi->base);
> +	spi_master_put(mpc83xx_spi->bitbang.master);
> +
> +	return 0;
> +}
> +
> +static struct platform_driver mpc83xx_spi_driver = {
> +	.probe = mpc83xx_spi_probe,
> +	.remove = __devexit_p(mpc83xx_spi_remove),
> +	.driver = {
> +		   .name = "mpc83xx_spi",
> +	},
> +};
> +
> +static int __init mpc83xx_spi_init(void)
> +{
> +	return platform_driver_register(&mpc83xx_spi_driver);
> +}
> +
> +static void __exit mpc83xx_spi_exit(void)
> +{
> +	platform_driver_unregister(&mpc83xx_spi_driver);
> +}
> +
> +module_init(mpc83xx_spi_init);
> +module_exit(mpc83xx_spi_exit);
> +
> +MODULE_AUTHOR("Kumar Gala");
> +MODULE_DESCRIPTION("Simple MPC83xx SPI Driver");
> +MODULE_LICENSE("GPL");
> 

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [PATCH][UPDATE] spi: Added spi master driver for Freescale MPC83xx SPI controller
  2006-04-10 19:01   ` David Brownell
@ 2006-04-10 19:05     ` Kumar Gala
  2006-04-10 19:17       ` [PATCH][2.16.17-rc1-mm2] " Kumar Gala
  0 siblings, 1 reply; 18+ messages in thread
From: Kumar Gala @ 2006-04-10 19:05 UTC (permalink / raw)
  To: David Brownell; +Cc: Greg KH, linux-kernel, spi-devel-general


On Apr 10, 2006, at 2:01 PM, David Brownell wrote:

> On Monday 10 April 2006 10:38 am, Kumar Gala wrote:
>> This driver supports the SPI controller on the MPC83xx SoC devices  
>> from
>> Freescale.  Note, this driver supports only the simple shift  
>> register SPI
>> controller and not the descriptor based CPM or QUICCEngine SPI  
>> controller.
>>
>> Signed-off-by: Kumar Gala <galak@kernel.crashing.org>
>
> Looks much better.  It could be improved further, but this doesn't
> have enough glitches that I'd object.  However, I think you may need
> to re-diff against the MM tree (or at least Greg's patches) since
> the Makefile and Kconfig updates will conflict with pxa2xx_spi.
>
> Signed-off-by: David Brownell <dbrownell@users.sourceforge.net>

I'll re-diff against 2.6.17-rc1-mm2 and resend that patch.

What other glitches does this have?  (stuff to add to my todo list)

- k

>> commit 1e01024d79c1805e880d8863e03b6db91fc2dd25
>> tree 21744404d18abbee7bc8387bd74ec018e413fab8
>> parent bc33ba02f8414e91a3b2afa877be2c54d6fce564
>> author Kumar Gala <galak@kernel.crashing.org> Mon, 10 Apr 2006  
>> 12:38:11 -0500
>> committer Kumar Gala <galak@kernel.crashing.org> Mon, 10 Apr 2006  
>> 12:38:11 -0500
>>
>>  drivers/spi/Kconfig       |   10 +
>>  drivers/spi/Makefile      |    1
>>  drivers/spi/spi_mpc83xx.c |  488 +++++++++++++++++++++++++++++++++ 
>> ++++++++++++
>>  3 files changed, 499 insertions(+), 0 deletions(-)
>>
>> diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
>> index 7a75fae..af937bc 100644
>> --- a/drivers/spi/Kconfig
>> +++ b/drivers/spi/Kconfig
>> @@ -75,6 +75,16 @@ config SPI_BUTTERFLY
>>  	  inexpensive battery powered microcontroller evaluation board.
>>  	  This same cable can be used to flash new firmware.
>>
>> +config SPI_MPC83xx
>> +	tristate "Freescale MPC83xx SPI controller"
>> +	depends on SPI_MASTER && PPC_83xx && EXPERIMENTAL
>> +	select SPI_BITBANG
>> +	help
>> +	  This enables using the Freescale MPC83xx SPI controller in  
>> master mode.
>> +	  Note, this driver uniquely supports the SPI controller on the  
>> MPC83xx
>> +	  family of PowerPC processors.  The MPC83xx uses a simple set  
>> of shift
>> +	  registers for data (opposed to the CPM based descriptor model).
>> +
>>  #
>>  # Add new SPI master controllers in alphabetical order above this  
>> line
>>  #
>> diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
>> index c2c87e8..502ac0b 100644
>> --- a/drivers/spi/Makefile
>> +++ b/drivers/spi/Makefile
>> @@ -13,6 +13,7 @@ obj-$(CONFIG_SPI_MASTER)		+= spi.o
>>  # SPI master controller drivers (bus)
>>  obj-$(CONFIG_SPI_BITBANG)		+= spi_bitbang.o
>>  obj-$(CONFIG_SPI_BUTTERFLY)		+= spi_butterfly.o
>> +obj-$(CONFIG_SPI_MPC83xx)		+= spi_mpc83xx.o
>>  # 	... add above this line ...
>>
>>  # SPI protocol drivers (device/link on bus)
>> diff --git a/drivers/spi/spi_mpc83xx.c b/drivers/spi/spi_mpc83xx.c
>> new file mode 100644
>> index 0000000..a5ecdec
>> --- /dev/null
>> +++ b/drivers/spi/spi_mpc83xx.c
>> @@ -0,0 +1,488 @@
>> +/*
>> + * MPC83xx SPI controller driver.
>> + *
>> + * Maintainer: Kumar Gala
>> + *
>> + * Copyright (C) 2006 Polycom, Inc.
>> + *
>> + * This program is free software; you can redistribute  it and/or  
>> modify it
>> + * under  the terms of  the GNU General  Public License as  
>> published by the
>> + * Free Software Foundation;  either version 2 of the  License,  
>> or (at your
>> + * option) any later version.
>> + */
>> +#include <linux/module.h>
>> +#include <linux/init.h>
>> +#include <linux/types.h>
>> +#include <linux/kernel.h>
>> +#include <linux/completion.h>
>> +#include <linux/interrupt.h>
>> +#include <linux/delay.h>
>> +#include <linux/irq.h>
>> +#include <linux/device.h>
>> +#include <linux/spi/spi.h>
>> +#include <linux/spi/spi_bitbang.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/fsl_devices.h>
>> +
>> +#include <asm/irq.h>
>> +#include <asm/io.h>
>> +
>> +/* SPI Controller registers */
>> +struct mpc83xx_spi_reg {
>> +	u8 res1[0x20];
>> +	__be32 mode;
>> +	__be32 event;
>> +	__be32 mask;
>> +	__be32 command;
>> +	__be32 transmit;
>> +	__be32 receive;
>> +};
>> +
>> +/* SPI Controller mode register definitions */
>> +#define	SPMODE_CI_INACTIVEHIGH	(1 << 29)
>> +#define	SPMODE_CP_BEGIN_EDGECLK	(1 << 28)
>> +#define	SPMODE_DIV16		(1 << 27)
>> +#define	SPMODE_REV		(1 << 26)
>> +#define	SPMODE_MS		(1 << 25)
>> +#define	SPMODE_ENABLE		(1 << 24)
>> +#define	SPMODE_LEN(x)		((x) << 20)
>> +#define	SPMODE_PM(x)		((x) << 16)
>> +
>> +/*
>> + * Default for SPI Mode:
>> + * 	SPI MODE 0 (inactive low, phase middle, MSB, 8-bit length,  
>> slow clk
>> + */
>> +#define	SPMODE_INIT_VAL (SPMODE_CI_INACTIVEHIGH | SPMODE_DIV16 |  
>> SPMODE_REV | \
>> +			 SPMODE_MS | SPMODE_LEN(7) | SPMODE_PM(0xf))
>> +
>> +/* SPIE register values */
>> +#define	SPIE_NE		0x00000200	/* Not empty */
>> +#define	SPIE_NF		0x00000100	/* Not full */
>> +
>> +/* SPIM register values */
>> +#define	SPIM_NE		0x00000200	/* Not empty */
>> +#define	SPIM_NF		0x00000100	/* Not full */
>> +
>> +/* SPI Controller driver's private data. */
>> +struct mpc83xx_spi {
>> +	/* bitbang has to be first */
>> +	struct spi_bitbang bitbang;
>> +	struct completion done;
>> +
>> +	struct mpc83xx_spi_reg __iomem *base;
>> +
>> +	/* rx & tx bufs from the spi_transfer */
>> +	const void *tx;
>> +	void *rx;
>> +
>> +	/* functions to deal with different sized buffers */
>> +	void (*get_rx) (u32 rx_data, struct mpc83xx_spi *);
>> +	u32(*get_tx) (struct mpc83xx_spi *);
>> +
>> +	unsigned int count;
>> +	u32 irq;
>> +
>> +	unsigned nsecs;		/* (clock cycle time)/2 */
>> +
>> +	u32 sysclk;
>> +	void (*activate_cs) (u8 cs, u8 polarity);
>> +	void (*deactivate_cs) (u8 cs, u8 polarity);
>> +};
>> +
>> +static inline void mpc83xx_spi_write_reg(__be32 __iomem * reg,  
>> u32 val)
>> +{
>> +	out_be32(reg, val);
>> +}
>> +
>> +static inline u32 mpc83xx_spi_read_reg(__be32 __iomem * reg)
>> +{
>> +	return in_be32(reg);
>> +}
>> +
>> +#define MPC83XX_SPI_RX_BUF(type) 					  \
>> +void mpc83xx_spi_rx_buf_##type(u32 data, struct mpc83xx_spi  
>> *mpc83xx_spi) \
>> +{									  \
>> +	type * rx = mpc83xx_spi->rx;					  \
>> +	*rx++ = (type)data;						  \
>> +	mpc83xx_spi->rx = rx;						  \
>> +}
>> +
>> +#define MPC83XX_SPI_TX_BUF(type)				\
>> +u32 mpc83xx_spi_tx_buf_##type(struct mpc83xx_spi *mpc83xx_spi)	\
>> +{								\
>> +	u32 data;						\
>> +	const type * tx = mpc83xx_spi->tx;			\
>> +	data = *tx++;						\
>> +	mpc83xx_spi->tx = tx;					\
>> +	return data;						\
>> +}
>> +
>> +MPC83XX_SPI_RX_BUF(u8)
>> +MPC83XX_SPI_RX_BUF(u16)
>> +MPC83XX_SPI_RX_BUF(u32)
>> +MPC83XX_SPI_TX_BUF(u8)
>> +MPC83XX_SPI_TX_BUF(u16)
>> +MPC83XX_SPI_TX_BUF(u32)
>> +
>> +static void mpc83xx_spi_chipselect(struct spi_device *spi, int  
>> value)
>> +{
>> +	struct mpc83xx_spi *mpc83xx_spi;
>> +	u8 pol = spi->mode & SPI_CS_HIGH ? 1 : 0;
>> +
>> +	mpc83xx_spi = spi_master_get_devdata(spi->master);
>> +
>> +	if (value == BITBANG_CS_INACTIVE) {
>> +		if (mpc83xx_spi->deactivate_cs)
>> +			mpc83xx_spi->deactivate_cs(spi->chip_select, pol);
>> +	}
>> +
>> +	if (value == BITBANG_CS_ACTIVE) {
>> +		u32 regval = mpc83xx_spi_read_reg(&mpc83xx_spi->base->mode);
>> +		u32 len = spi->bits_per_word;
>> +		if (len == 32)
>> +			len = 0;
>> +		else
>> +			len = len - 1;
>> +
>> +		/* mask out bits we are going to set */
>> +		regval &= ~0x38ff0000;
>> +
>> +		if (spi->mode & SPI_CPHA)
>> +			regval |= SPMODE_CP_BEGIN_EDGECLK;
>> +		if (spi->mode & SPI_CPOL)
>> +			regval |= SPMODE_CI_INACTIVEHIGH;
>> +
>> +		regval |= SPMODE_LEN(len);
>> +
>> +		if ((mpc83xx_spi->sysclk / spi->max_speed_hz) >= 64) {
>> +			u8 pm = mpc83xx_spi->sysclk / (spi->max_speed_hz * 64);
>> +			regval |= SPMODE_PM(pm) | SPMODE_DIV16;
>> +		} else {
>> +			u8 pm = mpc83xx_spi->sysclk / (spi->max_speed_hz * 4);
>> +			regval |= SPMODE_PM(pm);
>> +		}
>> +
>> +		mpc83xx_spi_write_reg(&mpc83xx_spi->base->mode, regval);
>> +		if (mpc83xx_spi->activate_cs)
>> +			mpc83xx_spi->activate_cs(spi->chip_select, pol);
>> +	}
>> +}
>> +
>> +static
>> +int mpc83xx_spi_setup_transfer(struct spi_device *spi, struct  
>> spi_transfer *t)
>> +{
>> +	struct mpc83xx_spi *mpc83xx_spi;
>> +	u32 regval;
>> +	u8 bits_per_word;
>> +	u32 hz;
>> +
>> +	mpc83xx_spi = spi_master_get_devdata(spi->master);
>> +
>> +	if (t) {
>> +		bits_per_word = t->bits_per_word;
>> +		hz = t->speed_hz;
>> +	} else {
>> +		bits_per_word = 0;
>> +		hz = 0;
>> +	}
>> +
>> +	/* spi_transfer level calls that work per-word */
>> +	if (!bits_per_word)
>> +		bits_per_word = spi->bits_per_word;
>> +
>> +	/* Make sure its a bit width we support [4..16, 32] */
>> +	if ((bits_per_word < 4)
>> +	    || ((bits_per_word > 16) && (bits_per_word != 32)))
>> +		return -EINVAL;
>> +
>> +	if (bits_per_word <= 8) {
>> +		mpc83xx_spi->get_rx = mpc83xx_spi_rx_buf_u8;
>> +		mpc83xx_spi->get_tx = mpc83xx_spi_tx_buf_u8;
>> +	} else {
>> +		if (bits_per_word <= 16) {
>> +			mpc83xx_spi->get_rx = mpc83xx_spi_rx_buf_u16;
>> +			mpc83xx_spi->get_tx = mpc83xx_spi_tx_buf_u16;
>> +		} else {
>> +			if (bits_per_word <= 32) {
>> +				mpc83xx_spi->get_rx = mpc83xx_spi_rx_buf_u32;
>> +				mpc83xx_spi->get_tx = mpc83xx_spi_tx_buf_u32;
>> +			} else {
>> +				return -EINVAL;
>> +			}
>> +		}
>> +	}
>> +
>> +	/* nsecs = (clock period)/2 */
>> +	if (!hz)
>> +		hz = spi->max_speed_hz;
>> +	mpc83xx_spi->nsecs = (1000000000 / 2) / hz;
>> +	if (mpc83xx_spi->nsecs > MAX_UDELAY_MS * 1000)
>> +		return -EINVAL;
>> +
>> +	if (bits_per_word == 32)
>> +		bits_per_word = 0;
>> +	else
>> +		bits_per_word = bits_per_word - 1;
>> +
>> +	regval = mpc83xx_spi_read_reg(&mpc83xx_spi->base->mode);
>> +
>> +	/* Mask out bits_per_wordgth */
>> +	regval &= 0xff0fffff;
>> +	regval |= SPMODE_LEN(bits_per_word);
>> +
>> +	mpc83xx_spi_write_reg(&mpc83xx_spi->base->mode, regval);
>> +
>> +	return 0;
>> +}
>> +
>> +static int mpc83xx_spi_setup(struct spi_device *spi)
>> +{
>> +	struct spi_bitbang *bitbang;
>> +	struct mpc83xx_spi *mpc83xx_spi;
>> +	int retval;
>> +
>> +	if (!spi->max_speed_hz)
>> +		return -EINVAL;
>> +
>> +	bitbang = spi_master_get_devdata(spi->master);
>> +	mpc83xx_spi = spi_master_get_devdata(spi->master);
>> +
>> +	if (!spi->bits_per_word)
>> +		spi->bits_per_word = 8;
>> +
>> +	retval = mpc83xx_spi_setup_transfer(spi, NULL);
>> +	if (retval < 0)
>> +		return retval;
>> +
>> +	dev_dbg(&spi->dev, "%s, mode %d, %u bits/w, %u nsec\n",
>> +		__FUNCTION__, spi->mode & (SPI_CPOL | SPI_CPHA),
>> +		spi->bits_per_word, 2 * mpc83xx_spi->nsecs);
>> +
>> +	/* NOTE we _need_ to call chipselect() early, ideally with adapter
>> +	 * setup, unless the hardware defaults cooperate to avoid confusion
>> +	 * between normal (active low) and inverted chipselects.
>> +	 */
>> +
>> +	/* deselect chip (low or high) */
>> +	spin_lock(&bitbang->lock);
>> +	if (!bitbang->busy) {
>> +		bitbang->chipselect(spi, BITBANG_CS_INACTIVE);
>> +		ndelay(mpc83xx_spi->nsecs);
>> +	}
>> +	spin_unlock(&bitbang->lock);
>> +
>> +	return 0;
>> +}
>> +
>> +static int mpc83xx_spi_bufs(struct spi_device *spi, struct  
>> spi_transfer *t)
>> +{
>> +	struct mpc83xx_spi *mpc83xx_spi;
>> +	u32 word;
>> +
>> +	mpc83xx_spi = spi_master_get_devdata(spi->master);
>> +
>> +	mpc83xx_spi->tx = t->tx_buf;
>> +	mpc83xx_spi->rx = t->rx_buf;
>> +	mpc83xx_spi->count = t->len;
>> +	INIT_COMPLETION(mpc83xx_spi->done);
>> +
>> +	/* enable rx ints */
>> +	mpc83xx_spi_write_reg(&mpc83xx_spi->base->mask, SPIM_NE);
>> +
>> +	/* transmit word */
>> +	word = mpc83xx_spi->get_tx(mpc83xx_spi);
>> +	mpc83xx_spi_write_reg(&mpc83xx_spi->base->transmit, word);
>> +
>> +	wait_for_completion(&mpc83xx_spi->done);
>> +
>> +	/* disable rx ints */
>> +	mpc83xx_spi_write_reg(&mpc83xx_spi->base->mask, 0);
>> +
>> +	return t->len - mpc83xx_spi->count;
>> +}
>> +
>> +irqreturn_t mpc83xx_spi_irq(s32 irq, void *context_data,
>> +			    struct pt_regs * ptregs)
>> +{
>> +	struct mpc83xx_spi *mpc83xx_spi = context_data;
>> +	u32 event;
>> +	irqreturn_t ret = IRQ_NONE;
>> +
>> +	/* Get interrupt events(tx/rx) */
>> +	event = mpc83xx_spi_read_reg(&mpc83xx_spi->base->event);
>> +
>> +	/* We need handle RX first */
>> +	if (event & SPIE_NE) {
>> +		u32 rx_data = mpc83xx_spi_read_reg(&mpc83xx_spi->base->receive);
>> +
>> +		if (mpc83xx_spi->rx)
>> +			mpc83xx_spi->get_rx(rx_data, mpc83xx_spi);
>> +
>> +		ret = IRQ_HANDLED;
>> +	}
>> +
>> +	if ((event & SPIE_NF) == 0)
>> +		/* spin until TX is done */
>> +		while (((event =
>> +			 mpc83xx_spi_read_reg(&mpc83xx_spi->base->event)) &
>> +						SPIE_NF) == 0)
>> +			 ;
>> +
>> +	mpc83xx_spi->count -= 1;
>> +	if (mpc83xx_spi->count) {
>> +		if (mpc83xx_spi->tx) {
>> +			u32 word = mpc83xx_spi->get_tx(mpc83xx_spi);
>> +			mpc83xx_spi_write_reg(&mpc83xx_spi->base->transmit,
>> +					      word);
>> +		}
>> +	} else {
>> +		complete(&mpc83xx_spi->done);
>> +	}
>> +
>> +	/* Clear the events */
>> +	mpc83xx_spi_write_reg(&mpc83xx_spi->base->event, event);
>> +
>> +	return ret;
>> +}
>> +
>> +static int __devinit mpc83xx_spi_probe(struct platform_device *dev)
>> +{
>> +	struct spi_master *master;
>> +	struct mpc83xx_spi *mpc83xx_spi;
>> +	struct fsl_spi_platform_data *pdata;
>> +	struct resource *r;
>> +	u32 regval;
>> +	int ret = 0;
>> +
>> +	/* Get resources(memory, IRQ) associated with the device */
>> +	master = spi_alloc_master(&dev->dev, sizeof(struct mpc83xx_spi));
>> +
>> +	if (master == NULL) {
>> +		ret = -ENOMEM;
>> +		goto err;
>> +	}
>> +
>> +	platform_set_drvdata(dev, master);
>> +	pdata = dev->dev.platform_data;
>> +
>> +	if (pdata == NULL) {
>> +		ret = -ENODEV;
>> +		goto free_master;
>> +	}
>> +
>> +	r = platform_get_resource(dev, IORESOURCE_MEM, 0);
>> +	if (r == NULL) {
>> +		ret = -ENODEV;
>> +		goto free_master;
>> +	}
>> +
>> +	mpc83xx_spi = spi_master_get_devdata(master);
>> +	mpc83xx_spi->bitbang.master = spi_master_get(master);
>> +	mpc83xx_spi->bitbang.chipselect = mpc83xx_spi_chipselect;
>> +	mpc83xx_spi->bitbang.setup_transfer = mpc83xx_spi_setup_transfer;
>> +	mpc83xx_spi->bitbang.txrx_bufs = mpc83xx_spi_bufs;
>> +	mpc83xx_spi->sysclk = pdata->sysclk;
>> +	mpc83xx_spi->activate_cs = pdata->activate_cs;
>> +	mpc83xx_spi->deactivate_cs = pdata->deactivate_cs;
>> +	mpc83xx_spi->get_rx = mpc83xx_spi_rx_buf_u8;
>> +	mpc83xx_spi->get_tx = mpc83xx_spi_tx_buf_u8;
>> +
>> +	mpc83xx_spi->bitbang.master->setup = mpc83xx_spi_setup;
>> +	init_completion(&mpc83xx_spi->done);
>> +
>> +	mpc83xx_spi->base = ioremap(r->start, r->end - r->start + 1);
>> +	if (mpc83xx_spi->base == NULL) {
>> +		ret = -ENOMEM;
>> +		goto put_master;
>> +	}
>> +
>> +	mpc83xx_spi->irq = platform_get_irq(dev, 0);
>> +
>> +	if (mpc83xx_spi->irq < 0) {
>> +		ret = -ENXIO;
>> +		goto unmap_io;
>> +	}
>> +
>> +	/* Register for SPI Interrupt */
>> +	ret = request_irq(mpc83xx_spi->irq, mpc83xx_spi_irq,
>> +			  0, "mpc83xx_spi", mpc83xx_spi);
>> +
>> +	if (ret != 0)
>> +		goto unmap_io;
>> +
>> +	master->bus_num = pdata->bus_num;
>> +	master->num_chipselect = pdata->max_chipselect;
>> +
>> +	/* SPI controller initializations */
>> +	mpc83xx_spi_write_reg(&mpc83xx_spi->base->mode, 0);
>> +	mpc83xx_spi_write_reg(&mpc83xx_spi->base->mask, 0);
>> +	mpc83xx_spi_write_reg(&mpc83xx_spi->base->command, 0);
>> +	mpc83xx_spi_write_reg(&mpc83xx_spi->base->event, 0xffffffff);
>> +
>> +	/* Enable SPI interface */
>> +	regval = pdata->initial_spmode | SPMODE_INIT_VAL | SPMODE_ENABLE;
>> +	mpc83xx_spi_write_reg(&mpc83xx_spi->base->mode, regval);
>> +
>> +	ret = spi_bitbang_start(&mpc83xx_spi->bitbang);
>> +
>> +	if (ret != 0)
>> +		goto free_irq;
>> +
>> +	printk(KERN_INFO
>> +	       "%s: MPC83xx SPI Controller driver at 0x%p (irq = %d)\n",
>> +	       dev->dev.bus_id, mpc83xx_spi->base, mpc83xx_spi->irq);
>> +
>> +	return ret;
>> +
>> +free_irq:
>> +	free_irq(mpc83xx_spi->irq, mpc83xx_spi);
>> +unmap_io:
>> +	iounmap(mpc83xx_spi->base);
>> +put_master:
>> +	spi_master_put(master);
>> +free_master:
>> +	kfree(master);
>> +err:
>> +	return ret;
>> +}
>> +
>> +static int __devexit mpc83xx_spi_remove(struct platform_device *dev)
>> +{
>> +	struct mpc83xx_spi *mpc83xx_spi;
>> +	struct spi_master *master;
>> +
>> +	master = platform_get_drvdata(dev);
>> +	mpc83xx_spi = spi_master_get_devdata(master);
>> +
>> +	spi_bitbang_stop(&mpc83xx_spi->bitbang);
>> +	free_irq(mpc83xx_spi->irq, mpc83xx_spi);
>> +	iounmap(mpc83xx_spi->base);
>> +	spi_master_put(mpc83xx_spi->bitbang.master);
>> +
>> +	return 0;
>> +}
>> +
>> +static struct platform_driver mpc83xx_spi_driver = {
>> +	.probe = mpc83xx_spi_probe,
>> +	.remove = __devexit_p(mpc83xx_spi_remove),
>> +	.driver = {
>> +		   .name = "mpc83xx_spi",
>> +	},
>> +};
>> +
>> +static int __init mpc83xx_spi_init(void)
>> +{
>> +	return platform_driver_register(&mpc83xx_spi_driver);
>> +}
>> +
>> +static void __exit mpc83xx_spi_exit(void)
>> +{
>> +	platform_driver_unregister(&mpc83xx_spi_driver);
>> +}
>> +
>> +module_init(mpc83xx_spi_init);
>> +module_exit(mpc83xx_spi_exit);
>> +
>> +MODULE_AUTHOR("Kumar Gala");
>> +MODULE_DESCRIPTION("Simple MPC83xx SPI Driver");
>> +MODULE_LICENSE("GPL");
>>


^ permalink raw reply	[flat|nested] 18+ messages in thread

* [PATCH][2.16.17-rc1-mm2] spi: Added spi master driver for Freescale MPC83xx SPI controller
  2006-04-10 19:05     ` Kumar Gala
@ 2006-04-10 19:17       ` Kumar Gala
  2006-04-10 20:03         ` [spi-devel-general] " Vitaly Wool
  2006-04-11 14:39         ` [PATCH][2.16.17-rc1-mm2][UPDATE] " Kumar Gala
  0 siblings, 2 replies; 18+ messages in thread
From: Kumar Gala @ 2006-04-10 19:17 UTC (permalink / raw)
  To: David Brownell; +Cc: Greg KH, linux-kernel, spi-devel-general

This driver supports the SPI controller on the MPC83xx SoC devices from
Freescale.  Note, this driver supports only the simple shift register SPI
controller and not the descriptor based CPM or QUICCEngine SPI controller.

Signed-off-by: Kumar Gala <galak@kernel.crashing.org>
Signed-off-by: David Brownell <dbrownell@users.sourceforge.net>

---
commit 127ef1f9d0df9065f2a4ac633d7d25f8680a9501
tree 902f2c3fe37d6914d715e2b9b2ce09223a1f3432
parent 8e55334e29b2996d921641911a3802f9d004566d
author Kumar Gala <galak@kernel.crashing.org> Mon, 10 Apr 2006 14:12:23 -0500
committer Kumar Gala <galak@kernel.crashing.org> Mon, 10 Apr 2006 14:12:23 -0500

 drivers/spi/Kconfig       |   12 +
 drivers/spi/Makefile      |    1 
 drivers/spi/spi_mpc83xx.c |  488 +++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 501 insertions(+), 0 deletions(-)

diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index 9ce1d01..3867c6e 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -75,6 +75,18 @@ config SPI_BUTTERFLY
 	  inexpensive battery powered microcontroller evaluation board.
 	  This same cable can be used to flash new firmware.
 
+config SPI_MPC83xx
+	tristate "Freescale MPC83xx SPI controller"
+	depends on SPI_MASTER && PPC_83xx && EXPERIMENTAL
+	select SPI_BITBANG
+	help
+	  This enables using the Freescale MPC83xx SPI controller in master
+	  mode.
+
+	  Note, this driver uniquely supports the SPI controller on the MPC83xx
+	  family of PowerPC processors.  The MPC83xx uses a simple set of shift
+	  registers for data (opposed to the CPM based descriptor model).
+
 config SPI_PXA2XX
 	tristate "PXA2xx SSP SPI master"
 	depends on SPI_MASTER && ARCH_PXA && EXPERIMENTAL
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index 1bca5f9..5a410ca 100644
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -14,6 +14,7 @@ obj-$(CONFIG_SPI_MASTER)		+= spi.o
 obj-$(CONFIG_SPI_BITBANG)		+= spi_bitbang.o
 obj-$(CONFIG_SPI_BUTTERFLY)		+= spi_butterfly.o
 obj-$(CONFIG_SPI_PXA2XX)		+= pxa2xx_spi.o
+obj-$(CONFIG_SPI_MPC83xx)		+= spi_mpc83xx.o
 # 	... add above this line ...
 
 # SPI protocol drivers (device/link on bus)
diff --git a/drivers/spi/spi_mpc83xx.c b/drivers/spi/spi_mpc83xx.c
new file mode 100644
index 0000000..a5ecdec
--- /dev/null
+++ b/drivers/spi/spi_mpc83xx.c
@@ -0,0 +1,488 @@
+/*
+ * MPC83xx SPI controller driver.
+ *
+ * Maintainer: Kumar Gala
+ *
+ * Copyright (C) 2006 Polycom, Inc.
+ *
+ * This program is free software; you can redistribute  it and/or modify it
+ * under  the terms of  the GNU General  Public License as published by the
+ * Free Software Foundation;  either version 2 of the  License, or (at your
+ * option) any later version.
+ */
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/completion.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/irq.h>
+#include <linux/device.h>
+#include <linux/spi/spi.h>
+#include <linux/spi/spi_bitbang.h>
+#include <linux/platform_device.h>
+#include <linux/fsl_devices.h>
+
+#include <asm/irq.h>
+#include <asm/io.h>
+
+/* SPI Controller registers */
+struct mpc83xx_spi_reg {
+	u8 res1[0x20];
+	__be32 mode;
+	__be32 event;
+	__be32 mask;
+	__be32 command;
+	__be32 transmit;
+	__be32 receive;
+};
+
+/* SPI Controller mode register definitions */
+#define	SPMODE_CI_INACTIVEHIGH	(1 << 29)
+#define	SPMODE_CP_BEGIN_EDGECLK	(1 << 28)
+#define	SPMODE_DIV16		(1 << 27)
+#define	SPMODE_REV		(1 << 26)
+#define	SPMODE_MS		(1 << 25)
+#define	SPMODE_ENABLE		(1 << 24)
+#define	SPMODE_LEN(x)		((x) << 20)
+#define	SPMODE_PM(x)		((x) << 16)
+
+/*
+ * Default for SPI Mode:
+ * 	SPI MODE 0 (inactive low, phase middle, MSB, 8-bit length, slow clk
+ */
+#define	SPMODE_INIT_VAL (SPMODE_CI_INACTIVEHIGH | SPMODE_DIV16 | SPMODE_REV | \
+			 SPMODE_MS | SPMODE_LEN(7) | SPMODE_PM(0xf))
+
+/* SPIE register values */
+#define	SPIE_NE		0x00000200	/* Not empty */
+#define	SPIE_NF		0x00000100	/* Not full */
+
+/* SPIM register values */
+#define	SPIM_NE		0x00000200	/* Not empty */
+#define	SPIM_NF		0x00000100	/* Not full */
+
+/* SPI Controller driver's private data. */
+struct mpc83xx_spi {
+	/* bitbang has to be first */
+	struct spi_bitbang bitbang;
+	struct completion done;
+
+	struct mpc83xx_spi_reg __iomem *base;
+
+	/* rx & tx bufs from the spi_transfer */
+	const void *tx;
+	void *rx;
+
+	/* functions to deal with different sized buffers */
+	void (*get_rx) (u32 rx_data, struct mpc83xx_spi *);
+	u32(*get_tx) (struct mpc83xx_spi *);
+
+	unsigned int count;
+	u32 irq;
+
+	unsigned nsecs;		/* (clock cycle time)/2 */
+
+	u32 sysclk;
+	void (*activate_cs) (u8 cs, u8 polarity);
+	void (*deactivate_cs) (u8 cs, u8 polarity);
+};
+
+static inline void mpc83xx_spi_write_reg(__be32 __iomem * reg, u32 val)
+{
+	out_be32(reg, val);
+}
+
+static inline u32 mpc83xx_spi_read_reg(__be32 __iomem * reg)
+{
+	return in_be32(reg);
+}
+
+#define MPC83XX_SPI_RX_BUF(type) 					  \
+void mpc83xx_spi_rx_buf_##type(u32 data, struct mpc83xx_spi *mpc83xx_spi) \
+{									  \
+	type * rx = mpc83xx_spi->rx;					  \
+	*rx++ = (type)data;						  \
+	mpc83xx_spi->rx = rx;						  \
+}
+
+#define MPC83XX_SPI_TX_BUF(type)				\
+u32 mpc83xx_spi_tx_buf_##type(struct mpc83xx_spi *mpc83xx_spi)	\
+{								\
+	u32 data;						\
+	const type * tx = mpc83xx_spi->tx;			\
+	data = *tx++;						\
+	mpc83xx_spi->tx = tx;					\
+	return data;						\
+}
+
+MPC83XX_SPI_RX_BUF(u8)
+MPC83XX_SPI_RX_BUF(u16)
+MPC83XX_SPI_RX_BUF(u32)
+MPC83XX_SPI_TX_BUF(u8)
+MPC83XX_SPI_TX_BUF(u16)
+MPC83XX_SPI_TX_BUF(u32)
+
+static void mpc83xx_spi_chipselect(struct spi_device *spi, int value)
+{
+	struct mpc83xx_spi *mpc83xx_spi;
+	u8 pol = spi->mode & SPI_CS_HIGH ? 1 : 0;
+
+	mpc83xx_spi = spi_master_get_devdata(spi->master);
+
+	if (value == BITBANG_CS_INACTIVE) {
+		if (mpc83xx_spi->deactivate_cs)
+			mpc83xx_spi->deactivate_cs(spi->chip_select, pol);
+	}
+
+	if (value == BITBANG_CS_ACTIVE) {
+		u32 regval = mpc83xx_spi_read_reg(&mpc83xx_spi->base->mode);
+		u32 len = spi->bits_per_word;
+		if (len == 32)
+			len = 0;
+		else
+			len = len - 1;
+
+		/* mask out bits we are going to set */
+		regval &= ~0x38ff0000;
+
+		if (spi->mode & SPI_CPHA)
+			regval |= SPMODE_CP_BEGIN_EDGECLK;
+		if (spi->mode & SPI_CPOL)
+			regval |= SPMODE_CI_INACTIVEHIGH;
+
+		regval |= SPMODE_LEN(len);
+
+		if ((mpc83xx_spi->sysclk / spi->max_speed_hz) >= 64) {
+			u8 pm = mpc83xx_spi->sysclk / (spi->max_speed_hz * 64);
+			regval |= SPMODE_PM(pm) | SPMODE_DIV16;
+		} else {
+			u8 pm = mpc83xx_spi->sysclk / (spi->max_speed_hz * 4);
+			regval |= SPMODE_PM(pm);
+		}
+
+		mpc83xx_spi_write_reg(&mpc83xx_spi->base->mode, regval);
+		if (mpc83xx_spi->activate_cs)
+			mpc83xx_spi->activate_cs(spi->chip_select, pol);
+	}
+}
+
+static
+int mpc83xx_spi_setup_transfer(struct spi_device *spi, struct spi_transfer *t)
+{
+	struct mpc83xx_spi *mpc83xx_spi;
+	u32 regval;
+	u8 bits_per_word;
+	u32 hz;
+
+	mpc83xx_spi = spi_master_get_devdata(spi->master);
+
+	if (t) {
+		bits_per_word = t->bits_per_word;
+		hz = t->speed_hz;
+	} else {
+		bits_per_word = 0;
+		hz = 0;
+	}
+
+	/* spi_transfer level calls that work per-word */
+	if (!bits_per_word)
+		bits_per_word = spi->bits_per_word;
+
+	/* Make sure its a bit width we support [4..16, 32] */
+	if ((bits_per_word < 4)
+	    || ((bits_per_word > 16) && (bits_per_word != 32)))
+		return -EINVAL;
+
+	if (bits_per_word <= 8) {
+		mpc83xx_spi->get_rx = mpc83xx_spi_rx_buf_u8;
+		mpc83xx_spi->get_tx = mpc83xx_spi_tx_buf_u8;
+	} else {
+		if (bits_per_word <= 16) {
+			mpc83xx_spi->get_rx = mpc83xx_spi_rx_buf_u16;
+			mpc83xx_spi->get_tx = mpc83xx_spi_tx_buf_u16;
+		} else {
+			if (bits_per_word <= 32) {
+				mpc83xx_spi->get_rx = mpc83xx_spi_rx_buf_u32;
+				mpc83xx_spi->get_tx = mpc83xx_spi_tx_buf_u32;
+			} else {
+				return -EINVAL;
+			}
+		}
+	}
+
+	/* nsecs = (clock period)/2 */
+	if (!hz)
+		hz = spi->max_speed_hz;
+	mpc83xx_spi->nsecs = (1000000000 / 2) / hz;
+	if (mpc83xx_spi->nsecs > MAX_UDELAY_MS * 1000)
+		return -EINVAL;
+
+	if (bits_per_word == 32)
+		bits_per_word = 0;
+	else
+		bits_per_word = bits_per_word - 1;
+
+	regval = mpc83xx_spi_read_reg(&mpc83xx_spi->base->mode);
+
+	/* Mask out bits_per_wordgth */
+	regval &= 0xff0fffff;
+	regval |= SPMODE_LEN(bits_per_word);
+
+	mpc83xx_spi_write_reg(&mpc83xx_spi->base->mode, regval);
+
+	return 0;
+}
+
+static int mpc83xx_spi_setup(struct spi_device *spi)
+{
+	struct spi_bitbang *bitbang;
+	struct mpc83xx_spi *mpc83xx_spi;
+	int retval;
+
+	if (!spi->max_speed_hz)
+		return -EINVAL;
+
+	bitbang = spi_master_get_devdata(spi->master);
+	mpc83xx_spi = spi_master_get_devdata(spi->master);
+
+	if (!spi->bits_per_word)
+		spi->bits_per_word = 8;
+
+	retval = mpc83xx_spi_setup_transfer(spi, NULL);
+	if (retval < 0)
+		return retval;
+
+	dev_dbg(&spi->dev, "%s, mode %d, %u bits/w, %u nsec\n",
+		__FUNCTION__, spi->mode & (SPI_CPOL | SPI_CPHA),
+		spi->bits_per_word, 2 * mpc83xx_spi->nsecs);
+
+	/* NOTE we _need_ to call chipselect() early, ideally with adapter
+	 * setup, unless the hardware defaults cooperate to avoid confusion
+	 * between normal (active low) and inverted chipselects.
+	 */
+
+	/* deselect chip (low or high) */
+	spin_lock(&bitbang->lock);
+	if (!bitbang->busy) {
+		bitbang->chipselect(spi, BITBANG_CS_INACTIVE);
+		ndelay(mpc83xx_spi->nsecs);
+	}
+	spin_unlock(&bitbang->lock);
+
+	return 0;
+}
+
+static int mpc83xx_spi_bufs(struct spi_device *spi, struct spi_transfer *t)
+{
+	struct mpc83xx_spi *mpc83xx_spi;
+	u32 word;
+
+	mpc83xx_spi = spi_master_get_devdata(spi->master);
+
+	mpc83xx_spi->tx = t->tx_buf;
+	mpc83xx_spi->rx = t->rx_buf;
+	mpc83xx_spi->count = t->len;
+	INIT_COMPLETION(mpc83xx_spi->done);
+
+	/* enable rx ints */
+	mpc83xx_spi_write_reg(&mpc83xx_spi->base->mask, SPIM_NE);
+
+	/* transmit word */
+	word = mpc83xx_spi->get_tx(mpc83xx_spi);
+	mpc83xx_spi_write_reg(&mpc83xx_spi->base->transmit, word);
+
+	wait_for_completion(&mpc83xx_spi->done);
+
+	/* disable rx ints */
+	mpc83xx_spi_write_reg(&mpc83xx_spi->base->mask, 0);
+
+	return t->len - mpc83xx_spi->count;
+}
+
+irqreturn_t mpc83xx_spi_irq(s32 irq, void *context_data,
+			    struct pt_regs * ptregs)
+{
+	struct mpc83xx_spi *mpc83xx_spi = context_data;
+	u32 event;
+	irqreturn_t ret = IRQ_NONE;
+
+	/* Get interrupt events(tx/rx) */
+	event = mpc83xx_spi_read_reg(&mpc83xx_spi->base->event);
+
+	/* We need handle RX first */
+	if (event & SPIE_NE) {
+		u32 rx_data = mpc83xx_spi_read_reg(&mpc83xx_spi->base->receive);
+
+		if (mpc83xx_spi->rx)
+			mpc83xx_spi->get_rx(rx_data, mpc83xx_spi);
+
+		ret = IRQ_HANDLED;
+	}
+
+	if ((event & SPIE_NF) == 0)
+		/* spin until TX is done */
+		while (((event =
+			 mpc83xx_spi_read_reg(&mpc83xx_spi->base->event)) &
+						SPIE_NF) == 0)
+			 ;
+
+	mpc83xx_spi->count -= 1;
+	if (mpc83xx_spi->count) {
+		if (mpc83xx_spi->tx) {
+			u32 word = mpc83xx_spi->get_tx(mpc83xx_spi);
+			mpc83xx_spi_write_reg(&mpc83xx_spi->base->transmit,
+					      word);
+		}
+	} else {
+		complete(&mpc83xx_spi->done);
+	}
+
+	/* Clear the events */
+	mpc83xx_spi_write_reg(&mpc83xx_spi->base->event, event);
+
+	return ret;
+}
+
+static int __devinit mpc83xx_spi_probe(struct platform_device *dev)
+{
+	struct spi_master *master;
+	struct mpc83xx_spi *mpc83xx_spi;
+	struct fsl_spi_platform_data *pdata;
+	struct resource *r;
+	u32 regval;
+	int ret = 0;
+
+	/* Get resources(memory, IRQ) associated with the device */
+	master = spi_alloc_master(&dev->dev, sizeof(struct mpc83xx_spi));
+
+	if (master == NULL) {
+		ret = -ENOMEM;
+		goto err;
+	}
+
+	platform_set_drvdata(dev, master);
+	pdata = dev->dev.platform_data;
+
+	if (pdata == NULL) {
+		ret = -ENODEV;
+		goto free_master;
+	}
+
+	r = platform_get_resource(dev, IORESOURCE_MEM, 0);
+	if (r == NULL) {
+		ret = -ENODEV;
+		goto free_master;
+	}
+
+	mpc83xx_spi = spi_master_get_devdata(master);
+	mpc83xx_spi->bitbang.master = spi_master_get(master);
+	mpc83xx_spi->bitbang.chipselect = mpc83xx_spi_chipselect;
+	mpc83xx_spi->bitbang.setup_transfer = mpc83xx_spi_setup_transfer;
+	mpc83xx_spi->bitbang.txrx_bufs = mpc83xx_spi_bufs;
+	mpc83xx_spi->sysclk = pdata->sysclk;
+	mpc83xx_spi->activate_cs = pdata->activate_cs;
+	mpc83xx_spi->deactivate_cs = pdata->deactivate_cs;
+	mpc83xx_spi->get_rx = mpc83xx_spi_rx_buf_u8;
+	mpc83xx_spi->get_tx = mpc83xx_spi_tx_buf_u8;
+
+	mpc83xx_spi->bitbang.master->setup = mpc83xx_spi_setup;
+	init_completion(&mpc83xx_spi->done);
+
+	mpc83xx_spi->base = ioremap(r->start, r->end - r->start + 1);
+	if (mpc83xx_spi->base == NULL) {
+		ret = -ENOMEM;
+		goto put_master;
+	}
+
+	mpc83xx_spi->irq = platform_get_irq(dev, 0);
+
+	if (mpc83xx_spi->irq < 0) {
+		ret = -ENXIO;
+		goto unmap_io;
+	}
+
+	/* Register for SPI Interrupt */
+	ret = request_irq(mpc83xx_spi->irq, mpc83xx_spi_irq,
+			  0, "mpc83xx_spi", mpc83xx_spi);
+
+	if (ret != 0)
+		goto unmap_io;
+
+	master->bus_num = pdata->bus_num;
+	master->num_chipselect = pdata->max_chipselect;
+
+	/* SPI controller initializations */
+	mpc83xx_spi_write_reg(&mpc83xx_spi->base->mode, 0);
+	mpc83xx_spi_write_reg(&mpc83xx_spi->base->mask, 0);
+	mpc83xx_spi_write_reg(&mpc83xx_spi->base->command, 0);
+	mpc83xx_spi_write_reg(&mpc83xx_spi->base->event, 0xffffffff);
+
+	/* Enable SPI interface */
+	regval = pdata->initial_spmode | SPMODE_INIT_VAL | SPMODE_ENABLE;
+	mpc83xx_spi_write_reg(&mpc83xx_spi->base->mode, regval);
+
+	ret = spi_bitbang_start(&mpc83xx_spi->bitbang);
+
+	if (ret != 0)
+		goto free_irq;
+
+	printk(KERN_INFO
+	       "%s: MPC83xx SPI Controller driver at 0x%p (irq = %d)\n",
+	       dev->dev.bus_id, mpc83xx_spi->base, mpc83xx_spi->irq);
+
+	return ret;
+
+free_irq:
+	free_irq(mpc83xx_spi->irq, mpc83xx_spi);
+unmap_io:
+	iounmap(mpc83xx_spi->base);
+put_master:
+	spi_master_put(master);
+free_master:
+	kfree(master);
+err:
+	return ret;
+}
+
+static int __devexit mpc83xx_spi_remove(struct platform_device *dev)
+{
+	struct mpc83xx_spi *mpc83xx_spi;
+	struct spi_master *master;
+
+	master = platform_get_drvdata(dev);
+	mpc83xx_spi = spi_master_get_devdata(master);
+
+	spi_bitbang_stop(&mpc83xx_spi->bitbang);
+	free_irq(mpc83xx_spi->irq, mpc83xx_spi);
+	iounmap(mpc83xx_spi->base);
+	spi_master_put(mpc83xx_spi->bitbang.master);
+
+	return 0;
+}
+
+static struct platform_driver mpc83xx_spi_driver = {
+	.probe = mpc83xx_spi_probe,
+	.remove = __devexit_p(mpc83xx_spi_remove),
+	.driver = {
+		   .name = "mpc83xx_spi",
+	},
+};
+
+static int __init mpc83xx_spi_init(void)
+{
+	return platform_driver_register(&mpc83xx_spi_driver);
+}
+
+static void __exit mpc83xx_spi_exit(void)
+{
+	platform_driver_unregister(&mpc83xx_spi_driver);
+}
+
+module_init(mpc83xx_spi_init);
+module_exit(mpc83xx_spi_exit);
+
+MODULE_AUTHOR("Kumar Gala");
+MODULE_DESCRIPTION("Simple MPC83xx SPI Driver");
+MODULE_LICENSE("GPL");





^ permalink raw reply related	[flat|nested] 18+ messages in thread

* Re: [spi-devel-general] [PATCH][2.16.17-rc1-mm2] spi: Added spi master driver for Freescale MPC83xx SPI controller
  2006-04-10 19:17       ` [PATCH][2.16.17-rc1-mm2] " Kumar Gala
@ 2006-04-10 20:03         ` Vitaly Wool
  2006-04-10 20:22           ` Kumar Gala
  2006-04-11 14:39         ` [PATCH][2.16.17-rc1-mm2][UPDATE] " Kumar Gala
  1 sibling, 1 reply; 18+ messages in thread
From: Vitaly Wool @ 2006-04-10 20:03 UTC (permalink / raw)
  To: Kumar Gala; +Cc: David Brownell, Greg KH, linux-kernel, spi-devel-general

Hi,

Kumar Gala wrote:

>+irqreturn_t mpc83xx_spi_irq(s32 irq, void *context_data,
>+			    struct pt_regs * ptregs)
>+{
>+	struct mpc83xx_spi *mpc83xx_spi = context_data;
>+	u32 event;
>+	irqreturn_t ret = IRQ_NONE;
>+
>+	/* Get interrupt events(tx/rx) */
>+	event = mpc83xx_spi_read_reg(&mpc83xx_spi->base->event);
>+
>+	/* We need handle RX first */
>+	if (event & SPIE_NE) {
>+		u32 rx_data = mpc83xx_spi_read_reg(&mpc83xx_spi->base->receive);
>+
>+		if (mpc83xx_spi->rx)
>+			mpc83xx_spi->get_rx(rx_data, mpc83xx_spi);
>+
>+		ret = IRQ_HANDLED;
>+	}
>+
>+	if ((event & SPIE_NF) == 0)
>+		/* spin until TX is done */
>+		while (((event =
>+			 mpc83xx_spi_read_reg(&mpc83xx_spi->base->event)) &
>+						SPIE_NF) == 0)
>+			 ;
>  
>
This is a potentially endless loop so two questions here.
First, did you do any measurements on how long it can loop here?
The, what if a bus error occurs and this bit is never set?

>+
>+	mpc83xx_spi->count -= 1;
>+	if (mpc83xx_spi->count) {
>+		if (mpc83xx_spi->tx) {
>+			u32 word = mpc83xx_spi->get_tx(mpc83xx_spi);
>+			mpc83xx_spi_write_reg(&mpc83xx_spi->base->transmit,
>+					      word);
>+		}
>+	} else {
>+		complete(&mpc83xx_spi->done);
>+	}
>  
>
So, if it's not SPIE_NF, it's not marked as HANDLED?

Vitaly

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [spi-devel-general] [PATCH][2.16.17-rc1-mm2] spi: Added spi master driver for Freescale MPC83xx SPI controller
  2006-04-10 20:03         ` [spi-devel-general] " Vitaly Wool
@ 2006-04-10 20:22           ` Kumar Gala
  2006-04-10 21:06             ` David Brownell
  0 siblings, 1 reply; 18+ messages in thread
From: Kumar Gala @ 2006-04-10 20:22 UTC (permalink / raw)
  To: Vitaly Wool; +Cc: David Brownell, Greg KH, linux-kernel, spi-devel-general


On Apr 10, 2006, at 3:03 PM, Vitaly Wool wrote:

> Hi,
>
> Kumar Gala wrote:
>
>> +irqreturn_t mpc83xx_spi_irq(s32 irq, void *context_data,
>> +			    struct pt_regs * ptregs)
>> +{
>> +	struct mpc83xx_spi *mpc83xx_spi = context_data;
>> +	u32 event;
>> +	irqreturn_t ret = IRQ_NONE;
>> +
>> +	/* Get interrupt events(tx/rx) */
>> +	event = mpc83xx_spi_read_reg(&mpc83xx_spi->base->event);
>> +
>> +	/* We need handle RX first */
>> +	if (event & SPIE_NE) {
>> +		u32 rx_data = mpc83xx_spi_read_reg(&mpc83xx_spi->base->receive);
>> +
>> +		if (mpc83xx_spi->rx)
>> +			mpc83xx_spi->get_rx(rx_data, mpc83xx_spi);
>> +
>> +		ret = IRQ_HANDLED;
>> +	}
>> +
>> +	if ((event & SPIE_NF) == 0)
>> +		/* spin until TX is done */
>> +		while (((event =
>> +			 mpc83xx_spi_read_reg(&mpc83xx_spi->base->event)) &
>> +						SPIE_NF) == 0)
>> +			 ;
>>
> This is a potentially endless loop so two questions here.
> First, did you do any measurements on how long it can loop here?
> The, what if a bus error occurs and this bit is never set?

In my measurements the if is never true, so we never spin.

I wasn't particular happy about the spinning for ever, wasn't sure  
what would be better.  I need to ensure we've gotten both a TX & RX  
event before transmitting the next character.  I'm open to  
suggestions on how to do this better.

>> +
>> +	mpc83xx_spi->count -= 1;
>> +	if (mpc83xx_spi->count) {
>> +		if (mpc83xx_spi->tx) {
>> +			u32 word = mpc83xx_spi->get_tx(mpc83xx_spi);
>> +			mpc83xx_spi_write_reg(&mpc83xx_spi->base->transmit,
>> +					      word);
>> +		}
>> +	} else {
>> +		complete(&mpc83xx_spi->done);
>> +	}
>>
> So, if it's not SPIE_NF, it's not marked as HANDLED?

I wasn't sure what the best thing here was.  In truth we will only  
get an interrupt if SPIE_NF is set so it should always be handled.



^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [spi-devel-general] [PATCH][2.16.17-rc1-mm2] spi: Added spi master driver for Freescale MPC83xx SPI controller
  2006-04-10 20:22           ` Kumar Gala
@ 2006-04-10 21:06             ` David Brownell
  2006-04-10 21:10               ` Kumar Gala
  0 siblings, 1 reply; 18+ messages in thread
From: David Brownell @ 2006-04-10 21:06 UTC (permalink / raw)
  To: Kumar Gala; +Cc: Vitaly Wool, Greg KH, linux-kernel, spi-devel-general

On Monday 10 April 2006 1:22 pm, Kumar Gala wrote:
> 

> I wasn't particular happy about the spinning for ever, wasn't sure  
> what would be better.  I need to ensure we've gotten both a TX & RX  
> event before transmitting the next character.  I'm open to  
> suggestions on how to do this better.

I figured that it's impossible to get an RX event without the TX
having completed.  :)

- Dave


^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [spi-devel-general] [PATCH][2.16.17-rc1-mm2] spi: Added spi master driver for Freescale MPC83xx SPI controller
  2006-04-10 21:06             ` David Brownell
@ 2006-04-10 21:10               ` Kumar Gala
  0 siblings, 0 replies; 18+ messages in thread
From: Kumar Gala @ 2006-04-10 21:10 UTC (permalink / raw)
  To: David Brownell; +Cc: Vitaly Wool, Greg KH, linux-kernel, spi-devel-general


On Apr 10, 2006, at 4:06 PM, David Brownell wrote:

> On Monday 10 April 2006 1:22 pm, Kumar Gala wrote:
>>
>
>> I wasn't particular happy about the spinning for ever, wasn't sure
>> what would be better.  I need to ensure we've gotten both a TX & RX
>> event before transmitting the next character.  I'm open to
>> suggestions on how to do this better.
>
> I figured that it's impossible to get an RX event without the TX
> having completed.  :)

Should I just get ride of the spin loop then?

- k

^ permalink raw reply	[flat|nested] 18+ messages in thread

* [PATCH][2.16.17-rc1-mm2][UPDATE] spi: Added spi master driver for Freescale MPC83xx SPI controller
  2006-04-10 19:17       ` [PATCH][2.16.17-rc1-mm2] " Kumar Gala
  2006-04-10 20:03         ` [spi-devel-general] " Vitaly Wool
@ 2006-04-11 14:39         ` Kumar Gala
  1 sibling, 0 replies; 18+ messages in thread
From: Kumar Gala @ 2006-04-11 14:39 UTC (permalink / raw)
  To: Andrew Morton, Greg KH; +Cc: David Brownell, linux-kernel, spi-devel-general

This driver supports the SPI controller on the MPC83xx SoC devices from
Freescale.  Note, this driver supports only the simple shift register SPI
controller and not the descriptor based CPM or QUICCEngine SPI controller.

Signed-off-by: Kumar Gala <galak@kernel.crashing.org>
Signed-off-by: David Brownell <dbrownell@users.sourceforge.net>

---
commit 389718b2764251b5c4b6139926f462cba4ee515a
tree 9d7808a89cb01e4e6a9f79c2558f55fbf272c068
parent 8e55334e29b2996d921641911a3802f9d004566d
author Kumar Gala <galak@kernel.crashing.org> Tue, 11 Apr 2006 09:38:19 -0500
committer Kumar Gala <galak@kernel.crashing.org> Tue, 11 Apr 2006 09:38:19 -0500

 drivers/spi/Kconfig         |   12 +
 drivers/spi/Makefile        |    1 
 drivers/spi/spi_mpc83xx.c   |  483 +++++++++++++++++++++++++++++++++++++++++++
 include/linux/fsl_devices.h |   11 +
 4 files changed, 507 insertions(+), 0 deletions(-)

diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index 9ce1d01..3867c6e 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -75,6 +75,18 @@ config SPI_BUTTERFLY
 	  inexpensive battery powered microcontroller evaluation board.
 	  This same cable can be used to flash new firmware.
 
+config SPI_MPC83xx
+	tristate "Freescale MPC83xx SPI controller"
+	depends on SPI_MASTER && PPC_83xx && EXPERIMENTAL
+	select SPI_BITBANG
+	help
+	  This enables using the Freescale MPC83xx SPI controller in master
+	  mode.
+
+	  Note, this driver uniquely supports the SPI controller on the MPC83xx
+	  family of PowerPC processors.  The MPC83xx uses a simple set of shift
+	  registers for data (opposed to the CPM based descriptor model).
+
 config SPI_PXA2XX
 	tristate "PXA2xx SSP SPI master"
 	depends on SPI_MASTER && ARCH_PXA && EXPERIMENTAL
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index 1bca5f9..5a410ca 100644
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -14,6 +14,7 @@ obj-$(CONFIG_SPI_MASTER)		+= spi.o
 obj-$(CONFIG_SPI_BITBANG)		+= spi_bitbang.o
 obj-$(CONFIG_SPI_BUTTERFLY)		+= spi_butterfly.o
 obj-$(CONFIG_SPI_PXA2XX)		+= pxa2xx_spi.o
+obj-$(CONFIG_SPI_MPC83xx)		+= spi_mpc83xx.o
 # 	... add above this line ...
 
 # SPI protocol drivers (device/link on bus)
diff --git a/drivers/spi/spi_mpc83xx.c b/drivers/spi/spi_mpc83xx.c
new file mode 100644
index 0000000..6468d22
--- /dev/null
+++ b/drivers/spi/spi_mpc83xx.c
@@ -0,0 +1,483 @@
+/*
+ * MPC83xx SPI controller driver.
+ *
+ * Maintainer: Kumar Gala
+ *
+ * Copyright (C) 2006 Polycom, Inc.
+ *
+ * This program is free software; you can redistribute  it and/or modify it
+ * under  the terms of  the GNU General  Public License as published by the
+ * Free Software Foundation;  either version 2 of the  License, or (at your
+ * option) any later version.
+ */
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/completion.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/irq.h>
+#include <linux/device.h>
+#include <linux/spi/spi.h>
+#include <linux/spi/spi_bitbang.h>
+#include <linux/platform_device.h>
+#include <linux/fsl_devices.h>
+
+#include <asm/irq.h>
+#include <asm/io.h>
+
+/* SPI Controller registers */
+struct mpc83xx_spi_reg {
+	u8 res1[0x20];
+	__be32 mode;
+	__be32 event;
+	__be32 mask;
+	__be32 command;
+	__be32 transmit;
+	__be32 receive;
+};
+
+/* SPI Controller mode register definitions */
+#define	SPMODE_CI_INACTIVEHIGH	(1 << 29)
+#define	SPMODE_CP_BEGIN_EDGECLK	(1 << 28)
+#define	SPMODE_DIV16		(1 << 27)
+#define	SPMODE_REV		(1 << 26)
+#define	SPMODE_MS		(1 << 25)
+#define	SPMODE_ENABLE		(1 << 24)
+#define	SPMODE_LEN(x)		((x) << 20)
+#define	SPMODE_PM(x)		((x) << 16)
+
+/*
+ * Default for SPI Mode:
+ * 	SPI MODE 0 (inactive low, phase middle, MSB, 8-bit length, slow clk
+ */
+#define	SPMODE_INIT_VAL (SPMODE_CI_INACTIVEHIGH | SPMODE_DIV16 | SPMODE_REV | \
+			 SPMODE_MS | SPMODE_LEN(7) | SPMODE_PM(0xf))
+
+/* SPIE register values */
+#define	SPIE_NE		0x00000200	/* Not empty */
+#define	SPIE_NF		0x00000100	/* Not full */
+
+/* SPIM register values */
+#define	SPIM_NE		0x00000200	/* Not empty */
+#define	SPIM_NF		0x00000100	/* Not full */
+
+/* SPI Controller driver's private data. */
+struct mpc83xx_spi {
+	/* bitbang has to be first */
+	struct spi_bitbang bitbang;
+	struct completion done;
+
+	struct mpc83xx_spi_reg __iomem *base;
+
+	/* rx & tx bufs from the spi_transfer */
+	const void *tx;
+	void *rx;
+
+	/* functions to deal with different sized buffers */
+	void (*get_rx) (u32 rx_data, struct mpc83xx_spi *);
+	u32(*get_tx) (struct mpc83xx_spi *);
+
+	unsigned int count;
+	u32 irq;
+
+	unsigned nsecs;		/* (clock cycle time)/2 */
+
+	u32 sysclk;
+	void (*activate_cs) (u8 cs, u8 polarity);
+	void (*deactivate_cs) (u8 cs, u8 polarity);
+};
+
+static inline void mpc83xx_spi_write_reg(__be32 __iomem * reg, u32 val)
+{
+	out_be32(reg, val);
+}
+
+static inline u32 mpc83xx_spi_read_reg(__be32 __iomem * reg)
+{
+	return in_be32(reg);
+}
+
+#define MPC83XX_SPI_RX_BUF(type) 					  \
+void mpc83xx_spi_rx_buf_##type(u32 data, struct mpc83xx_spi *mpc83xx_spi) \
+{									  \
+	type * rx = mpc83xx_spi->rx;					  \
+	*rx++ = (type)data;						  \
+	mpc83xx_spi->rx = rx;						  \
+}
+
+#define MPC83XX_SPI_TX_BUF(type)				\
+u32 mpc83xx_spi_tx_buf_##type(struct mpc83xx_spi *mpc83xx_spi)	\
+{								\
+	u32 data;						\
+	const type * tx = mpc83xx_spi->tx;			\
+	data = *tx++;						\
+	mpc83xx_spi->tx = tx;					\
+	return data;						\
+}
+
+MPC83XX_SPI_RX_BUF(u8)
+MPC83XX_SPI_RX_BUF(u16)
+MPC83XX_SPI_RX_BUF(u32)
+MPC83XX_SPI_TX_BUF(u8)
+MPC83XX_SPI_TX_BUF(u16)
+MPC83XX_SPI_TX_BUF(u32)
+
+static void mpc83xx_spi_chipselect(struct spi_device *spi, int value)
+{
+	struct mpc83xx_spi *mpc83xx_spi;
+	u8 pol = spi->mode & SPI_CS_HIGH ? 1 : 0;
+
+	mpc83xx_spi = spi_master_get_devdata(spi->master);
+
+	if (value == BITBANG_CS_INACTIVE) {
+		if (mpc83xx_spi->deactivate_cs)
+			mpc83xx_spi->deactivate_cs(spi->chip_select, pol);
+	}
+
+	if (value == BITBANG_CS_ACTIVE) {
+		u32 regval = mpc83xx_spi_read_reg(&mpc83xx_spi->base->mode);
+		u32 len = spi->bits_per_word;
+		if (len == 32)
+			len = 0;
+		else
+			len = len - 1;
+
+		/* mask out bits we are going to set */
+		regval &= ~0x38ff0000;
+
+		if (spi->mode & SPI_CPHA)
+			regval |= SPMODE_CP_BEGIN_EDGECLK;
+		if (spi->mode & SPI_CPOL)
+			regval |= SPMODE_CI_INACTIVEHIGH;
+
+		regval |= SPMODE_LEN(len);
+
+		if ((mpc83xx_spi->sysclk / spi->max_speed_hz) >= 64) {
+			u8 pm = mpc83xx_spi->sysclk / (spi->max_speed_hz * 64);
+			regval |= SPMODE_PM(pm) | SPMODE_DIV16;
+		} else {
+			u8 pm = mpc83xx_spi->sysclk / (spi->max_speed_hz * 4);
+			regval |= SPMODE_PM(pm);
+		}
+
+		mpc83xx_spi_write_reg(&mpc83xx_spi->base->mode, regval);
+		if (mpc83xx_spi->activate_cs)
+			mpc83xx_spi->activate_cs(spi->chip_select, pol);
+	}
+}
+
+static
+int mpc83xx_spi_setup_transfer(struct spi_device *spi, struct spi_transfer *t)
+{
+	struct mpc83xx_spi *mpc83xx_spi;
+	u32 regval;
+	u8 bits_per_word;
+	u32 hz;
+
+	mpc83xx_spi = spi_master_get_devdata(spi->master);
+
+	if (t) {
+		bits_per_word = t->bits_per_word;
+		hz = t->speed_hz;
+	} else {
+		bits_per_word = 0;
+		hz = 0;
+	}
+
+	/* spi_transfer level calls that work per-word */
+	if (!bits_per_word)
+		bits_per_word = spi->bits_per_word;
+
+	/* Make sure its a bit width we support [4..16, 32] */
+	if ((bits_per_word < 4)
+	    || ((bits_per_word > 16) && (bits_per_word != 32)))
+		return -EINVAL;
+
+	if (bits_per_word <= 8) {
+		mpc83xx_spi->get_rx = mpc83xx_spi_rx_buf_u8;
+		mpc83xx_spi->get_tx = mpc83xx_spi_tx_buf_u8;
+	} else if (bits_per_word <= 16) {
+		mpc83xx_spi->get_rx = mpc83xx_spi_rx_buf_u16;
+		mpc83xx_spi->get_tx = mpc83xx_spi_tx_buf_u16;
+	} else if (bits_per_word <= 32) {
+		mpc83xx_spi->get_rx = mpc83xx_spi_rx_buf_u32;
+		mpc83xx_spi->get_tx = mpc83xx_spi_tx_buf_u32;
+	} else 
+		return -EINVAL;
+
+	/* nsecs = (clock period)/2 */
+	if (!hz)
+		hz = spi->max_speed_hz;
+	mpc83xx_spi->nsecs = (1000000000 / 2) / hz;
+	if (mpc83xx_spi->nsecs > MAX_UDELAY_MS * 1000)
+		return -EINVAL;
+
+	if (bits_per_word == 32)
+		bits_per_word = 0;
+	else
+		bits_per_word = bits_per_word - 1;
+
+	regval = mpc83xx_spi_read_reg(&mpc83xx_spi->base->mode);
+
+	/* Mask out bits_per_wordgth */
+	regval &= 0xff0fffff;
+	regval |= SPMODE_LEN(bits_per_word);
+
+	mpc83xx_spi_write_reg(&mpc83xx_spi->base->mode, regval);
+
+	return 0;
+}
+
+static int mpc83xx_spi_setup(struct spi_device *spi)
+{
+	struct spi_bitbang *bitbang;
+	struct mpc83xx_spi *mpc83xx_spi;
+	int retval;
+
+	if (!spi->max_speed_hz)
+		return -EINVAL;
+
+	bitbang = spi_master_get_devdata(spi->master);
+	mpc83xx_spi = spi_master_get_devdata(spi->master);
+
+	if (!spi->bits_per_word)
+		spi->bits_per_word = 8;
+
+	retval = mpc83xx_spi_setup_transfer(spi, NULL);
+	if (retval < 0)
+		return retval;
+
+	dev_dbg(&spi->dev, "%s, mode %d, %u bits/w, %u nsec\n",
+		__FUNCTION__, spi->mode & (SPI_CPOL | SPI_CPHA),
+		spi->bits_per_word, 2 * mpc83xx_spi->nsecs);
+
+	/* NOTE we _need_ to call chipselect() early, ideally with adapter
+	 * setup, unless the hardware defaults cooperate to avoid confusion
+	 * between normal (active low) and inverted chipselects.
+	 */
+
+	/* deselect chip (low or high) */
+	spin_lock(&bitbang->lock);
+	if (!bitbang->busy) {
+		bitbang->chipselect(spi, BITBANG_CS_INACTIVE);
+		ndelay(mpc83xx_spi->nsecs);
+	}
+	spin_unlock(&bitbang->lock);
+
+	return 0;
+}
+
+static int mpc83xx_spi_bufs(struct spi_device *spi, struct spi_transfer *t)
+{
+	struct mpc83xx_spi *mpc83xx_spi;
+	u32 word;
+
+	mpc83xx_spi = spi_master_get_devdata(spi->master);
+
+	mpc83xx_spi->tx = t->tx_buf;
+	mpc83xx_spi->rx = t->rx_buf;
+	mpc83xx_spi->count = t->len;
+	INIT_COMPLETION(mpc83xx_spi->done);
+
+	/* enable rx ints */
+	mpc83xx_spi_write_reg(&mpc83xx_spi->base->mask, SPIM_NE);
+
+	/* transmit word */
+	word = mpc83xx_spi->get_tx(mpc83xx_spi);
+	mpc83xx_spi_write_reg(&mpc83xx_spi->base->transmit, word);
+
+	wait_for_completion(&mpc83xx_spi->done);
+
+	/* disable rx ints */
+	mpc83xx_spi_write_reg(&mpc83xx_spi->base->mask, 0);
+
+	return t->len - mpc83xx_spi->count;
+}
+
+irqreturn_t mpc83xx_spi_irq(s32 irq, void *context_data,
+			    struct pt_regs * ptregs)
+{
+	struct mpc83xx_spi *mpc83xx_spi = context_data;
+	u32 event;
+	irqreturn_t ret = IRQ_NONE;
+
+	/* Get interrupt events(tx/rx) */
+	event = mpc83xx_spi_read_reg(&mpc83xx_spi->base->event);
+
+	/* We need handle RX first */
+	if (event & SPIE_NE) {
+		u32 rx_data = mpc83xx_spi_read_reg(&mpc83xx_spi->base->receive);
+
+		if (mpc83xx_spi->rx)
+			mpc83xx_spi->get_rx(rx_data, mpc83xx_spi);
+
+		ret = IRQ_HANDLED;
+	}
+
+	if ((event & SPIE_NF) == 0)
+		/* spin until TX is done */
+		while (((event =
+			 mpc83xx_spi_read_reg(&mpc83xx_spi->base->event)) &
+						SPIE_NF) == 0)
+			 cpu_relax();
+
+	mpc83xx_spi->count -= 1;
+	if (mpc83xx_spi->count) {
+		if (mpc83xx_spi->tx) {
+			u32 word = mpc83xx_spi->get_tx(mpc83xx_spi);
+			mpc83xx_spi_write_reg(&mpc83xx_spi->base->transmit,
+					      word);
+		}
+	} else {
+		complete(&mpc83xx_spi->done);
+	}
+
+	/* Clear the events */
+	mpc83xx_spi_write_reg(&mpc83xx_spi->base->event, event);
+
+	return ret;
+}
+
+static int __devinit mpc83xx_spi_probe(struct platform_device *dev)
+{
+	struct spi_master *master;
+	struct mpc83xx_spi *mpc83xx_spi;
+	struct fsl_spi_platform_data *pdata;
+	struct resource *r;
+	u32 regval;
+	int ret = 0;
+
+	/* Get resources(memory, IRQ) associated with the device */
+	master = spi_alloc_master(&dev->dev, sizeof(struct mpc83xx_spi));
+
+	if (master == NULL) {
+		ret = -ENOMEM;
+		goto err;
+	}
+
+	platform_set_drvdata(dev, master);
+	pdata = dev->dev.platform_data;
+
+	if (pdata == NULL) {
+		ret = -ENODEV;
+		goto free_master;
+	}
+
+	r = platform_get_resource(dev, IORESOURCE_MEM, 0);
+	if (r == NULL) {
+		ret = -ENODEV;
+		goto free_master;
+	}
+
+	mpc83xx_spi = spi_master_get_devdata(master);
+	mpc83xx_spi->bitbang.master = spi_master_get(master);
+	mpc83xx_spi->bitbang.chipselect = mpc83xx_spi_chipselect;
+	mpc83xx_spi->bitbang.setup_transfer = mpc83xx_spi_setup_transfer;
+	mpc83xx_spi->bitbang.txrx_bufs = mpc83xx_spi_bufs;
+	mpc83xx_spi->sysclk = pdata->sysclk;
+	mpc83xx_spi->activate_cs = pdata->activate_cs;
+	mpc83xx_spi->deactivate_cs = pdata->deactivate_cs;
+	mpc83xx_spi->get_rx = mpc83xx_spi_rx_buf_u8;
+	mpc83xx_spi->get_tx = mpc83xx_spi_tx_buf_u8;
+
+	mpc83xx_spi->bitbang.master->setup = mpc83xx_spi_setup;
+	init_completion(&mpc83xx_spi->done);
+
+	mpc83xx_spi->base = ioremap(r->start, r->end - r->start + 1);
+	if (mpc83xx_spi->base == NULL) {
+		ret = -ENOMEM;
+		goto put_master;
+	}
+
+	mpc83xx_spi->irq = platform_get_irq(dev, 0);
+
+	if (mpc83xx_spi->irq < 0) {
+		ret = -ENXIO;
+		goto unmap_io;
+	}
+
+	/* Register for SPI Interrupt */
+	ret = request_irq(mpc83xx_spi->irq, mpc83xx_spi_irq,
+			  0, "mpc83xx_spi", mpc83xx_spi);
+
+	if (ret != 0)
+		goto unmap_io;
+
+	master->bus_num = pdata->bus_num;
+	master->num_chipselect = pdata->max_chipselect;
+
+	/* SPI controller initializations */
+	mpc83xx_spi_write_reg(&mpc83xx_spi->base->mode, 0);
+	mpc83xx_spi_write_reg(&mpc83xx_spi->base->mask, 0);
+	mpc83xx_spi_write_reg(&mpc83xx_spi->base->command, 0);
+	mpc83xx_spi_write_reg(&mpc83xx_spi->base->event, 0xffffffff);
+
+	/* Enable SPI interface */
+	regval = pdata->initial_spmode | SPMODE_INIT_VAL | SPMODE_ENABLE;
+	mpc83xx_spi_write_reg(&mpc83xx_spi->base->mode, regval);
+
+	ret = spi_bitbang_start(&mpc83xx_spi->bitbang);
+
+	if (ret != 0)
+		goto free_irq;
+
+	printk(KERN_INFO
+	       "%s: MPC83xx SPI Controller driver at 0x%p (irq = %d)\n",
+	       dev->dev.bus_id, mpc83xx_spi->base, mpc83xx_spi->irq);
+
+	return ret;
+
+free_irq:
+	free_irq(mpc83xx_spi->irq, mpc83xx_spi);
+unmap_io:
+	iounmap(mpc83xx_spi->base);
+put_master:
+	spi_master_put(master);
+free_master:
+	kfree(master);
+err:
+	return ret;
+}
+
+static int __devexit mpc83xx_spi_remove(struct platform_device *dev)
+{
+	struct mpc83xx_spi *mpc83xx_spi;
+	struct spi_master *master;
+
+	master = platform_get_drvdata(dev);
+	mpc83xx_spi = spi_master_get_devdata(master);
+
+	spi_bitbang_stop(&mpc83xx_spi->bitbang);
+	free_irq(mpc83xx_spi->irq, mpc83xx_spi);
+	iounmap(mpc83xx_spi->base);
+	spi_master_put(mpc83xx_spi->bitbang.master);
+
+	return 0;
+}
+
+static struct platform_driver mpc83xx_spi_driver = {
+	.probe = mpc83xx_spi_probe,
+	.remove = __devexit_p(mpc83xx_spi_remove),
+	.driver = {
+		   .name = "mpc83xx_spi",
+	},
+};
+
+static int __init mpc83xx_spi_init(void)
+{
+	return platform_driver_register(&mpc83xx_spi_driver);
+}
+
+static void __exit mpc83xx_spi_exit(void)
+{
+	platform_driver_unregister(&mpc83xx_spi_driver);
+}
+
+module_init(mpc83xx_spi_init);
+module_exit(mpc83xx_spi_exit);
+
+MODULE_AUTHOR("Kumar Gala");
+MODULE_DESCRIPTION("Simple MPC83xx SPI Driver");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/fsl_devices.h b/include/linux/fsl_devices.h
index a3a0e07..121b860 100644
--- a/include/linux/fsl_devices.h
+++ b/include/linux/fsl_devices.h
@@ -110,5 +110,16 @@ struct fsl_usb2_platform_data {
 #define FSL_USB2_PORT0_ENABLED	0x00000001
 #define FSL_USB2_PORT1_ENABLED	0x00000002
 
+struct fsl_spi_platform_data {
+	u32 	initial_spmode;	/* initial SPMODE value */
+	u16	bus_num;	
+
+	/* board specific information */
+	u16	max_chipselect;
+	void	(*activate_cs)(u8 cs, u8 polarity);
+	void	(*deactivate_cs)(u8 cs, u8 polarity);
+	u32	sysclk;
+};
+
 #endif				/* _FSL_DEVICE_H_ */
 #endif				/* __KERNEL__ */


^ permalink raw reply related	[flat|nested] 18+ messages in thread

end of thread, other threads:[~2006-04-11 14:41 UTC | newest]

Thread overview: 18+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2006-04-06 18:30 [PATCH] spi: Added spi master driver for Freescale MPC83xx SPI controller Kumar Gala
2006-04-07  5:22 ` David Brownell
2006-04-07  9:16   ` [spi-devel-general] " Vitaly Wool
2006-04-07 16:09     ` David Brownell
2006-04-07 17:04       ` Kumar Gala
2006-04-08  1:25         ` David Brownell
2006-04-07 14:04   ` Kumar Gala
2006-04-07 15:54     ` David Brownell
2006-04-07 16:44       ` Kumar Gala
2006-04-10 17:38 ` [PATCH][UPDATE] " Kumar Gala
2006-04-10 19:01   ` David Brownell
2006-04-10 19:05     ` Kumar Gala
2006-04-10 19:17       ` [PATCH][2.16.17-rc1-mm2] " Kumar Gala
2006-04-10 20:03         ` [spi-devel-general] " Vitaly Wool
2006-04-10 20:22           ` Kumar Gala
2006-04-10 21:06             ` David Brownell
2006-04-10 21:10               ` Kumar Gala
2006-04-11 14:39         ` [PATCH][2.16.17-rc1-mm2][UPDATE] " Kumar Gala

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).