--- drivers/spi/mpc5200_spi.c | 160 +++++++++++++++++++++++++++++----------------- 1 file changed, 103 insertions(+), 57 deletions(-) Index: work-powerpc.git/drivers/spi/mpc5200_spi.c =================================================================== --- work-powerpc.git.orig/drivers/spi/mpc5200_spi.c +++ work-powerpc.git/drivers/spi/mpc5200_spi.c @@ -25,6 +25,7 @@ #include #include +#include #define SPI_CTRL1 0x0 #define SPI_CTRL1_SPIE (1 << 7) @@ -40,7 +41,7 @@ #define SPI_CTRL2_SPC0 (1 << 0) #define SPI_BAUD 0x4 -#define BAUD_SPPR_SHIFT 3 +#define BAUD_SPPR_SHIFT 4 #define BAUD_SPR_SHIFT 0 #define SPI_STATUS 0x5 @@ -66,8 +67,8 @@ struct mpc5200_spi { /* data buffers */ const unsigned char *tx; unsigned char *rx; - int len; - int count; + int len; /* buffer length */ + int count; /* position in buffer */ unsigned int speed_hz; }; @@ -107,7 +108,7 @@ static int mpc5200_spi_setupxfer(struct struct spi_transfer *t) { struct mpc5200_spi *port = spi_master_get_devdata(spi->master); - unsigned int bpw, hz, div, spr, sppr, act_div; + unsigned int bpw, hz, div, spr, sppr; bpw = t ? t->bits_per_word : spi->bits_per_word; hz = t ? t->speed_hz : spi->max_speed_hz; @@ -115,8 +116,8 @@ static int mpc5200_spi_setupxfer(struct /* FIXME: This is a limitation of our particular hardware, * and should not be in the driver */ - if(hz > 10000000) - hz = 10000000; +// if(hz > 10000000) +// hz = 10000000; if (bpw != 8) { dev_err(&spi->dev, "invalid bits-per-word (%d)\n", bpw); @@ -125,27 +126,27 @@ static int mpc5200_spi_setupxfer(struct div = port->ipb_freq / hz; + /* + * divisor = (sppr+1) * (1<<(spr+1)) + * spr and sppr are 0..7 + * sppr should be as big as possible + */ + /* 3 bits for sppr size, 1 for spr+1, 1 because fls starts with 1 */ spr = 0; - sppr = 7; - - while (spr < 7) { - act_div = (sppr + 1) * (2 << (spr + 1)); - if (port->ipb_freq / act_div <= hz) - break; + while (div > 8) { spr++; + div /= 2; } + spr = spr - 1; + sppr = div - 1; + if (spr > 7) + spr = sppr = 7; - sppr = 0; - while (sppr < 7) { - act_div = (sppr + 1) * (2 << (spr + 1)); - if (port->ipb_freq / act_div <= hz) - break; - sppr++; - } + port->speed_hz = port->ipb_freq / ((sppr + 1) * (1 << (spr + 1))); - port->speed_hz = port->ipb_freq / ((sppr + 1) * (2 << (spr + 1))); + dev_info(&spi->dev, "sppr: %i, spr %i, div: %i, ffs(div): %i, rate: %i\n", sppr, spr, div, fls(div), port->ipb_freq / (sppr + 1) / (1 << (spr + 1))); - dev_dbg(&spi->dev, "%s speed wanted: %d speed: %d\n",__FUNCTION__, hz, port->ipb_freq / (sppr + 1) / (2 << (spr + 1)) ); + dev_dbg(&spi->dev, "%s speed wanted: %d speed: %d\n",__FUNCTION__, hz, port->ipb_freq / (sppr + 1) / (1 << (spr + 1)) ); writeb( (sppr << BAUD_SPPR_SHIFT) | (spr << BAUD_SPR_SHIFT), port->base + SPI_BAUD); return 0; @@ -177,6 +178,7 @@ static int mpc5200_spi_txrx(struct spi_d struct mpc5200_spi *port = spi_master_get_devdata(spi->master); int count = 0; unsigned int status; + u8 data = 0; dev_dbg(&spi->dev, "txrx: tx %p, rx %p, len %d\n", t->tx_buf, t->rx_buf, t->len); @@ -185,44 +187,89 @@ static int mpc5200_spi_txrx(struct spi_d port->rx = t->rx_buf; port->len = t->len; - while(count < t->len) { - unsigned char data; - unsigned long start; - - writeb(hw_txbyte(port, count), port->base + SPI_DATA); - - start = jiffies; - do { - status = readb(port->base + SPI_STATUS); - if (status & SPI_STATUS_WCOL) { - dev_err(&spi->dev, "Write collision on master only bus?\n"); - goto out; - } - if(time_after(jiffies, start + msecs_to_jiffies(10000))) { - dev_err(&spi->dev, "SPI timeout\n"); - goto out; - } - } while (!(status & SPI_STATUS_SPIF)); - - data = readb(port->base + SPI_DATA); - - if (port->rx) - port->rx[count] = data; - - count++; - - if (port->speed_hz < 200000) - udelay(1000000 / port->speed_hz); - } - - dev_dbg(&spi->dev, "transfer done\n"); -out: - return count; + /* interrupt mode for slow speeds */ + if (port->speed_hz < 2000000) { + port->count = 0; + out_8(port->base + SPI_CTRL1, in_8(port->base + SPI_CTRL1) | SPI_CTRL1_SPIE); + + /* just write the first character, IRQ handler takes from here */ + if (port->tx) + data = port->tx[port->count]; + + init_completion(&port->done); + + out_8(port->base + SPI_DATA, data); + + wait_for_completion_interruptible(&port->done); + } else { + /* polling for higher speeds */ + out_8(port->base + SPI_CTRL1, in_8(port->base + SPI_CTRL1) & ~SPI_CTRL1_SPIE); + while(count < t->len) { + unsigned long start; + + writeb(hw_txbyte(port, count), port->base + SPI_DATA); + + start = jiffies; + do { + status = readb(port->base + SPI_STATUS); + if (status & SPI_STATUS_WCOL) { + dev_err(&spi->dev, "Write collision on master only bus?\n"); + goto out; + } + if(time_after(jiffies, start + msecs_to_jiffies(10000))) { + dev_err(&spi->dev, "SPI timeout\n"); + goto out; + } + } while (!(status & SPI_STATUS_SPIF)); + + data = readb(port->base + SPI_DATA); + + if (port->rx) + port->rx[count] = data; + + count++; + + /* 1 spiclk cycle delay (specs say about 0.5) fix */ + __delay(tb_ticks_per_sec / port->speed_hz / 2); + } + out: + port->count = count; + } + return port->count; } static irqreturn_t mpc5200_spi_irq(int irq, void *dev) { - printk("%s\n",__FUNCTION__); + struct mpc5200_spi *port = dev; + u8 status; + u8 data; + + data = in_8(port->base + SPI_DATA); + port->rx[port->count++] = data; + + /* read status before possible return, so irq flag is cleared */ + status = in_8(port->base + SPI_STATUS); + + /* it seems we get interrupt after last transfered byte */ + if (status == 0) + return IRQ_NONE; + + if (status != SPI_STATUS_SPIF) + printk(KERN_ALERT "%s: %i status: %x count: %i/%i\n", __func__, __LINE__, + status, port->count, port->len); + + if (port->count == port->len) { + complete(&port->done); + return IRQ_HANDLED; + } + + /* delay 0.5 clock cycle on < 200kHz speeds, as specs/errata say */ + if (port->speed_hz < 200000) + __delay(tb_ticks_per_sec / port->speed_hz / 2); + + data = port->tx[port->count]; + out_8(port->base + SPI_DATA, data); + return IRQ_HANDLED; } @@ -259,8 +306,7 @@ mpc5200_spi_probe(struct of_device *odev * but is it even worth implementing this? * spi@f00 has only 1 byte fifo, so maybe for low speeds it makes sense */ - //port->irq = irq_of_parse_and_map(odev->node, 1); - port->irq = irq_of_parse_and_map(odev->node, 0); + port->irq = irq_of_parse_and_map(odev->node, 1); port->base = ioremap(res.start, res.end - res.start + 1); if (!port->base) {