summaryrefslogtreecommitdiff
path: root/drivers/spi/mpc8xx_spi.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/spi/mpc8xx_spi.c')
-rw-r--r--drivers/spi/mpc8xx_spi.c113
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)