From 931e06de64100e28031627964321da3fdb449378 Mon Sep 17 00:00:00 2001 From: Michael Sevakis Date: Tue, 4 May 2010 10:07:53 +0000 Subject: i.MX31/Gigabeat S: Actually enable DPTC which can set optimal voltage for 528MHz. Requires an SPI and PMIC interface rework because of the low-latency needs for the DPTC to work best with minimal panicing. SPI can work with multitasking and asynchronously from interrupt handlers or normal code. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@25800 a1c6a512-1295-4272-9138-f99709370657 --- firmware/target/arm/imx31/spi-imx31.c | 419 ++++++++++++++++++++-------------- 1 file changed, 249 insertions(+), 170 deletions(-) (limited to 'firmware/target/arm/imx31/spi-imx31.c') diff --git a/firmware/target/arm/imx31/spi-imx31.c b/firmware/target/arm/imx31/spi-imx31.c index ac063f9b10..3f66257c95 100644 --- a/firmware/target/arm/imx31/spi-imx31.c +++ b/firmware/target/arm/imx31/spi-imx31.c @@ -38,19 +38,18 @@ static __attribute__((interrupt("IRQ"))) void CSPI3_HANDLER(void); #endif /* State data associatated with each CSPI module */ -static struct spi_module_descriptor +static struct spi_module_desc { - struct cspi_map * const base; - int enab; - struct spi_node *last; - enum IMX31_CG_LIST cg; - enum IMX31_INT_LIST ints; - int byte_size; - void (*handler)(void); - struct mutex m; - struct wakeup w; - struct spi_transfer *trans; - int rxcount; + struct cspi_map * const base; /* CSPI module address */ + struct spi_transfer_desc *head; /* Running job */ + struct spi_transfer_desc *tail; /* Most recent job added */ + const struct spi_node *last_node; /* Last node used for module */ + void (*handler)(void); /* Interrupt handler */ + int rxcount; /* Independent copy of txcount */ + int8_t enab; /* Enable count */ + int8_t byte_size; /* Size of transfers in bytes */ + int8_t cg; /* Clock-gating value */ + int8_t ints; /* AVIC vector number */ } spi_descs[SPI_NUM_CSPI] = /* Init non-zero members */ { @@ -80,93 +79,224 @@ static struct spi_module_descriptor #endif }; +/* Reset the module */ +static void spi_reset(struct spi_module_desc * const desc) +{ + /* Reset by leaving it disabled */ + struct cspi_map * const base = desc->base; + base->conreg &= ~CSPI_CONREG_EN; +} + +/* Write the context for the node and remember it to avoid unneeded reconfigure */ +static bool spi_set_context(struct spi_module_desc *desc, + struct spi_transfer_desc *xfer) +{ + const struct spi_node * const node = xfer->node; + struct cspi_map * const base = desc->base; + + if (desc->enab == 0) + return false; + + if (node == desc->last_node) + return true; + + /* Errata says CSPI should be disabled when writing PERIODREG. */ + base->conreg &= ~CSPI_CONREG_EN; + + /* Switch the module's node */ + desc->last_node = node; + desc->byte_size = (((node->conreg >> 8) & 0x1f) + 1 + 7) / 8 - 1; + + /* Set the wait-states */ + base->periodreg = node->periodreg & 0xffff; + + /* Keep reserved and start bits cleared. Keep enabled bit. */ + base->conreg = + (node->conreg & ~(0xfcc8e000 | CSPI_CONREG_XCH | CSPI_CONREG_SMC)); + return true; +} + + +/* Fill the TX fifo. Returns the number of remaining words. */ +static int tx_fill_fifo(struct spi_module_desc * const desc, + struct cspi_map * const base, + struct spi_transfer_desc * const xfer) +{ + int count = xfer->count; + int size = desc->byte_size; + + while ((base->statreg & CSPI_STATREG_TF) == 0) + { + uint32_t word = 0; + + switch (size & 3) + { + case 3: + word = *(unsigned char *)(xfer->txbuf + 3) << 24; + case 2: + word |= *(unsigned char *)(xfer->txbuf + 2) << 16; + case 1: + word |= *(unsigned char *)(xfer->txbuf + 1) << 8; + case 0: + word |= *(unsigned char *)(xfer->txbuf + 0); + } + + xfer->txbuf += size + 1; /* Increment buffer */ + + base->txdata = word; /* Write to FIFO */ + + if (--count == 0) + break; + } + + xfer->count = count; + + return count; +} + +/* Start a transfer on the SPI */ +static bool start_transfer(struct spi_module_desc * const desc, + struct spi_transfer_desc * const xfer) +{ + struct cspi_map * const base = desc->base; + unsigned long intreg; + + if (!spi_set_context(desc, xfer)) + return false; + + base->conreg |= CSPI_CONREG_EN; /* Enable module */ + + desc->rxcount = xfer->count; + + intreg = (xfer->count < 8) ? + CSPI_INTREG_TCEN : /* Trans. complete: TX will run out in prefill */ + CSPI_INTREG_THEN; /* INT when TX half-empty */ + + intreg |= (xfer->count < 4) ? + CSPI_INTREG_RREN : /* Must grab data on every word */ + CSPI_INTREG_RHEN; /* Enough data to wait for half-full */ + + tx_fill_fifo(desc, base, xfer); + + base->statreg = CSPI_STATREG_TC; /* Ack 'complete' */ + base->intreg = intreg; /* Enable interrupts */ + base->conreg |= CSPI_CONREG_XCH; /* Begin transfer */ + + return true; +} + /* Common code for interrupt handlers */ static void spi_interrupt(enum spi_module_number spi) { - struct spi_module_descriptor *desc = &spi_descs[spi]; + struct spi_module_desc *desc = &spi_descs[spi]; struct cspi_map * const base = desc->base; - struct spi_transfer *trans = desc->trans; + unsigned long intreg = base->intreg; + struct spi_transfer_desc *xfer = desc->head; int inc = desc->byte_size + 1; - if (desc->rxcount > 0) + /* Data received - empty out RXFIFO */ + while ((base->statreg & CSPI_STATREG_RR) != 0) { - /* Data received - empty out RXFIFO */ - while ((base->statreg & CSPI_STATREG_RR) != 0) - { - uint32_t word = base->rxdata; + uint32_t word = base->rxdata; + if (desc->rxcount <= 0) + continue; + + if (xfer->rxbuf != NULL) + { + /* There is a receive buffer */ switch (desc->byte_size & 3) { case 3: - *(unsigned char *)(trans->rxbuf + 3) = word >> 24; + *(unsigned char *)(xfer->rxbuf + 3) = word >> 24; case 2: - *(unsigned char *)(trans->rxbuf + 2) = word >> 16; + *(unsigned char *)(xfer->rxbuf + 2) = word >> 16; case 1: - *(unsigned char *)(trans->rxbuf + 1) = word >> 8; + *(unsigned char *)(xfer->rxbuf + 1) = word >> 8; case 0: - *(unsigned char *)(trans->rxbuf + 0) = word; + *(unsigned char *)(xfer->rxbuf + 0) = word; } - trans->rxbuf += inc; + xfer->rxbuf += inc; + } - if (--desc->rxcount < 4) + if (--desc->rxcount < 4) + { + if (desc->rxcount == 0) + { + /* No more to receive - stop RX interrupts */ + intreg &= ~(CSPI_INTREG_RHEN | CSPI_INTREG_RREN); + base->intreg = intreg; + } + else if (intreg & CSPI_INTREG_RHEN) { - unsigned long intreg = base->intreg; - - if (desc->rxcount <= 0) - { - /* No more to receive - stop RX interrupts */ - intreg &= ~(CSPI_INTREG_RHEN | CSPI_INTREG_RREN); - base->intreg = intreg; - break; - } - else if (!(intreg & CSPI_INTREG_RREN)) - { - /* < 4 words expected - switch to RX ready */ - intreg &= ~CSPI_INTREG_RHEN; - base->intreg = intreg | CSPI_INTREG_RREN; - } + /* < 4 words expected - switch to RX ready */ + intreg &= ~CSPI_INTREG_RHEN; + intreg |= CSPI_INTREG_RREN; + base->intreg = intreg; } } } - if (trans->count > 0) + if (xfer->count > 0) { - /* Data to transmit - fill TXFIFO or write until exhausted */ - while ((base->statreg & CSPI_STATREG_TF) == 0) + /* Data to transmit - fill TXFIFO or write until exhausted. */ + if (tx_fill_fifo(desc, base, xfer) != 0) + return; + + /* Out of data - stop TX interrupts, enable TC interrupt. */ + intreg &= ~CSPI_INTREG_THEN; + intreg |= CSPI_INTREG_TCEN; + base->intreg = intreg; + } + + if ((intreg & CSPI_INTREG_TCEN) && (base->statreg & CSPI_STATREG_TC)) + { + /* Outbound transfer is complete. */ + intreg &= ~CSPI_INTREG_TCEN; + base->intreg = intreg; + base->statreg = CSPI_STATREG_TC; /* Ack 'complete' */ + } + + if (intreg != 0) + return; + + /* All interrupts are masked; we're done with current transfer. */ + for (;;) + { + struct spi_transfer_desc *next = xfer->next; + spi_transfer_cb_fn_type callback = xfer->callback; + xfer->next = NULL; + + base->conreg &= ~CSPI_CONREG_EN; /* Disable module */ + + if (next == xfer) { - uint32_t word = 0; + /* Last job on queue */ + desc->head = NULL; - switch (desc->byte_size & 3) - { - case 3: - word = *(unsigned char *)(trans->txbuf + 3) << 24; - case 2: - word |= *(unsigned char *)(trans->txbuf + 2) << 16; - case 1: - word |= *(unsigned char *)(trans->txbuf + 1) << 8; - case 0: - word |= *(unsigned char *)(trans->txbuf + 0); - } + if (callback != NULL) + callback(xfer); - trans->txbuf += inc; + /* Callback may have restarted transfers. */ + } + else + { + /* Queue next job. */ + desc->head = next; - base->txdata = word; + if (callback != NULL) + callback(xfer); - if (--trans->count <= 0) + if (!start_transfer(desc, next)) { - /* Out of data - stop TX interrupts */ - base->intreg &= ~CSPI_INTREG_THEN; - break; + xfer = next; + xfer->count = -1; + continue; /* Failed: try next */ } } - } - /* If all interrupts have been remasked - we're done */ - if (base->intreg == 0) - { - base->statreg = CSPI_STATREG_TC | CSPI_STATREG_BO; - wakeup_signal(&desc->w); + break; } } @@ -192,105 +322,50 @@ static __attribute__((interrupt("IRQ"))) void CSPI3_HANDLER(void) } #endif -/* Write the context for the node and remember it to avoid unneeded reconfigure */ -static bool spi_set_context(struct spi_node *node, - struct spi_module_descriptor *desc) -{ - struct cspi_map * const base = desc->base; - - if ((base->conreg & CSPI_CONREG_EN) == 0) - return false; - - if (node != desc->last) - { - /* Switch the module's node */ - desc->last = node; - desc->byte_size = (((node->conreg >> 8) & 0x1f) + 1 + 7) / 8 - 1; - - /* Keep reserved and start bits cleared. Keep enabled bit. */ - base->conreg = - (node->conreg & ~(0xfcc8e000 | CSPI_CONREG_XCH | CSPI_CONREG_SMC)) - | CSPI_CONREG_EN; - /* Set the wait-states */ - base->periodreg = node->periodreg & 0xffff; - /* Clear out any spuriously-pending interrupts */ - base->statreg = CSPI_STATREG_TC | CSPI_STATREG_BO; - } - - return true; -} - -static void spi_reset(struct cspi_map * const base) -{ - /* Reset */ - base->conreg &= ~CSPI_CONREG_EN; - base->conreg |= CSPI_CONREG_EN; - base->intreg = 0; - base->statreg = CSPI_STATREG_TC | CSPI_STATREG_BO; -} - -/* Initialize each of the used SPI descriptors */ +/* Initialize the SPI driver */ void spi_init(void) { - int i; - + unsigned i; for (i = 0; i < SPI_NUM_CSPI; i++) { - struct spi_module_descriptor * const desc = &spi_descs[i]; - mutex_init(&desc->m); - wakeup_init(&desc->w); + struct spi_module_desc * const desc = &spi_descs[i]; + ccm_module_clock_gating(desc->cg, CGM_ON_RUN_WAIT); + spi_reset(desc); + ccm_module_clock_gating(desc->cg, CGM_OFF); } } -/* Get mutually-exclusive access to the node */ -void spi_lock(struct spi_node *node) -{ - mutex_lock(&spi_descs[node->num].m); -} - -/* Release mutual exclusion */ -void spi_unlock(struct spi_node *node) -{ - mutex_unlock(&spi_descs[node->num].m); -} - /* Enable the specified module for the node */ -void spi_enable_module(struct spi_node *node) +void spi_enable_module(const struct spi_node *node) { - struct spi_module_descriptor * const desc = &spi_descs[node->num]; - - mutex_lock(&desc->m); + struct spi_module_desc * const desc = &spi_descs[node->num]; if (++desc->enab == 1) { - /* First enable for this module */ - struct cspi_map * const base = desc->base; - /* Enable clock-gating register */ ccm_module_clock_gating(desc->cg, CGM_ON_RUN_WAIT); /* Reset */ - spi_reset(base); - desc->last = NULL; + spi_reset(desc); + desc->last_node = NULL; /* Enable interrupt at controller level */ avic_enable_int(desc->ints, INT_TYPE_IRQ, INT_PRIO_DEFAULT, desc->handler); } - - mutex_unlock(&desc->m); } -/* Disabled the specified module for the node */ -void spi_disable_module(struct spi_node *node) +/* Disable the specified module for the node */ +void spi_disable_module(const struct spi_node *node) { - struct spi_module_descriptor * const desc = &spi_descs[node->num]; - - mutex_lock(&desc->m); + struct spi_module_desc * const desc = &spi_descs[node->num]; if (desc->enab > 0 && --desc->enab == 0) { /* Last enable for this module */ struct cspi_map * const base = desc->base; + /* Wait for outstanding transactions */ + while (*(void ** volatile)&desc->head != NULL); + /* Disable interrupt at controller level */ avic_disable_int(desc->ints); @@ -300,53 +375,57 @@ void spi_disable_module(struct spi_node *node) /* Disable interface clock */ ccm_module_clock_gating(desc->cg, CGM_OFF); } - - mutex_unlock(&desc->m); } /* Send and/or receive data on the specified node */ -int spi_transfer(struct spi_node *node, struct spi_transfer *trans) +bool spi_transfer(struct spi_transfer_desc *xfer) { - struct spi_module_descriptor * const desc = &spi_descs[node->num]; - int retval; - - if (trans->count <= 0) - return true; - - mutex_lock(&desc->m); + bool retval; + struct spi_module_desc * desc; + int oldlevel; - retval = spi_set_context(node, desc); + if (xfer->count == 0) + return true; /* No data? No problem. */ - if (retval) + if (xfer->count < 0 || xfer->next != NULL || xfer->node == NULL) { - struct cspi_map * const base = desc->base; - unsigned long intreg; - - desc->trans = trans; - desc->rxcount = trans->count; - - /* Enable needed interrupts - FIFOs will start filling */ - intreg = CSPI_INTREG_THEN; + /* Can't pass a busy descriptor, requires a node and negative size + * is invalid to pass. */ + return false; + } - intreg |= (trans->count < 4) ? - CSPI_INTREG_RREN : /* Must grab data on every word */ - CSPI_INTREG_RHEN; /* Enough data to wait for half-full */ + oldlevel = disable_irq_save(); - base->intreg = intreg; + desc = &spi_descs[xfer->node->num]; - /* Start transfer */ - base->conreg |= CSPI_CONREG_XCH; + if (desc->head == NULL) + { + /* No transfers in progress; start interface. */ + retval = start_transfer(desc, xfer); - if (wakeup_wait(&desc->w, HZ) != OBJ_WAIT_SUCCEEDED) + if (retval) { - base->intreg = 0; /* Stop SPI ints */ - spi_reset(base); /* Reset module (esp. to empty FIFOs) */ - desc->last = NULL; /* Force reconfigure */ - retval = false; + /* Start ok: actually put it in the queue. */ + desc->head = xfer; + desc->tail = xfer; + xfer->next = xfer; /* First, self-reference terminate */ } + else + { + xfer->count = -1; /* Signal error */ + } + } + else + { + /* Already running: simply add to end and the final INT on the + * running transfer will pick it up. */ + desc->tail->next = xfer; /* Add to tail */ + desc->tail = xfer; /* New tail */ + xfer->next = xfer; /* Self-reference terminate */ + retval = true; } - mutex_unlock(&desc->m); + restore_irq(oldlevel); return retval; } -- cgit v1.2.3