summaryrefslogtreecommitdiffstats
path: root/drivers/spi
diff options
context:
space:
mode:
authorSascha Hauer <s.hauer@pengutronix.de>2012-11-16 14:02:49 +0100
committerSascha Hauer <s.hauer@pengutronix.de>2012-11-16 14:02:49 +0100
commit0bf6b41cfa98a68b5c59c372bb5ab067b1e98449 (patch)
tree66266ef1e234ddb3f33816af571403906421de76 /drivers/spi
parent8d094bcab0580595765e8ce216c4d23d3c6823e2 (diff)
parent630c4ecc5e0a9ddf533de260daa7b414b5c0d0b0 (diff)
downloadbarebox-0bf6b41cfa98a68b5c59c372bb5ab067b1e98449.tar.gz
barebox-0bf6b41cfa98a68b5c59c372bb5ab067b1e98449.tar.xz
Merge branch 'for-next/spi'
Diffstat (limited to 'drivers/spi')
-rw-r--r--drivers/spi/atmel_spi.c260
-rw-r--r--drivers/spi/omap3_spi.c14
-rw-r--r--drivers/spi/spi.c12
3 files changed, 209 insertions, 77 deletions
diff --git a/drivers/spi/atmel_spi.c b/drivers/spi/atmel_spi.c
index 43aec8e30b..793933eefb 100644
--- a/drivers/spi/atmel_spi.c
+++ b/drivers/spi/atmel_spi.c
@@ -66,6 +66,106 @@ static inline bool atmel_spi_is_v2(void)
return !cpu_is_at91rm9200();
}
+/*
+ * Earlier SPI controllers (e.g. on at91rm9200) have a design bug whereby
+ * they assume that spi slave device state will not change on deselect, so
+ * that automagic deselection is OK. ("NPCSx rises if no data is to be
+ * transmitted") Not so! Workaround uses nCSx pins as GPIOs; or newer
+ * controllers have CSAAT and friends.
+ *
+ * Since the CSAAT functionality is a bit weird on newer controllers as
+ * well, we use GPIO to control nCSx pins on all controllers, updating
+ * MR.PCS to avoid confusing the controller. Using GPIOs also lets us
+ * support active-high chipselects despite the controller's belief that
+ * only active-low devices/systems exists.
+ *
+ * However, at91rm9200 has a second erratum whereby nCS0 doesn't work
+ * right when driven with GPIO. ("Mode Fault does not allow more than one
+ * Master on Chip Select 0.") No workaround exists for that ... so for
+ * nCS0 on that chip, we (a) don't use the GPIO, (b) can't support CS_HIGH,
+ * and (c) will trigger that first erratum in some cases.
+ *
+ * TODO: Test if the atmel_spi_is_v2() branch below works on
+ * AT91RM9200 if we use some other register than CSR0. However, don't
+ * do this unconditionally since AP7000 has an errata where the BITS
+ * field in CSR0 overrides all other CSRs.
+ */
+
+static void cs_activate(struct atmel_spi *as, struct spi_device *spi)
+{
+ struct spi_master *master = &as->master;
+ int npcs_pin;
+ unsigned active = spi->mode & SPI_CS_HIGH;
+ u32 mr, csr;
+
+ BUG_ON(spi->chip_select >= master->num_chipselect);
+ npcs_pin = as->cs_pins[spi->chip_select];
+
+ csr = (u32)spi->controller_data;
+
+ if (atmel_spi_is_v2()) {
+ /*
+ * Always use CSR0. This ensures that the clock
+ * switches to the correct idle polarity before we
+ * toggle the CS.
+ */
+ spi_writel(as, CSR0, csr);
+ spi_writel(as, MR, SPI_BF(PCS, 0x0e) | SPI_BIT(MODFDIS)
+ | SPI_BIT(MSTR));
+ mr = spi_readl(as, MR);
+ gpio_set_value(npcs_pin, active);
+ } else {
+ u32 cpol = (spi->mode & SPI_CPOL) ? SPI_BIT(CPOL) : 0;
+ int i;
+ u32 csr;
+
+ /* Make sure clock polarity is correct */
+ for (i = 0; i < spi->master->num_chipselect; i++) {
+ csr = spi_readl(as, CSR0 + 4 * i);
+ if ((csr ^ cpol) & SPI_BIT(CPOL))
+ spi_writel(as, CSR0 + 4 * i,
+ csr ^ SPI_BIT(CPOL));
+ }
+
+ mr = spi_readl(as, MR);
+ mr = SPI_BFINS(PCS, ~(1 << spi->chip_select), mr);
+ if (npcs_pin != AT91_PIN_PA3)
+ gpio_set_value(npcs_pin, active);
+ spi_writel(as, MR, mr);
+ }
+
+ dev_dbg(&spi->dev, "activate %u%s, mr %08x\n",
+ npcs_pin, active ? " (high)" : "",
+ mr);
+}
+
+static void cs_deactivate(struct atmel_spi *as, struct spi_device *spi)
+{
+ struct spi_master *master = &as->master;
+ int npcs_pin;
+ unsigned active = spi->mode & SPI_CS_HIGH;
+ u32 mr;
+
+ BUG_ON(spi->chip_select >= master->num_chipselect);
+ npcs_pin = as->cs_pins[spi->chip_select];
+
+ /* only deactivate *this* device; sometimes transfers to
+ * another device may be active when this routine is called.
+ */
+ mr = spi_readl(as, MR);
+ if (~SPI_BFEXT(PCS, mr) & (1 << spi->chip_select)) {
+ mr = SPI_BFINS(PCS, 0xf, mr);
+ spi_writel(as, MR, mr);
+ }
+
+ dev_dbg(&spi->dev, "DEactivate %u%s, mr %08x\n",
+ npcs_pin, active ? " (low)" : "",
+ mr);
+
+ if (atmel_spi_is_v2() || npcs_pin != AT91_PIN_PA3)
+ gpio_set_value(npcs_pin, !active);
+}
+
static int atmel_spi_setup(struct spi_device *spi)
{
struct spi_master *master = spi->master;
@@ -75,10 +175,18 @@ static int atmel_spi_setup(struct spi_device *spi)
unsigned int bits = spi->bits_per_word;
unsigned long bus_hz;
- if (spi->controller_data) {
- csr = (u32)spi->controller_data;
- spi_writel(as, CSR0, csr);
- return 0;
+ if (spi->chip_select > spi->master->num_chipselect) {
+ dev_dbg(&spi->dev,
+ "setup: invalid chipselect %u (%u defined)\n",
+ spi->chip_select, spi->master->num_chipselect);
+ return -EINVAL;
+ }
+
+ if (bits < 8 || bits > 16) {
+ dev_dbg(&spi->dev,
+ "setup: invalid bits_per_word %u (8 to 16)\n",
+ bits);
+ return -EINVAL;
}
dev_dbg(master->dev, "%s mode 0x%08x bits_per_word: %d speed: %d\n",
@@ -131,28 +239,14 @@ static int atmel_spi_setup(struct spi_device *spi)
"setup: %lu Hz bpw %u mode 0x%x -> csr%d %08x\n",
bus_hz / scbr, bits, spi->mode, spi->chip_select, csr);
- spi_writel(as, CSR0, csr);
-
- /*
- * store the csr-setting when bits are defined. This happens usually
- * after the specific spi_device driver has been probed.
- */
- if (bits > 0)
- spi->controller_data = (void *)csr;
-
- return 0;
-}
+ spi->controller_data = (void *)csr;
-static void atmel_spi_chipselect(struct spi_device *spi, struct atmel_spi *as, int on)
-{
- struct spi_master *master = &as->master;
- int cs_pin;
- int val = ((spi->mode & SPI_CS_HIGH) != 0) == on;
+ cs_deactivate(as, spi);
- BUG_ON(spi->chip_select >= master->num_chipselect);
- cs_pin = as->cs_pins[spi->chip_select];
+ if (!atmel_spi_is_v2())
+ spi_writel(as, CSR0 + 4 * spi->chip_select, csr);
- gpio_direction_output(cs_pin, val);
+ return 0;
}
static int atmel_spi_xchg(struct atmel_spi *as, u32 tx_val)
@@ -178,20 +272,57 @@ static int atmel_spi_xchg(struct atmel_spi *as, u32 tx_val)
return spi_readl(as, RDR) & 0xffff;
}
+static int atmel_spi_do_xfer(struct spi_device *spi, struct atmel_spi *as,
+ struct spi_transfer *t)
+{
+ unsigned int bits = spi->bits_per_word;
+ u32 tx_val;
+ int i = 0, rx_val;
+
+ if (bits <= 8) {
+ const u8 *txbuf = t->tx_buf;
+ u8 *rxbuf = t->rx_buf;
+
+ while (i < t->len) {
+ tx_val = txbuf ? txbuf[i] : 0;
+
+ rx_val = atmel_spi_xchg(as, tx_val);
+ if (rx_val < 0)
+ return rx_val;
+
+ if (rxbuf)
+ rxbuf[i] = rx_val;
+ i++;
+ }
+ } else if (bits <= 16) {
+ const u16 *txbuf = t->tx_buf;
+ u16 *rxbuf = t->rx_buf;
+
+ while (i < t->len >> 1) {
+ tx_val = txbuf ? txbuf[i] : 0;
+
+ rx_val = atmel_spi_xchg(as, tx_val);
+ if (rx_val < 0)
+ return rx_val;
+
+ if (rxbuf)
+ rxbuf[i] = rx_val;
+ i++;
+ }
+ }
+
+ return t->len;
+}
+
static int atmel_spi_transfer(struct spi_device *spi, struct spi_message *mesg)
{
int ret;
struct spi_master *master = spi->master;
struct atmel_spi *as = to_atmel_spi(master);
struct spi_transfer *t = NULL;
- unsigned int bits = spi->bits_per_word;
+ unsigned int cs_change;
mesg->actual_length = 0;
- ret = master->setup(spi);
- if (ret < 0) {
- dev_dbg(master->dev, "transfer: master setup failed\n");
- return ret;
- }
dev_dbg(master->dev, " csr0: %08x\n", spi_readl(as, CSR0));
@@ -202,50 +333,38 @@ static int atmel_spi_transfer(struct spi_device *spi, struct spi_message *mesg)
t, t->len, t->tx_buf, t->rx_buf);
}
#endif
- atmel_spi_chipselect(spi, as, 1);
+
+ cs_activate(as, spi);
+
+ cs_change = 0;
+
list_for_each_entry(t, &mesg->transfers, transfer_list) {
- u32 tx_val;
- int i = 0, rx_val;
-
- mesg->actual_length += t->len;
- if (bits <= 8) {
- const u8 *txbuf = t->tx_buf;
- u8 *rxbuf = t->rx_buf;
-
- while (i < t->len) {
- tx_val = txbuf ? txbuf[i] : 0;
-
- rx_val = atmel_spi_xchg(as, tx_val);
- if (rx_val < 0) {
- ret = rx_val;
- goto out;
- }
-
- if (rxbuf)
- rxbuf[i] = rx_val;
- i++;
- }
- } else if (bits <= 16) {
- const u16 *txbuf = t->tx_buf;
- u16 *rxbuf = t->rx_buf;
-
- while (i < t->len >> 1) {
- tx_val = txbuf ? txbuf[i] : 0;
-
- rx_val = atmel_spi_xchg(as, tx_val);
- if (rx_val < 0) {
- ret = rx_val;
- goto out;
- }
-
- if (rxbuf)
- rxbuf[i] = rx_val;
- i++;
- }
+
+ if (cs_change) {
+ udelay(1);
+ cs_deactivate(as, spi);
+ udelay(1);
+ cs_activate(as, spi);
}
+
+ cs_change = t->cs_change;
+
+ ret = atmel_spi_do_xfer(spi, as, t);
+ if (ret < 0)
+ goto err;
+ mesg->actual_length += ret;
+
+ if (cs_change)
+ cs_activate(as, spi);
}
-out:
- atmel_spi_chipselect(spi, as, 0);
+
+ if (!cs_change)
+ cs_deactivate(as, spi);
+
+ return 0;
+
+err:
+ cs_deactivate(as, spi);
return ret;
}
@@ -265,6 +384,7 @@ static int atmel_spi_probe(struct device_d *dev)
master = &as->master;
master->dev = dev;
+ master->bus_num = dev->id;
as->clk = clk_get(dev, "spi_clk");
if (IS_ERR(as->clk)) {
diff --git a/drivers/spi/omap3_spi.c b/drivers/spi/omap3_spi.c
index 29cb69ae2f..f81b4aa9d1 100644
--- a/drivers/spi/omap3_spi.c
+++ b/drivers/spi/omap3_spi.c
@@ -327,10 +327,10 @@ static int omap3_spi_setup(struct spi_device *spi)
{
struct spi_master *master = spi->master;
- if (((master->bus_num == 0) && (spi->chip_select > 3)) ||
- ((master->bus_num == 1) && (spi->chip_select > 1)) ||
+ if (((master->bus_num == 1) && (spi->chip_select > 3)) ||
((master->bus_num == 2) && (spi->chip_select > 1)) ||
- ((master->bus_num == 3) && (spi->chip_select > 0))) {
+ ((master->bus_num == 3) && (spi->chip_select > 1)) ||
+ ((master->bus_num == 4) && (spi->chip_select > 0))) {
printf("SPI error: unsupported chip select %i \
on bus %i\n", spi->chip_select, master->bus_num);
return -EINVAL;
@@ -363,10 +363,10 @@ static int omap3_spi_probe(struct device_d *dev)
/*
* OMAP3 McSPI (MultiChannel SPI) has 4 busses (modules)
* with different number of chip selects (CS, channels):
- * McSPI1 has 4 CS (bus 0, cs 0 - 3)
- * McSPI2 has 2 CS (bus 1, cs 0 - 1)
- * McSPI3 has 2 CS (bus 2, cs 0 - 1)
- * McSPI4 has 1 CS (bus 3, cs 0)
+ * McSPI1 has 4 CS (bus 1, cs 0 - 3)
+ * McSPI2 has 2 CS (bus 2, cs 0 - 1)
+ * McSPI3 has 2 CS (bus 3, cs 0 - 1)
+ * McSPI4 has 1 CS (bus 4, cs 0)
*
* The board code has to make sure that it does not use
* invalid buses or chip selects.
diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c
index 44040e5f62..6a5bd6d2b8 100644
--- a/drivers/spi/spi.c
+++ b/drivers/spi/spi.c
@@ -219,6 +219,18 @@ int spi_register_master(struct spi_master *master)
}
EXPORT_SYMBOL(spi_register_master);
+struct spi_master *spi_get_master(int bus)
+{
+ struct spi_master* m;
+
+ list_for_each_entry(m, &spi_master_list, list) {
+ if (m->bus_num == bus)
+ return m;
+ }
+
+ return NULL;
+}
+
int spi_sync(struct spi_device *spi, struct spi_message *message)
{
return spi->master->transfer(spi, message);