diff options
Diffstat (limited to 'drivers/spi/mpc8xx_spi.c')
-rw-r--r-- | drivers/spi/mpc8xx_spi.c | 113 |
1 files changed, 93 insertions, 20 deletions
diff --git a/drivers/spi/mpc8xx_spi.c b/drivers/spi/mpc8xx_spi.c index 5c8d7609351..e1448cc6196 100644 --- a/drivers/spi/mpc8xx_spi.c +++ b/drivers/spi/mpc8xx_spi.c @@ -18,6 +18,7 @@ #include <common.h> #include <dm.h> +#include <malloc.h> #include <mpc8xx.h> #include <spi.h> #include <linux/delay.h> @@ -29,7 +30,8 @@ #define CPM_SPI_BASE_RX CPM_SPI_BASE #define CPM_SPI_BASE_TX (CPM_SPI_BASE + sizeof(cbd_t)) -#define MAX_BUFFER 0x104 +#define MAX_BUFFER 0x8000 /* Max possible is 0xffff. We want power of 2 */ +#define MIN_HWORD_XFER 64 /* Minimum size for 16 bits transfer */ struct mpc8xx_priv { spi_t __iomem *spi; @@ -37,6 +39,8 @@ struct mpc8xx_priv { int max_cs; }; +static char dummy_buffer[MAX_BUFFER]; + static int mpc8xx_spi_set_mode(struct udevice *dev, uint mod) { return 0; @@ -44,6 +48,21 @@ static int mpc8xx_spi_set_mode(struct udevice *dev, uint mod) static int mpc8xx_spi_set_speed(struct udevice *dev, uint speed) { + immap_t __iomem *immr = (immap_t __iomem *)CONFIG_SYS_IMMR; + cpm8xx_t __iomem *cp = &immr->im_cpm; + u8 pm = (gd->arch.brg_clk - 1) / (speed * 16); + + if (pm > 16) { + setbits_be16(&cp->cp_spmode, SPMODE_DIV16); + pm /= 16; + if (pm > 16) + pm = 16; + } else { + clrbits_be16(&cp->cp_spmode, SPMODE_DIV16); + } + + clrsetbits_be16(&cp->cp_spmode, SPMODE_PM(0xf), SPMODE_PM(pm)); + return 0; } @@ -101,10 +120,6 @@ static int mpc8xx_spi_probe(struct udevice *dev) while (in_be16(&cp->cp_cpcr) & CPM_CR_FLG) ; -/* 5 */ - /* Set SDMA configuration register */ - out_be32(&immr->im_siu_conf.sc_sdcr, 0x0001); - /* 6 */ /* Set to big endian. */ out_8(&spi->spi_tfcr, SMC_EB); @@ -145,37 +160,52 @@ static void mpc8xx_spi_cs_deactivate(struct udevice *dev) dm_gpio_set_value(&priv->gpios[platdata->cs], 0); } -static int mpc8xx_spi_xfer(struct udevice *dev, unsigned int bitlen, - const void *dout, void *din, unsigned long flags) +static int mpc8xx_spi_xfer_one(struct udevice *dev, size_t count, + const void *dout, void *din) { immap_t __iomem *immr = (immap_t __iomem *)CONFIG_SYS_IMMR; cpm8xx_t __iomem *cp = &immr->im_cpm; cbd_t __iomem *tbdf, *rbdf; + void *bufout, *bufin; + u16 spmode_len; int tm; - size_t count = (bitlen + 7) / 8; - - if (count > MAX_BUFFER) - return -EINVAL; tbdf = (cbd_t __iomem *)&cp->cp_dpmem[CPM_SPI_BASE_TX]; rbdf = (cbd_t __iomem *)&cp->cp_dpmem[CPM_SPI_BASE_RX]; - /* Set CS for device */ - if (flags & SPI_XFER_BEGIN) - mpc8xx_spi_cs_activate(dev); + if (!(count & 1) && count >= MIN_HWORD_XFER) { + spmode_len = SPMODE_LEN(16); + if (dout) { + int i; + + bufout = malloc(count); + for (i = 0; i < count; i += 2) + *(u16 *)(bufout + i) = swab16(*(u16 *)(dout + i)); + } else { + bufout = NULL; + } + if (din) + bufin = malloc(count); + else + bufin = NULL; + } else { + spmode_len = SPMODE_LEN(8); + bufout = (void *)dout; + bufin = din; + } /* Setting tx bd status and data length */ - out_be32(&tbdf->cbd_bufaddr, (ulong)dout); + out_be32(&tbdf->cbd_bufaddr, bufout ? (ulong)bufout : (ulong)dummy_buffer); out_be16(&tbdf->cbd_sc, BD_SC_READY | BD_SC_LAST | BD_SC_WRAP); out_be16(&tbdf->cbd_datlen, count); /* Setting rx bd status and data length */ - out_be32(&rbdf->cbd_bufaddr, (ulong)din); + out_be32(&rbdf->cbd_bufaddr, bufin ? (ulong)bufin : (ulong)dummy_buffer); out_be16(&rbdf->cbd_sc, BD_SC_EMPTY | BD_SC_WRAP); out_be16(&rbdf->cbd_datlen, 0); /* rx length has no significance */ - clrsetbits_be16(&cp->cp_spmode, ~SPMODE_LOOP, SPMODE_REV | SPMODE_MSTR | - SPMODE_EN | SPMODE_LEN(8) | SPMODE_PM(0x8)); + clrsetbits_be16(&cp->cp_spmode, ~(SPMODE_LOOP | SPMODE_PM(0xf) | SPMODE_DIV16), + SPMODE_REV | SPMODE_MSTR | SPMODE_EN | spmode_len); out_8(&cp->cp_spim, 0); /* Mask all SPI events */ out_8(&cp->cp_spie, SPI_EMASK); /* Clear all SPI events */ @@ -196,13 +226,56 @@ static int mpc8xx_spi_xfer(struct udevice *dev, unsigned int bitlen, } if (tm >= 1000) - printf("*** spi_xfer: Time out while xferring to/from SPI!\n"); + return -ETIMEDOUT; + + if (!(count & 1) && count > MIN_HWORD_XFER) { + if (dout) + free(bufout); + if (din) { + int i; + + bufout = malloc(count); + for (i = 0; i < count; i += 2) + *(u16 *)(din + i) = swab16(*(u16 *)(bufin + i)); + free(bufin); + } + } + + return 0; +} + +static int mpc8xx_spi_xfer(struct udevice *dev, unsigned int bitlen, + const void *dout, void *din, unsigned long flags) +{ + size_t count = (bitlen + 7) / 8; + size_t offset = 0; + int ret = 0; + + if (!din && !dout) + return -EINVAL; + + /* Set CS for device */ + if (flags & SPI_XFER_BEGIN) + mpc8xx_spi_cs_activate(dev); + + while (count > 0 && !ret) { + size_t chunk = min(count, (size_t)MAX_BUFFER); + const void *out = dout ? dout + offset : NULL; + void *in = din ? din + offset : NULL; + + ret = mpc8xx_spi_xfer_one(dev, chunk, out, in); + offset += chunk; + count -= chunk; + } /* Clear CS for device */ if (flags & SPI_XFER_END) mpc8xx_spi_cs_deactivate(dev); - return 0; + if (ret) + printf("*** spi_xfer: Time out while xferring to/from SPI!\n"); + + return ret; } static int mpc8xx_spi_ofdata_to_platdata(struct udevice *dev) |