|
@@ -46,6 +46,7 @@ struct atmel_spi {
|
|
struct clk *clk;
|
|
struct clk *clk;
|
|
struct platform_device *pdev;
|
|
struct platform_device *pdev;
|
|
unsigned new_1:1;
|
|
unsigned new_1:1;
|
|
|
|
+ struct spi_device *stay;
|
|
|
|
|
|
u8 stopping;
|
|
u8 stopping;
|
|
struct list_head queue;
|
|
struct list_head queue;
|
|
@@ -62,29 +63,62 @@ struct atmel_spi {
|
|
/*
|
|
/*
|
|
* Earlier SPI controllers (e.g. on at91rm9200) have a design bug whereby
|
|
* 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
|
|
* they assume that spi slave device state will not change on deselect, so
|
|
- * that automagic deselection is OK. Not so! Workaround uses nCSx pins
|
|
|
|
- * as GPIOs; or newer controllers have CSAAT and friends.
|
|
|
|
|
|
+ * 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.
|
|
|
|
|
|
+ * 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.
|
|
*/
|
|
*/
|
|
|
|
|
|
-static inline void cs_activate(struct spi_device *spi)
|
|
|
|
|
|
+static void cs_activate(struct atmel_spi *as, struct spi_device *spi)
|
|
{
|
|
{
|
|
unsigned gpio = (unsigned) spi->controller_data;
|
|
unsigned gpio = (unsigned) spi->controller_data;
|
|
unsigned active = spi->mode & SPI_CS_HIGH;
|
|
unsigned active = spi->mode & SPI_CS_HIGH;
|
|
|
|
+ u32 mr;
|
|
|
|
+
|
|
|
|
+ mr = spi_readl(as, MR);
|
|
|
|
+ mr = SPI_BFINS(PCS, ~(1 << spi->chip_select), mr);
|
|
|
|
+
|
|
|
|
+ dev_dbg(&spi->dev, "activate %u%s, mr %08x\n",
|
|
|
|
+ gpio, active ? " (high)" : "",
|
|
|
|
+ mr);
|
|
|
|
|
|
- dev_dbg(&spi->dev, "activate %u%s\n", gpio, active ? " (high)" : "");
|
|
|
|
- gpio_set_value(gpio, active);
|
|
|
|
|
|
+ if (!(cpu_is_at91rm9200() && spi->chip_select == 0))
|
|
|
|
+ gpio_set_value(gpio, active);
|
|
|
|
+ spi_writel(as, MR, mr);
|
|
}
|
|
}
|
|
|
|
|
|
-static inline void cs_deactivate(struct spi_device *spi)
|
|
|
|
|
|
+static void cs_deactivate(struct atmel_spi *as, struct spi_device *spi)
|
|
{
|
|
{
|
|
unsigned gpio = (unsigned) spi->controller_data;
|
|
unsigned gpio = (unsigned) spi->controller_data;
|
|
unsigned active = spi->mode & SPI_CS_HIGH;
|
|
unsigned active = spi->mode & SPI_CS_HIGH;
|
|
|
|
+ u32 mr;
|
|
|
|
|
|
- dev_dbg(&spi->dev, "DEactivate %u%s\n", gpio, active ? " (low)" : "");
|
|
|
|
- gpio_set_value(gpio, !active);
|
|
|
|
|
|
+ /* 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",
|
|
|
|
+ gpio, active ? " (low)" : "",
|
|
|
|
+ mr);
|
|
|
|
+
|
|
|
|
+ if (!(cpu_is_at91rm9200() && spi->chip_select == 0))
|
|
|
|
+ gpio_set_value(gpio, !active);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
/*
|
|
@@ -140,6 +174,7 @@ static void atmel_spi_next_xfer(struct spi_master *master,
|
|
|
|
|
|
/* REVISIT: when xfer->delay_usecs == 0, the PDC "next transfer"
|
|
/* REVISIT: when xfer->delay_usecs == 0, the PDC "next transfer"
|
|
* mechanism might help avoid the IRQ latency between transfers
|
|
* mechanism might help avoid the IRQ latency between transfers
|
|
|
|
+ * (and improve the nCS0 errata handling on at91rm9200 chips)
|
|
*
|
|
*
|
|
* We're also waiting for ENDRX before we start the next
|
|
* We're also waiting for ENDRX before we start the next
|
|
* transfer because we need to handle some difficult timing
|
|
* transfer because we need to handle some difficult timing
|
|
@@ -169,17 +204,25 @@ static void atmel_spi_next_message(struct spi_master *master)
|
|
{
|
|
{
|
|
struct atmel_spi *as = spi_master_get_devdata(master);
|
|
struct atmel_spi *as = spi_master_get_devdata(master);
|
|
struct spi_message *msg;
|
|
struct spi_message *msg;
|
|
- u32 mr;
|
|
|
|
|
|
+ struct spi_device *spi;
|
|
|
|
|
|
BUG_ON(as->current_transfer);
|
|
BUG_ON(as->current_transfer);
|
|
|
|
|
|
msg = list_entry(as->queue.next, struct spi_message, queue);
|
|
msg = list_entry(as->queue.next, struct spi_message, queue);
|
|
|
|
+ spi = msg->spi;
|
|
|
|
|
|
- /* Select the chip */
|
|
|
|
- mr = spi_readl(as, MR);
|
|
|
|
- mr = SPI_BFINS(PCS, ~(1 << msg->spi->chip_select), mr);
|
|
|
|
- spi_writel(as, MR, mr);
|
|
|
|
- cs_activate(msg->spi);
|
|
|
|
|
|
+ dev_dbg(master->cdev.dev, "start message %p for %s\n",
|
|
|
|
+ msg, spi->dev.bus_id);
|
|
|
|
+
|
|
|
|
+ /* select chip if it's not still active */
|
|
|
|
+ if (as->stay) {
|
|
|
|
+ if (as->stay != spi) {
|
|
|
|
+ cs_deactivate(as, as->stay);
|
|
|
|
+ cs_activate(as, spi);
|
|
|
|
+ }
|
|
|
|
+ as->stay = NULL;
|
|
|
|
+ } else
|
|
|
|
+ cs_activate(as, spi);
|
|
|
|
|
|
atmel_spi_next_xfer(master, msg);
|
|
atmel_spi_next_xfer(master, msg);
|
|
}
|
|
}
|
|
@@ -232,9 +275,13 @@ static void atmel_spi_dma_unmap_xfer(struct spi_master *master,
|
|
|
|
|
|
static void
|
|
static void
|
|
atmel_spi_msg_done(struct spi_master *master, struct atmel_spi *as,
|
|
atmel_spi_msg_done(struct spi_master *master, struct atmel_spi *as,
|
|
- struct spi_message *msg, int status)
|
|
|
|
|
|
+ struct spi_message *msg, int status, int stay)
|
|
{
|
|
{
|
|
- cs_deactivate(msg->spi);
|
|
|
|
|
|
+ if (!stay || status < 0)
|
|
|
|
+ cs_deactivate(as, msg->spi);
|
|
|
|
+ else
|
|
|
|
+ as->stay = msg->spi;
|
|
|
|
+
|
|
list_del(&msg->queue);
|
|
list_del(&msg->queue);
|
|
msg->status = status;
|
|
msg->status = status;
|
|
|
|
|
|
@@ -324,7 +371,7 @@ atmel_spi_interrupt(int irq, void *dev_id)
|
|
/* Clear any overrun happening while cleaning up */
|
|
/* Clear any overrun happening while cleaning up */
|
|
spi_readl(as, SR);
|
|
spi_readl(as, SR);
|
|
|
|
|
|
- atmel_spi_msg_done(master, as, msg, -EIO);
|
|
|
|
|
|
+ atmel_spi_msg_done(master, as, msg, -EIO, 0);
|
|
} else if (pending & SPI_BIT(ENDRX)) {
|
|
} else if (pending & SPI_BIT(ENDRX)) {
|
|
ret = IRQ_HANDLED;
|
|
ret = IRQ_HANDLED;
|
|
|
|
|
|
@@ -342,12 +389,13 @@ atmel_spi_interrupt(int irq, void *dev_id)
|
|
|
|
|
|
if (msg->transfers.prev == &xfer->transfer_list) {
|
|
if (msg->transfers.prev == &xfer->transfer_list) {
|
|
/* report completed message */
|
|
/* report completed message */
|
|
- atmel_spi_msg_done(master, as, msg, 0);
|
|
|
|
|
|
+ atmel_spi_msg_done(master, as, msg, 0,
|
|
|
|
+ xfer->cs_change);
|
|
} else {
|
|
} else {
|
|
if (xfer->cs_change) {
|
|
if (xfer->cs_change) {
|
|
- cs_deactivate(msg->spi);
|
|
|
|
|
|
+ cs_deactivate(as, msg->spi);
|
|
udelay(1);
|
|
udelay(1);
|
|
- cs_activate(msg->spi);
|
|
|
|
|
|
+ cs_activate(as, msg->spi);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
/*
|
|
@@ -410,6 +458,14 @@ static int atmel_spi_setup(struct spi_device *spi)
|
|
return -EINVAL;
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ /* see notes above re chipselect */
|
|
|
|
+ if (cpu_is_at91rm9200()
|
|
|
|
+ && spi->chip_select == 0
|
|
|
|
+ && (spi->mode & SPI_CS_HIGH)) {
|
|
|
|
+ dev_dbg(&spi->dev, "setup: can't be active-high\n");
|
|
|
|
+ return -EINVAL;
|
|
|
|
+ }
|
|
|
|
+
|
|
/* speed zero convention is used by some upper layers */
|
|
/* speed zero convention is used by some upper layers */
|
|
bus_hz = clk_get_rate(as->clk);
|
|
bus_hz = clk_get_rate(as->clk);
|
|
if (spi->max_speed_hz) {
|
|
if (spi->max_speed_hz) {
|
|
@@ -446,6 +502,14 @@ static int atmel_spi_setup(struct spi_device *spi)
|
|
return ret;
|
|
return ret;
|
|
spi->controller_state = (void *)npcs_pin;
|
|
spi->controller_state = (void *)npcs_pin;
|
|
gpio_direction_output(npcs_pin, !(spi->mode & SPI_CS_HIGH));
|
|
gpio_direction_output(npcs_pin, !(spi->mode & SPI_CS_HIGH));
|
|
|
|
+ } else {
|
|
|
|
+ unsigned long flags;
|
|
|
|
+
|
|
|
|
+ spin_lock_irqsave(&as->lock, flags);
|
|
|
|
+ if (as->stay == spi)
|
|
|
|
+ as->stay = NULL;
|
|
|
|
+ cs_deactivate(as, spi);
|
|
|
|
+ spin_unlock_irqrestore(&as->lock, flags);
|
|
}
|
|
}
|
|
|
|
|
|
dev_dbg(&spi->dev,
|
|
dev_dbg(&spi->dev,
|
|
@@ -502,6 +566,7 @@ static int atmel_spi_transfer(struct spi_device *spi, struct spi_message *msg)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+#ifdef VERBOSE
|
|
list_for_each_entry(xfer, &msg->transfers, transfer_list) {
|
|
list_for_each_entry(xfer, &msg->transfers, transfer_list) {
|
|
dev_dbg(controller,
|
|
dev_dbg(controller,
|
|
" xfer %p: len %u tx %p/%08x rx %p/%08x\n",
|
|
" xfer %p: len %u tx %p/%08x rx %p/%08x\n",
|
|
@@ -509,6 +574,7 @@ static int atmel_spi_transfer(struct spi_device *spi, struct spi_message *msg)
|
|
xfer->tx_buf, xfer->tx_dma,
|
|
xfer->tx_buf, xfer->tx_dma,
|
|
xfer->rx_buf, xfer->rx_dma);
|
|
xfer->rx_buf, xfer->rx_dma);
|
|
}
|
|
}
|
|
|
|
+#endif
|
|
|
|
|
|
msg->status = -EINPROGRESS;
|
|
msg->status = -EINPROGRESS;
|
|
msg->actual_length = 0;
|
|
msg->actual_length = 0;
|
|
@@ -524,8 +590,21 @@ static int atmel_spi_transfer(struct spi_device *spi, struct spi_message *msg)
|
|
|
|
|
|
static void atmel_spi_cleanup(struct spi_device *spi)
|
|
static void atmel_spi_cleanup(struct spi_device *spi)
|
|
{
|
|
{
|
|
- if (spi->controller_state)
|
|
|
|
- gpio_free((unsigned int)spi->controller_data);
|
|
|
|
|
|
+ struct atmel_spi *as = spi_master_get_devdata(spi->master);
|
|
|
|
+ unsigned gpio = (unsigned) spi->controller_data;
|
|
|
|
+ unsigned long flags;
|
|
|
|
+
|
|
|
|
+ if (!spi->controller_state)
|
|
|
|
+ return;
|
|
|
|
+
|
|
|
|
+ spin_lock_irqsave(&as->lock, flags);
|
|
|
|
+ if (as->stay == spi) {
|
|
|
|
+ as->stay = NULL;
|
|
|
|
+ cs_deactivate(as, spi);
|
|
|
|
+ }
|
|
|
|
+ spin_unlock_irqrestore(&as->lock, flags);
|
|
|
|
+
|
|
|
|
+ gpio_free(gpio);
|
|
}
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
/*-------------------------------------------------------------------------*/
|