Hi Mark, On Tuesday 20 August 2013 06:55 PM, Sourav Poddar wrote: > The patch add basic support for the quad spi controller. > > QSPI is a kind of spi module that allows single, > dual and quad read access to external spi devices. The module > has a memory mapped interface which provide direct interface > for accessing data form external spi devices. > > The patch will configure controller clocks, device control > register and for defining low level transfer apis which > will be used by the spi framework to transfer data to > the slave spi device(flash in this case). > > Test details: > ------------- > Tested this on dra7 board. > Test1: Ran mtd_stesstest for 40000 iterations. > - All iterations went through without failure. > Test2: Use mtd utilities: > - flash_erase to erase the flash device > - mtd_debug read to read data back. > - mtd_debug write to write to the data flash. > diff between the write and read data shows zero. > > Acked-by: Felipe Balbi<ba...@ti.com> > Reviewed-by: Felipe Balbi<ba...@ti.com> > Signed-off-by: Sourav Poddar<sourav.pod...@ti.com> > --- Can these patch be picked for v3.12?
Note: this patch depends on patch[1] . [1]: http://comments.gmane.org/gmane.linux.kernel.samsung-soc/21329 > v10->v11: > - Fix a typo > Left 3 warnings(80 characters) for better readibilty of code. > These was posted till now as a two patch series. > Dropping the 2nd patch as of now, will add once the support > for multiple data lines are added in SPI framework(the patch is > under review). > > Documentation/devicetree/bindings/spi/ti_qspi.txt | 22 + > drivers/spi/Kconfig | 8 + > drivers/spi/Makefile | 1 + > drivers/spi/spi-ti-qspi.c | 561 > +++++++++++++++++++++ > 4 files changed, 592 insertions(+), 0 deletions(-) > create mode 100644 Documentation/devicetree/bindings/spi/ti_qspi.txt > create mode 100644 drivers/spi/spi-ti-qspi.c > > diff --git a/Documentation/devicetree/bindings/spi/ti_qspi.txt > b/Documentation/devicetree/bindings/spi/ti_qspi.txt > new file mode 100644 > index 0000000..398ef59 > --- /dev/null > +++ b/Documentation/devicetree/bindings/spi/ti_qspi.txt > @@ -0,0 +1,22 @@ > +TI QSPI controller. > + > +Required properties: > +- compatible : should be "ti,dra7xxx-qspi". > +- reg: Should contain QSPI registers location and length. > +- #address-cells, #size-cells : Must be present if the device has sub-nodes > +- ti,hwmods: Name of the hwmod associated to the QSPI > + > +Recommended properties: > +- spi-max-frequency: Definition as per > + Documentation/devicetree/bindings/spi/spi-bus.txt > + > +Example: > + > +qspi: qspi@4b300000 { > + compatible = "ti,dra7xxx-qspi"; > + reg =<0x4b300000 0x100>; > + #address-cells =<1>; > + #size-cells =<0>; > + spi-max-frequency =<25000000>; > + ti,hwmods = "qspi"; > +}; > diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig > index 92a9345..1c4e758 100644 > --- a/drivers/spi/Kconfig > +++ b/drivers/spi/Kconfig > @@ -285,6 +285,14 @@ config SPI_OMAP24XX > SPI master controller for OMAP24XX and later Multichannel SPI > (McSPI) modules. > > +config SPI_TI_QSPI > + tristate "DRA7xxx QSPI controller support" > + depends on ARCH_OMAP2PLUS || COMPILE_TEST > + help > + QSPI master controller for DRA7xxx used for flash devices. > + This device supports single, dual and quad read support, while > + it only supports single write mode. > + > config SPI_OMAP_100K > tristate "OMAP SPI 100K" > depends on ARCH_OMAP850 || ARCH_OMAP730 > diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile > index 33f9c09..a174030 100644 > --- a/drivers/spi/Makefile > +++ b/drivers/spi/Makefile > @@ -46,6 +46,7 @@ obj-$(CONFIG_SPI_OCTEON) += spi-octeon.o > obj-$(CONFIG_SPI_OMAP_UWIRE) += spi-omap-uwire.o > obj-$(CONFIG_SPI_OMAP_100K) += spi-omap-100k.o > obj-$(CONFIG_SPI_OMAP24XX) += spi-omap2-mcspi.o > +obj-$(CONFIG_SPI_TI_QSPI) += spi-ti-qspi.o > obj-$(CONFIG_SPI_ORION) += spi-orion.o > obj-$(CONFIG_SPI_PL022) += spi-pl022.o > obj-$(CONFIG_SPI_PPC4xx) += spi-ppc4xx.o > diff --git a/drivers/spi/spi-ti-qspi.c b/drivers/spi/spi-ti-qspi.c > new file mode 100644 > index 0000000..09e2415 > --- /dev/null > +++ b/drivers/spi/spi-ti-qspi.c > @@ -0,0 +1,561 @@ > +/* > + * TI QSPI driver > + * > + * Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.com > + * Author: Sourav Poddar<sourav.pod...@ti.com> > + * > + * This program is free software; you can redistribute it and/or > + * modify it under the terms of the GPLv2. > + * > + * This program is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR /PURPOSE. See the > + * GNU General Public License for more details. > + */ > + > +#include<linux/kernel.h> > +#include<linux/init.h> > +#include<linux/interrupt.h> > +#include<linux/module.h> > +#include<linux/device.h> > +#include<linux/delay.h> > +#include<linux/dma-mapping.h> > +#include<linux/dmaengine.h> > +#include<linux/omap-dma.h> > +#include<linux/platform_device.h> > +#include<linux/err.h> > +#include<linux/clk.h> > +#include<linux/io.h> > +#include<linux/slab.h> > +#include<linux/pm_runtime.h> > +#include<linux/of.h> > +#include<linux/of_device.h> > +#include<linux/pinctrl/consumer.h> > + > +#include<linux/spi/spi.h> > + > +struct ti_qspi_regs { > + u32 clkctrl; > +}; > + > +struct ti_qspi { > + struct completion transfer_complete; > + > + /* IRQ synchronization */ > + spinlock_t lock; > + > + /* list synchronization */ > + struct mutex list_lock; > + > + struct spi_master *master; > + void __iomem *base; > + struct clk *fclk; > + struct device *dev; > + > + struct ti_qspi_regs ctx_reg; > + > + u32 spi_max_frequency; > + u32 cmd; > + u32 dc; > + u32 stat; > +}; > + > +#define QSPI_PID (0x0) > +#define QSPI_SYSCONFIG (0x10) > +#define QSPI_INTR_STATUS_RAW_SET (0x20) > +#define QSPI_INTR_STATUS_ENABLED_CLEAR (0x24) > +#define QSPI_INTR_ENABLE_SET_REG (0x28) > +#define QSPI_INTR_ENABLE_CLEAR_REG (0x2c) > +#define QSPI_SPI_CLOCK_CNTRL_REG (0x40) > +#define QSPI_SPI_DC_REG (0x44) > +#define QSPI_SPI_CMD_REG (0x48) > +#define QSPI_SPI_STATUS_REG (0x4c) > +#define QSPI_SPI_DATA_REG (0x50) > +#define QSPI_SPI_SETUP0_REG (0x54) > +#define QSPI_SPI_SWITCH_REG (0x64) > +#define QSPI_SPI_SETUP1_REG (0x58) > +#define QSPI_SPI_SETUP2_REG (0x5c) > +#define QSPI_SPI_SETUP3_REG (0x60) > +#define QSPI_SPI_DATA_REG_1 (0x68) > +#define QSPI_SPI_DATA_REG_2 (0x6c) > +#define QSPI_SPI_DATA_REG_3 (0x70) > + > +#define QSPI_COMPLETION_TIMEOUT msecs_to_jiffies(2000) > + > +#define QSPI_FCLK 192000000 > + > +/* Clock Control */ > +#define QSPI_CLK_EN (1<< 31) > +#define QSPI_CLK_DIV_MAX 0xffff > + > +/* Command */ > +#define QSPI_EN_CS(n) (n<< 28) > +#define QSPI_WLEN(n) ((n - 1)<< 19) > +#define QSPI_3_PIN (1<< 18) > +#define QSPI_RD_SNGL (1<< 16) > +#define QSPI_WR_SNGL (2<< 16) > +#define QSPI_RD_DUAL (3<< 16) > +#define QSPI_RD_QUAD (7<< 16) > +#define QSPI_INVAL (4<< 16) > +#define QSPI_WC_CMD_INT_EN (1<< 14) > +#define QSPI_FLEN(n) ((n - 1)<< 0) > + > +/* STATUS REGISTER */ > +#define WC 0x02 > + > +/* INTERRUPT REGISTER */ > +#define QSPI_WC_INT_EN (1<< 1) > +#define QSPI_WC_INT_DISABLE (1<< 1) > + > +/* Device Control */ > +#define QSPI_DD(m, n) (m<< (3 + n * 8)) > +#define QSPI_CKPHA(n) (1<< (2 + n * 8)) > +#define QSPI_CSPOL(n) (1<< (1 + n * 8)) > +#define QSPI_CKPOL(n) (1<< (n * 8)) > + > +#define QSPI_FRAME 4096 > + > +#define QSPI_AUTOSUSPEND_TIMEOUT 2000 > + > +static inline unsigned long ti_qspi_read(struct ti_qspi *qspi, > + unsigned long reg) > +{ > + return readl(qspi->base + reg); > +} > + > +static inline void ti_qspi_write(struct ti_qspi *qspi, > + unsigned long val, unsigned long reg) > +{ > + writel(val, qspi->base + reg); > +} > + > +static int ti_qspi_setup(struct spi_device *spi) > +{ > + struct ti_qspi *qspi = spi_master_get_devdata(spi->master); > + struct ti_qspi_regs *ctx_reg =&qspi->ctx_reg; > + int clk_div = 0, ret; > + u32 clk_ctrl_reg, clk_rate, clk_mask; > + > + if (spi->master->busy) { > + dev_dbg(qspi->dev, "master busy doing other trasnfers\n"); > + return -EBUSY; > + } > + > + if (!qspi->spi_max_frequency) { > + dev_err(qspi->dev, "spi max frequency not defined\n"); > + return -EINVAL; > + } > + > + clk_rate = clk_get_rate(qspi->fclk); > + > + clk_div = DIV_ROUND_UP(clk_rate, qspi->spi_max_frequency) - 1; > + > + if (clk_div< 0) { > + dev_dbg(qspi->dev, "clock divider< 0, using /1 divider\n"); > + return -EINVAL; > + } > + > + if (clk_div> QSPI_CLK_DIV_MAX) { > + dev_dbg(qspi->dev, "clock divider>%d , using /%d divider\n", > + QSPI_CLK_DIV_MAX, QSPI_CLK_DIV_MAX + 1); > + return -EINVAL; > + } > + > + dev_dbg(qspi->dev, "hz: %d, clock divider %d\n", > + qspi->spi_max_frequency, clk_div); > + > + ret = pm_runtime_get_sync(qspi->dev); > + if (ret) { > + dev_err(qspi->dev, "pm_runtime_get_sync() failed\n"); > + return ret; > + } > + > + clk_ctrl_reg = ti_qspi_read(qspi, QSPI_SPI_CLOCK_CNTRL_REG); > + > + clk_ctrl_reg&= ~QSPI_CLK_EN; > + > + /* disable SCLK */ > + ti_qspi_write(qspi, clk_ctrl_reg, QSPI_SPI_CLOCK_CNTRL_REG); > + > + /* enable SCLK */ > + clk_mask = QSPI_CLK_EN | clk_div; > + ti_qspi_write(qspi, clk_mask, QSPI_SPI_CLOCK_CNTRL_REG); > + ctx_reg->clkctrl = clk_mask; > + > + pm_runtime_mark_last_busy(qspi->dev); > + ret = pm_runtime_put_autosuspend(qspi->dev); > + if (ret< 0) { > + dev_err(qspi->dev, "pm_runtime_put_autosuspend() failed\n"); > + return ret; > + } > + > + return 0; > +} > + > +static void ti_qspi_restore_ctx(struct ti_qspi *qspi) > +{ > + struct ti_qspi_regs *ctx_reg =&qspi->ctx_reg; > + > + ti_qspi_write(qspi, ctx_reg->clkctrl, QSPI_SPI_CLOCK_CNTRL_REG); > +} > + > +static int qspi_write_msg(struct ti_qspi *qspi, struct spi_transfer *t) > +{ > + int wlen, count, ret; > + unsigned int cmd; > + const u8 *txbuf; > + > + txbuf = t->tx_buf; > + cmd = qspi->cmd | QSPI_WR_SNGL; > + count = t->len; > + wlen = t->bits_per_word; > + > + while (count) { > + switch (wlen) { > + case 8: > + dev_dbg(qspi->dev, "tx cmd %08x dc %08x data %02x\n", > + cmd, qspi->dc, *txbuf); > + writeb(*txbuf, qspi->base + QSPI_SPI_DATA_REG); > + ti_qspi_write(qspi, cmd, QSPI_SPI_CMD_REG); > + ret = > wait_for_completion_timeout(&qspi->transfer_complete, > + QSPI_COMPLETION_TIMEOUT); > + if (ret == 0) { > + dev_err(qspi->dev, "write timed out\n"); > + return -ETIMEDOUT; > + } > + txbuf += 1; > + count -= 1; > + break; > + case 16: > + dev_dbg(qspi->dev, "tx cmd %08x dc %08x data %04x\n", > + cmd, qspi->dc, *txbuf); > + writew(*((u16 *)txbuf), qspi->base + QSPI_SPI_DATA_REG); > + ti_qspi_write(qspi, cmd, QSPI_SPI_CMD_REG); > + ret = > wait_for_completion_timeout(&qspi->transfer_complete, > + QSPI_COMPLETION_TIMEOUT); > + if (ret == 0) { > + dev_err(qspi->dev, "write timed out\n"); > + return -ETIMEDOUT; > + } > + txbuf += 2; > + count -= 2; > + break; > + case 32: > + dev_dbg(qspi->dev, "tx cmd %08x dc %08x data %08x\n", > + cmd, qspi->dc, *txbuf); > + writel(*((u32 *)txbuf), qspi->base + QSPI_SPI_DATA_REG); > + ti_qspi_write(qspi, cmd, QSPI_SPI_CMD_REG); > + ret = > wait_for_completion_timeout(&qspi->transfer_complete, > + QSPI_COMPLETION_TIMEOUT); > + if (ret == 0) { > + dev_err(qspi->dev, "write timed out\n"); > + return -ETIMEDOUT; > + } > + txbuf += 4; > + count -= 4; > + break; > + } > + } > + > + return 0; > +} > + > +static int qspi_read_msg(struct ti_qspi *qspi, struct spi_transfer *t) > +{ > + int wlen, count, ret; > + unsigned int cmd; > + u8 *rxbuf; > + > + rxbuf = t->rx_buf; > + cmd = qspi->cmd | QSPI_RD_SNGL; > + count = t->len; > + wlen = t->bits_per_word; > + > + while (count) { > + dev_dbg(qspi->dev, "rx cmd %08x dc %08x\n", cmd, qspi->dc); > + ti_qspi_write(qspi, cmd, QSPI_SPI_CMD_REG); > + ret = wait_for_completion_timeout(&qspi->transfer_complete, > + QSPI_COMPLETION_TIMEOUT); > + if (ret == 0) { > + dev_err(qspi->dev, "read timed out\n"); > + return -ETIMEDOUT; > + } > + switch (wlen) { > + case 8: > + *rxbuf = readb(qspi->base + QSPI_SPI_DATA_REG); > + rxbuf += 1; > + count -= 1; > + break; > + case 16: > + *((u16 *)rxbuf) = readw(qspi->base + QSPI_SPI_DATA_REG); > + rxbuf += 2; > + count -= 2; > + break; > + case 32: > + *((u32 *)rxbuf) = readl(qspi->base + QSPI_SPI_DATA_REG); > + rxbuf += 4; > + count -= 4; > + break; > + } > + } > + > + return 0; > +} > + > +static int qspi_transfer_msg(struct ti_qspi *qspi, struct spi_transfer *t) > +{ > + int ret; > + > + if (t->tx_buf) { > + ret = qspi_write_msg(qspi, t); > + if (ret) { > + dev_dbg(qspi->dev, "Error while writing\n"); > + return ret; > + } > + } > + > + if (t->rx_buf) { > + ret = qspi_read_msg(qspi, t); > + if (ret) { > + dev_dbg(qspi->dev, "Error while reading\n"); > + return ret; > + } > + } > + > + return 0; > +} > + > +static int ti_qspi_start_transfer_one(struct spi_master *master, > + struct spi_message *m) > +{ > + struct ti_qspi *qspi = spi_master_get_devdata(master); > + struct spi_device *spi = m->spi; > + struct spi_transfer *t; > + int status = 0, ret; > + int frame_length; > + > + /* setup device control reg */ > + qspi->dc = 0; > + > + if (spi->mode& SPI_CPHA) > + qspi->dc |= QSPI_CKPHA(spi->chip_select); > + if (spi->mode& SPI_CPOL) > + qspi->dc |= QSPI_CKPOL(spi->chip_select); > + if (spi->mode& SPI_CS_HIGH) > + qspi->dc |= QSPI_CSPOL(spi->chip_select); > + > + frame_length = (m->frame_length<< 3) / spi->bits_per_word; > + > + frame_length = clamp(frame_length, 0, QSPI_FRAME); > + > + /* setup command reg */ > + qspi->cmd = 0; > + qspi->cmd |= QSPI_EN_CS(spi->chip_select); > + qspi->cmd |= QSPI_FLEN(frame_length); > + qspi->cmd |= QSPI_WC_CMD_INT_EN; > + > + ti_qspi_write(qspi, QSPI_WC_INT_EN, QSPI_INTR_ENABLE_SET_REG); > + ti_qspi_write(qspi, qspi->dc, QSPI_SPI_DC_REG); > + > + mutex_lock(&qspi->list_lock); > + > + list_for_each_entry(t,&m->transfers, transfer_list) { > + qspi->cmd |= QSPI_WLEN(t->bits_per_word); > + > + ret = qspi_transfer_msg(qspi, t); > + if (ret) { > + dev_dbg(qspi->dev, "transfer message failed\n"); > + return -EINVAL; > + } > + > + m->actual_length += t->len; > + } > + > + mutex_unlock(&qspi->list_lock); > + > + m->status = status; > + spi_finalize_current_message(master); > + > + ti_qspi_write(qspi, qspi->cmd | QSPI_INVAL, QSPI_SPI_CMD_REG); > + > + return status; > +} > + > +static irqreturn_t ti_qspi_isr(int irq, void *dev_id) > +{ > + struct ti_qspi *qspi = dev_id; > + u16 int_stat; > + > + irqreturn_t ret = IRQ_HANDLED; > + > + spin_lock(&qspi->lock); > + > + int_stat = ti_qspi_read(qspi, QSPI_INTR_STATUS_ENABLED_CLEAR); > + qspi->stat = ti_qspi_read(qspi, QSPI_SPI_STATUS_REG); > + > + if (!int_stat) { > + dev_dbg(qspi->dev, "No IRQ triggered\n"); > + ret = IRQ_NONE; > + goto out; > + } > + > + ret = IRQ_WAKE_THREAD; > + > + ti_qspi_write(qspi, QSPI_WC_INT_DISABLE, QSPI_INTR_ENABLE_CLEAR_REG); > + ti_qspi_write(qspi, QSPI_WC_INT_DISABLE, > + QSPI_INTR_STATUS_ENABLED_CLEAR); > + > +out: > + spin_unlock(&qspi->lock); > + > + return ret; > +} > + > +static irqreturn_t ti_qspi_threaded_isr(int this_irq, void *dev_id) > +{ > + struct ti_qspi *qspi = dev_id; > + unsigned long flags; > + > + spin_lock_irqsave(&qspi->lock, flags); > + > + if (qspi->stat& WC) > + complete(&qspi->transfer_complete); > + > + spin_unlock_irqrestore(&qspi->lock, flags); > + > + ti_qspi_write(qspi, QSPI_WC_INT_EN, QSPI_INTR_ENABLE_SET_REG); > + > + return IRQ_HANDLED; > +} > + > +static int ti_qspi_runtime_resume(struct device *dev) > +{ > + struct ti_qspi *qspi; > + struct spi_master *master; > + > + master = dev_get_drvdata(dev); > + qspi = spi_master_get_devdata(master); > + ti_qspi_restore_ctx(qspi); > + > + return 0; > +} > + > +static const struct of_device_id ti_qspi_match[] = { > + {.compatible = "ti,dra7xxx-qspi" }, > + {}, > +}; > +MODULE_DEVICE_TABLE(of, dra7xxx_qspi_match); > + > +static int ti_qspi_probe(struct platform_device *pdev) > +{ > + struct ti_qspi *qspi; > + struct spi_master *master; > + struct resource *r; > + struct device_node *np = pdev->dev.of_node; > + u32 max_freq; > + int ret = 0, num_cs, irq; > + > + master = spi_alloc_master(&pdev->dev, sizeof(*qspi)); > + if (!master) > + return -ENOMEM; > + > + master->mode_bits = SPI_CPOL | SPI_CPHA; > + > + master->bus_num = -1; > + master->flags = SPI_MASTER_HALF_DUPLEX; > + master->setup = ti_qspi_setup; > + master->auto_runtime_pm = true; > + master->transfer_one_message = ti_qspi_start_transfer_one; > + master->dev.of_node = pdev->dev.of_node; > + master->bits_per_word_mask = BIT(32 - 1) | BIT(16 - 1) | BIT(8 - 1); > + > + if (!of_property_read_u32(np, "num-cs",&num_cs)) > + master->num_chipselect = num_cs; > + > + platform_set_drvdata(pdev, master); > + > + qspi = spi_master_get_devdata(master); > + qspi->master = master; > + qspi->dev =&pdev->dev; > + > + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); > + > + irq = platform_get_irq(pdev, 0); > + if (irq< 0) { > + dev_err(&pdev->dev, "no irq resource?\n"); > + return irq; > + } > + > + spin_lock_init(&qspi->lock); > + mutex_init(&qspi->list_lock); > + > + qspi->base = devm_ioremap_resource(&pdev->dev, r); > + if (IS_ERR(qspi->base)) { > + ret = PTR_ERR(qspi->base); > + goto free_master; > + } > + > + ret = devm_request_threaded_irq(&pdev->dev, irq, ti_qspi_isr, > + ti_qspi_threaded_isr, 0, > + dev_name(&pdev->dev), qspi); > + if (ret< 0) { > + dev_err(&pdev->dev, "Failed to register ISR for IRQ %d\n", > + irq); > + goto free_master; > + } > + > + qspi->fclk = devm_clk_get(&pdev->dev, "fck"); > + if (IS_ERR(qspi->fclk)) { > + ret = PTR_ERR(qspi->fclk); > + dev_err(&pdev->dev, "could not get clk: %d\n", ret); > + } > + > + init_completion(&qspi->transfer_complete); > + > + pm_runtime_use_autosuspend(&pdev->dev); > + pm_runtime_set_autosuspend_delay(&pdev->dev, QSPI_AUTOSUSPEND_TIMEOUT); > + pm_runtime_enable(&pdev->dev); > + > + if (!of_property_read_u32(np, "spi-max-frequency",&max_freq)) > + qspi->spi_max_frequency = max_freq; > + > + ret = spi_register_master(master); > + if (ret) > + goto free_master; > + > + return 0; > + > +free_master: > + spi_master_put(master); > + return ret; > +} > + > +static int ti_qspi_remove(struct platform_device *pdev) > +{ > + struct ti_qspi *qspi = platform_get_drvdata(pdev); > + > + spi_unregister_master(qspi->master); > + > + return 0; > +} > + > +static const struct dev_pm_ops ti_qspi_pm_ops = { > + .runtime_resume = ti_qspi_runtime_resume, > +}; > + > +static struct platform_driver ti_qspi_driver = { > + .probe = ti_qspi_probe, > + .remove = ti_qspi_remove, > + .driver = { > + .name = "ti,dra7xxx-qspi", > + .owner = THIS_MODULE, > + .pm =&ti_qspi_pm_ops, > + .of_match_table = ti_qspi_match, > + } > +}; > + > +module_platform_driver(ti_qspi_driver); > + > +MODULE_AUTHOR("Sourav Poddar<sourav.pod...@ti.com>"); > +MODULE_LICENSE("GPL v2"); > +MODULE_DESCRIPTION("TI QSPI controller driver"); ------------------------------------------------------------------------------ Introducing Performance Central, a new site from SourceForge and AppDynamics. Performance Central is your source for news, insights, analysis and resources for efficient Application Performance Management. Visit us today! http://pubads.g.doubleclick.net/gampad/clk?id=48897511&iu=/4140/ostg.clktrk _______________________________________________ spi-devel-general mailing list spi-devel-general@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/spi-devel-general